aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/doc_links.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-04-05 13:30:20 +0100
committerGitHub <[email protected]>2021-04-05 13:30:20 +0100
commitc2be91dcd826e1529ac6ac431b3f871ec72abebc (patch)
treee267eed3fc8966093fbd79389e47a051c435cd7d /crates/ide/src/doc_links.rs
parentd8ee25bb976f50c0c0c8c247ca8bb030d9167bdb (diff)
parent8d786dc4c3ce26dbb3432023c7461bd879993bfd (diff)
Merge #8245
8245: Properly resolve intra doc links in hover and goto_definition r=matklad a=Veykril Unfortunately involves a bit of weird workarounds due to pulldown_cmark's incorrect lifetimes on `BrokenLinkCallback`... I should probably open an issue there asking for the fixes to be pushed to a release since they already exist in the repo for quite some time it seems. Fixes #8258, Fixes #8238 Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates/ide/src/doc_links.rs')
-rw-r--r--crates/ide/src/doc_links.rs132
1 files changed, 59 insertions, 73 deletions
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 67e2e5a1c..c5dc14a23 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,6 +1,9 @@
1//! Extracts, resolves and rewrites links and intra-doc links in markdown documentation. 1//! Extracts, resolves and rewrites links and intra-doc links in markdown documentation.
2 2
3use std::{convert::TryFrom, iter::once, ops::Range}; 3use std::{
4 convert::{TryFrom, TryInto},
5 iter::once,
6};
4 7
5use itertools::Itertools; 8use itertools::Itertools;
6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; 9use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
@@ -16,8 +19,7 @@ use ide_db::{
16 RootDatabase, 19 RootDatabase,
17}; 20};
18use syntax::{ 21use syntax::{
19 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, 22 ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TokenAtOffset, T,
20 TokenAtOffset, T,
21}; 23};
22 24
23use crate::{FilePosition, Semantics}; 25use crate::{FilePosition, Semantics};
@@ -26,12 +28,7 @@ pub(crate) type DocumentationLink = String;
26 28
27/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 29/// 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 { 30pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
29 let mut cb = |link: BrokenLink| { 31 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)); 32 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
36 33
37 let doc = map_links(doc, |target, title: &str| { 34 let doc = map_links(doc, |target, title: &str| {
@@ -123,74 +120,27 @@ pub(crate) fn external_docs(
123/// Extracts all links from a given markdown text. 120/// Extracts all links from a given markdown text.
124pub(crate) fn extract_definitions_from_markdown( 121pub(crate) fn extract_definitions_from_markdown(
125 markdown: &str, 122 markdown: &str,
126) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> { 123) -> Vec<(TextRange, String, Option<hir::Namespace>)> {
127 let mut res = vec![]; 124 Parser::new_with_broken_link_callback(
128 let mut cb = |link: BrokenLink| { 125 markdown,
129 // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong 126 Options::empty(),
130 // this is fixed in the repo but not on the crates.io release yet 127 Some(&mut broken_link_clone_cb),
131 Some(( 128 )
132 /*url*/ link.reference.to_owned().into(), 129 .into_offset_iter()
133 /*title*/ link.reference.to_owned().into(), 130 .filter_map(|(event, range)| {
134 ))
135 };
136 let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb));
137 for (event, range) in doc.into_offset_iter() {
138 if let Event::Start(Tag::Link(_, target, title)) = event { 131 if let Event::Start(Tag::Link(_, target, title)) = event {
139 let link = if target.is_empty() { title } else { target }; 132 let link = if target.is_empty() { title } else { target };
140 let (link, ns) = parse_intra_doc_link(&link); 133 let (link, ns) = parse_intra_doc_link(&link);
141 res.push((range, link.to_string(), ns)); 134 Some((
142 } 135 TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?),
143 } 136 link.to_string(),
144 res 137 ns,
145} 138 ))
146 139 } else {
147/// Extracts a link from a comment at the given position returning the spanning range, link and 140 None
148/// optionally it's namespace.
149pub(crate) fn extract_positioned_link_from_comment(
150 position: TextSize,
151 comment: &ast::Comment,
152) -> Option<(TextRange, String, Option<hir::Namespace>)> {
153 let doc_comment = comment.doc_comment()?;
154 let comment_start =
155 comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32);
156 let def_links = extract_definitions_from_markdown(doc_comment);
157 let (range, def_link, ns) =
158 def_links.into_iter().find_map(|(Range { start, end }, def_link, ns)| {
159 let range = TextRange::at(
160 comment_start + TextSize::from(start as u32),
161 TextSize::from((end - start) as u32),
162 );
163 range.contains(position).then(|| (range, def_link, ns))
164 })?;
165 Some((range, def_link, ns))
166}
167
168/// Turns a syntax node into it's [`Definition`] if it can hold docs.
169pub(crate) fn doc_owner_to_def(
170 sema: &Semantics<RootDatabase>,
171 item: &SyntaxNode,
172) -> Option<Definition> {
173 let res: hir::ModuleDef = match_ast! {
174 match item {
175 ast::SourceFile(_it) => sema.scope(item).module()?.into(),
176 ast::Fn(it) => sema.to_def(&it)?.into(),
177 ast::Struct(it) => sema.to_def(&it)?.into(),
178 ast::Enum(it) => sema.to_def(&it)?.into(),
179 ast::Union(it) => sema.to_def(&it)?.into(),
180 ast::Trait(it) => sema.to_def(&it)?.into(),
181 ast::Const(it) => sema.to_def(&it)?.into(),
182 ast::Static(it) => sema.to_def(&it)?.into(),
183 ast::TypeAlias(it) => sema.to_def(&it)?.into(),
184 ast::Variant(it) => sema.to_def(&it)?.into(),
185 ast::Trait(it) => sema.to_def(&it)?.into(),
186 ast::Impl(it) => return sema.to_def(&it).map(Definition::SelfType),
187 ast::Macro(it) => return sema.to_def(&it).map(Definition::Macro),
188 ast::TupleField(it) => return sema.to_def(&it).map(Definition::Field),
189 ast::RecordField(it) => return sema.to_def(&it).map(Definition::Field),
190 _ => return None,
191 } 141 }
192 }; 142 })
193 Some(Definition::ModuleDef(res)) 143 .collect()
194} 144}
195 145
196pub(crate) fn resolve_doc_path_for_def( 146pub(crate) fn resolve_doc_path_for_def(
@@ -220,6 +170,42 @@ pub(crate) fn resolve_doc_path_for_def(
220 } 170 }
221} 171}
222 172
173pub(crate) fn doc_attributes(
174 sema: &Semantics<RootDatabase>,
175 node: &SyntaxNode,
176) -> Option<(hir::AttrsWithOwner, Definition)> {
177 match_ast! {
178 match node {
179 ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
180 ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))),
181 ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Function(def)))),
182 ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(def))))),
183 ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Union(def))))),
184 ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(def))))),
185 ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Variant(def)))),
186 ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Trait(def)))),
187 ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Static(def)))),
188 ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Const(def)))),
189 ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::TypeAlias(def)))),
190 ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))),
191 ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
192 ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))),
193 ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))),
194 // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
195 _ => return None
196 }
197 }
198}
199
200fn broken_link_clone_cb<'a, 'b>(link: BrokenLink<'a>) -> Option<(CowStr<'b>, CowStr<'b>)> {
201 // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong
202 // this is fixed in the repo but not on the crates.io release yet
203 Some((
204 /*url*/ link.reference.to_owned().into(),
205 /*title*/ link.reference.to_owned().into(),
206 ))
207}
208
223// FIXME: 209// FIXME:
224// BUG: For Option::Some 210// BUG: For Option::Some
225// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some 211// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some