]> git.lizzy.rs Git - rust.git/commitdiff
Add hover actions as LSP extension
authorvsrs <vit@conrlab.com>
Wed, 3 Jun 2020 11:15:54 +0000 (14:15 +0300)
committervsrs <vit@conrlab.com>
Fri, 5 Jun 2020 11:59:26 +0000 (14:59 +0300)
crates/ra_ide/src/hover.rs
crates/ra_ide/src/lib.rs
crates/ra_ide_db/src/defs.rs
crates/rust-analyzer/src/config.rs
crates/rust-analyzer/src/lsp_ext.rs
crates/rust-analyzer/src/main_loop.rs
crates/rust-analyzer/src/main_loop/handlers.rs
editors/code/package.json
editors/code/src/client.ts
editors/code/src/config.ts
editors/code/src/lsp_ext.ts

index 9636cd0d6af7ad32bec9d4dff2084933c82d639b..baa9fc8a8ff958c8db98553a94751ab6992ec745 100644 (file)
 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<String>,
+    actions: Vec<HoverAction>,
 }
 
 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<RangeIn
         res.extend(hover_text_from_name_kind(db, name_kind));
 
         if !res.is_empty() {
+            if let Some(action) = show_implementations_action(db, name_kind) {
+                res.push_action(action);
+            }
+
             return Some(RangeInfo::new(range, res));
         }
     }
@@ -112,6 +155,26 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
     Some(RangeInfo::new(range, res))
 }
 
+fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+    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<String>,
     desc: Option<String>,
@@ -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<HoverAction>) {
         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);
+    }
+
 }
index 34c2d75fed2b025c5010c17d1c53408f3be02afb..a9601400f1884468acf4b6db14270896b6a1eff7 100644 (file)
@@ -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},
index 8b06cbfc54b7ca8681708f783e216ff77cad68b4..1db60b87fb82b9a6804b0d5817ee7081df872285 100644 (file)
@@ -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),
index 23168c3ae9a28f53d151054b4873eeeedbc71826..e7c8595772ce01dc98184a08f0e1e2b15912fcb5 100644 (file)
@@ -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<LinkedProject>,
@@ -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<T> {
@@ -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");
         }
     }
 }
index 3b957534ddd0bd4b5b0bf86c94740b6ada7dff9b..145a389ce176e375082e755d1c1775dfdb8e315e 100644 (file)
@@ -260,3 +260,37 @@ pub struct SnippetTextEdit {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub insert_text_format: Option<lsp_types::InsertTextFormat>,
 }
+
+pub enum HoverRequest {}
+
+impl Request for HoverRequest {
+    type Params = lsp_types::HoverParams;
+    type Result = Option<Hover>;
+    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<Range>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub actions: Option<Vec<CommandLinkGroup>>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
+pub struct CommandLinkGroup {
+    pub title: Option<String>,
+    pub commands: Vec<CommandLink>,
+}
+
+// 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<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub arguments: Option<Vec<serde_json::Value>>,
+}
index e60337b8e8b61fa582b58a4977bc2c84ef8fe3a4..752dbf145298add27dddd11e7f4a0802880587fe 100644 (file)
@@ -510,6 +510,7 @@ fn on_request(
         .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
         .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
         .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
+        .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
         .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
         .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
         .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
@@ -521,7 +522,6 @@ fn on_request(
         .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
         .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?
         .on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)?
-        .on::<lsp_types::request::HoverRequest>(handlers::handle_hover)?
         .on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)?
         .on::<lsp_types::request::Rename>(handlers::handle_rename)?
         .on::<lsp_types::request::References>(handlers::handle_references)?
index 6acf80c5827f86c48c46042577404915c02e1382..0958a231fd6aeb143790ad8b7581ab170c2f6bb5 100644 (file)
     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<Option<Hover>> {
+) -> Result<Option<lsp_ext::Hover>> {
     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<lsp_types::Location>,
+) -> 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<lsp_ext::CommandLinkGroup> {
+    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<lsp_ext::CommandLinkGroup> {
+    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()
+}
index 30ab7ba4a9f831fcd78b80cd0cae68047eb25b71..b9c57db3bcf74485248c99de1e9b9c72b92e190b 100644 (file)
                     "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
                 },
index 40ad1e3cd84780fb77558b356d425317e6ecf33c..9df6702839b4963e4f7a8cff7b9b6d7824061514 100644 (file)
@@ -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 = <vscode.MarkdownString>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 = (<any>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<any>, _documentSelector: lc.DocumentSelector | undefined): void {
index e8abf8284eb7380c8f641f369c5c006fcba8c776..d8f0037d4c9516b27b71eb1a86443a7093ad6600 100644 (file)
@@ -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<boolean>("lens.implementations"),
         };
     }
+
+    get hoverActions() {
+        return {
+            enable: this.get<boolean>("hoverActions.enable"),
+            implementations: this.get<boolean>("hoverActions.implementations"),
+        };
+    }
 }
index 9793b926c26e2863b5f88f119589da96fe3c46fa..e16ea799ce015bef81a14cb22785e24cd2eee351 100644 (file)
@@ -90,3 +90,15 @@ export interface SsrParams {
     parseOnly: boolean;
 }
 export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('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[];
+}