aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-30 09:02:29 +0100
committerZac Pullar-Strecker <[email protected]>2020-10-08 02:59:31 +0100
commitbfda0d25834250a3adbcd0d26953a1cdc6662e7f (patch)
tree439fa97a999360cb5fe4602e7ab26d66aa6a3662 /crates/ide
parente95e666b106b2f63ab2b350e656c9e8b96441fa7 (diff)
WIP: Command to open docs under cursor
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/lib.rs8
-rw-r--r--crates/ide/src/link_rewrite.rs82
2 files changed, 89 insertions, 1 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 57f3581b6..645369597 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -382,6 +382,14 @@ impl Analysis {
382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) 382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown))
383 } 383 }
384 384
385 /// Return URL(s) for the documentation of the symbol under the cursor.
386 pub fn get_doc_url(
387 &self,
388 position: FilePosition,
389 ) -> Cancelable<Option<link_rewrite::DocumentationLink>> {
390 self.with_db(|db| link_rewrite::get_doc_url(db, &position))
391 }
392
385 /// Computes parameter information for the given call expression. 393 /// Computes parameter information for the given call expression.
386 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { 394 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
387 self.with_db(|db| call_info::call_info(db, position)) 395 self.with_db(|db| call_info::call_info(db, position))
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs
index c317a2379..80005c06e 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/link_rewrite.rs
@@ -8,6 +8,16 @@ use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; 8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
9use url::Url; 9use url::Url;
10 10
11use crate::{FilePosition, Semantics};
12use hir::{get_doc_link, resolve_doc_link};
13use ide_db::{
14 defs::{classify_name, classify_name_ref, Definition},
15 RootDatabase,
16};
17use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
18
19pub type DocumentationLink = String;
20
11/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 21/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
12pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 22pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
13 let doc = Parser::new_with_broken_link_callback( 23 let doc = Parser::new_with_broken_link_callback(
@@ -80,6 +90,37 @@ pub fn remove_links(markdown: &str) -> String {
80 out 90 out
81} 91}
82 92
93pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T) -> Option<String> {
94 eprintln!("hir::doc_links::get_doc_link");
95 let module_def = definition.clone().try_into_module_def()?;
96
97 get_doc_link_impl(db, &module_def)
98}
99
100// TODO:
101// BUG: For Option
102// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
103// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
104//
105// BUG: For methods
106// import_map.path_of(ns) fails, is not designed to resolve methods
107fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> {
108 eprintln!("get_doc_link_impl: {:#?}", moddef);
109 let ns = ItemInNs::Types(moddef.clone().into());
110
111 let module = moddef.module(db)?;
112 let krate = module.krate();
113 let import_map = db.import_map(krate.into());
114 let base = once(krate.display_name(db).unwrap())
115 .chain(import_map.path_of(ns).unwrap().segments.iter().map(|name| format!("{}", name)))
116 .join("/");
117
118 get_doc_url(db, &krate)
119 .and_then(|url| url.join(&base).ok())
120 .and_then(|url| get_symbol_filename(db, &moddef).as_deref().and_then(|f| url.join(f).ok()))
121 .map(|url| url.into_string())
122}
123
83fn rewrite_intra_doc_link( 124fn rewrite_intra_doc_link(
84 db: &RootDatabase, 125 db: &RootDatabase,
85 def: Definition, 126 def: Definition,
@@ -138,7 +179,34 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
138 .map(|url| url.into_string()) 179 .map(|url| url.into_string())
139} 180}
140 181
141// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. 182// FIXME: This should either be moved, or the module should be renamed.
183/// Retrieve a link to documentation for the given symbol.
184pub fn get_doc_url(db: &RootDatabase, position: &FilePosition) -> Option<DocumentationLink> {
185 let sema = Semantics::new(db);
186 let file = sema.parse(position.file_id).syntax().clone();
187 let token = pick_best(file.token_at_offset(position.offset))?;
188 let token = sema.descend_into_macros(token);
189
190 let node = token.parent();
191 let definition = match_ast! {
192 match node {
193 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
194 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
195 _ => None,
196 }
197 };
198
199 match definition? {
200 Definition::Macro(t) => get_doc_link(db, &t),
201 Definition::Field(t) => get_doc_link(db, &t),
202 Definition::ModuleDef(t) => get_doc_link(db, &t),
203 Definition::SelfType(t) => get_doc_link(db, &t),
204 Definition::Local(t) => get_doc_link(db, &t),
205 Definition::TypeParam(t) => get_doc_link(db, &t),
206 }
207}
208
209/// Rewrites a markdown document, applying 'callback' to each link.
142fn map_links<'e>( 210fn map_links<'e>(
143 events: impl Iterator<Item = Event<'e>>, 211 events: impl Iterator<Item = Event<'e>>,
144 callback: impl Fn(&str, &str) -> (String, String), 212 callback: impl Fn(&str, &str) -> (String, String),
@@ -275,3 +343,15 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
275 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), 343 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
276 }) 344 })
277} 345}
346
347fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
348 return tokens.max_by_key(priority);
349 fn priority(n: &SyntaxToken) -> usize {
350 match n.kind() {
351 IDENT | INT_NUMBER => 3,
352 T!['('] | T![')'] => 2,
353 kind if kind.is_trivia() => 0,
354 _ => 1,
355 }
356 }
357}