aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/hover.rs
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-06-14 14:25:10 +0100
committerLukas Wirth <[email protected]>2021-06-14 14:25:10 +0100
commita93d166f0fecb748d8cb04aab7f5406bf6308c2d (patch)
treeceda7b8ea1bf1c2949c75191e4faa41693f8f19e /crates/ide/src/hover.rs
parent388a91c8a8d542f7a8e0ddff879cce4d4c2b20ae (diff)
Make documentation on hover configurable
Diffstat (limited to 'crates/ide/src/hover.rs')
-rw-r--r--crates/ide/src/hover.rs179
1 files changed, 86 insertions, 93 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index c08516805..23f2b48b4 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,8 +1,5 @@
1use either::Either; 1use either::Either;
2use hir::{ 2use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay};
3 AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, InFile, Module,
4 ModuleDef, Semantics,
5};
6use ide_db::{ 3use ide_db::{
7 base_db::SourceDatabase, 4 base_db::SourceDatabase,
8 defs::{Definition, NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
@@ -40,6 +37,7 @@ pub struct HoverConfig {
40 pub goto_type_def: bool, 37 pub goto_type_def: bool,
41 pub links_in_hover: bool, 38 pub links_in_hover: bool,
42 pub markdown: bool, 39 pub markdown: bool,
40 pub documentation: bool,
43} 41}
44 42
45impl HoverConfig { 43impl HoverConfig {
@@ -51,14 +49,15 @@ impl HoverConfig {
51 goto_type_def: false, 49 goto_type_def: false,
52 links_in_hover: true, 50 links_in_hover: true,
53 markdown: true, 51 markdown: true,
52 documentation: true,
54 }; 53 };
55 54
56 pub fn any(&self) -> bool { 55 pub fn any_actions(&self) -> bool {
57 self.implementations || self.references || self.runnable() || self.goto_type_def 56 self.implementations || self.references || self.runnable() || self.goto_type_def
58 } 57 }
59 58
60 pub fn none(&self) -> bool { 59 pub fn no_actions(&self) -> bool {
61 !self.any() 60 !self.any_actions()
62 } 61 }
63 62
64 pub fn runnable(&self) -> bool { 63 pub fn runnable(&self) -> bool {
@@ -97,9 +96,10 @@ pub(crate) fn hover(
97 db: &RootDatabase, 96 db: &RootDatabase,
98 position: FilePosition, 97 position: FilePosition,
99 links_in_hover: bool, 98 links_in_hover: bool,
99 documentation: bool,
100 markdown: bool, 100 markdown: bool,
101) -> Option<RangeInfo<HoverResult>> { 101) -> Option<RangeInfo<HoverResult>> {
102 let sema = Semantics::new(db); 102 let sema = hir::Semantics::new(db);
103 let file = sema.parse(position.file_id).syntax().clone(); 103 let file = sema.parse(position.file_id).syntax().clone();
104 let token = pick_best(file.token_at_offset(position.offset))?; 104 let token = pick_best(file.token_at_offset(position.offset))?;
105 let token = sema.descend_into_macros(token); 105 let token = sema.descend_into_macros(token);
@@ -131,7 +131,7 @@ pub(crate) fn hover(
131 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; 131 let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
132 let (idl_range, link, ns) = 132 let (idl_range, link, ns) =
133 extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| { 133 extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| {
134 let InFile { file_id, value: range } = doc_mapping.map(range)?; 134 let hir::InFile { file_id, value: range } = doc_mapping.map(range)?;
135 if file_id == position.file_id.into() && range.contains(position.offset) { 135 if file_id == position.file_id.into() && range.contains(position.offset) {
136 Some((range, link, ns)) 136 Some((range, link, ns))
137 } else { 137 } else {
@@ -151,12 +151,14 @@ pub(crate) fn hover(
151 151
152 if let Some(definition) = definition { 152 if let Some(definition) = definition {
153 let famous_defs = match &definition { 153 let famous_defs = match &definition {
154 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { 154 Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
155 Some(FamousDefs(&sema, sema.scope(&node).krate())) 155 Some(FamousDefs(&sema, sema.scope(&node).krate()))
156 } 156 }
157 _ => None, 157 _ => None,
158 }; 158 };
159 if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) { 159 if let Some(markup) =
160 hover_for_definition(db, definition, famous_defs.as_ref(), documentation)
161 {
160 res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown); 162 res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown);
161 if let Some(action) = show_implementations_action(db, definition) { 163 if let Some(action) = show_implementations_action(db, definition) {
162 res.actions.push(action); 164 res.actions.push(action);
@@ -261,8 +263,10 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
261 } 263 }
262 264
263 let adt = match def { 265 let adt = match def {
264 Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action), 266 Definition::ModuleDef(hir::ModuleDef::Trait(it)) => {
265 Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it), 267 return it.try_to_nav(db).map(to_action)
268 }
269 Definition::ModuleDef(hir::ModuleDef::Adt(it)) => Some(it),
266 Definition::SelfType(it) => it.self_ty(db).as_adt(), 270 Definition::SelfType(it) => it.self_ty(db).as_adt(),
267 _ => None, 271 _ => None,
268 }?; 272 }?;
@@ -271,25 +275,27 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
271 275
272fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> { 276fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
273 match def { 277 match def {
274 Definition::ModuleDef(ModuleDef::Function(it)) => it.try_to_nav(db).map(|nav_target| { 278 Definition::ModuleDef(hir::ModuleDef::Function(it)) => {
275 HoverAction::Reference(FilePosition { 279 it.try_to_nav(db).map(|nav_target| {
276 file_id: nav_target.file_id, 280 HoverAction::Reference(FilePosition {
277 offset: nav_target.focus_or_full_range().start(), 281 file_id: nav_target.file_id,
282 offset: nav_target.focus_or_full_range().start(),
283 })
278 }) 284 })
279 }), 285 }
280 _ => None, 286 _ => None,
281 } 287 }
282} 288}
283 289
284fn runnable_action( 290fn runnable_action(
285 sema: &Semantics<RootDatabase>, 291 sema: &hir::Semantics<RootDatabase>,
286 def: Definition, 292 def: Definition,
287 file_id: FileId, 293 file_id: FileId,
288) -> Option<HoverAction> { 294) -> Option<HoverAction> {
289 match def { 295 match def {
290 Definition::ModuleDef(it) => match it { 296 Definition::ModuleDef(it) => match it {
291 ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable), 297 hir::ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
292 ModuleDef::Function(func) => { 298 hir::ModuleDef::Function(func) => {
293 let src = func.source(sema.db)?; 299 let src = func.source(sema.db)?;
294 if src.file_id != file_id.into() { 300 if src.file_id != file_id.into() {
295 cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment); 301 cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
@@ -306,19 +312,19 @@ fn runnable_action(
306} 312}
307 313
308fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> { 314fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
309 let mut targets: Vec<ModuleDef> = Vec::new(); 315 let mut targets: Vec<hir::ModuleDef> = Vec::new();
310 let mut push_new_def = |item: ModuleDef| { 316 let mut push_new_def = |item: hir::ModuleDef| {
311 if !targets.contains(&item) { 317 if !targets.contains(&item) {
312 targets.push(item); 318 targets.push(item);
313 } 319 }
314 }; 320 };
315 321
316 if let Definition::GenericParam(GenericParam::TypeParam(it)) = def { 322 if let Definition::GenericParam(hir::GenericParam::TypeParam(it)) = def {
317 it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into())); 323 it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
318 } else { 324 } else {
319 let ty = match def { 325 let ty = match def {
320 Definition::Local(it) => it.ty(db), 326 Definition::Local(it) => it.ty(db),
321 Definition::GenericParam(GenericParam::ConstParam(it)) => it.ty(db), 327 Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
322 _ => return None, 328 _ => return None,
323 }; 329 };
324 330
@@ -348,29 +354,20 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
348 Some(HoverAction::GoToType(targets)) 354 Some(HoverAction::GoToType(targets))
349} 355}
350 356
351fn hover_markup( 357fn hover_markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
352 docs: Option<String>, 358 let mut buf = String::new();
353 desc: Option<String>,
354 mod_path: Option<String>,
355) -> Option<Markup> {
356 match desc {
357 Some(desc) => {
358 let mut buf = String::new();
359
360 if let Some(mod_path) = mod_path {
361 if !mod_path.is_empty() {
362 format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
363 }
364 }
365 format_to!(buf, "```rust\n{}\n```", desc);
366 359
367 if let Some(doc) = docs { 360 if let Some(mod_path) = mod_path {
368 format_to!(buf, "\n___\n\n{}", doc); 361 if !mod_path.is_empty() {
369 } 362 format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
370 Some(buf.into())
371 } 363 }
372 None => docs.map(Markup::from),
373 } 364 }
365 format_to!(buf, "```rust\n{}\n```", desc);
366
367 if let Some(doc) = docs {
368 format_to!(buf, "\n___\n\n{}", doc);
369 }
370 Some(buf.into())
374} 371}
375 372
376fn process_markup( 373fn process_markup(
@@ -396,11 +393,11 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
396 Definition::Field(f) => Some(f.parent_def(db).name(db)), 393 Definition::Field(f) => Some(f.parent_def(db).name(db)),
397 Definition::Local(l) => l.parent(db).name(db), 394 Definition::Local(l) => l.parent(db).name(db),
398 Definition::ModuleDef(md) => match md { 395 Definition::ModuleDef(md) => match md {
399 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { 396 hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
400 AssocItemContainer::Trait(t) => Some(t.name(db)), 397 hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
401 AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)), 398 hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
402 }, 399 },
403 ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), 400 hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
404 _ => None, 401 _ => None,
405 }, 402 },
406 _ => None, 403 _ => None,
@@ -408,7 +405,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
408 .map(|name| name.to_string()) 405 .map(|name| name.to_string())
409} 406}
410 407
411fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) -> String { 408fn render_path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
412 let crate_name = 409 let crate_name =
413 db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string()); 410 db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
414 let module_path = module 411 let module_path = module
@@ -420,6 +417,9 @@ fn render_path(db: &RootDatabase, module: Module, item_name: Option<String>) ->
420} 417}
421 418
422fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { 419fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
420 if let Definition::GenericParam(_) = def {
421 return None;
422 }
423 def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) 423 def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
424} 424}
425 425
@@ -427,60 +427,53 @@ fn hover_for_definition(
427 db: &RootDatabase, 427 db: &RootDatabase,
428 def: Definition, 428 def: Definition,
429 famous_defs: Option<&FamousDefs>, 429 famous_defs: Option<&FamousDefs>,
430 documentation: bool,
430) -> Option<Markup> { 431) -> Option<Markup> {
431 let mod_path = definition_mod_path(db, &def); 432 let mod_path = definition_mod_path(db, &def);
432 return match def { 433 let (label, docs) = match def {
433 Definition::Macro(it) => match &it.source(db)?.value { 434 Definition::Macro(it) => match &it.source(db)?.value {
434 Either::Left(mac) => { 435 Either::Left(mac) => {
435 let label = macro_label(mac); 436 let label = macro_label(mac);
436 from_def_source_labeled(db, it, Some(label), mod_path) 437 (label, it.attrs(db).docs())
437 } 438 }
438 Either::Right(_) => { 439 Either::Right(_) => {
439 // FIXME 440 // FIXME
440 None 441 return None;
441 } 442 }
442 }, 443 },
443 Definition::Field(def) => from_hir_fmt(db, def, mod_path), 444 Definition::Field(def) => label_and_docs(db, def),
444 Definition::ModuleDef(it) => match it { 445 Definition::ModuleDef(it) => match it {
445 ModuleDef::Module(it) => from_hir_fmt(db, it, mod_path), 446 hir::ModuleDef::Module(it) => label_and_docs(db, it),
446 ModuleDef::Function(it) => from_hir_fmt(db, it, mod_path), 447 hir::ModuleDef::Function(it) => label_and_docs(db, it),
447 ModuleDef::Adt(it) => from_hir_fmt(db, it, mod_path), 448 hir::ModuleDef::Adt(it) => label_and_docs(db, it),
448 ModuleDef::Variant(it) => from_hir_fmt(db, it, mod_path), 449 hir::ModuleDef::Variant(it) => label_and_docs(db, it),
449 ModuleDef::Const(it) => from_hir_fmt(db, it, mod_path), 450 hir::ModuleDef::Const(it) => label_and_docs(db, it),
450 ModuleDef::Static(it) => from_hir_fmt(db, it, mod_path), 451 hir::ModuleDef::Static(it) => label_and_docs(db, it),
451 ModuleDef::Trait(it) => from_hir_fmt(db, it, mod_path), 452 hir::ModuleDef::Trait(it) => label_and_docs(db, it),
452 ModuleDef::TypeAlias(it) => from_hir_fmt(db, it, mod_path), 453 hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it),
453 ModuleDef::BuiltinType(it) => famous_defs 454 hir::ModuleDef::BuiltinType(it) => {
454 .and_then(|fd| hover_for_builtin(fd, it)) 455 return famous_defs
455 .or_else(|| Some(Markup::fenced_block(&it.name()))), 456 .and_then(|fd| hover_for_builtin(fd, it))
457 .or_else(|| Some(Markup::fenced_block(&it.name())))
458 }
456 }, 459 },
457 Definition::Local(it) => hover_for_local(it, db), 460 Definition::Local(it) => return hover_for_local(it, db),
458 Definition::SelfType(impl_def) => { 461 Definition::SelfType(impl_def) => {
459 impl_def.self_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path)) 462 impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
460 } 463 }
461 Definition::GenericParam(it) => from_hir_fmt(db, it, None), 464 Definition::GenericParam(it) => label_and_docs(db, it),
462 Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), 465 Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
463 }; 466 };
464 467
465 fn from_hir_fmt<D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup> 468 return hover_markup(docs.filter(|_| documentation).map(Into::into), label, mod_path);
469
470 fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
466 where 471 where
467 D: HasAttrs + HirDisplay, 472 D: HasAttrs + HirDisplay,
468 { 473 {
469 let label = def.display(db).to_string(); 474 let label = def.display(db).to_string();
470 from_def_source_labeled(db, def, Some(label), mod_path) 475 let docs = def.attrs(db).docs();
471 } 476 (label, docs)
472
473 fn from_def_source_labeled<D>(
474 db: &RootDatabase,
475 def: D,
476 short_label: Option<String>,
477 mod_path: Option<String>,
478 ) -> Option<Markup>
479 where
480 D: HasAttrs,
481 {
482 let docs = def.attrs(db).docs().map(Into::into);
483 hover_markup(docs, short_label, mod_path)
484 } 477 }
485} 478}
486 479
@@ -504,11 +497,11 @@ fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option<Markup> {
504 } 497 }
505 Either::Right(_) => format!("{}self: {}", is_mut, ty), 498 Either::Right(_) => format!("{}self: {}", is_mut, ty),
506 }; 499 };
507 hover_markup(None, Some(desc), None) 500 hover_markup(None, desc, None)
508} 501}
509 502
510fn hover_for_keyword( 503fn hover_for_keyword(
511 sema: &Semantics<RootDatabase>, 504 sema: &hir::Semantics<RootDatabase>,
512 links_in_hover: bool, 505 links_in_hover: bool,
513 markdown: bool, 506 markdown: bool,
514 token: &SyntaxToken, 507 token: &SyntaxToken,
@@ -524,7 +517,7 @@ fn hover_for_keyword(
524 let markup = process_markup( 517 let markup = process_markup(
525 sema.db, 518 sema.db,
526 Definition::ModuleDef(doc_owner.into()), 519 Definition::ModuleDef(doc_owner.into()),
527 &hover_markup(Some(docs.into()), Some(token.text().into()), None)?, 520 &hover_markup(Some(docs.into()), token.text().into(), None)?,
528 links_in_hover, 521 links_in_hover,
529 markdown, 522 markdown,
530 ); 523 );
@@ -536,7 +529,7 @@ fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Opt
536 let primitive_mod = format!("prim_{}", builtin.name()); 529 let primitive_mod = format!("prim_{}", builtin.name());
537 let doc_owner = find_std_module(famous_defs, &primitive_mod)?; 530 let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
538 let docs = doc_owner.attrs(famous_defs.0.db).docs()?; 531 let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
539 hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None) 532 hover_markup(Some(docs.into()), builtin.name().to_string(), None)
540} 533}
541 534
542fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> { 535fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> {
@@ -572,12 +565,12 @@ mod tests {
572 565
573 fn check_hover_no_result(ra_fixture: &str) { 566 fn check_hover_no_result(ra_fixture: &str) {
574 let (analysis, position) = fixture::position(ra_fixture); 567 let (analysis, position) = fixture::position(ra_fixture);
575 assert!(analysis.hover(position, true, true).unwrap().is_none()); 568 assert!(analysis.hover(position, true, true, true).unwrap().is_none());
576 } 569 }
577 570
578 fn check(ra_fixture: &str, expect: Expect) { 571 fn check(ra_fixture: &str, expect: Expect) {
579 let (analysis, position) = fixture::position(ra_fixture); 572 let (analysis, position) = fixture::position(ra_fixture);
580 let hover = analysis.hover(position, true, true).unwrap().unwrap(); 573 let hover = analysis.hover(position, true, true, true).unwrap().unwrap();
581 574
582 let content = analysis.db.file_text(position.file_id); 575 let content = analysis.db.file_text(position.file_id);
583 let hovered_element = &content[hover.range]; 576 let hovered_element = &content[hover.range];
@@ -588,7 +581,7 @@ mod tests {
588 581
589 fn check_hover_no_links(ra_fixture: &str, expect: Expect) { 582 fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
590 let (analysis, position) = fixture::position(ra_fixture); 583 let (analysis, position) = fixture::position(ra_fixture);
591 let hover = analysis.hover(position, false, true).unwrap().unwrap(); 584 let hover = analysis.hover(position, false, true, true).unwrap().unwrap();
592 585
593 let content = analysis.db.file_text(position.file_id); 586 let content = analysis.db.file_text(position.file_id);
594 let hovered_element = &content[hover.range]; 587 let hovered_element = &content[hover.range];
@@ -599,7 +592,7 @@ mod tests {
599 592
600 fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { 593 fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
601 let (analysis, position) = fixture::position(ra_fixture); 594 let (analysis, position) = fixture::position(ra_fixture);
602 let hover = analysis.hover(position, true, false).unwrap().unwrap(); 595 let hover = analysis.hover(position, true, true, false).unwrap().unwrap();
603 596
604 let content = analysis.db.file_text(position.file_id); 597 let content = analysis.db.file_text(position.file_id);
605 let hovered_element = &content[hover.range]; 598 let hovered_element = &content[hover.range];
@@ -610,7 +603,7 @@ mod tests {
610 603
611 fn check_actions(ra_fixture: &str, expect: Expect) { 604 fn check_actions(ra_fixture: &str, expect: Expect) {
612 let (analysis, position) = fixture::position(ra_fixture); 605 let (analysis, position) = fixture::position(ra_fixture);
613 let hover = analysis.hover(position, true, true).unwrap().unwrap(); 606 let hover = analysis.hover(position, true, true, true).unwrap().unwrap();
614 expect.assert_debug_eq(&hover.info.actions) 607 expect.assert_debug_eq(&hover.info.actions)
615 } 608 }
616 609