diff options
-rw-r--r-- | crates/ide/src/doc_links.rs | 68 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 24 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 27 | ||||
-rw-r--r-- | crates/ide_db/src/defs.rs | 23 |
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) |
28 | pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 28 | pub(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( | |||
124 | pub(crate) fn extract_definitions_from_markdown( | 119 | pub(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 | 125 | fn 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( | |||
149 | pub(crate) fn extract_positioned_link_from_comment( | 144 | pub(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 | ||
222 | fn 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 | |||
1168 | pub 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 | ||
1177 | pub 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 | |
3816 | fn foo() {} | 3817 | pub 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 | ||
3825 | pub 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)] |