aboutsummaryrefslogtreecommitdiff
path: root/crates
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
parente95e666b106b2f63ab2b350e656c9e8b96441fa7 (diff)
WIP: Command to open docs under cursor
Diffstat (limited to 'crates')
-rw-r--r--crates/ide/src/lib.rs8
-rw-r--r--crates/ide/src/link_rewrite.rs82
-rw-r--r--crates/rust-analyzer/src/handlers.rs15
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs28
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
5 files changed, 132 insertions, 2 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}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 468655f9c..ec8c8fecd 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -34,7 +34,7 @@ use crate::{
34 config::RustfmtConfig, 34 config::RustfmtConfig,
35 from_json, from_proto, 35 from_json, from_proto,
36 global_state::{GlobalState, GlobalStateSnapshot}, 36 global_state::{GlobalState, GlobalStateSnapshot},
37 lsp_ext::{self, InlayHint, InlayHintsParams}, 37 lsp_ext::{self, DocumentationLink, InlayHint, InlayHintsParams, OpenDocsParams},
38 to_proto, LspError, Result, 38 to_proto, LspError, Result,
39}; 39};
40 40
@@ -1310,6 +1310,19 @@ pub(crate) fn handle_semantic_tokens_range(
1310 Ok(Some(semantic_tokens.into())) 1310 Ok(Some(semantic_tokens.into()))
1311} 1311}
1312 1312
1313pub(crate) fn handle_open_docs(
1314 snap: GlobalStateSnapshot,
1315 params: OpenDocsParams,
1316) -> Result<DocumentationLink> {
1317 let _p = profile::span("handle_open_docs");
1318 let position = from_proto::file_position(&snap, params.position)?;
1319
1320 // FIXME: Propogate or ignore this error instead of panicking.
1321 let remote = snap.analysis.get_doc_url(position)?.unwrap();
1322
1323 Ok(DocumentationLink { remote })
1324}
1325
1313fn implementation_title(count: usize) -> String { 1326fn implementation_title(count: usize) -> String {
1314 if count == 1 { 1327 if count == 1 {
1315 "1 implementation".into() 1328 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index fee0bb69c..83a20802f 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -347,3 +347,31 @@ pub struct CommandLink {
347 #[serde(skip_serializing_if = "Option::is_none")] 347 #[serde(skip_serializing_if = "Option::is_none")]
348 pub tooltip: Option<String>, 348 pub tooltip: Option<String>,
349} 349}
350
351pub enum OpenDocs {}
352
353impl Request for OpenDocs {
354 type Params = OpenDocsParams;
355 type Result = DocumentationLink;
356 const METHOD: &'static str = "rust-analyzer/openDocs";
357}
358
359#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
360#[serde(rename_all = "camelCase")]
361pub struct OpenDocsParams {
362 // TODO: I don't know the difference between these two methods of passing position.
363 #[serde(flatten)]
364 pub position: lsp_types::TextDocumentPositionParams,
365 // pub textDocument: lsp_types::TextDocumentIdentifier,
366 // pub position: lsp_types::Position,
367}
368
369#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
370#[serde(rename_all = "camelCase")]
371pub struct DocumentationLink {
372 pub remote: String, // TODO: Better API?
373 // #[serde(skip_serializing_if = "Option::is_none")]
374 // pub remote: Option<String>,
375 // #[serde(skip_serializing_if = "Option::is_none")]
376 // pub local: Option<String>
377}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4b7ac8224..75f85011d 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -384,6 +384,7 @@ impl GlobalState {
384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)? 384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)? 385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)? 386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
387 .on::<lsp_ext::OpenDocs>(handlers::handle_open_docs)?
387 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? 388 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
388 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? 389 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
389 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 390 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?