1 use std::path::{Path, PathBuf};
2 
3 use log::*;
4 use rls_analysis::{Def, DefKind};
5 use rls_span::{Column, Range, Row, Span, ZeroIndexed};
6 use rls_vfs::{self as vfs, Vfs};
7 use rustfmt_nightly::NewlineStyle;
8 use serde_derive::{Deserialize, Serialize};
9 
10 use crate::actions::format::Rustfmt;
11 use crate::actions::requests;
12 use crate::actions::InitActionContext;
13 use crate::config::FmtConfig;
14 use crate::lsp_data::*;
15 use crate::server::ResponseError;
16 
17 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
18 pub struct Tooltip {
19     pub contents: Vec<MarkedString>,
20     pub range: Range<ZeroIndexed>,
21 }
22 
23 /// Cleanup documentation code blocks. The `docs` are expected to have
24 /// the preceding `///` or `//!` prefixes already trimmed away. Rust code
25 /// blocks will ignore lines beginning with `#`. Code block annotations
26 /// that are common to Rust will be converted to `rust` allow for markdown
27 /// syntax coloring.
process_docs(docs: &str) -> String28 pub fn process_docs(docs: &str) -> String {
29     trace!("process_docs");
30     let mut in_codeblock = false;
31     let mut in_rust_codeblock = false;
32     let mut processed_docs = Vec::new();
33     let mut last_line_ignored = false;
34     for line in docs.lines() {
35         let trimmed = line.trim();
36         if trimmed.starts_with("```") {
37             in_rust_codeblock = trimmed == "```"
38                 || trimmed.contains("rust")
39                 || trimmed.contains("no_run")
40                 || trimmed.contains("ignore")
41                 || trimmed.contains("should_panic")
42                 || trimmed.contains("compile_fail");
43             in_codeblock = !in_codeblock;
44             if !in_codeblock {
45                 in_rust_codeblock = false;
46             }
47         }
48         let line = if in_rust_codeblock && trimmed.starts_with("```") {
49             "```rust".into()
50         } else {
51             line.to_string()
52         };
53 
54         // Racer sometimes pulls out comment block headers from the standard library.
55         let ignore_slashes = line.starts_with("////");
56 
57         let maybe_attribute = trimmed.starts_with("#[") || trimmed.starts_with("#![");
58         let is_attribute = maybe_attribute && in_rust_codeblock;
59         let is_hidden = trimmed.starts_with('#') && in_rust_codeblock && !is_attribute;
60 
61         let ignore_whitespace = last_line_ignored && trimmed.is_empty();
62         let ignore_line = ignore_slashes || ignore_whitespace || is_hidden;
63 
64         if !ignore_line {
65             processed_docs.push(line);
66             last_line_ignored = false;
67         } else {
68             last_line_ignored = true;
69         }
70     }
71 
72     processed_docs.join("\n")
73 }
74 
75 /// Extracts documentation from the `file` at the specified `row_start`.
76 /// If the row is equal to `0`, the scan will include the current row
77 /// and move _downward_. Otherwise, the scan will ignore the specified
78 /// row and move _upward_.
extract_docs( vfs: &Vfs, file: &Path, row_start: Row<ZeroIndexed>, ) -> Result<Vec<String>, vfs::Error>79 pub fn extract_docs(
80     vfs: &Vfs,
81     file: &Path,
82     row_start: Row<ZeroIndexed>,
83 ) -> Result<Vec<String>, vfs::Error> {
84     let up = row_start.0 > 0;
85     debug!("extract_docs: row_start = {:?}, up = {:?}, file = {:?}", row_start, up, file);
86 
87     let mut docs: Vec<String> = Vec::new();
88     let mut row = if up {
89         Row::new_zero_indexed(row_start.0.saturating_sub(1))
90     } else {
91         Row::new_zero_indexed(row_start.0)
92     };
93     let mut in_meta = false;
94     let mut hit_top = false;
95     loop {
96         let line = vfs.load_line(file, row)?;
97 
98         let next_row = if up {
99             Row::new_zero_indexed(row.0.saturating_sub(1))
100         } else {
101             Row::new_zero_indexed(row.0.saturating_add(1))
102         };
103 
104         if row == next_row {
105             hit_top = true;
106         } else {
107             row = next_row;
108         }
109 
110         let line = line.trim();
111 
112         let attr_start = line.starts_with("#[") || line.starts_with("#![");
113 
114         if attr_start && line.ends_with(']') && !hit_top {
115             // Ignore single-line attributes.
116             trace!(
117                 "extract_docs: ignoring single-line attribute, next_row: {:?}, up: {}",
118                 next_row,
119                 up
120             );
121             continue;
122         }
123 
124         // Continue with the next line when transitioning out of a
125         // multi-line attribute.
126         if attr_start || (line.ends_with(']') && !line.starts_with("//")) {
127             in_meta = !in_meta;
128             if !in_meta && !hit_top {
129                 trace!(
130                     "extract_docs: exiting multi-line attribute, next_row: {:?}, up: {}",
131                     next_row,
132                     up
133                 );
134                 continue;
135             };
136         }
137 
138         if !hit_top && in_meta {
139             // Ignore milti-line attributes.
140             trace!(
141                 "extract_docs: ignoring multi-line attribute, next_row: {:?}, up: {}, in_meta: {}",
142                 next_row,
143                 up,
144                 in_meta
145             );
146             continue;
147         } else if line.starts_with("////") {
148             trace!(
149                 "extract_docs: break on comment header block, next_row: {:?}, up: {}",
150                 next_row,
151                 up
152             );
153             break;
154         } else if line.starts_with("///") && !up {
155             trace!("extract_docs: break on non-module docs, next_row: {:?}, up: {}", next_row, up);
156             break;
157         } else if line.starts_with("//!") && up {
158             trace!("extract_docs: break on module docs, next_row: {:?}, up: {}", next_row, up);
159             break;
160         } else if line.starts_with("///") || line.starts_with("//!") {
161             let pos =
162                 if line.chars().nth(3).map(char::is_whitespace).unwrap_or(false) { 4 } else { 3 };
163             let doc_line = line[pos..].into();
164             if up {
165                 docs.insert(0, doc_line);
166             } else {
167                 docs.push(doc_line);
168             }
169         }
170 
171         if hit_top {
172             // The top of the file was reached.
173             debug!(
174                 "extract_docs: bailing out: prev_row == next_row; next_row = {:?}, up = {}",
175                 next_row, up
176             );
177             break;
178         } else if line.starts_with("//") {
179             trace!(
180                 "extract_docs: continuing after comment line, next_row: {:?}, up: {}",
181                 next_row,
182                 up
183             );
184             continue;
185         } else if line.is_empty() {
186             trace!(
187                 "extract_docs: continuing after empty line, next_row: {:?}, up: {}",
188                 next_row,
189                 up
190             );
191             continue;
192         } else {
193             trace!("extract_docs: end of docs, next_row: {:?}, up: {}", next_row, up);
194             break;
195         }
196     }
197     debug!(
198         "extract_docs: complete: row_end = {:?} (exclusive), up = {:?}, file = {:?}",
199         row, up, file
200     );
201     Ok(docs)
202 }
203 
extract_and_process_docs(vfs: &Vfs, file: &Path, row_start: Row<ZeroIndexed>) -> Option<String>204 fn extract_and_process_docs(vfs: &Vfs, file: &Path, row_start: Row<ZeroIndexed>) -> Option<String> {
205     extract_docs(vfs, &file, row_start)
206         .map_err(|e| {
207             error!("failed to extract docs: row: {:?}, file: {:?} ({:?})", row_start, file, e);
208         })
209         .ok()
210         .map(|docs| docs.join("\n"))
211         .map(|docs| process_docs(&docs))
212         .and_then(empty_to_none)
213 }
214 
215 /// Extracts a function, method, struct, enum, or trait declaration from source.
extract_decl( vfs: &Vfs, file: &Path, mut row: Row<ZeroIndexed>, ) -> Result<Vec<String>, vfs::Error>216 pub fn extract_decl(
217     vfs: &Vfs,
218     file: &Path,
219     mut row: Row<ZeroIndexed>,
220 ) -> Result<Vec<String>, vfs::Error> {
221     debug!("extract_decl: row_start: {:?}, file: {:?}", row, file);
222     let mut lines = Vec::new();
223     loop {
224         match vfs.load_line(file, row) {
225             Ok(line) => {
226                 row = Row::new_zero_indexed(row.0.saturating_add(1));
227                 let mut line = line.trim();
228                 if let Some(pos) = line.rfind('{') {
229                     line = &line[0..pos].trim_end();
230                     lines.push(line.into());
231                     break;
232                 } else if line.ends_with(';') {
233                     let pos = line.len() - 1;
234                     line = &line[0..pos].trim_end();
235                     lines.push(line.into());
236                     break;
237                 } else {
238                     lines.push(line.into());
239                 }
240             }
241             Err(e) => {
242                 error!("extract_decl: error: {:?}", e);
243                 return Err(e);
244             }
245         }
246     }
247     Ok(lines)
248 }
249 
tooltip_local_variable_usage( ctx: &InitActionContext, def: &Def, doc_url: Option<String>, ) -> Vec<MarkedString>250 fn tooltip_local_variable_usage(
251     ctx: &InitActionContext,
252     def: &Def,
253     doc_url: Option<String>,
254 ) -> Vec<MarkedString> {
255     debug!("tooltip_local_variable_usage: {}", def.name);
256 
257     let the_type = def.value.trim().into();
258     let mut context = String::new();
259     if ctx.config.lock().unwrap().show_hover_context {
260         match ctx.vfs.load_line(&def.span.file, def.span.range.row_start) {
261             Ok(line) => {
262                 context.push_str(line.trim());
263             }
264             Err(e) => {
265                 error!("tooltip_local_variable_usage: error = {:?}", e);
266             }
267         }
268         if context.ends_with('{') {
269             context.push_str(" ... }");
270         }
271     }
272 
273     let context = empty_to_none(context);
274     let docs = None;
275 
276     create_tooltip(the_type, doc_url, context, docs)
277 }
278 
tooltip_type(ctx: &InitActionContext, def: &Def, doc_url: Option<String>) -> Vec<MarkedString>279 fn tooltip_type(ctx: &InitActionContext, def: &Def, doc_url: Option<String>) -> Vec<MarkedString> {
280     debug!("tooltip_type: {}", def.name);
281 
282     let vfs = &ctx.vfs;
283 
284     let the_type = || def.value.trim().into();
285     let the_type = def_decl(def, &vfs, the_type);
286     let docs = def_docs(def, &vfs);
287     let context = None;
288 
289     create_tooltip(the_type, doc_url, context, docs)
290 }
291 
tooltip_field_or_variant( ctx: &InitActionContext, def: &Def, doc_url: Option<String>, ) -> Vec<MarkedString>292 fn tooltip_field_or_variant(
293     ctx: &InitActionContext,
294     def: &Def,
295     doc_url: Option<String>,
296 ) -> Vec<MarkedString> {
297     debug!("tooltip_field_or_variant: {}", def.name);
298 
299     let vfs = &ctx.vfs;
300 
301     let the_type = def.value.trim().into();
302     let docs = def_docs(def, &vfs);
303     let context = None;
304 
305     create_tooltip(the_type, doc_url, context, docs)
306 }
307 
tooltip_struct_enum_union_trait( ctx: &InitActionContext, def: &Def, doc_url: Option<String>, ) -> Vec<MarkedString>308 fn tooltip_struct_enum_union_trait(
309     ctx: &InitActionContext,
310     def: &Def,
311     doc_url: Option<String>,
312 ) -> Vec<MarkedString> {
313     debug!("tooltip_struct_enum_union_trait: {}", def.name);
314 
315     let vfs = &ctx.vfs;
316     let fmt_config = ctx.fmt_config();
317     // We hover often, so use the in-process one to speed things up.
318     let fmt = Rustfmt::Internal;
319 
320     // Fallback in case source extration fails.
321     let the_type = || match def.kind {
322         DefKind::Struct => format!("struct {}", def.name),
323         DefKind::Enum => format!("enum {}", def.name),
324         DefKind::Union => format!("union {}", def.name),
325         DefKind::Trait => format!("trait {}", def.value),
326         _ => def.value.trim().to_string(),
327     };
328 
329     let decl = def_decl(def, &vfs, the_type);
330 
331     let the_type = format_object(fmt, &fmt_config, decl);
332     let docs = def_docs(def, &vfs);
333     let context = None;
334 
335     create_tooltip(the_type, doc_url, context, docs)
336 }
337 
tooltip_mod(ctx: &InitActionContext, def: &Def, doc_url: Option<String>) -> Vec<MarkedString>338 fn tooltip_mod(ctx: &InitActionContext, def: &Def, doc_url: Option<String>) -> Vec<MarkedString> {
339     debug!("tooltip_mod: name: {}", def.name);
340 
341     let vfs = &ctx.vfs;
342 
343     let the_type = def.value.trim();
344     let the_type = the_type.replace("\\\\", "/");
345     let the_type = the_type.replace("\\", "/");
346 
347     let mod_path = if let Some(dir) = ctx.current_project.file_name() {
348         if Path::new(&the_type).starts_with(dir) {
349             the_type.chars().skip(dir.len() + 1).collect()
350         } else {
351             the_type
352         }
353     } else {
354         the_type
355     };
356 
357     let docs = def_docs(def, &vfs);
358     let context = None;
359 
360     create_tooltip(mod_path, doc_url, context, docs)
361 }
362 
363 fn tooltip_function_method(
364     ctx: &InitActionContext,
365     def: &Def,
366     doc_url: Option<String>,
367 ) -> Vec<MarkedString> {
368     debug!("tooltip_function_method: {}", def.name);
369 
370     let vfs = &ctx.vfs;
371     let fmt_config = ctx.fmt_config();
372     // We hover often, so use the in-process one to speed things up.
373     let fmt = Rustfmt::Internal;
374 
375     let the_type = || {
376         def.value
377             .trim()
378             .replacen("fn ", &format!("fn {}", def.name), 1)
379             .replace("> (", ">(")
380             .replace("->(", "-> (")
381     };
382 
383     let decl = def_decl(def, &vfs, the_type);
384 
385     let the_type = format_method(fmt, &fmt_config, decl);
386     let docs = def_docs(def, &vfs);
387     let context = None;
388 
389     create_tooltip(the_type, doc_url, context, docs)
390 }
391 
392 fn tooltip_local_variable_decl(
393     _ctx: &InitActionContext,
394     def: &Def,
395     doc_url: Option<String>,
396 ) -> Vec<MarkedString> {
397     debug!("tooltip_local_variable_decl: {}", def.name);
398 
399     let the_type = def.value.trim().into();
400     let docs = None;
401     let context = None;
402 
403     create_tooltip(the_type, doc_url, context, docs)
404 }
405 
406 fn tooltip_function_arg_usage(
407     _ctx: &InitActionContext,
408     def: &Def,
409     doc_url: Option<String>,
410 ) -> Vec<MarkedString> {
411     debug!("tooltip_function_arg_usage: {}", def.name);
412 
413     let the_type = def.value.trim().into();
414     let docs = None;
415     let context = None;
416 
417     create_tooltip(the_type, doc_url, context, docs)
418 }
419 
420 fn tooltip_function_signature_arg(
421     _ctx: &InitActionContext,
422     def: &Def,
423     doc_url: Option<String>,
424 ) -> Vec<MarkedString> {
425     debug!("tooltip_function_signature_arg: {}", def.name);
426 
427     let the_type = def.value.trim().into();
428     let docs = None;
429     let context = None;
430 
431     create_tooltip(the_type, doc_url, context, docs)
432 }
433 
434 fn tooltip_static_const_decl(
435     ctx: &InitActionContext,
436     def: &Def,
437     doc_url: Option<String>,
438 ) -> Vec<MarkedString> {
439     debug!("tooltip_static_const_decl: {}", def.name);
440 
441     let vfs = &ctx.vfs;
442 
443     let the_type = def_decl(def, &vfs, || def.value.trim().into());
444     let docs = def_docs(def, &vfs);
445     let context = None;
446 
447     create_tooltip(the_type, doc_url, context, docs)
448 }
449 
450 fn empty_to_none(s: String) -> Option<String> {
451     if s.trim().is_empty() {
452         None
453     } else {
454         Some(s)
455     }
456 }
457 
458 /// Extracts and processes source documentation for the give `def`.
459 fn def_docs(def: &Def, vfs: &Vfs) -> Option<String> {
460     let save_analysis_docs = || empty_to_none(def.docs.trim().into());
461     extract_and_process_docs(&vfs, def.span.file.as_ref(), def.span.range.row_start)
462         .or_else(save_analysis_docs)
463         .filter(|docs| !docs.trim().is_empty())
464 }
465 
466 /// Returns the type or function declaration from source. If source
467 /// extraction fails, the result of `the_type` is used as a fallback.
468 fn def_decl<F>(def: &Def, vfs: &Vfs, the_type: F) -> String
469 where
470     F: FnOnce() -> String,
471 {
472     extract_decl(vfs, &def.span.file, def.span.range.row_start)
473         .map(|lines| lines.join("\n"))
474         .ok()
475         .or_else(|| Some(the_type()))
476         .unwrap()
477 }
478 
479 /// Creates a tooltip using the function, type or other declaration and
480 /// optional doc URL, context, or markdown documentation. No additional
481 /// processing or formatting is performed.
482 fn create_tooltip(
483     the_type: String,
484     doc_url: Option<String>,
485     context: Option<String>,
486     docs: Option<String>,
487 ) -> Vec<MarkedString> {
488     let mut tooltip = vec![];
489     let rust = "rust".to_string();
490     if !the_type.trim().is_empty() {
491         tooltip.push(MarkedString::from_language_code(rust.clone(), the_type));
492     }
493     if let Some(doc_url) = doc_url {
494         tooltip.push(MarkedString::from_markdown(doc_url));
495     }
496     if let Some(context) = context {
497         tooltip.push(MarkedString::from_language_code(rust, context));
498     }
499     if let Some(docs) = docs {
500         tooltip.push(MarkedString::from_markdown(docs));
501     }
502     tooltip
503 }
504 
505 /// Collapses parent directory references inside of paths.
506 ///
507 /// # Example
508 ///
509 /// ```ignore
510 /// # use std::path::PathBuf;
511 ///
512 /// let path = PathBuf::from("libstd/../liballoc/string.rs");
513 /// let collapsed = collapse_parents(path);
514 /// let expected = PathBuf::from("liballoc/string.rs");
515 /// assert_eq!(expected, collapsed);
516 /// ```
517 fn collapse_parents(path: PathBuf) -> PathBuf {
518     use std::path::Component;
519     let mut components = Vec::new();
520     let mut skip;
521     let mut skip_prev = false;
522     for comp in path.components().rev() {
523         if comp == Component::ParentDir {
524             skip = true;
525         } else {
526             skip = false;
527         }
528         if !skip && !skip_prev {
529             components.insert(0, comp);
530         }
531         skip_prev = skip;
532     }
533 
534     components.iter().fold(PathBuf::new(), |mut path, comp| {
535         path.push(comp);
536         path
537     })
538 }
539 
540 /// Converts a racer `Match` to a save-analysis `Def`. Returns
541 /// `None` if the coordinates are not available on the match.
542 fn racer_match_to_def(ctx: &InitActionContext, m: &racer::Match) -> Option<Def> {
543     use racer::MatchType;
544     fn to_def_kind(kind: &MatchType) -> DefKind {
545         match kind {
546             MatchType::Struct(_) => DefKind::Struct,
547             MatchType::Union(_) => DefKind::Union,
548             MatchType::Module => DefKind::Mod,
549             MatchType::MatchArm => DefKind::Local,
550             MatchType::Function | MatchType::Method(_) => DefKind::Function,
551             MatchType::Crate => DefKind::Mod,
552             MatchType::Let(_)
553             | MatchType::IfLet(_)
554             | MatchType::WhileLet(_)
555             | MatchType::For(_) => DefKind::Local,
556             MatchType::StructField => DefKind::Field,
557             MatchType::Enum(_) => DefKind::Enum,
558             MatchType::EnumVariant(_) => DefKind::StructVariant,
559             MatchType::Type | MatchType::TypeParameter(_) | MatchType::AssocType => DefKind::Type,
560             MatchType::FnArg(_) => DefKind::Local,
561             MatchType::Trait => DefKind::Trait,
562             MatchType::Const => DefKind::Const,
563             MatchType::Static => DefKind::Static,
564             MatchType::Macro => DefKind::Macro,
565             MatchType::Builtin(_) => DefKind::Macro,
566             MatchType::UseAlias(m) => match m.mtype {
567                 MatchType::UseAlias(_) => unreachable!("Nested use aliases"),
568                 _ => to_def_kind(&m.mtype),
569             },
570         }
571     }
572     let kind = to_def_kind(&m.mtype);
573 
574     let contextstr = if kind == DefKind::Mod {
575         use std::env;
576 
577         let home = home::home_dir().unwrap_or_default();
578         let cargo_home =
579             env::var("CARGO_HOME").map(PathBuf::from).unwrap_or_else(|_| home.join(".cargo"));
580         let cargo_registry_src =
581             cargo_home.join("registry").join("src").join("github.com-1ecc6299db9ec823");
582         let rust_src_path = racer::get_rust_src_path().ok();
583 
584         let contextstr = m.contextstr.replacen("\\\\?\\", "", 1);
585         let contextstr_path = PathBuf::from(&contextstr);
586         let contextstr_path = collapse_parents(contextstr_path);
587 
588         // Attempt to tidy up the module path
589         rust_src_path
590             .and_then(|rust_src_path| {
591                 // Make the path relative to Rust src root
592                 contextstr_path.strip_prefix(rust_src_path).ok().map(ToOwned::to_owned)
593             })
594             .or_else(|| {
595                 // Make the path relative to the package root cached in Cargo registry
596                 contextstr_path.strip_prefix(cargo_registry_src).ok().map(ToOwned::to_owned)
597             })
598             .or_else(|| {
599                 // Make the path relative to the root of the project
600                 contextstr_path.strip_prefix(&ctx.current_project).ok().map(ToOwned::to_owned)
601             })
602             .and_then(|path| path.to_str().map(ToOwned::to_owned))
603             .unwrap_or_else(|| contextstr.to_string())
604     } else {
605         m.contextstr.trim_end_matches('{').trim().to_string()
606     };
607 
608     let filepath = m.filepath.clone();
609     let matchstr = m.matchstr.clone();
610     let matchstr_len = matchstr.len() as u32;
611     let docs = m.docs.trim().to_string();
612     m.coords.map(|coords| {
613         assert!(
614             coords.row.0 > 0,
615             "racer_match_to_def: racer returned `0` for a 1-based row: {:?}",
616             m
617         );
618         let (row, col1) = requests::from_racer_coord(coords);
619         let col2 = Column::new_zero_indexed(col1.0 + matchstr_len);
620         let row = Row::new_zero_indexed(row.0 - 1);
621         let span = Span::new(row, row, col1, col2, filepath);
622         let def = Def {
623             kind,
624             span,
625             name: matchstr,
626             value: contextstr,
627             qualname: "".to_string(),
628             distro_crate: false,
629             parent: None,
630             docs,
631         };
632         trace!(
633             "racer_match_to_def: Def {{ kind: {:?}, span: {:?}, name: {:?}, \
634              value: {:?}, qualname: {:?}, distro_crate: {:?}, \
635              parent: {:?}, docs.is_empty: {:?} }}",
636             def.kind,
637             def.span,
638             def.name,
639             def.value,
640             def.qualname,
641             def.distro_crate,
642             def.parent,
643             def.docs.is_empty()
644         );
645         def
646     })
647 }
648 
649 /// Uses racer to synthesize a `Def` for the given `span`. If no appropriate
650 /// match is found with coordinates, `None` is returned.
651 fn racer_def(ctx: &InitActionContext, span: &Span<ZeroIndexed>) -> Option<Def> {
652     let file_path = &span.file;
653 
654     if !file_path.as_path().exists() {
655         error!("racer_def: skipping non-existant file: {:?}", file_path);
656         return None;
657     }
658 
659     let name = ctx.vfs.load_line(file_path.as_path(), span.range.row_start).ok().and_then(|line| {
660         let col_start = span.range.col_start.0 as usize;
661         let col_end = span.range.col_end.0 as usize;
662         line.get(col_start..col_end).map(ToOwned::to_owned)
663     });
664 
665     debug!("racer_def: name: {:?}", name);
666 
667     let results = ::std::panic::catch_unwind(move || {
668         let cache = ctx.racer_cache();
669         let session = ctx.racer_session(&cache);
670         let row = span.range.row_end.one_indexed();
671         let coord = requests::racer_coord(row, span.range.col_end);
672         let location = racer::Location::Coords(coord);
673         trace!("racer_def: file_path: {:?}, location: {:?}", file_path, location);
674         let racer_match = racer::find_definition(file_path, location, &session);
675         trace!("racer_def: match: {:?}", racer_match);
676         racer_match
677             // Avoid creating tooltip text that is exactly the item being hovered over.
678             .filter(|m| name.as_ref().map(|name| name != &m.contextstr).unwrap_or(true))
679             .and_then(|m| racer_match_to_def(ctx, &m))
680     });
681 
682     let results = results.map_err(|_| {
683         error!("racer_def: racer panicked");
684     });
685 
686     results.unwrap_or(None)
687 }
688 
689 /// Formats a struct, enum, union, or trait. The original type is returned
690 /// in the event of an error.
691 fn format_object(rustfmt: Rustfmt, fmt_config: &FmtConfig, the_type: String) -> String {
692     debug!("format_object: {}", the_type);
693     let mut config = fmt_config.get_rustfmt_config().clone();
694     config.set().newline_style(NewlineStyle::Unix);
695     let trimmed = the_type.trim();
696 
697     // Normalize the ending for rustfmt.
698     let object = if trimmed.ends_with(')') {
699         format!("{};", trimmed)
700     } else if trimmed.ends_with('}') || trimmed.ends_with(';') {
701         trimmed.to_string()
702     } else if trimmed.ends_with('{') {
703         let trimmed = trimmed.trim_end_matches('{').to_string();
704         format!("{}{{}}", trimmed)
705     } else {
706         format!("{}{{}}", trimmed)
707     };
708 
709     let formatted = match rustfmt.format(object.clone(), config) {
710         Ok(lines) => match lines.rfind('{') {
711             Some(pos) => lines[0..pos].into(),
712             None => lines,
713         },
714         Err(e) => {
715             error!("format_object: error: {:?}, input: {:?}", e, object);
716             trimmed.to_string()
717         }
718     };
719 
720     // If it's a tuple, remove the trailing ';' and hide non-pub components
721     // for pub types.
722     let result = if formatted.trim().ends_with(';') {
723         let mut decl = formatted.trim().trim_end_matches(';');
724         if let (Some(pos), true) = (decl.rfind('('), decl.ends_with(')')) {
725             let mut hidden_count = 0;
726             let tuple_parts = decl[pos + 1..decl.len() - 1]
727                 .split(',')
728                 .map(|part| {
729                     let part = part.trim();
730                     if decl.starts_with("pub") && !part.starts_with("pub") {
731                         hidden_count += 1;
732                         "_".to_string()
733                     } else {
734                         part.to_string()
735                     }
736                 })
737                 .collect::<Vec<String>>();
738             decl = &decl[0..pos];
739             if hidden_count != tuple_parts.len() {
740                 format!("{}({})", decl, tuple_parts.join(", "))
741             } else {
742                 decl.to_string()
743             }
744         } else {
745             // Not a tuple.
746             decl.into()
747         }
748     } else {
749         // Not a tuple or unit struct.
750         formatted
751     };
752 
753     result.trim().into()
754 }
755 
756 /// Formats a method or function. The original type is returned
757 /// in the event of an error.
758 fn format_method(rustfmt: Rustfmt, fmt_config: &FmtConfig, the_type: String) -> String {
759     trace!("format_method: {}", the_type);
760     let the_type = the_type.trim().trim_end_matches(';').to_string();
761 
762     let mut config = fmt_config.get_rustfmt_config().clone();
763     config.set().newline_style(NewlineStyle::Unix);
764     let tab_spaces = config.tab_spaces();
765 
766     let method = format!("impl Dummy {{ {} {{ unimplemented!() }} }}", the_type);
767 
768     let result = match rustfmt.format(method.clone(), config) {
769         Ok(mut lines) => {
770             if let Some(front_pos) = lines.find('{') {
771                 lines = lines[front_pos..].chars().skip(1).collect();
772             }
773             if let Some(back_pos) = lines.rfind('{') {
774                 lines = lines[0..back_pos].into();
775             }
776             lines
777                 .lines()
778                 .filter(|line| line.trim() != "")
779                 .map(|line| {
780                     let mut spaces = tab_spaces + 1;
781                     let should_trim = |c: char| {
782                         spaces = spaces.saturating_sub(1);
783                         spaces > 0 && c.is_whitespace()
784                     };
785                     let line = line.trim_start_matches(should_trim);
786                     format!("{}\n", line)
787                 })
788                 .collect()
789         }
790         Err(e) => {
791             error!("format_method: error: {:?}, input: {:?}", e, method);
792             the_type
793         }
794     };
795 
796     result.trim().into()
797 }
798 
799 /// Builds a hover tooltip composed of the function signature or type declaration, doc URL
800 /// (if available in the save-analysis), source extracted documentation, and code context
801 /// for local variables.
802 pub fn tooltip(
803     ctx: &InitActionContext,
804     params: &TextDocumentPositionParams,
805 ) -> Result<Tooltip, ResponseError> {
806     let analysis = &ctx.analysis;
807 
808     let hover_file_path = parse_file_path!(&params.text_document.uri, "hover")?;
809     let hover_span = ctx.convert_pos_to_span(hover_file_path, params.position);
810     let hover_span_doc = analysis.docs(&hover_span).unwrap_or_else(|_| String::new());
811     let hover_span_typ = analysis.show_type(&hover_span).unwrap_or_else(|_| String::new());
812     let hover_span_def = analysis.id(&hover_span).and_then(|id| analysis.get_def(id));
813 
814     trace!("tooltip: span: {:?}", hover_span);
815     trace!("tooltip: span_doc: {:?}", hover_span_doc);
816     trace!("tooltip: span_typ: {:?}", hover_span_typ);
817     trace!("tooltip: span_def: {:?}", hover_span_def);
818 
819     let racer_fallback_enabled = ctx.config.lock().unwrap().racer_completion;
820 
821     // Fallback to racer if the def was not available and racer is enabled.
822     let hover_span_def = hover_span_def.or_else(|e| {
823         debug!("tooltip: racer_fallback_enabled: {}", racer_fallback_enabled);
824         if racer_fallback_enabled {
825             debug!("tooltip: span_def is empty, attempting with racer");
826             racer_def(&ctx, &hover_span).ok_or_else(|| {
827                 debug!("tooltip: racer returned an empty result");
828                 e
829             })
830         } else {
831             Err(e)
832         }
833     });
834 
835     let doc_url = analysis.doc_url(&hover_span).ok();
836 
837     let contents = if let Ok(def) = hover_span_def {
838         if def.kind == DefKind::Local && def.span == hover_span && def.qualname.contains('$') {
839             tooltip_local_variable_decl(&ctx, &def, doc_url)
840         } else if def.kind == DefKind::Local
841             && def.span != hover_span
842             && !def.qualname.contains('$')
843         {
844             tooltip_function_arg_usage(&ctx, &def, doc_url)
845         } else if def.kind == DefKind::Local && def.span != hover_span && def.qualname.contains('$')
846         {
847             tooltip_local_variable_usage(&ctx, &def, doc_url)
848         } else if def.kind == DefKind::Local && def.span == hover_span {
849             tooltip_function_signature_arg(&ctx, &def, doc_url)
850         } else {
851             match def.kind {
852                 DefKind::TupleVariant | DefKind::StructVariant | DefKind::Field => {
853                     tooltip_field_or_variant(&ctx, &def, doc_url)
854                 }
855                 DefKind::Enum | DefKind::Union | DefKind::Struct | DefKind::Trait => {
856                     tooltip_struct_enum_union_trait(&ctx, &def, doc_url)
857                 }
858                 DefKind::Function | DefKind::Method | DefKind::ForeignFunction => {
859                     tooltip_function_method(&ctx, &def, doc_url)
860                 }
861                 DefKind::Mod => tooltip_mod(&ctx, &def, doc_url),
862                 DefKind::Static | DefKind::ForeignStatic | DefKind::Const => {
863                     tooltip_static_const_decl(&ctx, &def, doc_url)
864                 }
865                 DefKind::Type => tooltip_type(&ctx, &def, doc_url),
866                 _ => {
867                     debug!(
868                         "tooltip: ignoring def: \
869                          name: {:?}, \
870                          kind: {:?}, \
871                          value: {:?}, \
872                          qualname: {:?}, \
873                          parent: {:?}",
874                         def.name, def.kind, def.value, def.qualname, def.parent
875                     );
876 
877                     Vec::default()
878                 }
879             }
880         }
881     } else {
882         debug!("tooltip: def is empty");
883         Vec::default()
884     };
885     debug!("tooltip: contents.len: {}", contents.len());
886     Ok(Tooltip { contents, range: hover_span.range })
887 }
888 
889 #[cfg(test)]
890 #[allow(clippy::expect_fun_call)]
891 pub mod test {
892     use super::*;
893     use crate::actions::format::Rustfmt;
894 
895     pub fn fixtures_dir() -> &'static Path {
896         Path::new(env!("FIXTURES_DIR"))
897     }
898 
899     /// Strips indentation from string literals by examining
900     /// the indent of the first non-empty line. Preceding
901     /// and trailing whitespace is also removed.
902     fn noindent(text: &str) -> String {
903         let indent = text
904             .lines()
905             .filter(|line| !line.trim().is_empty())
906             .peekable()
907             .peek()
908             .map(|first_non_empty_line| {
909                 first_non_empty_line
910                     .chars()
911                     .scan(0, |_, ch| if ch.is_whitespace() { Some(1) } else { None })
912                     .fuse()
913                     .sum()
914             })
915             .unwrap_or(0);
916 
917         text.lines()
918             .map(|line| line.chars().skip(indent).collect::<String>())
919             .collect::<Vec<String>>()
920             .join("\n")
921             .trim()
922             .to_string()
923     }
924 
925     #[test]
926     fn test_noindent() {
927         let lines = noindent(
928             "
929 
930             Hello, world ! ! !
931             The next line
932                 Indented line
933             Last line
934 
935         ",
936         );
937         assert_eq!("Hello, world ! ! !\nThe next line\n    Indented line\nLast line", &lines);
938 
939         let lines = noindent(
940             "
941 
942                 Hello, world ! ! !
943                 The next line
944                     Indented line
945                 Last line
946 
947         ",
948         );
949         assert_eq!("Hello, world ! ! !\nThe next line\n    Indented line\nLast line", &lines);
950     }
951 
952     #[test]
953     fn test_process_docs_rust_blocks() {
954         let docs = &noindent("
955             Brief one liner.
956 
957             Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vitae ex
958             vel mi egestas semper in non dolor. Proin ut arcu at odio hendrerit consequat.
959 
960             # Examples
961 
962             Donec ullamcorper risus quis massa sollicitudin, id faucibus nibh bibendum.
963 
964             ## Hidden code lines and proceeding whitespace is removed and meta attributes are preserved
965 
966             ```
967             # extern crate foo;
968 
969             use foo::bar;
970 
971             #[derive(Debug)]
972             struct Baz(u32);
973 
974             let baz = Baz(1);
975             ```
976 
977             ## Rust code block attributes are converted to 'rust'
978 
979             ```compile_fail,E0123
980             let foo = \"compile_fail\"
981             ```
982 
983             ```no_run
984             let foo = \"no_run\";
985             ```
986 
987             ```ignore
988             let foo = \"ignore\";
989             ```
990 
991             ```should_panic
992             let foo = \"should_panic\";
993             ```
994 
995             ```should_panic,ignore,no_run,compile_fail
996             let foo = \"should_panic,ignore,no_run,compile_fail\";
997             ```
998 
999             ## Inner comments and indentation is preserved
1000 
1001             ```
1002             /// inner doc comment
1003             fn foobar() {
1004                 // inner comment
1005                 let indent = 1;
1006             }
1007             ```
1008 
1009             ## Module attributes are preserved
1010 
1011             ```
1012             #![allow(dead_code, unused_imports)]
1013             ```
1014         ");
1015 
1016         let expected = noindent("
1017             Brief one liner.
1018 
1019             Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vitae ex
1020             vel mi egestas semper in non dolor. Proin ut arcu at odio hendrerit consequat.
1021 
1022             # Examples
1023 
1024             Donec ullamcorper risus quis massa sollicitudin, id faucibus nibh bibendum.
1025 
1026             ## Hidden code lines and proceeding whitespace is removed and meta attributes are preserved
1027 
1028             ```rust
1029             use foo::bar;
1030 
1031             #[derive(Debug)]
1032             struct Baz(u32);
1033 
1034             let baz = Baz(1);
1035             ```
1036 
1037             ## Rust code block attributes are converted to 'rust'
1038 
1039             ```rust
1040             let foo = \"compile_fail\"
1041             ```
1042 
1043             ```rust
1044             let foo = \"no_run\";
1045             ```
1046 
1047             ```rust
1048             let foo = \"ignore\";
1049             ```
1050 
1051             ```rust
1052             let foo = \"should_panic\";
1053             ```
1054 
1055             ```rust
1056             let foo = \"should_panic,ignore,no_run,compile_fail\";
1057             ```
1058 
1059             ## Inner comments and indentation is preserved
1060 
1061             ```rust
1062             /// inner doc comment
1063             fn foobar() {
1064                 // inner comment
1065                 let indent = 1;
1066             }
1067             ```
1068 
1069             ## Module attributes are preserved
1070 
1071             ```rust
1072             #![allow(dead_code, unused_imports)]
1073             ```
1074         ");
1075 
1076         let actual = process_docs(docs);
1077         assert_eq!(expected, actual);
1078     }
1079 
1080     #[test]
test_process_docs_bash_block()1081     fn test_process_docs_bash_block() {
1082         let expected = noindent(
1083             "
1084             Brief one liner.
1085 
1086             ```bash
1087             # non rust-block comment lines are preserved
1088             ls -la
1089             ```
1090         ",
1091         );
1092 
1093         let actual = process_docs(&expected);
1094         assert_eq!(expected, actual);
1095     }
1096 
1097     #[test]
test_process_docs_racer_returns_extra_slashes()1098     fn test_process_docs_racer_returns_extra_slashes() {
1099         let docs = noindent(
1100             "
1101             ////////////////////////////////////////////////////////////////////////////////
1102 
1103             Spawns a new thread, returning a [`JoinHandle`] for it.
1104 
1105             The join handle will implicitly *detach* the child thread upon being
1106             dropped. In this case, the child thread may outlive the parent (unless
1107         ",
1108         );
1109 
1110         let expected = noindent(
1111             "
1112             Spawns a new thread, returning a [`JoinHandle`] for it.
1113 
1114             The join handle will implicitly *detach* the child thread upon being
1115             dropped. In this case, the child thread may outlive the parent (unless
1116         ",
1117         );
1118 
1119         let actual = process_docs(&docs);
1120         assert_eq!(expected, actual);
1121     }
1122 
1123     #[test]
test_format_method()1124     fn test_format_method() {
1125         let fmt = Rustfmt::Internal;
1126         let config = &FmtConfig::default();
1127 
1128         let input = "fn foo() -> ()";
1129         let result = format_method(fmt.clone(), config, input.into());
1130         assert_eq!(input, &result, "function explicit void return");
1131 
1132         let input = "fn foo()";
1133         let expected = "fn foo()";
1134         let result = format_method(fmt.clone(), config, input.into());
1135         assert_eq!(expected, &result, "function");
1136 
1137         let input = "fn foo() -> Thing";
1138         let expected = "fn foo() -> Thing";
1139         let result = format_method(fmt.clone(), config, input.into());
1140         assert_eq!(expected, &result, "function with return");
1141 
1142         let input = "fn foo(&self);";
1143         let expected = "fn foo(&self)";
1144         let result = format_method(fmt.clone(), config, input.into());
1145         assert_eq!(expected, &result, "method");
1146 
1147         let input = "fn foo<T>(t: T) where T: Copy";
1148         let expected = noindent(
1149             "
1150             fn foo<T>(t: T)
1151             where
1152                 T: Copy,
1153         ",
1154         );
1155         let result = format_method(fmt.clone(), config, input.into());
1156         assert_eq!(expected, result, "function with generic parameters");
1157 
1158         let input = "fn foo<T>(&self, t: T) where T: Copy";
1159         let expected = noindent(
1160             "
1161             fn foo<T>(&self, t: T)
1162             where
1163                 T: Copy,
1164         ",
1165         );
1166         let result = format_method(fmt.clone(), config, input.into());
1167         assert_eq!(expected, result, "method with type parameters");
1168 
1169         let input = noindent(
1170             "   fn foo<T>(
1171                     &self,
1172             t: T)
1173                 where
1174             T: Copy
1175 
1176         ",
1177         );
1178         let expected = noindent(
1179             "
1180             fn foo<T>(&self, t: T)
1181             where
1182                 T: Copy,
1183         ",
1184         );
1185         let result = format_method(fmt.clone(), config, input);
1186         assert_eq!(expected, result, "method with type parameters; corrected spacing");
1187 
1188         let input = "fn really_really_really_really_long_name<T>(foo_thing: String, bar_thing: Thing, baz_thing: Vec<T>, foo_other: u32, bar_other: i32) -> Thing";
1189         let expected = noindent(
1190             "
1191             fn really_really_really_really_long_name<T>(
1192                 foo_thing: String,
1193                 bar_thing: Thing,
1194                 baz_thing: Vec<T>,
1195                 foo_other: u32,
1196                 bar_other: i32,
1197             ) -> Thing
1198         ",
1199         );
1200         let result = format_method(fmt.clone(), config, input.into());
1201         assert_eq!(expected, result, "long function signature");
1202 
1203         let input = "fn really_really_really_really_long_name(&self, foo_thing: String, bar_thing: Thing, baz_thing: Vec<T>, foo_other: u32, bar_other: i32) -> Thing";
1204         let expected = noindent(
1205             "
1206             fn really_really_really_really_long_name(
1207                 &self,
1208                 foo_thing: String,
1209                 bar_thing: Thing,
1210                 baz_thing: Vec<T>,
1211                 foo_other: u32,
1212                 bar_other: i32,
1213             ) -> Thing
1214         ",
1215         );
1216         let result = format_method(fmt.clone(), config, input.into());
1217         assert_eq!(expected, result, "long method signature with generic");
1218 
1219         let input = noindent(
1220             "
1221             fn matrix_something(
1222             _a_matrix: [[f32; 4]; 4],
1223             _b_matrix: [[f32; 4]; 4],
1224             _c_matrix: [[f32; 4]; 4],
1225             _d_matrix: [[f32; 4]; 4],
1226             )
1227         ",
1228         );
1229         let expected = noindent(
1230             "
1231             fn matrix_something(
1232                 _a_matrix: [[f32; 4]; 4],
1233                 _b_matrix: [[f32; 4]; 4],
1234                 _c_matrix: [[f32; 4]; 4],
1235                 _d_matrix: [[f32; 4]; 4],
1236             )
1237         ",
1238         );
1239         let result = format_method(fmt, config, input);
1240         assert_eq!(expected, result, "function with multiline args");
1241     }
1242 
1243     #[test]
test_extract_decl()1244     fn test_extract_decl() {
1245         let vfs = Vfs::new();
1246         let file = fixtures_dir().join("hover/src/test_extract_decl.rs");
1247 
1248         let expected = "pub fn foo() -> Foo<u32>";
1249         let row_start = Row::new_zero_indexed(0);
1250         let actual = extract_decl(&vfs, &file, row_start).expect("function declaration").join("\n");
1251         assert_eq!(expected, actual);
1252 
1253         let expected = "pub struct Foo<T>";
1254         let row_start = Row::new_zero_indexed(5);
1255         let actual = extract_decl(&vfs, &file, row_start).expect("struct declaration").join("\n");
1256         assert_eq!(expected, actual);
1257 
1258         let expected = "pub enum Bar";
1259         let row_start = Row::new_zero_indexed(10);
1260         let actual = extract_decl(&vfs, &file, row_start).expect("enum declaration").join("\n");
1261         assert_eq!(expected, actual);
1262 
1263         let expected = "pub struct NewType(pub u32, f32)";
1264         let row_start = Row::new_zero_indexed(15);
1265         let actual = extract_decl(&vfs, &file, row_start).expect("tuple declaration").join("\n");
1266         assert_eq!(expected, actual);
1267 
1268         let expected = "pub fn new() -> NewType";
1269         let row_start = Row::new_zero_indexed(18);
1270         let actual =
1271             extract_decl(&vfs, &file, row_start).expect("struct function declaration").join("\n");
1272         assert_eq!(expected, actual);
1273 
1274         let expected = "pub fn bar<T: Copy + Add>(&self, the_really_long_name_string: String, the_really_long_name_foo: Foo<T>) -> Vec<(String, Foo<T>)>";
1275         let row_start = Row::new_zero_indexed(22);
1276         let actual = extract_decl(&vfs, &file, row_start)
1277             .expect("long struct method declaration with generics")
1278             .join("\n");
1279         assert_eq!(expected, actual);
1280 
1281         let expected = "pub trait Baz<T> where T: Copy";
1282         let row_start = Row::new_zero_indexed(27);
1283         let actual = extract_decl(&vfs, &file, row_start).expect("enum declaration").join("\n");
1284         assert_eq!(expected, actual);
1285 
1286         let expected = "fn make_copy(&self) -> Self";
1287         let row_start = Row::new_zero_indexed(28);
1288         let actual =
1289             extract_decl(&vfs, &file, row_start).expect("trait method declaration").join("\n");
1290         assert_eq!(expected, actual);
1291 
1292         let expected = "fn make_copy(&self) -> Self";
1293         let row_start = Row::new_zero_indexed(32);
1294         let actual =
1295             extract_decl(&vfs, &file, row_start).expect("trait method implementation").join("\n");
1296         assert_eq!(expected, actual);
1297 
1298         let expected = noindent(
1299             "
1300             pub trait Qeh<T, U>
1301             where T: Copy,
1302             U: Clone
1303         ",
1304         );
1305         let row_start = Row::new_zero_indexed(37);
1306         let actual =
1307             extract_decl(&vfs, &file, row_start).expect("trait declaration multiline").join("\n");
1308         assert_eq!(expected, actual);
1309 
1310         let expected = noindent(
1311             "
1312             pub fn multiple_lines(
1313             s: String,
1314             i: i32
1315             )
1316         ",
1317         );
1318         let row_start = Row::new_zero_indexed(43);
1319         let actual = extract_decl(&vfs, &file, row_start)
1320             .expect("function declaration multiline")
1321             .join("\n");
1322         assert_eq!(expected, actual);
1323     }
1324 
1325     #[test]
test_format_object()1326     fn test_format_object() {
1327         let fmt = Rustfmt::Internal;
1328         let config = &FmtConfig::default();
1329 
1330         let input = "pub struct Box<T: ?Sized>(Unique<T>);";
1331         let result = format_object(fmt.clone(), config, input.into());
1332         assert_eq!(
1333             "pub struct Box<T: ?Sized>", &result,
1334             "tuple struct with all private fields has hidden components"
1335         );
1336 
1337         let input = "pub struct Thing(pub u32);";
1338         let result = format_object(fmt.clone(), config, input.into());
1339         assert_eq!(
1340             "pub struct Thing(pub u32)", &result,
1341             "tuple struct with trailing ';' from racer"
1342         );
1343 
1344         let input = "pub struct Thing(pub u32)";
1345         let result = format_object(fmt.clone(), config, input.into());
1346         assert_eq!("pub struct Thing(pub u32)", &result, "pub tuple struct");
1347 
1348         let input = "pub struct Thing(pub u32, i32)";
1349         let result = format_object(fmt.clone(), config, input.into());
1350         assert_eq!(
1351             "pub struct Thing(pub u32, _)", &result,
1352             "non-pub components of pub tuples should be hidden"
1353         );
1354 
1355         let input = "struct Thing(u32, i32)";
1356         let result = format_object(fmt.clone(), config, input.into());
1357         assert_eq!(
1358             "struct Thing(u32, i32)", &result,
1359             "private tuple struct may show private components"
1360         );
1361 
1362         let input = "pub struct Thing<T: Copy>";
1363         let result = format_object(fmt.clone(), config, input.into());
1364         assert_eq!("pub struct Thing<T: Copy>", &result, "pub struct");
1365 
1366         let input = "pub struct Thing<T: Copy> {";
1367         let result = format_object(fmt.clone(), config, input.into());
1368         assert_eq!(
1369             "pub struct Thing<T: Copy>", &result,
1370             "pub struct with trailing '{{' from racer"
1371         );
1372 
1373         let input = "pub struct Thing { x: i32 }";
1374         let result = format_object(fmt.clone(), config, input.into());
1375         assert_eq!("pub struct Thing", &result, "pub struct with body");
1376 
1377         let input = "pub enum Foobar { Foo, Bar }";
1378         let result = format_object(fmt.clone(), config, input.into());
1379         assert_eq!("pub enum Foobar", &result, "pub enum with body");
1380 
1381         let input = "pub trait Thing<T, U> where T: Copy + Sized, U: Clone";
1382         let expected = noindent(
1383             "
1384             pub trait Thing<T, U>
1385             where
1386                 T: Copy + Sized,
1387                 U: Clone,
1388         ",
1389         );
1390         let result = format_object(fmt, config, input.into());
1391         assert_eq!(expected, result, "trait with where clause");
1392     }
1393 
1394     #[test]
test_extract_decl_multiline_empty_function()1395     fn test_extract_decl_multiline_empty_function() {
1396         let vfs = Vfs::new();
1397         let file = fixtures_dir().join("hover/src/test_extract_decl_multiline_empty_function.rs");
1398 
1399         let expected = noindent(
1400             "
1401             fn matrix_something(
1402             _a_matrix: [[f32; 4]; 4],
1403             _b_matrix: [[f32; 4]; 4],
1404             _c_matrix: [[f32; 4]; 4],
1405             _d_matrix: [[f32; 4]; 4],
1406             )
1407         ",
1408         );
1409         let row_start = Row::new_zero_indexed(1);
1410         let actual = extract_decl(&vfs, &file, row_start)
1411             .expect("the empty body should not be extracted")
1412             .join("\n");
1413         assert_eq!(expected, actual);
1414     }
1415 
1416     #[test]
test_extract_docs_module_docs_with_attribute()1417     fn test_extract_docs_module_docs_with_attribute() {
1418         let vfs = Vfs::new();
1419         let file = fixtures_dir().join("hover/src/test_extract_docs_module_docs_with_attribute.rs");
1420         let row_start = Row::new_zero_indexed(0);
1421         let actual = extract_docs(&vfs, &file, row_start)
1422             .expect(&format!("failed to extract docs: {:?}", file))
1423             .join("\n");
1424 
1425         let expected = noindent(
1426             "
1427             Begin module docs
1428 
1429             Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
1430             tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus.
1431             In hac habitasse platea dictumst.
1432 
1433             End module docs.
1434         ",
1435         );
1436 
1437         assert_eq!(expected, actual, "module docs without a copyright header");
1438     }
1439 
1440     #[test]
test_extract_docs_module_docs_no_copyright()1441     fn test_extract_docs_module_docs_no_copyright() {
1442         let vfs = Vfs::new();
1443         let file = fixtures_dir().join("hover/src/test_extract_docs_module_docs_no_copyright.rs");
1444         let row_start = Row::new_zero_indexed(0);
1445         let actual = extract_docs(&vfs, &file, row_start)
1446             .expect(&format!("failed to extract docs: {:?}", file))
1447             .join("\n");
1448 
1449         let expected = noindent(
1450             "
1451             Begin module docs
1452 
1453             Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
1454             tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus.
1455             In hac habitasse platea dictumst.
1456 
1457             End module docs.
1458         ",
1459         );
1460 
1461         assert_eq!(expected, actual, "module docs without a copyright header");
1462     }
1463 
1464     #[test]
test_extract_docs_comment_block()1465     fn test_extract_docs_comment_block() {
1466         let vfs = Vfs::new();
1467         let file = fixtures_dir().join("hover/src/test_extract_docs_comment_block.rs");
1468         let row_start = Row::new_zero_indexed(11);
1469         let actual = extract_docs(&vfs, &file, row_start)
1470             .expect(&format!("failed to extract docs: {:?}", file))
1471             .join("\n");
1472 
1473         let expected = noindent(
1474             "
1475             The standard library often has comment header blocks that should not be
1476             included.
1477 
1478             Nam efficitur dapibus lectus consequat porta. Pellentesque augue metus,
1479             vestibulum nec massa at, aliquet consequat ex.
1480 
1481             End of spawn docs
1482         ",
1483         );
1484 
1485         assert_eq!(expected, actual);
1486     }
1487 
1488     #[test]
test_extract_docs_empty_line_before_decl()1489     fn test_extract_docs_empty_line_before_decl() {
1490         let vfs = Vfs::new();
1491         let file = fixtures_dir().join("hover/src/test_extract_docs_empty_line_before_decl.rs");
1492         let row_start = Row::new_zero_indexed(8);
1493         let actual = extract_docs(&vfs, &file, row_start)
1494             .expect(&format!("failed to extract docs: {:?}", file))
1495             .join("\n");
1496 
1497         let expected = noindent(
1498             "
1499             Begin empty before decl
1500 
1501             Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus,
1502             iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel
1503             lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat.
1504 
1505             End empty line before decl.
1506         ",
1507         );
1508 
1509         assert_eq!(expected, actual);
1510     }
1511 
1512     #[test]
test_extract_docs_module_docs()1513     fn test_extract_docs_module_docs() {
1514         let vfs = Vfs::new();
1515         let file = fixtures_dir().join("hover/src/test_extract_docs_module_docs.rs");
1516 
1517         let row_start = Row::new_zero_indexed(0);
1518         let actual = extract_docs(&vfs, &file, row_start)
1519             .expect(&format!("failed to extract docs: {:?}", file))
1520             .join("\n");
1521 
1522         let expected = noindent(
1523             "
1524             Begin module docs
1525 
1526             Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
1527             tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus.
1528             In hac habitasse platea dictumst.
1529 
1530             End module docs.
1531         ",
1532         );
1533 
1534         assert_eq!(expected, actual);
1535 
1536         let row_start = Row::new_zero_indexed(11);
1537         let actual = extract_docs(&vfs, &file, row_start)
1538             .expect(&format!("failed to extract docs: {:?}", file))
1539             .join("\n");
1540 
1541         let expected = noindent(
1542             "
1543             Begin first item docs
1544 
1545             The first item docs should not pick up the module docs.
1546         ",
1547         );
1548 
1549         assert_eq!(expected, actual);
1550     }
1551 
1552     #[test]
test_extract_docs_attributes()1553     fn test_extract_docs_attributes() {
1554         let vfs = Vfs::new();
1555         let file = fixtures_dir().join("hover/src/test_extract_docs_attributes.rs");
1556 
1557         let row_start = Row::new_zero_indexed(11);
1558         let actual = extract_docs(&vfs, &file, row_start)
1559             .expect(&format!("failed to extract docs: {:?}", file))
1560             .join("\n");
1561 
1562         let expected = noindent(
1563             "
1564             Begin multiline attribute
1565 
1566             Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus,
1567             iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel
1568             lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat.
1569 
1570             End multiline attribute
1571         ",
1572         );
1573 
1574         assert_eq!(expected, actual);
1575 
1576         let row_start = Row::new_zero_indexed(22);
1577         let actual = extract_docs(&vfs, &file, row_start)
1578             .expect(&format!("failed to extract docs: {:?}", file))
1579             .join("\n");
1580 
1581         let expected = noindent(
1582             "
1583             Begin single line attribute
1584 
1585             Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus,
1586             iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel
1587             lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat.
1588 
1589             End single line attribute.
1590         ",
1591         );
1592 
1593         assert_eq!(expected, actual);
1594     }
1595 
1596     #[test]
test_extract_docs_comment_first_line()1597     fn test_extract_docs_comment_first_line() {
1598         let vfs = Vfs::new();
1599         let file = fixtures_dir().join("hover/src/test_extract_docs_comment_first_line.rs");
1600 
1601         let row_start = Row::new_zero_indexed(1);
1602         let actual = extract_docs(&vfs, &file, row_start)
1603             .expect(&format!("failed to extract docs: {:?}", file))
1604             .join("\n");
1605 
1606         let expected = "First line comment";
1607 
1608         assert_eq!(expected, actual);
1609     }
1610 }
1611