mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 21:28:51 +00:00
Add hover actions as LSP extension
This commit is contained in:
parent
913a623281
commit
7d0dd17b09
11 changed files with 351 additions and 56 deletions
|
@ -13,14 +13,43 @@ use ra_ide_db::{
|
|||
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 @@ impl HoverResult {
|
|||
&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 pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ra_db::FileLoader;
|
||||
use ra_syntax::TextRange;
|
||||
|
||||
|
@ -241,7 +306,14 @@ mod tests {
|
|||
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 @@ mod tests {
|
|||
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 func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[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 func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[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 func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[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 func(foo: i32) { if true { <|>foo; }; }
|
|||
|
||||
#[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 func(foo: i32) { if true { <|>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 func(foo: i32) { if true { <|>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 @@ fn func(foo: i32) { if true { <|>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 func(foo: i32) { if true { <|>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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ pub use crate::{
|
|||
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},
|
||||
|
|
|
@ -18,7 +18,7 @@ use ra_syntax::{
|
|||
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),
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf};
|
|||
|
||||
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 @@ impl Default for Config {
|
|||
assist: AssistConfig::default(),
|
||||
call_info_full: true,
|
||||
lens: LensConfig::default(),
|
||||
hover: HoverConfig::default(),
|
||||
linked_projects: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +281,14 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ impl Config {
|
|||
|
||||
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);
|
||||
let get_bool = |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true);
|
||||
|
||||
let snippet_text_edit = get_bool("snippetTextEdit");
|
||||
self.assist.allow_snippets(snippet_text_edit);
|
||||
|
||||
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 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>,
|
||||
}
|
||||
|
|
|
@ -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)?
|
||||
|
|
|
@ -12,13 +12,14 @@ use lsp_types::{
|
|||
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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue