diff options
author | Lukas Wirth <[email protected]> | 2021-03-23 18:19:44 +0000 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2021-03-23 18:57:04 +0000 |
commit | 8b0d0bd9c7488fedfd0bdd34a0a9cb04da3f143a (patch) | |
tree | 755b2f33124306b9ae9ad658da66b543806928eb /crates | |
parent | 065a8e87cd015ccfe5c01ad6b603f4fdad18f768 (diff) |
Show item info when hovering intra doc links
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide/src/call_hierarchy.rs | 11 | ||||
-rw-r--r-- | crates/ide/src/doc_links.rs | 129 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 66 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 63 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 28 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/inject.rs | 6 |
6 files changed, 175 insertions, 128 deletions
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index 96021f677..5cd186565 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -50,16 +50,16 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
50 | for (file_id, references) in refs.references { | 50 | for (file_id, references) in refs.references { |
51 | let file = sema.parse(file_id); | 51 | let file = sema.parse(file_id); |
52 | let file = file.syntax(); | 52 | let file = file.syntax(); |
53 | for (r_range, _) in references { | 53 | for (relative_range, token) in references |
54 | let token = file.token_at_offset(r_range.start()).next()?; | 54 | .into_iter() |
55 | .filter_map(|(range, _)| Some(range).zip(file.token_at_offset(range.start()).next())) | ||
56 | { | ||
55 | let token = sema.descend_into_macros(token); | 57 | let token = sema.descend_into_macros(token); |
56 | // This target is the containing function | 58 | // This target is the containing function |
57 | if let Some(nav) = token.ancestors().find_map(|node| { | 59 | if let Some(nav) = token.ancestors().find_map(|node| { |
58 | let fn_ = ast::Fn::cast(node)?; | 60 | let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?; |
59 | let def = sema.to_def(&fn_)?; | ||
60 | def.try_to_nav(sema.db) | 61 | def.try_to_nav(sema.db) |
61 | }) { | 62 | }) { |
62 | let relative_range = r_range; | ||
63 | calls.add(&nav, relative_range); | 63 | calls.add(&nav, relative_range); |
64 | } | 64 | } |
65 | } | 65 | } |
@@ -87,7 +87,6 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio | |||
87 | let name_ref = call_node.name_ref()?; | 87 | let name_ref = call_node.name_ref()?; |
88 | let func_target = match call_node { | 88 | let func_target = match call_node { |
89 | FnCallNode::CallExpr(expr) => { | 89 | FnCallNode::CallExpr(expr) => { |
90 | //FIXME: Type::as_callable is broken | ||
91 | let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?; | 90 | let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?; |
92 | match callable.kind() { | 91 | match callable.kind() { |
93 | hir::CallableKind::Function(it) => it.try_to_nav(db), | 92 | hir::CallableKind::Function(it) => it.try_to_nav(db), |
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index c7c1f4fee..f12c9d442 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Resolves and rewrites links in markdown documentation. | 1 | //! Extracts, resolves and rewrites links and intra-doc links in markdown documentation. |
2 | 2 | ||
3 | use std::{convert::TryFrom, iter::once, ops::Range}; | 3 | use std::{convert::TryFrom, iter::once, ops::Range}; |
4 | 4 | ||
@@ -15,7 +15,10 @@ use ide_db::{ | |||
15 | defs::{Definition, NameClass, NameRefClass}, | 15 | defs::{Definition, NameClass, NameRefClass}, |
16 | RootDatabase, | 16 | RootDatabase, |
17 | }; | 17 | }; |
18 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 18 | use syntax::{ |
19 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, | ||
20 | TokenAtOffset, T, | ||
21 | }; | ||
19 | 22 | ||
20 | use crate::{FilePosition, Semantics}; | 23 | use crate::{FilePosition, Semantics}; |
21 | 24 | ||
@@ -60,29 +63,6 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi | |||
60 | out | 63 | out |
61 | } | 64 | } |
62 | 65 | ||
63 | pub(crate) fn extract_definitions_from_markdown( | ||
64 | markdown: &str, | ||
65 | ) -> Vec<(String, Option<hir::Namespace>, Range<usize>)> { | ||
66 | let mut res = vec![]; | ||
67 | let mut cb = |link: BrokenLink| { | ||
68 | // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong | ||
69 | // this is fixed in the repo but not on the crates.io release yet | ||
70 | Some(( | ||
71 | /*url*/ link.reference.to_owned().into(), | ||
72 | /*title*/ link.reference.to_owned().into(), | ||
73 | )) | ||
74 | }; | ||
75 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
76 | for (event, range) in doc.into_offset_iter() { | ||
77 | if let Event::Start(Tag::Link(_, target, title)) = event { | ||
78 | let link = if target.is_empty() { title } else { target }; | ||
79 | let (link, ns) = parse_link(&link); | ||
80 | res.push((link.to_string(), ns, range)); | ||
81 | } | ||
82 | } | ||
83 | res | ||
84 | } | ||
85 | |||
86 | /// Remove all links in markdown documentation. | 66 | /// Remove all links in markdown documentation. |
87 | pub(crate) fn remove_links(markdown: &str) -> String { | 67 | pub(crate) fn remove_links(markdown: &str) -> String { |
88 | let mut drop_link = false; | 68 | let mut drop_link = false; |
@@ -118,6 +98,105 @@ pub(crate) fn remove_links(markdown: &str) -> String { | |||
118 | out | 98 | out |
119 | } | 99 | } |
120 | 100 | ||
101 | pub(crate) fn extract_definitions_from_markdown( | ||
102 | markdown: &str, | ||
103 | ) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> { | ||
104 | let mut res = vec![]; | ||
105 | let mut cb = |link: BrokenLink| { | ||
106 | // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong | ||
107 | // this is fixed in the repo but not on the crates.io release yet | ||
108 | Some(( | ||
109 | /*url*/ link.reference.to_owned().into(), | ||
110 | /*title*/ link.reference.to_owned().into(), | ||
111 | )) | ||
112 | }; | ||
113 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
114 | for (event, range) in doc.into_offset_iter() { | ||
115 | if let Event::Start(Tag::Link(_, target, title)) = event { | ||
116 | let link = if target.is_empty() { title } else { target }; | ||
117 | let (link, ns) = parse_link(&link); | ||
118 | res.push((range, link.to_string(), ns)); | ||
119 | } | ||
120 | } | ||
121 | res | ||
122 | } | ||
123 | |||
124 | /// Extracts a link from a comment at the given position returning the spanning range, link and | ||
125 | /// optionally it's namespace. | ||
126 | pub(crate) fn extract_positioned_link_from_comment( | ||
127 | position: TextSize, | ||
128 | comment: &ast::Comment, | ||
129 | ) -> Option<(TextRange, String, Option<hir::Namespace>)> { | ||
130 | let doc_comment = comment.doc_comment()?; | ||
131 | let comment_start = | ||
132 | comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32); | ||
133 | let def_links = extract_definitions_from_markdown(doc_comment); | ||
134 | let (range, def_link, ns) = | ||
135 | def_links.into_iter().find_map(|(Range { start, end }, def_link, ns)| { | ||
136 | let range = TextRange::at( | ||
137 | comment_start + TextSize::from(start as u32), | ||
138 | TextSize::from((end - start) as u32), | ||
139 | ); | ||
140 | range.contains(position).then(|| (range, def_link, ns)) | ||
141 | })?; | ||
142 | Some((range, def_link, ns)) | ||
143 | } | ||
144 | |||
145 | /// Turns a syntax node into it's [`Definition`] if it can hold docs. | ||
146 | pub(crate) fn doc_owner_to_def( | ||
147 | sema: &Semantics<RootDatabase>, | ||
148 | item: &SyntaxNode, | ||
149 | ) -> Option<Definition> { | ||
150 | let res: hir::ModuleDef = match_ast! { | ||
151 | match item { | ||
152 | ast::SourceFile(_it) => sema.scope(item).module()?.into(), | ||
153 | ast::Fn(it) => sema.to_def(&it)?.into(), | ||
154 | ast::Struct(it) => sema.to_def(&it)?.into(), | ||
155 | ast::Enum(it) => sema.to_def(&it)?.into(), | ||
156 | ast::Union(it) => sema.to_def(&it)?.into(), | ||
157 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
158 | ast::Const(it) => sema.to_def(&it)?.into(), | ||
159 | ast::Static(it) => sema.to_def(&it)?.into(), | ||
160 | ast::TypeAlias(it) => sema.to_def(&it)?.into(), | ||
161 | ast::Variant(it) => sema.to_def(&it)?.into(), | ||
162 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
163 | ast::Impl(it) => return sema.to_def(&it).map(Definition::SelfType), | ||
164 | ast::MacroRules(it) => return sema.to_def(&it).map(Definition::Macro), | ||
165 | ast::TupleField(it) => return sema.to_def(&it).map(Definition::Field), | ||
166 | ast::RecordField(it) => return sema.to_def(&it).map(Definition::Field), | ||
167 | _ => return None, | ||
168 | } | ||
169 | }; | ||
170 | Some(Definition::ModuleDef(res)) | ||
171 | } | ||
172 | |||
173 | pub(crate) fn resolve_doc_path_for_def( | ||
174 | db: &dyn HirDatabase, | ||
175 | def: Definition, | ||
176 | link: &str, | ||
177 | ns: Option<hir::Namespace>, | ||
178 | ) -> Option<hir::ModuleDef> { | ||
179 | match def { | ||
180 | Definition::ModuleDef(def) => match def { | ||
181 | ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), | ||
182 | ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), | ||
183 | ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), | ||
184 | ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), | ||
185 | ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), | ||
186 | ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), | ||
187 | ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), | ||
188 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), | ||
189 | ModuleDef::BuiltinType(_) => None, | ||
190 | }, | ||
191 | Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), | ||
192 | Definition::Field(it) => it.resolve_doc_path(db, &link, ns), | ||
193 | Definition::SelfType(_) | ||
194 | | Definition::Local(_) | ||
195 | | Definition::GenericParam(_) | ||
196 | | Definition::Label(_) => None, | ||
197 | } | ||
198 | } | ||
199 | |||
121 | // FIXME: | 200 | // FIXME: |
122 | // BUG: For Option::Some | 201 | // BUG: For Option::Some |
123 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some | 202 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 473d48c2f..a2c97061f 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,18 +1,14 @@ | |||
1 | use std::ops::Range; | ||
2 | |||
3 | use either::Either; | 1 | use either::Either; |
4 | use hir::{HasAttrs, ModuleDef, Semantics}; | 2 | use hir::Semantics; |
5 | use ide_db::{ | 3 | use ide_db::{ |
6 | defs::{Definition, NameClass, NameRefClass}, | 4 | defs::{NameClass, NameRefClass}, |
7 | RootDatabase, | 5 | RootDatabase, |
8 | }; | 6 | }; |
9 | use syntax::{ | 7 | use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
10 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, TextSize, | ||
11 | TokenAtOffset, T, | ||
12 | }; | ||
13 | 8 | ||
14 | use crate::{ | 9 | use crate::{ |
15 | display::TryToNav, doc_links::extract_definitions_from_markdown, runnables::doc_owner_to_def, | 10 | display::TryToNav, |
11 | doc_links::{doc_owner_to_def, extract_positioned_link_from_comment, resolve_doc_path_for_def}, | ||
16 | FilePosition, NavigationTarget, RangeInfo, | 12 | FilePosition, NavigationTarget, RangeInfo, |
17 | }; | 13 | }; |
18 | 14 | ||
@@ -35,7 +31,9 @@ pub(crate) fn goto_definition( | |||
35 | let token = sema.descend_into_macros(original_token.clone()); | 31 | let token = sema.descend_into_macros(original_token.clone()); |
36 | let parent = token.parent()?; | 32 | let parent = token.parent()?; |
37 | if let Some(comment) = ast::Comment::cast(token) { | 33 | if let Some(comment) = ast::Comment::cast(token) { |
38 | let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; | 34 | let (_, link, ns) = extract_positioned_link_from_comment(position.offset, &comment)?; |
35 | let def = doc_owner_to_def(&sema, &parent)?; | ||
36 | let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?; | ||
39 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); | 37 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); |
40 | } | 38 | } |
41 | 39 | ||
@@ -61,54 +59,6 @@ pub(crate) fn goto_definition( | |||
61 | Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect())) | 59 | Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect())) |
62 | } | 60 | } |
63 | 61 | ||
64 | fn def_for_doc_comment( | ||
65 | sema: &Semantics<RootDatabase>, | ||
66 | position: FilePosition, | ||
67 | doc_comment: &ast::Comment, | ||
68 | ) -> Option<hir::ModuleDef> { | ||
69 | let parent = doc_comment.syntax().parent()?; | ||
70 | let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; | ||
71 | |||
72 | let def = doc_owner_to_def(sema, parent)?; | ||
73 | match def { | ||
74 | Definition::ModuleDef(def) => match def { | ||
75 | ModuleDef::Module(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
76 | ModuleDef::Function(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
77 | ModuleDef::Adt(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
78 | ModuleDef::Variant(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
79 | ModuleDef::Const(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
80 | ModuleDef::Static(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
81 | ModuleDef::Trait(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
82 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
83 | ModuleDef::BuiltinType(_) => return None, | ||
84 | }, | ||
85 | Definition::Macro(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
86 | Definition::Field(it) => it.resolve_doc_path(sema.db, &link, ns), | ||
87 | Definition::SelfType(_) | ||
88 | | Definition::Local(_) | ||
89 | | Definition::GenericParam(_) | ||
90 | | Definition::Label(_) => return None, | ||
91 | } | ||
92 | } | ||
93 | |||
94 | fn extract_positioned_link_from_comment( | ||
95 | position: FilePosition, | ||
96 | comment: &ast::Comment, | ||
97 | ) -> Option<(String, Option<hir::Namespace>)> { | ||
98 | let doc_comment = comment.doc_comment()?; | ||
99 | let comment_start = | ||
100 | comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32); | ||
101 | let def_links = extract_definitions_from_markdown(doc_comment); | ||
102 | let (def_link, ns, _) = def_links.into_iter().find(|&(_, _, Range { start, end })| { | ||
103 | TextRange::at( | ||
104 | comment_start + TextSize::from(start as u32), | ||
105 | TextSize::from((end - start) as u32), | ||
106 | ) | ||
107 | .contains(position.offset) | ||
108 | })?; | ||
109 | Some((def_link, ns)) | ||
110 | } | ||
111 | |||
112 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 62 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
113 | return tokens.max_by_key(priority); | 63 | return tokens.max_by_key(priority); |
114 | fn priority(n: &SyntaxToken) -> usize { | 64 | fn priority(n: &SyntaxToken) -> usize { |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index a3fb17c0a..c43089476 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -11,11 +11,14 @@ use ide_db::{ | |||
11 | }; | 11 | }; |
12 | use itertools::Itertools; | 12 | use itertools::Itertools; |
13 | use stdx::format_to; | 13 | use stdx::format_to; |
14 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 14 | use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
15 | 15 | ||
16 | use crate::{ | 16 | use crate::{ |
17 | display::{macro_label, TryToNav}, | 17 | display::{macro_label, TryToNav}, |
18 | doc_links::{remove_links, rewrite_links}, | 18 | doc_links::{ |
19 | doc_owner_to_def, extract_positioned_link_from_comment, remove_links, | ||
20 | resolve_doc_path_for_def, rewrite_links, | ||
21 | }, | ||
19 | markdown_remove::remove_markdown, | 22 | markdown_remove::remove_markdown, |
20 | markup::Markup, | 23 | markup::Markup, |
21 | runnables::{runnable_fn, runnable_mod}, | 24 | runnables::{runnable_fn, runnable_mod}, |
@@ -93,20 +96,35 @@ pub(crate) fn hover( | |||
93 | let mut res = HoverResult::default(); | 96 | let mut res = HoverResult::default(); |
94 | 97 | ||
95 | let node = token.parent()?; | 98 | let node = token.parent()?; |
99 | let mut range = None; | ||
96 | let definition = match_ast! { | 100 | let definition = match_ast! { |
97 | match node { | 101 | match node { |
98 | // we don't use NameClass::referenced_or_defined here as we do not want to resolve | 102 | // we don't use NameClass::referenced_or_defined here as we do not want to resolve |
99 | // field pattern shorthands to their definition | 103 | // field pattern shorthands to their definition |
100 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class { | 104 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class { |
101 | NameClass::ConstReference(def) => Some(def), | 105 | NameClass::ConstReference(def) => Some(def), |
102 | def => def.defined(sema.db), | 106 | def => def.defined(db), |
103 | }), | 107 | }), |
104 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), | 108 | ast::NameRef(name_ref) => { |
105 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) | 109 | NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(db)) |
106 | .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), | 110 | }, |
107 | _ => None, | 111 | ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else( |
112 | || NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(db)), | ||
113 | |d| d.defined(db), | ||
114 | ), | ||
115 | |||
116 | _ => ast::Comment::cast(token.clone()) | ||
117 | .and_then(|comment| { | ||
118 | let (idl_range, link, ns) = | ||
119 | extract_positioned_link_from_comment(position.offset, &comment)?; | ||
120 | range = Some(idl_range); | ||
121 | let def = doc_owner_to_def(&sema, &node)?; | ||
122 | resolve_doc_path_for_def(db, def, &link, ns) | ||
123 | }) | ||
124 | .map(Definition::ModuleDef), | ||
108 | } | 125 | } |
109 | }; | 126 | }; |
127 | |||
110 | if let Some(definition) = definition { | 128 | if let Some(definition) = definition { |
111 | let famous_defs = match &definition { | 129 | let famous_defs = match &definition { |
112 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { | 130 | Definition::ModuleDef(ModuleDef::BuiltinType(_)) => { |
@@ -128,15 +146,16 @@ pub(crate) fn hover( | |||
128 | res.actions.push(action); | 146 | res.actions.push(action); |
129 | } | 147 | } |
130 | 148 | ||
131 | let range = sema.original_range(&node).range; | 149 | let range = range.unwrap_or_else(|| sema.original_range(&node).range); |
132 | return Some(RangeInfo::new(range, res)); | 150 | return Some(RangeInfo::new(range, res)); |
133 | } | 151 | } |
134 | } | 152 | } |
135 | 153 | ||
136 | if token.kind() == syntax::SyntaxKind::COMMENT { | 154 | if token.kind() == syntax::SyntaxKind::COMMENT { |
137 | // don't highlight the entire parent node on comment hover | 155 | cov_mark::hit!(no_highlight_on_comment_hover); |
138 | return None; | 156 | return None; |
139 | } | 157 | } |
158 | |||
140 | if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) { | 159 | if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) { |
141 | return res; | 160 | return res; |
142 | } | 161 | } |
@@ -3483,6 +3502,7 @@ fn foo$0() {} | |||
3483 | 3502 | ||
3484 | #[test] | 3503 | #[test] |
3485 | fn hover_comments_dont_highlight_parent() { | 3504 | fn hover_comments_dont_highlight_parent() { |
3505 | cov_mark::check!(no_highlight_on_comment_hover); | ||
3486 | check_hover_no_result( | 3506 | check_hover_no_result( |
3487 | r#" | 3507 | r#" |
3488 | fn no_hover() { | 3508 | fn no_hover() { |
@@ -3755,4 +3775,29 @@ fn main() { | |||
3755 | "#]], | 3775 | "#]], |
3756 | ) | 3776 | ) |
3757 | } | 3777 | } |
3778 | |||
3779 | #[test] | ||
3780 | fn hover_intra_doc_links() { | ||
3781 | check( | ||
3782 | r#" | ||
3783 | /// This is the [`foo`](foo$0) function. | ||
3784 | fn foo() {} | ||
3785 | "#, | ||
3786 | expect![[r#" | ||
3787 | *[`foo`](foo)* | ||
3788 | |||
3789 | ```rust | ||
3790 | test | ||
3791 | ``` | ||
3792 | |||
3793 | ```rust | ||
3794 | fn foo() | ||
3795 | ``` | ||
3796 | |||
3797 | --- | ||
3798 | |||
3799 | This is the [`foo`](https://docs.rs/test/*/test/fn.foo.html) function. | ||
3800 | "#]], | ||
3801 | ); | ||
3802 | } | ||
3758 | } | 3803 | } |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index bea020b06..5b488e2c5 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -7,17 +7,13 @@ use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics}; | |||
7 | use ide_assists::utils::test_related_attribute; | 7 | use ide_assists::utils::test_related_attribute; |
8 | use ide_db::{ | 8 | use ide_db::{ |
9 | base_db::{FilePosition, FileRange}, | 9 | base_db::{FilePosition, FileRange}, |
10 | defs::Definition, | ||
11 | helpers::visit_file_defs, | 10 | helpers::visit_file_defs, |
12 | search::SearchScope, | 11 | search::SearchScope, |
13 | RootDatabase, SymbolKind, | 12 | RootDatabase, SymbolKind, |
14 | }; | 13 | }; |
15 | use itertools::Itertools; | 14 | use itertools::Itertools; |
16 | use rustc_hash::FxHashSet; | 15 | use rustc_hash::FxHashSet; |
17 | use syntax::{ | 16 | use syntax::ast::{self, AstNode, AttrsOwner}; |
18 | ast::{self, AstNode, AttrsOwner}, | ||
19 | match_ast, SyntaxNode, | ||
20 | }; | ||
21 | 17 | ||
22 | use crate::{ | 18 | use crate::{ |
23 | display::{ToNav, TryToNav}, | 19 | display::{ToNav, TryToNav}, |
@@ -271,28 +267,6 @@ pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> | |||
271 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) | 267 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) |
272 | } | 268 | } |
273 | 269 | ||
274 | // FIXME: figure out a proper API here. | ||
275 | pub(crate) fn doc_owner_to_def( | ||
276 | sema: &Semantics<RootDatabase>, | ||
277 | item: SyntaxNode, | ||
278 | ) -> Option<Definition> { | ||
279 | let res: hir::ModuleDef = match_ast! { | ||
280 | match item { | ||
281 | ast::SourceFile(_it) => sema.scope(&item).module()?.into(), | ||
282 | ast::Fn(it) => sema.to_def(&it)?.into(), | ||
283 | ast::Struct(it) => sema.to_def(&it)?.into(), | ||
284 | ast::Enum(it) => sema.to_def(&it)?.into(), | ||
285 | ast::Union(it) => sema.to_def(&it)?.into(), | ||
286 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
287 | ast::Const(it) => sema.to_def(&it)?.into(), | ||
288 | ast::Static(it) => sema.to_def(&it)?.into(), | ||
289 | ast::TypeAlias(it) => sema.to_def(&it)?.into(), | ||
290 | _ => return None, | ||
291 | } | ||
292 | }; | ||
293 | Some(Definition::ModuleDef(res)) | ||
294 | } | ||
295 | |||
296 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { | 270 | fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { |
297 | let attrs = match def { | 271 | let attrs = match def { |
298 | hir::ModuleDef::Module(it) => it.attrs(sema.db), | 272 | hir::ModuleDef::Module(it) => it.attrs(sema.db), |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 8e0940184..38bf49348 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -190,10 +190,10 @@ pub(super) fn doc_comment( | |||
190 | intra_doc_links.extend( | 190 | intra_doc_links.extend( |
191 | extract_definitions_from_markdown(line) | 191 | extract_definitions_from_markdown(line) |
192 | .into_iter() | 192 | .into_iter() |
193 | .filter_map(|(link, ns, range)| { | 193 | .filter_map(|(range, link, ns)| { |
194 | validate_intra_doc_link(sema.db, &def, &link, ns).zip(Some(range)) | 194 | Some(range).zip(validate_intra_doc_link(sema.db, &def, &link, ns)) |
195 | }) | 195 | }) |
196 | .map(|(def, Range { start, end })| { | 196 | .map(|(Range { start, end }, def)| { |
197 | ( | 197 | ( |
198 | def, | 198 | def, |
199 | TextRange::at( | 199 | TextRange::at( |