diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 99a29b43d2..e71513034c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -25,9 +25,12 @@ use serde::{de::DeserializeOwned, Deserialize}; use vfs::AbsPathBuf; use crate::{ - caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig, - line_index::OffsetEncoding, lsp_ext::supports_utf8, lsp_ext::WorkspaceSymbolSearchKind, + caps::completion_item_edit_resolve, + diagnostics::DiagnosticsMapConfig, + line_index::OffsetEncoding, + lsp_ext::supports_utf8, lsp_ext::WorkspaceSymbolSearchScope, + lsp_ext::{self, WorkspaceSymbolSearchKind}, }; // Defines the server-side configuration of the rust-analyzer. We generate @@ -221,6 +224,9 @@ config_data! { /// Whether to show `References` lens. Only applies when /// `#rust-analyzer.lens.enable#` is set. lens_references: bool = "false", + /// Internal config: use custom client-side commands even when the + /// client doesn't set the corresponding capability. + lens_forceCustomCommands: bool = "true", /// Disable project auto-discovery in favor of explicitly specified set /// of projects. @@ -405,6 +411,14 @@ pub struct WorkspaceSymbolConfig { pub search_kind: WorkspaceSymbolSearchKind, } +pub struct ClientCommandsConfig { + pub run_single: bool, + pub debug_single: bool, + pub show_reference: bool, + pub goto_location: bool, + pub trigger_parameter_hints: bool, +} + impl Config { pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { Config { @@ -858,6 +872,24 @@ impl Config { false ) } + pub fn client_commands(&self) -> ClientCommandsConfig { + let commands = + try_or!(self.caps.experimental.as_ref()?.get("commands")?, &serde_json::Value::Null); + let commands: Option = + serde_json::from_value(commands.clone()).ok(); + let force = commands.is_none() && self.data.lens_forceCustomCommands; + let commands = commands.map(|it| it.commands).unwrap_or_default(); + + let get = |name: &str| commands.iter().any(|it| it == name) || force; + + ClientCommandsConfig { + run_single: get("rust-analyzer.runSingle"), + debug_single: get("rust-analyzer.debugSingle"), + show_reference: get("rust-analyzer.showReferences"), + goto_location: get("rust-analyzer.gotoLocation"), + trigger_parameter_hints: get("editor.action.triggerParameterHints"), + } + } pub fn highlight_related(&self) -> HighlightRelatedConfig { HighlightRelatedConfig { diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 949a77a4e8..c9a25e086a 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -768,13 +768,8 @@ pub(crate) fn handle_completion( }; let line_index = snap.file_line_index(position.file_id)?; - let items = to_proto::completion_items( - snap.config.insert_replace_support(), - completion_config.enable_imports_on_the_fly, - &line_index, - text_document_position, - items, - ); + let items = + to_proto::completion_items(&snap.config, &line_index, text_document_position, items); let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; Ok(Some(completion_list.into())) @@ -1503,7 +1498,7 @@ fn show_impl_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, ) -> Option { - if snap.config.hover_actions().implementations { + if snap.config.hover_actions().implementations && snap.config.client_commands().show_reference { if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; @@ -1529,7 +1524,7 @@ fn show_ref_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, ) -> Option { - if snap.config.hover_actions().references { + if snap.config.hover_actions().references && snap.config.client_commands().show_reference { if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; @@ -1559,35 +1554,47 @@ fn runnable_action_links( snap: &GlobalStateSnapshot, runnable: Runnable, ) -> Option { - let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; let hover_actions_config = snap.config.hover_actions(); - if !hover_actions_config.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) { + if !hover_actions_config.runnable() { + return None; + } + + let cargo_spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id).ok()?; + if should_skip_target(&runnable, cargo_spec.as_ref()) { + return None; + } + + let client_commands_config = snap.config.client_commands(); + if !(client_commands_config.run_single || client_commands_config.debug_single) { return None; } let title = runnable.title(); - to_proto::runnable(snap, runnable).ok().map(|r| { - let mut group = lsp_ext::CommandLinkGroup::default(); + let r = to_proto::runnable(snap, runnable).ok()?; - if hover_actions_config.run { - let run_command = to_proto::command::run_single(&r, &title); - group.commands.push(to_command_link(run_command, r.label.clone())); - } + let mut group = lsp_ext::CommandLinkGroup::default(); - if hover_actions_config.debug { - let dbg_command = to_proto::command::debug_single(&r); - group.commands.push(to_command_link(dbg_command, r.label)); - } + if hover_actions_config.run && client_commands_config.run_single { + let run_command = to_proto::command::run_single(&r, &title); + group.commands.push(to_command_link(run_command, r.label.clone())); + } - group - }) + if hover_actions_config.debug && client_commands_config.debug_single { + let dbg_command = to_proto::command::debug_single(&r); + group.commands.push(to_command_link(dbg_command, r.label)); + } + + Some(group) } fn goto_type_action_links( snap: &GlobalStateSnapshot, nav_targets: &[HoverGotoTypeData], ) -> Option { - if !snap.config.hover_actions().goto_type_def || nav_targets.is_empty() { + if !snap.config.hover_actions().goto_type_def + || nav_targets.is_empty() + || !snap.config.client_commands().goto_location + { return None; } diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index d697ec44d1..521691d5ec 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -523,3 +523,8 @@ pub struct CompletionResolveData { pub full_import_path: String, pub imported_name: String, } + +#[derive(Debug, Deserialize, Default)] +pub struct ClientCommandOptions { + pub commands: Vec, +} diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 8bd3f7a9eb..906259b098 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -18,6 +18,7 @@ use vfs::AbsPath; use crate::{ cargo_target_spec::CargoTargetSpec, + config::Config, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, OffsetEncoding}, lsp_ext, semantic_tokens, Result, @@ -190,8 +191,7 @@ pub(crate) fn snippet_text_edit_vec( } pub(crate) fn completion_items( - insert_replace_support: bool, - enable_imports_on_the_fly: bool, + config: &Config, line_index: &LineIndex, tdpp: lsp_types::TextDocumentPositionParams, items: Vec, @@ -199,23 +199,14 @@ pub(crate) fn completion_items( let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default(); let mut res = Vec::with_capacity(items.len()); for item in items { - completion_item( - &mut res, - insert_replace_support, - enable_imports_on_the_fly, - line_index, - &tdpp, - max_relevance, - item, - ) + completion_item(&mut res, config, line_index, &tdpp, max_relevance, item) } res } fn completion_item( acc: &mut Vec, - insert_replace_support: bool, - enable_imports_on_the_fly: bool, + config: &Config, line_index: &LineIndex, tdpp: &lsp_types::TextDocumentPositionParams, max_relevance: u32, @@ -230,7 +221,7 @@ fn completion_item( let source_range = item.source_range(); for indel in item.text_edit().iter() { if indel.delete.contains_range(source_range) { - let insert_replace_support = insert_replace_support.then(|| tdpp.position); + let insert_replace_support = config.insert_replace_support().then(|| tdpp.position); text_edit = Some(if indel.delete == source_range { self::completion_text_edit(line_index, insert_replace_support, indel.clone()) } else { @@ -269,14 +260,14 @@ fn completion_item( lsp_item.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated]) } - if item.trigger_call_info() { + if item.trigger_call_info() && config.client_commands().trigger_parameter_hints { lsp_item.command = Some(command::trigger_parameter_hints()); } if item.is_snippet() { lsp_item.insert_text_format = Some(lsp_types::InsertTextFormat::Snippet); } - if enable_imports_on_the_fly { + if config.completion().enable_imports_on_the_fly { if let Some(import_edit) = item.import_to_add() { let import_path = &import_edit.import.import_path; if let Some(import_name) = import_path.segments().last() { @@ -992,6 +983,7 @@ pub(crate) fn code_lens( snap: &GlobalStateSnapshot, annotation: Annotation, ) -> Result<()> { + let client_commands_config = snap.config.client_commands(); match annotation.kind { AnnotationKind::Runnable(run) => { let line_index = snap.file_line_index(run.nav.file_id)?; @@ -1008,7 +1000,7 @@ pub(crate) fn code_lens( let r = runnable(snap, run)?; let lens_config = snap.config.lens(); - if lens_config.run { + if lens_config.run && client_commands_config.run_single { let command = command::run_single(&r, &title); acc.push(lsp_types::CodeLens { range: annotation_range, @@ -1016,7 +1008,7 @@ pub(crate) fn code_lens( data: None, }) } - if lens_config.debug && can_debug { + if lens_config.debug && can_debug && client_commands_config.debug_single { let command = command::debug_single(&r); acc.push(lsp_types::CodeLens { range: annotation_range, @@ -1026,6 +1018,9 @@ pub(crate) fn code_lens( } } AnnotationKind::HasImpls { position: file_position, data } => { + if !client_commands_config.show_reference { + return Ok(()); + } let line_index = snap.file_line_index(file_position.file_id)?; let annotation_range = range(&line_index, annotation.range); let url = url(snap, file_position.file_id); @@ -1069,6 +1064,9 @@ pub(crate) fn code_lens( }) } AnnotationKind::HasReferences { position: file_position, data } => { + if !client_commands_config.show_reference { + return Ok(()); + } let line_index = snap.file_line_index(file_position.file_id)?; let annotation_range = range(&line_index, annotation.range); let url = url(snap, file_position.file_id); @@ -1207,88 +1205,9 @@ mod tests { use std::sync::Arc; use ide::Analysis; - use ide_db::helpers::{ - insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, - SnippetCap, - }; use super::*; - #[test] - fn test_completion_with_ref() { - let fixture = r#" - struct Foo; - fn foo(arg: &Foo) {} - fn main() { - let arg = Foo; - foo($0) - }"#; - - let (offset, text) = test_utils::extract_offset(fixture); - let line_index = LineIndex { - index: Arc::new(ide::LineIndex::new(&text)), - endings: LineEndings::Unix, - encoding: OffsetEncoding::Utf16, - }; - let (analysis, file_id) = Analysis::from_single_file(text); - - let file_position = ide_db::base_db::FilePosition { file_id, offset }; - let mut items = analysis - .completions( - &ide::CompletionConfig { - enable_postfix_completions: true, - enable_imports_on_the_fly: true, - enable_self_on_the_fly: true, - add_call_parenthesis: true, - add_call_argument_snippets: true, - snippet_cap: SnippetCap::new(true), - insert_use: InsertUseConfig { - granularity: ImportGranularity::Item, - prefix_kind: PrefixKind::Plain, - enforce_granularity: true, - group: true, - skip_glob_imports: true, - }, - }, - file_position, - ) - .unwrap() - .unwrap(); - items.retain(|c| c.label().ends_with("arg")); - let items = completion_items( - false, - false, - &line_index, - lsp_types::TextDocumentPositionParams { - text_document: lsp_types::TextDocumentIdentifier { - uri: "file://main.rs".parse().unwrap(), - }, - position: position(&line_index, file_position.offset), - }, - items, - ); - let items: Vec<(String, Option)> = - items.into_iter().map(|c| (c.label, c.sort_text)).collect(); - - expect_test::expect![[r#" - [ - ( - "&arg", - Some( - "fffffff9", - ), - ), - ( - "arg", - Some( - "fffffffd", - ), - ), - ] - "#]] - .assert_debug_eq(&items); - } - #[test] fn conv_fold_line_folding_only_fixup() { let text = r#"mod a; diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 1fb5237ba7..98098b69a9 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@