aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-01-10 10:02:10 +0000
committerGitHub <[email protected]>2021-01-10 10:02:10 +0000
commit6a0a47dd1492975959f6719202c3fb175df0349c (patch)
tree703f5163b43e4964645b102e0152ed8510ee6e5e
parent4fddf40f5bd51066e3201f319f7a5c1466483b4d (diff)
parent65c45083cf01d3a189b28e964254e82680d90ba3 (diff)
Merge #6980
6980: Implement to support intra-doc link r=matklad a=sasurau4 Helps with #6168 This PR is very limited implementation to support intra-doc. It only support links indicate same file function. I want someone to feedback me about this implementation. If the approach is good, I will continue this PR to support other symbols like enum and struct. Co-authored-by: Daiki Ihara <[email protected]>
-rw-r--r--crates/ide/src/doc_links.rs26
-rw-r--r--crates/ide/src/goto_definition.rs90
2 files changed, 111 insertions, 5 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
3use std::{convert::TryFrom, iter::once}; 3use std::{convert::TryFrom, iter::once, ops::Range};
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; 6use 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
64pub(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.
65pub(crate) fn remove_links(markdown: &str) -> String { 89pub(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 @@
1use either::Either; 1use either::Either;
2use hir::Semantics; 2use hir::{HasAttrs, ModuleDef, Semantics};
3use ide_db::{ 3use 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};
8use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; 8use syntax::{
9 ast::{self, NameOwner},
10 match_ast, AstNode, AstToken,
11 SyntaxKind::*,
12 SyntaxToken, TextSize, TokenAtOffset, T,
13};
9 14
10use crate::{ 15use 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
81fn 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
121fn 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
71fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 138fn 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)
1221pub fn bar() { }
1222
1223/// You might want to see [`std::fs::read()`] too.
1224pub fn foo() { }
1225 //^^^
1226
1227}"#,
1228 )
1229 }
1148} 1230}