diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide/src/doc_links.rs | 26 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 90 | ||||
-rw-r--r-- | crates/ide/src/inlay_hints.rs | 26 |
3 files changed, 130 insertions, 12 deletions
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 1ff818de2..678d22d03 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! Resolves and rewrites links in markdown documentation. | 1 | //! Resolves and rewrites links in markdown documentation. |
2 | 2 | ||
3 | use std::{convert::TryFrom, iter::once}; | 3 | use std::{convert::TryFrom, iter::once, ops::Range}; |
4 | 4 | ||
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; | 6 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; |
@@ -61,6 +61,30 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Defi | |||
61 | out | 61 | out |
62 | } | 62 | } |
63 | 63 | ||
64 | pub(crate) fn extract_definitions_from_markdown( | ||
65 | markdown: &str, | ||
66 | ) -> Vec<(String, Option<hir::Namespace>, Range<usize>)> { | ||
67 | let mut res = vec![]; | ||
68 | let mut cb = |link: BrokenLink| { | ||
69 | Some(( | ||
70 | /*url*/ link.reference.to_owned().into(), | ||
71 | /*title*/ link.reference.to_owned().into(), | ||
72 | )) | ||
73 | }; | ||
74 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
75 | for (event, range) in doc.into_offset_iter() { | ||
76 | match event { | ||
77 | Event::Start(Tag::Link(_link_type, ref target, ref title)) => { | ||
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 | } | ||
84 | } | ||
85 | res | ||
86 | } | ||
87 | |||
64 | /// Remove all links in markdown documentation. | 88 | /// Remove all links in markdown documentation. |
65 | pub(crate) fn remove_links(markdown: &str) -> String { | 89 | pub(crate) fn remove_links(markdown: &str) -> String { |
66 | let mut drop_link = false; | 90 | let mut drop_link = false; |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 95b4cb9e3..227f20943 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,14 +1,20 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::Semantics; | 2 | use hir::{HasAttrs, ModuleDef, Semantics}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | base_db::FileId, | 4 | base_db::FileId, |
5 | defs::{NameClass, NameRefClass}, | 5 | defs::{Definition, NameClass, NameRefClass}, |
6 | symbol_index, RootDatabase, | 6 | symbol_index, RootDatabase, |
7 | }; | 7 | }; |
8 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 8 | use syntax::{ |
9 | ast::{self, NameOwner}, | ||
10 | match_ast, AstNode, AstToken, | ||
11 | SyntaxKind::*, | ||
12 | SyntaxToken, TextSize, TokenAtOffset, T, | ||
13 | }; | ||
9 | 14 | ||
10 | use crate::{ | 15 | use crate::{ |
11 | display::{ToNav, TryToNav}, | 16 | display::{ToNav, TryToNav}, |
17 | doc_links::extract_definitions_from_markdown, | ||
12 | FilePosition, NavigationTarget, RangeInfo, SymbolKind, | 18 | FilePosition, NavigationTarget, RangeInfo, SymbolKind, |
13 | }; | 19 | }; |
14 | 20 | ||
@@ -30,6 +36,10 @@ pub(crate) fn goto_definition( | |||
30 | let original_token = pick_best(file.token_at_offset(position.offset))?; | 36 | let original_token = pick_best(file.token_at_offset(position.offset))?; |
31 | let token = sema.descend_into_macros(original_token.clone()); | 37 | let token = sema.descend_into_macros(original_token.clone()); |
32 | let parent = token.parent(); | 38 | let parent = token.parent(); |
39 | if let Some(comment) = ast::Comment::cast(token.clone()) { | ||
40 | let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; | ||
41 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); | ||
42 | } | ||
33 | 43 | ||
34 | let nav_targets = match_ast! { | 44 | let nav_targets = match_ast! { |
35 | match parent { | 45 | match parent { |
@@ -68,11 +78,68 @@ pub(crate) fn goto_definition( | |||
68 | Some(RangeInfo::new(original_token.text_range(), nav_targets)) | 78 | Some(RangeInfo::new(original_token.text_range(), nav_targets)) |
69 | } | 79 | } |
70 | 80 | ||
81 | fn def_for_doc_comment( | ||
82 | sema: &Semantics<RootDatabase>, | ||
83 | position: FilePosition, | ||
84 | doc_comment: &ast::Comment, | ||
85 | ) -> Option<hir::ModuleDef> { | ||
86 | let parent = doc_comment.syntax().parent(); | ||
87 | let db = sema.db; | ||
88 | let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; | ||
89 | let link = &link; | ||
90 | let name = match_ast! { | ||
91 | match parent { | ||
92 | ast::Name(name) => Some(name), | ||
93 | ast::Fn(func) => func.name(), | ||
94 | _ => None, | ||
95 | } | ||
96 | }?; | ||
97 | let definition = NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db))?; | ||
98 | match definition { | ||
99 | Definition::ModuleDef(def) => match def { | ||
100 | ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), | ||
101 | ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns), | ||
102 | ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns), | ||
103 | ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns), | ||
104 | ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns), | ||
105 | ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns), | ||
106 | ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns), | ||
107 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns), | ||
108 | ModuleDef::BuiltinType(_) => return None, | ||
109 | }, | ||
110 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), | ||
111 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), | ||
112 | Definition::SelfType(_) | ||
113 | | Definition::Local(_) | ||
114 | | Definition::TypeParam(_) | ||
115 | | Definition::LifetimeParam(_) | ||
116 | | Definition::ConstParam(_) | ||
117 | | Definition::Label(_) => return None, | ||
118 | } | ||
119 | } | ||
120 | |||
121 | fn extract_positioned_link_from_comment( | ||
122 | position: FilePosition, | ||
123 | comment: &ast::Comment, | ||
124 | ) -> Option<(String, Option<hir::Namespace>)> { | ||
125 | let comment_range = comment.syntax().text_range(); | ||
126 | let doc_comment = comment.doc_comment()?; | ||
127 | let def_links = extract_definitions_from_markdown(doc_comment); | ||
128 | let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| { | ||
129 | let matched_position = comment_range.start() + TextSize::from(def_link_range.start as u32); | ||
130 | match position.offset.checked_sub(matched_position) { | ||
131 | Some(distance) => distance, | ||
132 | None => comment_range.end(), | ||
133 | } | ||
134 | })?; | ||
135 | Some((def_link.to_string(), ns.clone())) | ||
136 | } | ||
137 | |||
71 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 138 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
72 | return tokens.max_by_key(priority); | 139 | return tokens.max_by_key(priority); |
73 | fn priority(n: &SyntaxToken) -> usize { | 140 | fn priority(n: &SyntaxToken) -> usize { |
74 | match n.kind() { | 141 | match n.kind() { |
75 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 2, | 142 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | COMMENT => 2, |
76 | kind if kind.is_trivia() => 0, | 143 | kind if kind.is_trivia() => 0, |
77 | _ => 1, | 144 | _ => 1, |
78 | } | 145 | } |
@@ -1145,4 +1212,19 @@ fn foo<'foo>(_: &'foo ()) { | |||
1145 | }"#, | 1212 | }"#, |
1146 | ) | 1213 | ) |
1147 | } | 1214 | } |
1215 | |||
1216 | #[test] | ||
1217 | fn goto_def_for_intra_rustdoc_link_same_file() { | ||
1218 | check( | ||
1219 | r#" | ||
1220 | /// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar) | ||
1221 | pub fn bar() { } | ||
1222 | |||
1223 | /// You might want to see [`std::fs::read()`] too. | ||
1224 | pub fn foo() { } | ||
1225 | //^^^ | ||
1226 | |||
1227 | }"#, | ||
1228 | ) | ||
1229 | } | ||
1148 | } | 1230 | } |
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index a74829cd0..3e9a65d9c 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs | |||
@@ -353,13 +353,25 @@ fn is_argument_similar_to_param_name( | |||
353 | } | 353 | } |
354 | match get_string_representation(argument) { | 354 | match get_string_representation(argument) { |
355 | None => false, | 355 | None => false, |
356 | Some(mut repr) => { | 356 | Some(argument_string) => { |
357 | let param_name = param_name.to_ascii_lowercase(); | 357 | let num_leading_underscores = |
358 | let argument_string = { | 358 | argument_string.bytes().take_while(|&c| c == b'_').count(); |
359 | repr.make_ascii_lowercase(); | 359 | |
360 | repr.trim_start_matches('_') | 360 | // Does the argument name begin with the parameter name? Ignore leading underscores. |
361 | }; | 361 | let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores); |
362 | argument_string.starts_with(¶m_name) || argument_string.ends_with(¶m_name) | 362 | let starts_with_pattern = param_name.bytes().all( |
363 | |expected| matches!(arg_bytes.next(), Some(actual) if expected.eq_ignore_ascii_case(&actual)), | ||
364 | ); | ||
365 | |||
366 | if starts_with_pattern { | ||
367 | return true; | ||
368 | } | ||
369 | |||
370 | // Does the argument name end with the parameter name? | ||
371 | let mut arg_bytes = argument_string.bytes().skip(num_leading_underscores); | ||
372 | param_name.bytes().rev().all( | ||
373 | |expected| matches!(arg_bytes.next_back(), Some(actual) if expected.eq_ignore_ascii_case(&actual)), | ||
374 | ) | ||
363 | } | 375 | } |
364 | } | 376 | } |
365 | } | 377 | } |