From a93d166f0fecb748d8cb04aab7f5406bf6308c2d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 14 Jun 2021 15:25:10 +0200 Subject: Make documentation on hover configurable --- crates/ide/src/hover.rs | 179 +++++++++++++++++------------------ crates/ide/src/lib.rs | 3 +- crates/rust-analyzer/src/config.rs | 10 +- crates/rust-analyzer/src/handlers.rs | 16 ++-- docs/user/generated_config.adoc | 15 ++- editors/code/package.json | 15 ++- 6 files changed, 125 insertions(+), 113 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 @@ use either::Either; -use hir::{ - AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, InFile, Module, - ModuleDef, Semantics, -}; +use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay}; use ide_db::{ base_db::SourceDatabase, defs::{Definition, NameClass, NameRefClass}, @@ -40,6 +37,7 @@ pub struct HoverConfig { pub goto_type_def: bool, pub links_in_hover: bool, pub markdown: bool, + pub documentation: bool, } impl HoverConfig { @@ -51,14 +49,15 @@ impl HoverConfig { goto_type_def: false, links_in_hover: true, markdown: true, + documentation: true, }; - pub fn any(&self) -> bool { + pub fn any_actions(&self) -> bool { self.implementations || self.references || self.runnable() || self.goto_type_def } - pub fn none(&self) -> bool { - !self.any() + pub fn no_actions(&self) -> bool { + !self.any_actions() } pub fn runnable(&self) -> bool { @@ -97,9 +96,10 @@ pub(crate) fn hover( db: &RootDatabase, position: FilePosition, links_in_hover: bool, + documentation: bool, markdown: bool, ) -> Option> { - let sema = Semantics::new(db); + let sema = hir::Semantics::new(db); let file = sema.parse(position.file_id).syntax().clone(); let token = pick_best(file.token_at_offset(position.offset))?; let token = sema.descend_into_macros(token); @@ -131,7 +131,7 @@ pub(crate) fn hover( let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; let (idl_range, link, ns) = extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| { - let InFile { file_id, value: range } = doc_mapping.map(range)?; + let hir::InFile { file_id, value: range } = doc_mapping.map(range)?; if file_id == position.file_id.into() && range.contains(position.offset) { Some((range, link, ns)) } else { @@ -151,12 +151,14 @@ pub(crate) fn hover( if let Some(definition) = definition { let famous_defs = match &definition { - Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { + Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { Some(FamousDefs(&sema, sema.scope(&node).krate())) } _ => None, }; - if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) { + if let Some(markup) = + hover_for_definition(db, definition, famous_defs.as_ref(), documentation) + { res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown); if let Some(action) = show_implementations_action(db, definition) { res.actions.push(action); @@ -261,8 +263,10 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option return it.try_to_nav(db).map(to_action), - Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it), + Definition::ModuleDef(hir::ModuleDef::Trait(it)) => { + return it.try_to_nav(db).map(to_action) + } + Definition::ModuleDef(hir::ModuleDef::Adt(it)) => Some(it), Definition::SelfType(it) => it.self_ty(db).as_adt(), _ => None, }?; @@ -271,25 +275,27 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option Option { match def { - Definition::ModuleDef(ModuleDef::Function(it)) => it.try_to_nav(db).map(|nav_target| { - HoverAction::Reference(FilePosition { - file_id: nav_target.file_id, - offset: nav_target.focus_or_full_range().start(), + Definition::ModuleDef(hir::ModuleDef::Function(it)) => { + it.try_to_nav(db).map(|nav_target| { + HoverAction::Reference(FilePosition { + file_id: nav_target.file_id, + offset: nav_target.focus_or_full_range().start(), + }) }) - }), + } _ => None, } } fn runnable_action( - sema: &Semantics, + sema: &hir::Semantics, def: Definition, file_id: FileId, ) -> Option { match def { Definition::ModuleDef(it) => match it { - ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable), - ModuleDef::Function(func) => { + hir::ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable), + hir::ModuleDef::Function(func) => { let src = func.source(sema.db)?; if src.file_id != file_id.into() { cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment); @@ -306,19 +312,19 @@ fn runnable_action( } fn goto_type_action(db: &RootDatabase, def: Definition) -> Option { - let mut targets: Vec = Vec::new(); - let mut push_new_def = |item: ModuleDef| { + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { if !targets.contains(&item) { targets.push(item); } }; - if let Definition::GenericParam(GenericParam::TypeParam(it)) = def { + if let Definition::GenericParam(hir::GenericParam::TypeParam(it)) = def { it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into())); } else { let ty = match def { Definition::Local(it) => it.ty(db), - Definition::GenericParam(GenericParam::ConstParam(it)) => it.ty(db), + Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db), _ => return None, }; @@ -348,29 +354,20 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option { Some(HoverAction::GoToType(targets)) } -fn hover_markup( - docs: Option, - desc: Option, - mod_path: Option, -) -> Option { - match desc { - Some(desc) => { - let mut buf = String::new(); - - if let Some(mod_path) = mod_path { - if !mod_path.is_empty() { - format_to!(buf, "```rust\n{}\n```\n\n", mod_path); - } - } - format_to!(buf, "```rust\n{}\n```", desc); +fn hover_markup(docs: Option, desc: String, mod_path: Option) -> Option { + let mut buf = String::new(); - if let Some(doc) = docs { - format_to!(buf, "\n___\n\n{}", doc); - } - Some(buf.into()) + if let Some(mod_path) = mod_path { + if !mod_path.is_empty() { + format_to!(buf, "```rust\n{}\n```\n\n", mod_path); } - None => docs.map(Markup::from), } + format_to!(buf, "```rust\n{}\n```", desc); + + if let Some(doc) = docs { + format_to!(buf, "\n___\n\n{}", doc); + } + Some(buf.into()) } fn process_markup( @@ -396,11 +393,11 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option Definition::Field(f) => Some(f.parent_def(db).name(db)), Definition::Local(l) => l.parent(db).name(db), Definition::ModuleDef(md) => match md { - ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { - AssocItemContainer::Trait(t) => Some(t.name(db)), - AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)), + hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { + hir::AssocItemContainer::Trait(t) => Some(t.name(db)), + hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)), }, - ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), + hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), _ => None, }, _ => None, @@ -408,7 +405,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option .map(|name| name.to_string()) } -fn render_path(db: &RootDatabase, module: Module, item_name: Option) -> String { +fn render_path(db: &RootDatabase, module: hir::Module, item_name: Option) -> String { let crate_name = db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string()); let module_path = module @@ -420,6 +417,9 @@ fn render_path(db: &RootDatabase, module: Module, item_name: Option) -> } fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option { + if let Definition::GenericParam(_) = def { + return None; + } def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def))) } @@ -427,60 +427,53 @@ fn hover_for_definition( db: &RootDatabase, def: Definition, famous_defs: Option<&FamousDefs>, + documentation: bool, ) -> Option { let mod_path = definition_mod_path(db, &def); - return match def { + let (label, docs) = match def { Definition::Macro(it) => match &it.source(db)?.value { Either::Left(mac) => { let label = macro_label(mac); - from_def_source_labeled(db, it, Some(label), mod_path) + (label, it.attrs(db).docs()) } Either::Right(_) => { // FIXME - None + return None; } }, - Definition::Field(def) => from_hir_fmt(db, def, mod_path), + Definition::Field(def) => label_and_docs(db, def), Definition::ModuleDef(it) => match it { - ModuleDef::Module(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::Function(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::Adt(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::Variant(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::Const(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::Static(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::Trait(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::TypeAlias(it) => from_hir_fmt(db, it, mod_path), - ModuleDef::BuiltinType(it) => famous_defs - .and_then(|fd| hover_for_builtin(fd, it)) - .or_else(|| Some(Markup::fenced_block(&it.name()))), + hir::ModuleDef::Module(it) => label_and_docs(db, it), + hir::ModuleDef::Function(it) => label_and_docs(db, it), + hir::ModuleDef::Adt(it) => label_and_docs(db, it), + hir::ModuleDef::Variant(it) => label_and_docs(db, it), + hir::ModuleDef::Const(it) => label_and_docs(db, it), + hir::ModuleDef::Static(it) => label_and_docs(db, it), + hir::ModuleDef::Trait(it) => label_and_docs(db, it), + hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it), + hir::ModuleDef::BuiltinType(it) => { + return famous_defs + .and_then(|fd| hover_for_builtin(fd, it)) + .or_else(|| Some(Markup::fenced_block(&it.name()))) + } }, - Definition::Local(it) => hover_for_local(it, db), + Definition::Local(it) => return hover_for_local(it, db), Definition::SelfType(impl_def) => { - impl_def.self_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path)) + impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))? } - Definition::GenericParam(it) => from_hir_fmt(db, it, None), - Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), + Definition::GenericParam(it) => label_and_docs(db, it), + Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))), }; - fn from_hir_fmt(db: &RootDatabase, def: D, mod_path: Option) -> Option + return hover_markup(docs.filter(|_| documentation).map(Into::into), label, mod_path); + + fn label_and_docs(db: &RootDatabase, def: D) -> (String, Option) where D: HasAttrs + HirDisplay, { let label = def.display(db).to_string(); - from_def_source_labeled(db, def, Some(label), mod_path) - } - - fn from_def_source_labeled( - db: &RootDatabase, - def: D, - short_label: Option, - mod_path: Option, - ) -> Option - where - D: HasAttrs, - { - let docs = def.attrs(db).docs().map(Into::into); - hover_markup(docs, short_label, mod_path) + let docs = def.attrs(db).docs(); + (label, docs) } } @@ -504,11 +497,11 @@ fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option { } Either::Right(_) => format!("{}self: {}", is_mut, ty), }; - hover_markup(None, Some(desc), None) + hover_markup(None, desc, None) } fn hover_for_keyword( - sema: &Semantics, + sema: &hir::Semantics, links_in_hover: bool, markdown: bool, token: &SyntaxToken, @@ -524,7 +517,7 @@ fn hover_for_keyword( let markup = process_markup( sema.db, Definition::ModuleDef(doc_owner.into()), - &hover_markup(Some(docs.into()), Some(token.text().into()), None)?, + &hover_markup(Some(docs.into()), token.text().into(), None)?, links_in_hover, markdown, ); @@ -536,7 +529,7 @@ fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Opt let primitive_mod = format!("prim_{}", builtin.name()); let doc_owner = find_std_module(famous_defs, &primitive_mod)?; let docs = doc_owner.attrs(famous_defs.0.db).docs()?; - hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None) + hover_markup(Some(docs.into()), builtin.name().to_string(), None) } fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option { @@ -572,12 +565,12 @@ mod tests { fn check_hover_no_result(ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); - assert!(analysis.hover(position, true, true).unwrap().is_none()); + assert!(analysis.hover(position, true, true, true).unwrap().is_none()); } fn check(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis.hover(position, true, true).unwrap().unwrap(); + let hover = analysis.hover(position, true, true, true).unwrap().unwrap(); let content = analysis.db.file_text(position.file_id); let hovered_element = &content[hover.range]; @@ -588,7 +581,7 @@ mod tests { fn check_hover_no_links(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis.hover(position, false, true).unwrap().unwrap(); + let hover = analysis.hover(position, false, true, true).unwrap().unwrap(); let content = analysis.db.file_text(position.file_id); let hovered_element = &content[hover.range]; @@ -599,7 +592,7 @@ mod tests { fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis.hover(position, true, false).unwrap().unwrap(); + let hover = analysis.hover(position, true, true, false).unwrap().unwrap(); let content = analysis.db.file_text(position.file_id); let hovered_element = &content[hover.range]; @@ -610,7 +603,7 @@ mod tests { fn check_actions(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); - let hover = analysis.hover(position, true, true).unwrap().unwrap(); + let hover = analysis.hover(position, true, true, true).unwrap().unwrap(); expect.assert_debug_eq(&hover.info.actions) } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0511efae3..8d0270319 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -407,9 +407,10 @@ impl Analysis { &self, position: FilePosition, links_in_hover: bool, + documentation: bool, markdown: bool, ) -> Cancellable>> { - self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) + self.with_db(|db| hover::hover(db, position, links_in_hover, documentation, markdown)) } /// Return URL(s) for the documentation of the symbol under the cursor. diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 3b20d741a..5d3deb232 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -141,6 +141,11 @@ config_data! { /// their contents. highlighting_strings: bool = "true", + /// Whether to show documentation on hover. + hover_documentation: bool = "true", + /// Use markdown syntax for links in hover. + hover_linksInHover: bool = "true", + /// Whether to show `Debug` action. Only applies when /// `#rust-analyzer.hoverActions.enable#` is set. hoverActions_debug: bool = "true", @@ -158,8 +163,6 @@ config_data! { /// Whether to show `Run` action. Only applies when /// `#rust-analyzer.hoverActions.enable#` is set. hoverActions_run: bool = "true", - /// Use markdown syntax for links in hover. - hoverActions_linksInHover: bool = "true", /// Whether to show inlay type hints for method chains. inlayHints_chainingHints: bool = "true", @@ -726,7 +729,7 @@ impl Config { run: self.data.hoverActions_enable && self.data.hoverActions_run, debug: self.data.hoverActions_enable && self.data.hoverActions_debug, goto_type_def: self.data.hoverActions_enable && self.data.hoverActions_gotoTypeDef, - links_in_hover: self.data.hoverActions_linksInHover, + links_in_hover: self.data.hover_linksInHover, markdown: try_or!( self.caps .text_document @@ -739,6 +742,7 @@ impl Config { &[] ) .contains(&MarkupKind::Markdown), + documentation: self.data.hover_documentation, } } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index ccf66294f..eff1e6c93 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -862,11 +862,15 @@ pub(crate) fn handle_hover( let _p = profile::span("handle_hover"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; let hover_config = snap.config.hover(); - let info = - match snap.analysis.hover(position, hover_config.links_in_hover, hover_config.markdown)? { - None => return Ok(None), - Some(info) => info, - }; + let info = match snap.analysis.hover( + position, + hover_config.links_in_hover, + hover_config.documentation, + hover_config.markdown, + )? { + None => return Ok(None), + Some(info) => info, + }; let line_index = snap.file_line_index(position.file_id)?; let range = to_proto::range(&line_index, info.range); let hover = lsp_ext::Hover { @@ -1587,7 +1591,7 @@ fn prepare_hover_actions( snap: &GlobalStateSnapshot, actions: &[HoverAction], ) -> Vec { - if snap.config.hover().none() || !snap.config.hover_actions() { + if snap.config.hover().no_actions() || !snap.config.hover_actions() { return Vec::new(); } diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 34a91486b..4105d784f 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -205,6 +205,16 @@ In some editors (e.g. vscode) semantic tokens override other highlighting gramma By disabling semantic tokens for strings, other grammars can be used to highlight their contents. -- +[[rust-analyzer.hover.documentation]]rust-analyzer.hover.documentation (default: `true`):: ++ +-- +Whether to show documentation on hover. +-- +[[rust-analyzer.hover.linksInHover]]rust-analyzer.hover.linksInHover (default: `true`):: ++ +-- +Use markdown syntax for links in hover. +-- [[rust-analyzer.hoverActions.debug]]rust-analyzer.hoverActions.debug (default: `true`):: + -- @@ -240,11 +250,6 @@ Whether to show `References` action. Only applies when Whether to show `Run` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set. -- -[[rust-analyzer.hoverActions.linksInHover]]rust-analyzer.hoverActions.linksInHover (default: `true`):: -+ --- -Use markdown syntax for links in hover. --- [[rust-analyzer.inlayHints.chainingHints]]rust-analyzer.inlayHints.chainingHints (default: `true`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 0f3ed48a0..43a5cc2b5 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -640,6 +640,16 @@ "default": true, "type": "boolean" }, + "rust-analyzer.hover.documentation": { + "markdownDescription": "Whether to show documentation on hover.", + "default": true, + "type": "boolean" + }, + "rust-analyzer.hover.linksInHover": { + "markdownDescription": "Use markdown syntax for links in hover.", + "default": true, + "type": "boolean" + }, "rust-analyzer.hoverActions.debug": { "markdownDescription": "Whether to show `Debug` action. Only applies when\n`#rust-analyzer.hoverActions.enable#` is set.", "default": true, @@ -670,11 +680,6 @@ "default": true, "type": "boolean" }, - "rust-analyzer.hoverActions.linksInHover": { - "markdownDescription": "Use markdown syntax for links in hover.", - "default": true, - "type": "boolean" - }, "rust-analyzer.inlayHints.chainingHints": { "markdownDescription": "Whether to show inlay type hints for method chains.", "default": true, -- cgit v1.2.3