From 7d0dd17b09240385333805637ea17992a8089cf2 Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 3 Jun 2020 14:15:54 +0300 Subject: [PATCH] Add hover actions as LSP extension --- crates/ra_ide/src/hover.rs | 134 ++++++++++++++++-- crates/ra_ide/src/lib.rs | 2 +- crates/ra_ide_db/src/defs.rs | 2 +- crates/rust-analyzer/src/config.rs | 28 ++-- crates/rust-analyzer/src/lsp_ext.rs | 34 +++++ crates/rust-analyzer/src/main_loop.rs | 2 +- .../rust-analyzer/src/main_loop/handlers.rs | 119 ++++++++++++---- editors/code/package.json | 16 ++- editors/code/src/client.ts | 45 ++++++ editors/code/src/config.ts | 13 +- editors/code/src/lsp_ext.ts | 12 ++ 11 files changed, 351 insertions(+), 56 deletions(-) diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 9636cd0d6af..baa9fc8a8ff 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -13,14 +13,43 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ - display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, - FilePosition, RangeInfo, + display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, + FilePosition, RangeInfo, NavigationTarget, }; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HoverConfig { + pub implementations: bool, +} + +impl Default for HoverConfig { + fn default() -> Self { + Self { implementations: true } + } +} + +impl HoverConfig { + pub const NO_ACTIONS: Self = Self { implementations: false }; + + pub fn any(&self) -> bool { + self.implementations + } + + pub fn none(&self) -> bool { + !self.any() + } +} + +#[derive(Debug, Clone)] +pub enum HoverAction { + Implementaion(FilePosition), +} + /// Contains the results when hovering over an item #[derive(Debug, Default)] pub struct HoverResult { results: Vec, + actions: Vec, } impl HoverResult { @@ -48,10 +77,20 @@ pub fn results(&self) -> &[String] { &self.results } + pub fn actions(&self) -> &[HoverAction] { + &self.actions + } + + pub fn push_action(&mut self, action: HoverAction) { + self.actions.push(action); + } + /// Returns the results converted into markup /// for displaying in a UI + /// + /// Does not process actions! pub fn to_markup(&self) -> String { - self.results.join("\n\n---\n") + self.results.join("\n\n___\n") } } @@ -82,6 +121,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option Option Option { + fn to_action(nav_target: NavigationTarget) -> HoverAction { + HoverAction::Implementaion(FilePosition { + file_id: nav_target.file_id(), + offset: nav_target.range().start(), + }) + } + + match def { + Definition::ModuleDef(it) => match it { + ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))), + ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))), + ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))), + ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))), + _ => None, + }, + _ => None, + } +} + fn hover_text( docs: Option, desc: Option, @@ -228,6 +291,8 @@ fn priority(n: &SyntaxToken) -> usize { #[cfg(test)] mod tests { + use super::*; + use ra_db::FileLoader; use ra_syntax::TextRange; @@ -241,7 +306,14 @@ fn trim_markup_opt(s: Option<&str>) -> Option<&str> { s.map(trim_markup) } - fn check_hover_result(fixture: &str, expected: &[&str]) -> String { + fn assert_impl_action(action: &HoverAction, position: u32) { + let offset = match action { + HoverAction::Implementaion(pos) => pos.offset + }; + assert_eq!(offset, position.into()); + } + + fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec) { let (analysis, position) = analysis_and_position(fixture); let hover = analysis.hover(position).unwrap().unwrap(); let mut results = Vec::from(hover.info.results()); @@ -256,7 +328,7 @@ fn check_hover_result(fixture: &str, expected: &[&str]) -> String { assert_eq!(hover.info.len(), expected.len()); let content = analysis.db.file_text(position.file_id); - content[hover.range].to_string() + (content[hover.range].to_string(), hover.info.actions().to_vec()) } fn check_hover_no_result(fixture: &str) { @@ -746,7 +818,7 @@ fn test_hover_tuple_field() { #[test] fn test_hover_through_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id { @@ -767,7 +839,7 @@ fn bar() { #[test] fn test_hover_through_expr_in_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id { @@ -785,7 +857,7 @@ fn foo(bar:u32) { #[test] fn test_hover_through_expr_in_macro_recursive() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id_deep { @@ -806,7 +878,7 @@ fn foo(bar:u32) { #[test] fn test_hover_through_func_in_macro_recursive() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id_deep { @@ -830,7 +902,7 @@ fn foo() { #[test] fn test_hover_through_literal_string_in_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( r#" //- /lib.rs macro_rules! arr { @@ -849,7 +921,7 @@ fn foo() { #[test] fn test_hover_through_assert_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( r#" //- /lib.rs #[rustc_builtin_macro] @@ -925,13 +997,14 @@ async fn foo<|>() {} #[test] fn test_hover_trait_show_qualifiers() { - check_hover_result( + let (_, actions) = check_hover_result( " //- /lib.rs unsafe trait foo<|>() {} ", &["unsafe trait foo"], ); + assert_impl_action(&actions[0], 13); } #[test] @@ -1052,4 +1125,41 @@ fn foo() { &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], ); } + + #[test] + fn test_hover_trait_hash_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait foo<|>() {} + ", + &["trait foo"], + ); + assert_impl_action(&actions[0], 6); + } + + #[test] + fn test_hover_struct_hash_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + struct foo<|>() {} + ", + &["struct foo"], + ); + assert_impl_action(&actions[0], 7); + } + + #[test] + fn test_hover_union_hash_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + union foo<|>() {} + ", + &["union foo"], + ); + assert_impl_action(&actions[0], 6); + } + } diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 34c2d75fed2..a9601400f18 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -66,7 +66,7 @@ macro_rules! eprintln { display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, expand_macro::ExpandedMacro, folding_ranges::{Fold, FoldKind}, - hover::HoverResult, + hover::{HoverResult, HoverAction, HoverConfig}, inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, runnables::{Runnable, RunnableKind, TestId}, diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 8b06cbfc54b..1db60b87fb8 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -18,7 +18,7 @@ use crate::RootDatabase; // FIXME: a more precise name would probably be `Symbol`? -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Definition { Macro(MacroDef), Field(Field), diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 23168c3ae9a..e7c8595772c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -11,7 +11,7 @@ use lsp_types::ClientCapabilities; use ra_flycheck::FlycheckConfig; -use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; +use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig, HoverConfig}; use ra_project_model::{CargoConfig, JsonProject, ProjectManifest}; use serde::Deserialize; @@ -34,6 +34,7 @@ pub struct Config { pub assist: AssistConfig, pub call_info_full: bool, pub lens: LensConfig, + pub hover: HoverConfig, pub with_sysroot: bool, pub linked_projects: Vec, @@ -124,6 +125,7 @@ pub struct ClientCapsConfig { pub work_done_progress: bool, pub code_action_group: bool, pub resolve_code_action: bool, + pub hover_actions: bool, } impl Default for Config { @@ -162,6 +164,7 @@ fn default() -> Self { assist: AssistConfig::default(), call_info_full: true, lens: LensConfig::default(), + hover: HoverConfig::default(), linked_projects: Vec::new(), } } @@ -278,6 +281,14 @@ pub fn update(&mut self, value: &serde_json::Value) { } } + let mut use_hover_actions = false; + set(value, "/hoverActions/enable", &mut use_hover_actions); + if use_hover_actions { + set(value, "/hoverActions/implementations", &mut self.hover.implementations); + } else { + self.hover = HoverConfig::NO_ACTIONS; + } + log::info!("Config::update() = {:#?}", self); fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option { @@ -331,17 +342,14 @@ pub fn update_caps(&mut self, caps: &ClientCapabilities) { self.assist.allow_snippets(false); if let Some(experimental) = &caps.experimental { - let snippet_text_edit = - experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true); - self.assist.allow_snippets(snippet_text_edit); + let get_bool = |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true); - let code_action_group = - experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true); - self.client_caps.code_action_group = code_action_group; + let snippet_text_edit = get_bool("snippetTextEdit"); + self.assist.allow_snippets(snippet_text_edit); - let resolve_code_action = - experimental.get("resolveCodeAction").and_then(|it| it.as_bool()) == Some(true); - self.client_caps.resolve_code_action = resolve_code_action; + self.client_caps.code_action_group = get_bool("codeActionGroup"); + self.client_caps.resolve_code_action = get_bool("resolveCodeAction"); + self.client_caps.hover_actions = get_bool("hoverActions"); } } } diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 3b957534ddd..145a389ce17 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -260,3 +260,37 @@ pub struct SnippetTextEdit { #[serde(skip_serializing_if = "Option::is_none")] pub insert_text_format: Option, } + +pub enum HoverRequest {} + +impl Request for HoverRequest { + type Params = lsp_types::HoverParams; + type Result = Option; + const METHOD: &'static str = "textDocument/hover"; +} + +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +pub struct Hover { + pub contents: lsp_types::HoverContents, + #[serde(skip_serializing_if = "Option::is_none")] + pub range: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub actions: Option>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)] +pub struct CommandLinkGroup { + pub title: Option, + pub commands: Vec, +} + +// LSP v3.15 Command does not have a `tooltip` field, vscode supports one. +#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)] +pub struct CommandLink { + pub title: String, + pub command: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub tooltip: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub arguments: Option>, +} diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index e60337b8e8b..752dbf14529 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -510,6 +510,7 @@ fn on_request( .on::(handlers::handle_inlay_hints)? .on::(handlers::handle_code_action)? .on::(handlers::handle_resolve_code_action)? + .on::(handlers::handle_hover)? .on::(handlers::handle_on_type_formatting)? .on::(handlers::handle_document_symbol)? .on::(handlers::handle_workspace_symbol)? @@ -521,7 +522,6 @@ fn on_request( .on::(handlers::handle_code_lens_resolve)? .on::(handlers::handle_folding_range)? .on::(handlers::handle_signature_help)? - .on::(handlers::handle_hover)? .on::(handlers::handle_prepare_rename)? .on::(handlers::handle_rename)? .on::(handlers::handle_references)? diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 6acf80c5827..0958a231fd6 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -12,13 +12,14 @@ CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight, - DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location, - MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, - SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, - SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit, + DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, MarkupContent, + MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams, + SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, + TextDocumentIdentifier, Url, WorkspaceEdit, }; use ra_ide::{ - FileId, FilePosition, FileRange, Query, RangeInfo, RunnableKind, SearchScope, TextEdit, + FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, RunnableKind, SearchScope, + TextEdit, }; use ra_prof::profile; use ra_project_model::TargetKind; @@ -537,7 +538,7 @@ pub fn handle_signature_help( pub fn handle_hover( snap: GlobalStateSnapshot, params: lsp_types::HoverParams, -) -> Result> { +) -> Result> { let _p = profile("handle_hover"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; let info = match snap.analysis().hover(position)? { @@ -546,12 +547,13 @@ pub fn handle_hover( }; let line_index = snap.analysis.file_line_index(position.file_id)?; let range = to_proto::range(&line_index, info.range); - let res = Hover { + let res = lsp_ext::Hover { contents: HoverContents::Markup(MarkupContent { kind: MarkupKind::Markdown, value: crate::markdown::format_docs(&info.info.to_markup()), }), range: Some(range), + actions: Some(prepare_hover_actions(&world, info.info.actions())), }; Ok(Some(res)) } @@ -924,24 +926,13 @@ pub fn handle_code_lens_resolve( _ => vec![], }; - let title = if locations.len() == 1 { - "1 implementation".into() - } else { - format!("{} implementations", locations.len()) - }; - - // We cannot use the 'editor.action.showReferences' command directly - // because that command requires vscode types which we convert in the handler - // on the client side. - let cmd = Command { + let title = implementation_title(locations.len()); + let cmd = show_references_command( title, - command: "rust-analyzer.showReferences".into(), - arguments: Some(vec![ - to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(), - to_value(code_lens.range.start).unwrap(), - to_value(locations).unwrap(), - ]), - }; + &lens_params.text_document_position_params.text_document.uri, + code_lens.range.start, + locations, + ); Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) } None => Ok(CodeLens { @@ -1145,3 +1136,83 @@ pub fn handle_semantic_tokens_range( let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } + +fn implementation_title(count: usize) -> String { + if count == 1 { + "1 implementation".into() + } else { + format!("{} implementations", count) + } +} + +fn show_references_command( + title: String, + uri: &lsp_types::Url, + position: lsp_types::Position, + locations: Vec, +) -> Command { + // We cannot use the 'editor.action.showReferences' command directly + // because that command requires vscode types which we convert in the handler + // on the client side. + + Command { + title, + command: "rust-analyzer.showReferences".into(), + arguments: Some(vec![ + to_value(uri).unwrap(), + to_value(position).unwrap(), + to_value(locations).unwrap(), + ]), + } +} + +fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { + lsp_ext::CommandLink { + tooltip: Some(tooltip), + title: command.title, + command: command.command, + arguments: command.arguments, + } +} + +fn show_impl_command_link( + world: &WorldSnapshot, + position: &FilePosition, +) -> Option { + if world.config.hover.implementations { + if let Some(nav_data) = world.analysis().goto_implementation(*position).unwrap_or(None) { + let uri = to_proto::url(world, position.file_id).ok()?; + let line_index = world.analysis().file_line_index(position.file_id).ok()?; + let position = to_proto::position(&line_index, position.offset); + let locations: Vec<_> = nav_data + .info + .iter() + .filter_map(|it| to_proto::location(world, it.file_range()).ok()) + .collect(); + let title = implementation_title(locations.len()); + let command = show_references_command(title, &uri, position, locations); + + return Some(lsp_ext::CommandLinkGroup { + commands: vec![to_command_link(command, "Go to implementations".into())], + ..Default::default() + }); + } + } + None +} + +fn prepare_hover_actions( + world: &WorldSnapshot, + actions: &[HoverAction], +) -> Vec { + if world.config.hover.none() || !world.config.client_caps.hover_actions { + return Vec::new(); + } + + actions + .iter() + .filter_map(|it| match it { + HoverAction::Implementaion(position) => show_impl_command_link(world, position), + }) + .collect() +} diff --git a/editors/code/package.json b/editors/code/package.json index 30ab7ba4a9f..b9c57db3bcf 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -462,17 +462,27 @@ "default": true }, "rust-analyzer.lens.run": { - "markdownDescription": "Whether to show Run lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Run` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true }, "rust-analyzer.lens.debug": { - "markdownDescription": "Whether to show Debug lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Debug` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true }, "rust-analyzer.lens.implementations": { - "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Implementations` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.enable": { + "description": "Whether to show HoverActions in Rust files.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.implementations": { + "markdownDescription": "Whether to show `Implementations` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", "type": "boolean", "default": true }, diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 40ad1e3cd84..9df6702839b 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -7,6 +7,29 @@ import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.pr import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; import { assert } from './util'; +function toTrusted(obj: vscode.MarkedString): vscode.MarkedString { + const md = obj; + if (md && md.value.includes("```rust")) { + md.isTrusted = true; + return md; + } + return obj; +} + +function renderCommand(cmd: CommandLink) { + return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; +} + +function renderHoverActions(actions: CommandLinkGroup[]): vscode.MarkdownString { + const text = actions.map(group => + (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ') + ).join('___'); + + const result = new vscode.MarkdownString(text); + result.isTrusted = true; + return result; +} + export function createClient(serverPath: string, cwd: string): lc.LanguageClient { // '.' Is the fallback if no folder is open // TODO?: Workspace folders support Uri's (eg: file://test.txt). @@ -35,6 +58,27 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient if (res === undefined) throw new Error('busy'); return res; }, + 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) { + // Workaround to support command links (trusted vscode.MarkdownString) in hovers + // https://github.com/microsoft/vscode/issues/33577 + hover.contents = hover.contents.map(toTrusted); + + const actions = (result).actions; + if (actions) { + hover.contents.push(renderHoverActions(actions)); + } + } + return hover; + }, + (error) => { + client.logFailedRequest(lc.HoverRequest.type, error); + return Promise.resolve(null); + }); + }, // Using custom handling of CodeActions where each code action is resloved lazily // That's why we are not waiting for any command or edits async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { @@ -129,6 +173,7 @@ class ExperimentalFeatures implements lc.StaticFeature { caps.snippetTextEdit = true; caps.codeActionGroup = true; caps.resolveCodeAction = true; + caps.hoverActions = true; capabilities.experimental = caps; } initialize(_capabilities: lc.ServerCapabilities, _documentSelector: lc.DocumentSelector | undefined): void { diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index e8abf8284eb..d8f0037d4c9 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -16,10 +16,8 @@ export class Config { "files", "highlighting", "updates.channel", - "lens.enable", - "lens.run", - "lens.debug", - "lens.implementations", + "lens", // works as lens.* + "hoverActions", // works as hoverActions.* ] .map(opt => `${this.rootSection}.${opt}`); @@ -132,4 +130,11 @@ export class Config { implementations: this.get("lens.implementations"), }; } + + get hoverActions() { + return { + enable: this.get("hoverActions.enable"), + implementations: this.get("hoverActions.implementations"), + }; + } } diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 9793b926c26..e16ea799ce0 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -90,3 +90,15 @@ export interface SsrParams { parseOnly: boolean; } export const ssr = new lc.RequestType('experimental/ssr'); + +export interface CommandLink extends lc.Command { + /** + * A tooltip for the command, when represented in the UI. + */ + tooltip?: string; +} + +export interface CommandLinkGroup { + title?: string; + commands: CommandLink[]; +} -- 2.44.0