From 20c64cc0e6a025179c68cb0e76e4eca5f986271d Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Thu, 22 Jul 2021 22:08:28 -0400 Subject: [PATCH 01/19] feat: Extend the server with the hover_range capability --- crates/ide/src/hover.rs | 7 +++++++ crates/ide/src/lib.rs | 9 +++++++++ crates/rust-analyzer/src/handlers.rs | 25 ++++++++++++++++++++----- crates/rust-analyzer/src/lsp_ext.rs | 9 ++++++++- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 35601f2efc7a..afa67f72bed6 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -241,6 +241,13 @@ fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option Option> { +} + fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option { fn to_action(nav_target: NavigationTarget) -> HoverAction { HoverAction::Implementation(FilePosition { diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 83173e1c6b8a..b6b741a22ab6 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -423,6 +423,15 @@ impl Analysis { self.with_db(|db| hover::hover(db, position, config)) } + /// Returns a short text displaying the type for the expression. + pub fn hover_range( + &self, + config: &HoverConfig, + range: FileRange, + ) -> Cancellable>> { + self.with_db(|db| hover::hover_range(db, range, config)) + } + /// Return URL(s) for the documentation of the symbol under the cursor. pub fn external_docs( &self, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 52b557f15688..ede3103abfa0 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -867,14 +867,29 @@ pub(crate) fn handle_signature_help( pub(crate) fn handle_hover( snap: GlobalStateSnapshot, - params: lsp_types::HoverParams, + params: lsp_ext::HoverParams, ) -> Result> { let _p = profile::span("handle_hover"); - let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let info = match snap.analysis.hover(&snap.config.hover(), position)? { - None => return Ok(None), - Some(info) => info, + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let range = from_proto::file_range(&snap, params.text_document, params.range)?; + + let info = if range.end - range.start == 1 { + // It's a hover over a position + match snap + .analysis + .hover(&snap.config.hover(), FilePosition { file_id, offset: range.start })? + { + None => return Ok(None), + Some(info) => info, + } + } else { + // It's a hover over a range + match snap.analysis.hover_range(&snap.config.hover(), range)? { + None => return Ok(None), + Some(info) => info, + } }; + let line_index = snap.file_line_index(position.file_id)?; let range = to_proto::range(&line_index, info.range); let hover = lsp_ext::Hover { diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index f11ad396e702..f6abd95e1e6c 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -376,11 +376,18 @@ pub struct SnippetTextEdit { pub enum HoverRequest {} impl Request for HoverRequest { - type Params = lsp_types::HoverParams; + type Params = HoverParams; type Result = Option; const METHOD: &'static str = "textDocument/hover"; } +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct HoverParams { + pub text_document: TextDocumentIdentifier, + pub range: Range, +} + #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] pub struct Hover { #[serde(flatten)] From 8ca3bb8fcd3c3ac8eb232086f6286eb96f4bac79 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sat, 24 Jul 2021 21:54:48 -0400 Subject: [PATCH 02/19] feat: Add the hover_range capability --- crates/ide/src/hover.rs | 28 +++++++++++++++++++++++++--- crates/rust-analyzer/src/handlers.rs | 7 ++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index afa67f72bed6..692c3ff676a8 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -1,7 +1,7 @@ use either::Either; use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics}; use ide_db::{ - base_db::SourceDatabase, + base_db::{FileRange, SourceDatabase}, defs::{Definition, NameClass, NameRefClass}, helpers::{ generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES}, @@ -12,8 +12,12 @@ use ide_db::{ use itertools::Itertools; use stdx::format_to; use syntax::{ - algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction, - SyntaxKind::*, SyntaxToken, T, + algo::{self, find_node_at_range}, + ast, + display::fn_as_proc_macro_label, + match_ast, AstNode, AstToken, Direction, + SyntaxKind::*, + SyntaxToken, T, }; use crate::{ @@ -246,6 +250,24 @@ pub(crate) fn hover_range( range: FileRange, config: &HoverConfig, ) -> Option> { + let sema = hir::Semantics::new(db); + let file = sema.parse(range.file_id).syntax().clone(); + let expr = find_node_at_range::(&file, range.range)?; + let ty = sema.type_of_expr(&expr)?; + + if ty.is_unknown() { + return None; + } + + let mut res = HoverResult::default(); + + res.markup = if config.markdown() { + Markup::fenced_block(&ty.display(db)) + } else { + ty.display(db).to_string().into() + }; + + Some(RangeInfo::new(range.range, res)) } fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option { diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index ede3103abfa0..6fae4b04a40d 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -873,24 +873,25 @@ pub(crate) fn handle_hover( let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let range = from_proto::file_range(&snap, params.text_document, params.range)?; - let info = if range.end - range.start == 1 { + let info = if range.range.is_empty() { // It's a hover over a position match snap .analysis - .hover(&snap.config.hover(), FilePosition { file_id, offset: range.start })? + .hover(&snap.config.hover(), FilePosition { file_id, offset: range.range.start() })? { None => return Ok(None), Some(info) => info, } } else { // It's a hover over a range + log::info!("Triggered range hover"); match snap.analysis.hover_range(&snap.config.hover(), range)? { None => return Ok(None), Some(info) => info, } }; - let line_index = snap.file_line_index(position.file_id)?; + let line_index = snap.file_line_index(range.file_id)?; let range = to_proto::range(&line_index, info.range); let hover = lsp_ext::Hover { hover: lsp_types::Hover { From 18644720eb808f39545ab208370d5f705bfc54d3 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sun, 25 Jul 2021 17:26:54 -0400 Subject: [PATCH 03/19] feat: Completed the client side implementation of rust-analyzer/hoverRange --- crates/rust-analyzer/src/handlers.rs | 46 +++++++++++------- crates/rust-analyzer/src/lsp_ext.rs | 12 ++++- crates/rust-analyzer/src/main_loop.rs | 1 + editors/code/src/client.ts | 68 ++++++++++++++++++++++----- editors/code/src/commands.ts | 24 ++++++++++ editors/code/src/lsp_ext.ts | 7 +++ editors/code/src/main.ts | 1 + 7 files changed, 129 insertions(+), 30 deletions(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 6fae4b04a40d..a84d8dfd82be 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -867,28 +867,40 @@ pub(crate) fn handle_signature_help( pub(crate) fn handle_hover( snap: GlobalStateSnapshot, - params: lsp_ext::HoverParams, + params: lsp_types::HoverParams, ) -> Result> { let _p = profile::span("handle_hover"); + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let info = match snap.analysis.hover(&snap.config.hover(), position)? { + None => return Ok(None), + Some(info) => info, + }; + + let line_index = snap.file_line_index(position.file_id)?; + let range = to_proto::range(&line_index, info.range); + let hover = lsp_ext::Hover { + hover: lsp_types::Hover { + contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)), + range: Some(range), + }, + actions: prepare_hover_actions(&snap, &info.info.actions), + }; + + Ok(Some(hover)) +} + +pub(crate) fn handle_hover_range( + snap: GlobalStateSnapshot, + params: lsp_ext::HoverRangeParams, +) -> Result> { + let _p = profile::span("handle_hover_range"); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let range = from_proto::file_range(&snap, params.text_document, params.range)?; - let info = if range.range.is_empty() { - // It's a hover over a position - match snap - .analysis - .hover(&snap.config.hover(), FilePosition { file_id, offset: range.range.start() })? - { - None => return Ok(None), - Some(info) => info, - } - } else { - // It's a hover over a range - log::info!("Triggered range hover"); - match snap.analysis.hover_range(&snap.config.hover(), range)? { - None => return Ok(None), - Some(info) => info, - } + log::info!("Triggered range hover"); + let info = match snap.analysis.hover_range(&snap.config.hover(), range)? { + None => return Ok(None), + Some(info) => info, }; let line_index = snap.file_line_index(range.file_id)?; diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index f6abd95e1e6c..7aed93f992ab 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -376,14 +376,22 @@ pub struct SnippetTextEdit { pub enum HoverRequest {} impl Request for HoverRequest { - type Params = HoverParams; + type Params = lsp_types::HoverParams; type Result = Option; const METHOD: &'static str = "textDocument/hover"; } +pub enum HoverRangeRequest {} + +impl Request for HoverRangeRequest { + type Params = HoverRangeParams; + type Result = Option; + const METHOD: &'static str = "rust-analyzer/hoverRange"; +} + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct HoverParams { +pub struct HoverRangeParams { pub text_document: TextDocumentIdentifier, pub range: Range, } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 35fce79f5eb6..af3cb0c8792b 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -542,6 +542,7 @@ impl GlobalState { .on::(handlers::handle_code_action) .on::(handlers::handle_code_action_resolve) .on::(handlers::handle_hover) + .on::(handlers::handle_hover_range) .on::(handlers::handle_open_docs) .on::(handlers::handle_open_cargo_toml) .on::(handlers::handle_move_item) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index f13ae07e148f..f0c10698a0c8 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -56,21 +56,67 @@ export function createClient(serverPath: string, workspace: Workspace, extraEnv: traceOutputChannel, middleware: { async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) { - return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then( - (result) => { - const hover = client.protocol2CodeConverter.asHover(result); - if (hover) { + const editor = vscode.window.activeTextEditor; + const selection = editor?.selection; + return selection?.contains(position) + ? client + .sendRequest( + ra.hoverRange, + { + textDocument: + client.code2ProtocolConverter.asTextDocumentIdentifier( + document + ), + range: client.code2ProtocolConverter.asRange( + editor?.selection + ), + }, + token + ) + .then( + (result) => + client.protocol2CodeConverter.asHover(result), + (error) => { + client.handleFailedRequest( + lc.HoverRequest.type, + undefined, + error, + null + ); + return Promise.resolve(null); + } + ) + : client + .sendRequest( + lc.HoverRequest.type, + client.code2ProtocolConverter.asTextDocumentPositionParams( + document, + position + ), + token + ) + .then( + (result) => { + const hover = + client.protocol2CodeConverter.asHover(result); + if (hover) { const actions = (result).actions; if (actions) { - hover.contents.push(renderHoverActions(actions)); + hover.contents.push(renderHoverActions(actions)); } + } + return hover; + }, + (error) => { + client.handleFailedRequest( + lc.HoverRequest.type, + token, + error, + null + ); + return Promise.resolve(null); } - return hover; - }, - (error) => { - client.handleFailedRequest(lc.HoverRequest.type, token, error, null); - return Promise.resolve(null); - }); + ); }, // Using custom handling of CodeActions to support action groups and snippet edits. // Note that this means we have to re-implement lazy edit resolving ourselves as well. diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 20ef1573f6cb..7f5cdd4876ea 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -116,6 +116,30 @@ export function matchingBrace(ctx: Ctx): Cmd { }; } +export function hoverRange(ctx: Ctx): Cmd { + return async () => { + const editor = ctx.activeRustEditor; + const client = ctx.client; + if (!editor || !client) return; + + client + .sendRequest(ra.hoverRange, { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( + editor.document + ), + range: client.code2ProtocolConverter.asRange(editor.selection), + }) + .then( + (result) => client.protocol2CodeConverter.asHover(result), + (error) => { + client.handleFailedRequest(lc.HoverRequest.type, undefined, error, null); + return Promise.resolve(null); + } + ); + }; +} + + export function joinLines(ctx: Ctx): Cmd { return async () => { const editor = ctx.activeRustEditor; diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 3a5460a899c8..2570a2d1f5d5 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -19,6 +19,13 @@ export const serverStatus = new lc.NotificationType("experim export const reloadWorkspace = new lc.RequestType0("rust-analyzer/reloadWorkspace"); +export const hoverRange = new lc.RequestType("rust-analyzer/hoverRange"); + +export interface HoverRangeParams { + textDocument: lc.TextDocumentIdentifier; + range: lc.Range; +} + export interface SyntaxTreeParams { textDocument: lc.TextDocumentIdentifier; range: lc.Range | null; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 9487267617ab..659d91e5ff43 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -118,6 +118,7 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) { ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace); ctx.registerCommand('matchingBrace', commands.matchingBrace); ctx.registerCommand('joinLines', commands.joinLines); + ctx.registerCommand('hoverRange', commands.hoverRange); ctx.registerCommand('parentModule', commands.parentModule); ctx.registerCommand('syntaxTree', commands.syntaxTree); ctx.registerCommand('viewHir', commands.viewHir); From 79860808cb73ed8ecef6bc0991505a1678b799ed Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sun, 25 Jul 2021 17:28:25 -0400 Subject: [PATCH 04/19] chore: Remove unnecessary log --- crates/rust-analyzer/src/handlers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index a84d8dfd82be..988c163db5f2 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -897,7 +897,6 @@ pub(crate) fn handle_hover_range( let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let range = from_proto::file_range(&snap, params.text_document, params.range)?; - log::info!("Triggered range hover"); let info = match snap.analysis.hover_range(&snap.config.hover(), range)? { None => return Ok(None), Some(info) => info, From 9f21891950abdfa9fc51a778c37ca02f0b239d12 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sun, 25 Jul 2021 17:50:16 -0400 Subject: [PATCH 05/19] refactor: Remove unnecessary command --- editors/code/src/commands.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 7f5cdd4876ea..20ef1573f6cb 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -116,30 +116,6 @@ export function matchingBrace(ctx: Ctx): Cmd { }; } -export function hoverRange(ctx: Ctx): Cmd { - return async () => { - const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; - - client - .sendRequest(ra.hoverRange, { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document - ), - range: client.code2ProtocolConverter.asRange(editor.selection), - }) - .then( - (result) => client.protocol2CodeConverter.asHover(result), - (error) => { - client.handleFailedRequest(lc.HoverRequest.type, undefined, error, null); - return Promise.resolve(null); - } - ); - }; -} - - export function joinLines(ctx: Ctx): Cmd { return async () => { const editor = ctx.activeRustEditor; From 2b5798e927a24d58e38e7cd4d5c96cb68a00d82a Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Sun, 25 Jul 2021 17:54:06 -0400 Subject: [PATCH 06/19] fix: Unregister the removed command --- editors/code/src/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 659d91e5ff43..9487267617ab 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -118,7 +118,6 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) { ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace); ctx.registerCommand('matchingBrace', commands.matchingBrace); ctx.registerCommand('joinLines', commands.joinLines); - ctx.registerCommand('hoverRange', commands.hoverRange); ctx.registerCommand('parentModule', commands.parentModule); ctx.registerCommand('syntaxTree', commands.syntaxTree); ctx.registerCommand('viewHir', commands.viewHir); From 1a0a5da1a488033e4b9fdb4c4cd32854ff2859d6 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Mon, 26 Jul 2021 12:14:14 -0400 Subject: [PATCH 07/19] refactor: Make handle_hover handle ranges too --- crates/ide/src/lib.rs | 2 +- crates/rust-analyzer/src/handlers.rs | 49 +++++++++++---------------- crates/rust-analyzer/src/lsp_ext.rs | 24 +++++++------ crates/rust-analyzer/src/main_loop.rs | 1 - editors/code/src/client.ts | 44 +++--------------------- editors/code/src/lsp_ext.ts | 6 ++-- 6 files changed, 42 insertions(+), 84 deletions(-) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index b6b741a22ab6..91544067125d 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -423,7 +423,7 @@ impl Analysis { self.with_db(|db| hover::hover(db, position, config)) } - /// Returns a short text displaying the type for the expression. + /// Returns a short text displaying the type of the expression. pub fn hover_range( &self, config: &HoverConfig, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 988c163db5f2..90202fdcecc8 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -36,7 +36,10 @@ use crate::{ from_proto, global_state::{GlobalState, GlobalStateSnapshot}, line_index::LineEndings, - lsp_ext::{self, InlayHint, InlayHintsParams, ViewCrateGraphParams, WorkspaceSymbolParams}, + lsp_ext::{ + self, InlayHint, InlayHintsParams, PositionOrRange, ViewCrateGraphParams, + WorkspaceSymbolParams, + }, lsp_utils::all_edits_are_disjoint, to_proto, LspError, Result, }; @@ -867,42 +870,30 @@ pub(crate) fn handle_signature_help( pub(crate) fn handle_hover( snap: GlobalStateSnapshot, - params: lsp_types::HoverParams, + params: lsp_ext::HoverParams, ) -> Result> { let _p = profile::span("handle_hover"); - let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let info = match snap.analysis.hover(&snap.config.hover(), position)? { - None => return Ok(None), - Some(info) => info, - }; - - let line_index = snap.file_line_index(position.file_id)?; - let range = to_proto::range(&line_index, info.range); - let hover = lsp_ext::Hover { - hover: lsp_types::Hover { - contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)), - range: Some(range), - }, - actions: prepare_hover_actions(&snap, &info.info.actions), - }; - - Ok(Some(hover)) -} - -pub(crate) fn handle_hover_range( - snap: GlobalStateSnapshot, - params: lsp_ext::HoverRangeParams, -) -> Result> { - let _p = profile::span("handle_hover_range"); let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; - let range = from_proto::file_range(&snap, params.text_document, params.range)?; + let hover_result = match params.position { + PositionOrRange::Position(position) => { + let position = from_proto::file_position( + &snap, + lsp_types::TextDocumentPositionParams::new(params.text_document, position), + )?; + snap.analysis.hover(&snap.config.hover(), position)? + } + PositionOrRange::Range(range) => { + let range = from_proto::file_range(&snap, params.text_document, range)?; + snap.analysis.hover_range(&snap.config.hover(), range)? + } + }; - let info = match snap.analysis.hover_range(&snap.config.hover(), range)? { + let info = match hover_result { None => return Ok(None), Some(info) => info, }; - let line_index = snap.file_line_index(range.file_id)?; + let line_index = snap.file_line_index(file_id)?; let range = to_proto::range(&line_index, info.range); let hover = lsp_ext::Hover { hover: lsp_types::Hover { diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 7aed93f992ab..d697ec44d155 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -376,24 +376,26 @@ pub struct SnippetTextEdit { pub enum HoverRequest {} impl Request for HoverRequest { - type Params = lsp_types::HoverParams; + type Params = HoverParams; type Result = Option; const METHOD: &'static str = "textDocument/hover"; } -pub enum HoverRangeRequest {} +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HoverParams { + pub text_document: TextDocumentIdentifier, + pub position: PositionOrRange, -impl Request for HoverRangeRequest { - type Params = HoverRangeParams; - type Result = Option; - const METHOD: &'static str = "rust-analyzer/hoverRange"; + #[serde(flatten)] + pub work_done_progress_params: WorkDoneProgressParams, } -#[derive(Deserialize, Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct HoverRangeParams { - pub text_document: TextDocumentIdentifier, - pub range: Range, +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum PositionOrRange { + Position(lsp_types::Position), + Range(lsp_types::Range), } #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index af3cb0c8792b..35fce79f5eb6 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -542,7 +542,6 @@ impl GlobalState { .on::(handlers::handle_code_action) .on::(handlers::handle_code_action_resolve) .on::(handlers::handle_hover) - .on::(handlers::handle_hover_range) .on::(handlers::handle_open_docs) .on::(handlers::handle_open_cargo_toml) .on::(handlers::handle_move_item) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index f0c10698a0c8..272cfca66d8e 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -57,45 +57,11 @@ export function createClient(serverPath: string, workspace: Workspace, extraEnv: middleware: { async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) { const editor = vscode.window.activeTextEditor; - const selection = editor?.selection; - return selection?.contains(position) - ? client - .sendRequest( - ra.hoverRange, - { - textDocument: - client.code2ProtocolConverter.asTextDocumentIdentifier( - document - ), - range: client.code2ProtocolConverter.asRange( - editor?.selection - ), - }, - token - ) - .then( - (result) => - client.protocol2CodeConverter.asHover(result), - (error) => { - client.handleFailedRequest( - lc.HoverRequest.type, - undefined, - error, - null - ); - return Promise.resolve(null); - } - ) - : client - .sendRequest( - lc.HoverRequest.type, - client.code2ProtocolConverter.asTextDocumentPositionParams( - document, - position - ), - token - ) - .then( + const positionOrRange = editor?.selection?.contains(position) ? client.code2ProtocolConverter.asRange(editor.selection) : client.code2ProtocolConverter.asPosition(position); + return client.sendRequest(ra.hover, { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + position: positionOrRange + }, token).then( (result) => { const hover = client.protocol2CodeConverter.asHover(result); diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 2570a2d1f5d5..deb0db56eeea 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -19,11 +19,11 @@ export const serverStatus = new lc.NotificationType("experim export const reloadWorkspace = new lc.RequestType0("rust-analyzer/reloadWorkspace"); -export const hoverRange = new lc.RequestType("rust-analyzer/hoverRange"); +export const hover = new lc.RequestType("textDocument/hover"); -export interface HoverRangeParams { +export interface HoverParams extends lc.WorkDoneProgressParams{ textDocument: lc.TextDocumentIdentifier; - range: lc.Range; + position: lc.Range | lc.Position; } export interface SyntaxTreeParams { From 6310786ca5531f8d7f93eb07ab3a96d0f7addd25 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Mon, 26 Jul 2021 15:34:44 -0400 Subject: [PATCH 08/19] test: Add hover_range tests --- crates/ide/src/hover.rs | 106 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 692c3ff676a8..735c535e5eb2 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -652,6 +652,21 @@ mod tests { expect.assert_debug_eq(&hover.info.actions) } + fn check_hover_range(ra_fixture: &str, expect: Expect) { + let (analysis, range) = fixture::range(ra_fixture); + let hover = analysis + .hover_range( + &HoverConfig { + links_in_hover: false, + documentation: Some(HoverDocFormat::Markdown), + }, + range, + ) + .unwrap() + .unwrap(); + expect.assert_eq(hover.info.markup.as_str()) + } + #[test] fn hover_shows_type_of_an_expression() { check( @@ -3911,4 +3926,95 @@ struct Foo; "#]], ); } + + #[test] + fn hover_range_math() { + check_hover_range( + r#" +fn f() { let expr = $01 + 2 * 3$0 } + "#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = 1 $0+ 2 * $03 } + "#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = 1 + $02 * 3$0 } + "#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + } + + #[test] + fn hover_range_arrays() { + check_hover_range( + r#" +fn f() { let expr = $0[1, 2, 3, 4]$0 } + "#, + expect![[r#" + ```rust + [i32; 4] + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = [1, 2, $03, 4]$0 } + "#, + expect![[r#" + ```rust + [i32; 4] + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr = [1, 2, $03$0, 4] } + "#, + expect![[r#" + ```rust + i32 + ```"#]], + ); + } + + #[test] + fn hover_range_functions() { + check_hover_range( + r#" +fn f(a: &[T]) { } +fn b() { $0f$0(&[1, 2, 3, 4, 5]); } + "#, + expect![[r#" + ```rust + fn f(&[i32]) + ```"#]], + ); + + check_hover_range( + r#" +fn f(a: &[T]) { } +fn b() { f($0&[1, 2, 3, 4, 5]$0); } + "#, + expect![[r#" + ```rust + &[i32; 5] + ```"#]], + ); + } } From 48f43df7d64378e0d58e74791fd1f9cbabcd0e18 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Mon, 26 Jul 2021 15:55:09 -0400 Subject: [PATCH 09/19] docs: Improve the comments in crates/ide/src/hover.rs --- crates/ide/src/hover.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 735c535e5eb2..5f283b31fbb6 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -71,12 +71,12 @@ pub struct HoverResult { pub actions: Vec, } -// Feature: Hover -// -// Shows additional information, like type of an expression or documentation for definition when "focusing" code. -// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. -// -// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[] +/// Feature: Hover +/// +/// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code. +/// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. +/// +/// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif pub(crate) fn hover( db: &RootDatabase, position: FilePosition, @@ -98,8 +98,8 @@ pub(crate) fn hover( let mut range = None; let definition = match_ast! { match node { - // we don't use NameClass::referenced_or_defined here as we do not want to resolve - // field pattern shorthands to their definition + // We don't use NameClass::referenced_or_defined here as we do not want to resolve + // field pattern shorthands to their definition. ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class { NameClass::Definition(it) | NameClass::ConstReference(it) => it, NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def), @@ -245,6 +245,12 @@ fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option Date: Mon, 26 Jul 2021 16:19:12 -0400 Subject: [PATCH 10/19] test: Add tests for when hovering fails --- crates/ide/src/hover.rs | 74 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 5f283b31fbb6..292a1faacfe8 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -673,6 +673,21 @@ mod tests { expect.assert_eq(hover.info.markup.as_str()) } + fn check_hover_range_no_results(ra_fixture: &str) { + let (analysis, range) = fixture::range(ra_fixture); + let hover = analysis + .hover_range( + &HoverConfig { + links_in_hover: false, + documentation: Some(HoverDocFormat::Markdown), + }, + range, + ) + .unwrap(); + println!("AAA {:#?}", hover); + assert!(hover.is_none()); + } + #[test] fn hover_shows_type_of_an_expression() { check( @@ -3937,7 +3952,7 @@ struct Foo; fn hover_range_math() { check_hover_range( r#" -fn f() { let expr = $01 + 2 * 3$0 } +fn f() { let expr = $01 + 2 * 3$0 } "#, expect![[r#" ```rust @@ -3947,7 +3962,7 @@ fn f() { let expr = $01 + 2 * 3$0 } check_hover_range( r#" -fn f() { let expr = 1 $0+ 2 * $03 } +fn f() { let expr = 1 $0+ 2 * $03 } "#, expect![[r#" ```rust @@ -3957,7 +3972,7 @@ fn f() { let expr = 1 $0+ 2 * $03 } check_hover_range( r#" -fn f() { let expr = 1 + $02 * 3$0 } +fn f() { let expr = 1 + $02 * 3$0 } "#, expect![[r#" ```rust @@ -3970,7 +3985,7 @@ fn f() { let expr = 1 + $02 * 3$0 } fn hover_range_arrays() { check_hover_range( r#" -fn f() { let expr = $0[1, 2, 3, 4]$0 } +fn f() { let expr = $0[1, 2, 3, 4]$0 } "#, expect![[r#" ```rust @@ -3980,7 +3995,7 @@ fn f() { let expr = $0[1, 2, 3, 4]$0 } check_hover_range( r#" -fn f() { let expr = [1, 2, $03, 4]$0 } +fn f() { let expr = [1, 2, $03, 4]$0 } "#, expect![[r#" ```rust @@ -3990,7 +4005,7 @@ fn f() { let expr = [1, 2, $03, 4]$0 } check_hover_range( r#" -fn f() { let expr = [1, 2, $03$0, 4] } +fn f() { let expr = [1, 2, $03$0, 4] } "#, expect![[r#" ```rust @@ -4023,4 +4038,51 @@ fn b() { f($0&[1, 2, 3, 4, 5]$0); } ```"#]], ); } + + #[test] + fn hover_range_shows_nothing_when_invalid() { + check_hover_range_no_results( + r#" +fn f(a: &[T]) { } +fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0 + "#, + ); + + check_hover_range_no_results( + r#" +fn f$0(a: &[T]) { } +fn b() { f(&[1, 2, 3,$0 4, 5]); } + "#, + ); + + check_hover_range_no_results( + r#" +fn $0f() { let expr = [1, 2, 3, 4]$0 } + "#, + ); + } + + #[test] + fn hover_range_shows_unit_for_statements() { + check_hover_range( + r#" +fn f(a: &[T]) { } +fn b() { $0f(&[1, 2, 3, 4, 5]); }$0 + "#, + expect![[r#" + ```rust + () + ```"#]], + ); + + check_hover_range( + r#" +fn f() { let expr$0 = $0[1, 2, 3, 4] } + "#, + expect![[r#" + ```rust + () + ```"#]], + ); + } } From ca85185bc6eef45fa34ac293288bbe9bd11bb684 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Mon, 26 Jul 2021 17:05:59 -0400 Subject: [PATCH 11/19] docs: Update the lsp-extensions.md with the Hover Range capability --- docs/dev/lsp-extensions.md | 33 +++++++++++++++++++++++++++++---- editors/code/src/lsp_ext.ts | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index ffc94b178a54..e13f81e1ff0f 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -13,7 +13,7 @@ need to adjust this doc as well and ping this issue: This document describes LSP extensions used by rust-analyzer. It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ). We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority. -All capabilities are enabled via `experimental` field of `ClientCapabilities` or `ServerCapabilities`. +All capabilities are enabled via the `experimental` field of `ClientCapabilities` or `ServerCapabilities`. Requests which we hope to upstream live under `experimental/` namespace. Requests, which are likely to always remain specific to `rust-analyzer` are under `rust-analyzer/` namespace. @@ -29,7 +29,7 @@ https://clangd.llvm.org/extensions.html#utf-8-offsets **Issue:** https://github.com/microsoft/language-server-protocol/issues/567 -The `initializationOptions` filed of the `InitializeParams` of the initialization request should contain `"rust-analyzer"` section of the configuration. +The `initializationOptions` field of the `InitializeParams` of the initialization request should contain the `"rust-analyzer"` section of the configuration. `rust-analyzer` normally sends a `"workspace/configuration"` request with `{ "items": ["rust-analyzer"] }` payload. However, the server can't do this during initialization. @@ -81,7 +81,7 @@ At the moment, rust-analyzer guarantees that only a single edit will have `Inser **Experimental Client Capability:** `{ "codeActionGroup": boolean }` -If this capability is set, `CodeAction` returned from the server contain an additional field, `group`: +If this capability is set, `CodeAction`s returned from the server contain an additional field, `group`: ```typescript interface CodeAction { @@ -209,7 +209,7 @@ fn main() { **Experimental Server Capability:** `{ "onEnter": boolean }` -This request is sent from client to server to handle Enter keypress. +This request is sent from client to server to handle the Enter key press. **Method:** `experimental/onEnter` @@ -658,6 +658,31 @@ interface TestInfo { } ``` +## Hover Range + +**Issue:** https://github.com/microsoft/language-server-protocol/issues/377 + +This request build upon the current `textDocument/hover` to show the type of the expression currently selected. + +```typescript +interface HoverParams extends lc.WorkDoneProgressParams { + textDocument: lc.TextDocumentIdentifier; + position: lc.Range | lc.Position; +} +``` + +Whenever the client sends a `Range`, it is understood as the current selection and any hover included in the range will show the type of the expression if possible. + +### Example + +```rust +fn main() { + let expression = $01 + 2 * 3$0; +} +``` + +Triggering a hover inside the selection above will show a result of `i32`. + ## Move Item **Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6823 diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index deb0db56eeea..ac632a015676 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -21,7 +21,7 @@ export const reloadWorkspace = new lc.RequestType0("rust-analyzer/re export const hover = new lc.RequestType("textDocument/hover"); -export interface HoverParams extends lc.WorkDoneProgressParams{ +export interface HoverParams extends lc.WorkDoneProgressParams { textDocument: lc.TextDocumentIdentifier; position: lc.Range | lc.Position; } From c6fab1993ab84186be26733e339d80f2015c41ee Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Mon, 26 Jul 2021 17:07:04 -0400 Subject: [PATCH 12/19] test: Update lsp_ext.rs hash --- docs/dev/lsp-extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index e13f81e1ff0f..dc8e93a645a8 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@