aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--editors/code/package.json9
-rw-r--r--editors/code/src/commands.ts25
-rw-r--r--editors/code/src/lsp_ext.ts11
-rw-r--r--editors/code/src/main.ts1
9 files changed, 176 insertions, 4 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)?
diff --git a/editors/code/package.json b/editors/code/package.json
index 6a712a8a8..4bd3117fc 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -182,6 +182,11 @@
182 "command": "rust-analyzer.toggleInlayHints", 182 "command": "rust-analyzer.toggleInlayHints",
183 "title": "Toggle inlay hints", 183 "title": "Toggle inlay hints",
184 "category": "Rust Analyzer" 184 "category": "Rust Analyzer"
185 },
186 {
187 "command": "rust-analyzer.openDocs",
188 "title": "Open docs under cursor",
189 "category": "Rust Analyzer"
185 } 190 }
186 ], 191 ],
187 "keybindings": [ 192 "keybindings": [
@@ -1044,6 +1049,10 @@
1044 { 1049 {
1045 "command": "rust-analyzer.toggleInlayHints", 1050 "command": "rust-analyzer.toggleInlayHints",
1046 "when": "inRustProject" 1051 "when": "inRustProject"
1052 },
1053 {
1054 "command": "rust-analyzer.openDocs",
1055 "when": "inRustProject"
1047 } 1056 }
1048 ] 1057 ]
1049 } 1058 }
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 1a90f1b7d..b22cd450b 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -419,10 +419,31 @@ export function gotoLocation(ctx: Ctx): Cmd {
419 }; 419 };
420} 420}
421 421
422export function openDocs(ctx: Ctx): Cmd {
423 return async () => {
424 console.log("running openDocs");
425
426 const client = ctx.client;
427 const editor = vscode.window.activeTextEditor;
428 if (!editor || !client) {
429 console.log("not yet ready");
430 return
431 };
432
433 const position = editor.selection.active;
434 const textDocument = { uri: editor.document.uri.toString() };
435
436 const doclink = await client.sendRequest(ra.openDocs, { position, textDocument });
437
438 vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink.remote));
439 };
440
441}
442
422export function resolveCodeAction(ctx: Ctx): Cmd { 443export function resolveCodeAction(ctx: Ctx): Cmd {
423 const client = ctx.client; 444 const client = ctx.client;
424 return async (params: ra.ResolveCodeActionParams) => { 445 return async () => {
425 const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); 446 const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, null);
426 if (!item) { 447 if (!item) {
427 return; 448 return;
428 } 449 }
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index f286b68a6..569e747bd 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -118,3 +118,14 @@ export interface CommandLinkGroup {
118 title?: string; 118 title?: string;
119 commands: CommandLink[]; 119 commands: CommandLink[];
120} 120}
121
122export interface DocumentationLink {
123 remote: string;
124}
125
126export interface OpenDocsParams {
127 textDocument: lc.TextDocumentIdentifier;
128 position: lc.Position;
129}
130
131export const openDocs = new lc.RequestType<OpenDocsParams, DocumentationLink, void>('rust-analyzer/openDocs');
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 2896d90ac..09543e348 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -110,6 +110,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
110 ctx.registerCommand('run', commands.run); 110 ctx.registerCommand('run', commands.run);
111 ctx.registerCommand('debug', commands.debug); 111 ctx.registerCommand('debug', commands.debug);
112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig); 112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
113 ctx.registerCommand('openDocs', commands.openDocs);
113 114
114 defaultOnEnter.dispose(); 115 defaultOnEnter.dispose();
115 ctx.registerCommand('onEnter', commands.onEnter); 116 ctx.registerCommand('onEnter', commands.onEnter);