aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-03-29 20:23:45 +0100
committerLukas Wirth <[email protected]>2021-03-30 13:03:32 +0100
commit9df78ec4a4e41ca94b25f292aba90e266f104f02 (patch)
tree0b670a721b9fd5de261de18b871f20552e23f0fb /crates
parent0b68e03bf56c00f63fcc65e7879cc64c6d5c4f30 (diff)
Properly resolve intra doc links in hover and goto_definition
Diffstat (limited to 'crates')
-rw-r--r--crates/ide/src/doc_links.rs68
-rw-r--r--crates/ide/src/goto_definition.rs24
-rw-r--r--crates/ide/src/hover.rs27
-rw-r--r--crates/ide_db/src/defs.rs23
4 files changed, 103 insertions, 39 deletions
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 99276168f..69442278b 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -26,12 +26,7 @@ pub(crate) type DocumentationLink = String;
26 26
27/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 27/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
28pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 28pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
29 let mut cb = |link: BrokenLink| { 29 let mut cb = broken_link_clone_cb;
30 Some((
31 /*url*/ link.reference.to_owned().into(),
32 /*title*/ link.reference.to_owned().into(),
33 ))
34 };
35 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); 30 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
36 31
37 let doc = map_links(doc, |target, title: &str| { 32 let doc = map_links(doc, |target, title: &str| {
@@ -124,24 +119,24 @@ pub(crate) fn external_docs(
124pub(crate) fn extract_definitions_from_markdown( 119pub(crate) fn extract_definitions_from_markdown(
125 markdown: &str, 120 markdown: &str,
126) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> { 121) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> {
127 let mut res = vec![]; 122 extract_definitions_from_markdown_(markdown, &mut broken_link_clone_cb).collect()
128 let mut cb = |link: BrokenLink| { 123}
129 // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong 124
130 // this is fixed in the repo but not on the crates.io release yet 125fn extract_definitions_from_markdown_<'a>(
131 Some(( 126 markdown: &'a str,
132 /*url*/ link.reference.to_owned().into(), 127 cb: &'a mut dyn FnMut(BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)>,
133 /*title*/ link.reference.to_owned().into(), 128) -> impl Iterator<Item = (Range<usize>, String, Option<hir::Namespace>)> + 'a {
134 )) 129 Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(cb))
135 }; 130 .into_offset_iter()
136 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); 131 .filter_map(|(event, range)| {
137 for (event, range) in doc.into_offset_iter() { 132 if let Event::Start(Tag::Link(_, target, title)) = event {
138 if let Event::Start(Tag::Link(_, target, title)) = event { 133 let link = if target.is_empty() { title } else { target };
139 let link = if target.is_empty() { title } else { target }; 134 let (link, ns) = parse_intra_doc_link(&link);
140 let (link, ns) = parse_intra_doc_link(&link); 135 Some((range, link.to_string(), ns))
141 res.push((range, link.to_string(), ns)); 136 } else {
142 } 137 None
143 } 138 }
144 res 139 })
145} 140}
146 141
147/// Extracts a link from a comment at the given position returning the spanning range, link and 142/// Extracts a link from a comment at the given position returning the spanning range, link and
@@ -149,20 +144,24 @@ pub(crate) fn extract_definitions_from_markdown(
149pub(crate) fn extract_positioned_link_from_comment( 144pub(crate) fn extract_positioned_link_from_comment(
150 position: TextSize, 145 position: TextSize,
151 comment: &ast::Comment, 146 comment: &ast::Comment,
147 docs: hir::Documentation,
152) -> Option<(TextRange, String, Option<hir::Namespace>)> { 148) -> Option<(TextRange, String, Option<hir::Namespace>)> {
153 let doc_comment = comment.doc_comment()?; 149 let doc_comment = comment.doc_comment()?.to_string() + "\n" + docs.as_str();
154 let comment_start = 150 let comment_start =
155 comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32); 151 comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32);
156 let def_links = extract_definitions_from_markdown(doc_comment); 152 let len = comment.syntax().text_range().len().into();
157 let (range, def_link, ns) = 153 let mut cb = broken_link_clone_cb;
158 def_links.into_iter().find_map(|(Range { start, end }, def_link, ns)| { 154 // because pulldown_cmarks lifetimes are wrong we gotta dance around a few temporaries here
155 let res = extract_definitions_from_markdown_(&doc_comment, &mut cb)
156 .take_while(|&(Range { end, .. }, ..)| end < len)
157 .find_map(|(Range { start, end }, def_link, ns)| {
159 let range = TextRange::at( 158 let range = TextRange::at(
160 comment_start + TextSize::from(start as u32), 159 comment_start + TextSize::from(start as u32),
161 TextSize::from((end - start) as u32), 160 TextSize::from((end - start) as u32),
162 ); 161 );
163 range.contains(position).then(|| (range, def_link, ns)) 162 range.contains(position).then(|| (range, def_link, ns))
164 })?; 163 });
165 Some((range, def_link, ns)) 164 res
166} 165}
167 166
168/// Turns a syntax node into it's [`Definition`] if it can hold docs. 167/// Turns a syntax node into it's [`Definition`] if it can hold docs.
@@ -220,6 +219,15 @@ pub(crate) fn resolve_doc_path_for_def(
220 } 219 }
221} 220}
222 221
222fn broken_link_clone_cb<'a, 'b>(link: BrokenLink<'a>) -> Option<(CowStr<'b>, CowStr<'b>)> {
223 // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong
224 // this is fixed in the repo but not on the crates.io release yet
225 Some((
226 /*url*/ link.reference.to_owned().into(),
227 /*title*/ link.reference.to_owned().into(),
228 ))
229}
230
223// FIXME: 231// FIXME:
224// BUG: For Option::Some 232// BUG: For Option::Some
225// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some 233// 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 c6556c487..4e4d1b200 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -31,7 +31,8 @@ pub(crate) fn goto_definition(
31 let token = sema.descend_into_macros(original_token.clone()); 31 let token = sema.descend_into_macros(original_token.clone());
32 let parent = token.parent()?; 32 let parent = token.parent()?;
33 if let Some(comment) = ast::Comment::cast(token) { 33 if let Some(comment) = ast::Comment::cast(token) {
34 let (_, link, ns) = extract_positioned_link_from_comment(position.offset, &comment)?; 34 let docs = doc_owner_to_def(&sema, &parent)?.docs(db)?;
35 let (_, link, ns) = extract_positioned_link_from_comment(position.offset, &comment, docs)?;
35 let def = doc_owner_to_def(&sema, &parent)?; 36 let def = doc_owner_to_def(&sema, &parent)?;
36 let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?; 37 let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?;
37 return Some(RangeInfo::new(original_token.text_range(), vec![nav])); 38 return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
@@ -1158,4 +1159,25 @@ fn fn_macro() {}
1158 "#, 1159 "#,
1159 ) 1160 )
1160 } 1161 }
1162
1163 #[test]
1164 fn goto_intra_doc_links() {
1165 check(
1166 r#"
1167
1168pub mod theitem {
1169 /// This is the item. Cool!
1170 pub struct TheItem;
1171 //^^^^^^^
1172}
1173
1174/// Gives you a [`TheItem$0`].
1175///
1176/// [`TheItem`]: theitem::TheItem
1177pub fn gimme() -> theitem::TheItem {
1178 theitem::TheItem
1179}
1180"#,
1181 );
1182 }
1161} 1183}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 02a1a5b37..5a497e92d 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -115,10 +115,11 @@ pub(crate) fn hover(
115 115
116 _ => ast::Comment::cast(token.clone()) 116 _ => ast::Comment::cast(token.clone())
117 .and_then(|comment| { 117 .and_then(|comment| {
118 let def = doc_owner_to_def(&sema, &node)?;
119 let docs = def.docs(db)?;
118 let (idl_range, link, ns) = 120 let (idl_range, link, ns) =
119 extract_positioned_link_from_comment(position.offset, &comment)?; 121 extract_positioned_link_from_comment(position.offset, &comment, docs)?;
120 range = Some(idl_range); 122 range = Some(idl_range);
121 let def = doc_owner_to_def(&sema, &node)?;
122 resolve_doc_path_for_def(db, def, &link, ns) 123 resolve_doc_path_for_def(db, def, &link, ns)
123 }) 124 })
124 .map(Definition::ModuleDef), 125 .map(Definition::ModuleDef),
@@ -3812,23 +3813,33 @@ fn main() {
3812 fn hover_intra_doc_links() { 3813 fn hover_intra_doc_links() {
3813 check( 3814 check(
3814 r#" 3815 r#"
3815/// This is the [`foo`](foo$0) function. 3816
3816fn foo() {} 3817pub mod theitem {
3818 /// This is the item. Cool!
3819 pub struct TheItem;
3820}
3821
3822/// Gives you a [`TheItem$0`].
3823///
3824/// [`TheItem`]: theitem::TheItem
3825pub fn gimme() -> theitem::TheItem {
3826 theitem::TheItem
3827}
3817"#, 3828"#,
3818 expect![[r#" 3829 expect![[r#"
3819 *[`foo`](foo)* 3830 *[`TheItem`]*
3820 3831
3821 ```rust 3832 ```rust
3822 test 3833 test::theitem
3823 ``` 3834 ```
3824 3835
3825 ```rust 3836 ```rust
3826 fn foo() 3837 pub struct TheItem
3827 ``` 3838 ```
3828 3839
3829 --- 3840 ---
3830 3841
3831 This is the [`foo`](https://docs.rs/test/*/test/fn.foo.html) function. 3842 This is the item. Cool!
3832 "#]], 3843 "#]],
3833 ); 3844 );
3834 } 3845 }
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index de0dc2a40..378bac7b2 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -79,6 +79,29 @@ impl Definition {
79 }; 79 };
80 Some(name) 80 Some(name)
81 } 81 }
82
83 pub fn docs(&self, db: &RootDatabase) -> Option<hir::Documentation> {
84 match self {
85 Definition::Macro(it) => it.docs(db),
86 Definition::Field(it) => it.docs(db),
87 Definition::ModuleDef(def) => match def {
88 hir::ModuleDef::Module(it) => it.docs(db),
89 hir::ModuleDef::Function(it) => it.docs(db),
90 hir::ModuleDef::Adt(def) => match def {
91 hir::Adt::Struct(it) => it.docs(db),
92 hir::Adt::Union(it) => it.docs(db),
93 hir::Adt::Enum(it) => it.docs(db),
94 },
95 hir::ModuleDef::Variant(it) => it.docs(db),
96 hir::ModuleDef::Const(it) => it.docs(db),
97 hir::ModuleDef::Static(it) => it.docs(db),
98 hir::ModuleDef::Trait(it) => it.docs(db),
99 hir::ModuleDef::TypeAlias(it) => it.docs(db),
100 hir::ModuleDef::BuiltinType(_) => None,
101 },
102 _ => None,
103 }
104 }
82} 105}
83 106
84#[derive(Debug)] 107#[derive(Debug)]