diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide/src/lib.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/link_rewrite.rs | 82 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 15 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 28 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 1 |
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}; | |||
8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | 8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; |
9 | use url::Url; | 9 | use url::Url; |
10 | 10 | ||
11 | use crate::{FilePosition, Semantics}; | ||
12 | use hir::{get_doc_link, resolve_doc_link}; | ||
13 | use ide_db::{ | ||
14 | defs::{classify_name, classify_name_ref, Definition}, | ||
15 | RootDatabase, | ||
16 | }; | ||
17 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | ||
18 | |||
19 | pub 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) |
12 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 22 | pub 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 | ||
93 | pub 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 | ||
107 | fn 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 | |||
83 | fn rewrite_intra_doc_link( | 124 | fn 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. | ||
184 | pub 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. | ||
142 | fn map_links<'e>( | 210 | fn 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 | |||
347 | fn 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 | ||
1313 | pub(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 | |||
1313 | fn implementation_title(count: usize) -> String { | 1326 | fn 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 | |||
351 | pub enum OpenDocs {} | ||
352 | |||
353 | impl 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")] | ||
361 | pub 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")] | ||
371 | pub 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)? |