Merge #9732
9732: feat: gate custom clint-side commands behind capabilities r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
33f12a3608
@ -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<lsp_ext::ClientCommandOptions> =
|
||||
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 {
|
||||
|
@ -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<lsp_ext::CommandLinkGroup> {
|
||||
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<lsp_ext::CommandLinkGroup> {
|
||||
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<lsp_ext::CommandLinkGroup> {
|
||||
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<lsp_ext::CommandLinkGroup> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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<String>,
|
||||
}
|
||||
|
@ -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<CompletionItem>,
|
||||
@ -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<lsp_types::CompletionItem>,
|
||||
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<String>)> =
|
||||
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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!---
|
||||
lsp_ext.rs hash: 5f96a69eb3a5ebc3
|
||||
lsp_ext.rs hash: ad52054176909945
|
||||
|
||||
If you need to change the above hash to make the test pass, please check if you
|
||||
need to adjust this doc as well and ping this issue:
|
||||
@ -743,3 +743,23 @@ const enum WorkspaceSymbolSearchKind {
|
||||
AllSymbols = "allSymbols"
|
||||
}
|
||||
```
|
||||
|
||||
## Client Commands
|
||||
|
||||
**Experimental Client Capability:** `{ "commands?": ClientCommandOptions }`
|
||||
|
||||
Certain LSP types originating on the server, notably code lenses, embed commands.
|
||||
Commands can be serviced either by the server or by the client.
|
||||
However, the server doesn't know which commands are available on the client.
|
||||
|
||||
This extensions allows the client to communicate this info.
|
||||
|
||||
|
||||
```typescript
|
||||
export interface ClientCommandOptions {
|
||||
/**
|
||||
* The commands to be executed on the client
|
||||
*/
|
||||
commands: string[];
|
||||
}
|
||||
```
|
||||
|
@ -353,6 +353,12 @@ Whether to show `Method References` lens. Only applies when
|
||||
Whether to show `References` lens. Only applies when
|
||||
`#rust-analyzer.lens.enable#` is set.
|
||||
--
|
||||
[[rust-analyzer.lens.forceCustomCommands]]rust-analyzer.lens.forceCustomCommands (default: `true`)::
|
||||
+
|
||||
--
|
||||
Internal config: use custom client-side commands even when the
|
||||
client doesn't set the corresponding capability.
|
||||
--
|
||||
[[rust-analyzer.linkedProjects]]rust-analyzer.linkedProjects (default: `[]`)::
|
||||
+
|
||||
--
|
||||
|
@ -789,6 +789,11 @@
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.lens.forceCustomCommands": {
|
||||
"markdownDescription": "Internal config: use custom client-side commands even when the\nclient doesn't set the corresponding capability.",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rust-analyzer.linkedProjects": {
|
||||
"markdownDescription": "Disable project auto-discovery in favor of explicitly specified set\nof projects.\n\nElements must be paths pointing to `Cargo.toml`,\n`rust-project.json`, or JSON objects in `rust-project.json` format.",
|
||||
"default": [],
|
||||
|
@ -178,6 +178,15 @@ class ExperimentalFeatures implements lc.StaticFeature {
|
||||
caps.codeActionGroup = true;
|
||||
caps.hoverActions = true;
|
||||
caps.serverStatusNotification = true;
|
||||
caps.commands = {
|
||||
commands: [
|
||||
"rust-analyzer.runSingle",
|
||||
"rust-analyzer.debugSingle",
|
||||
"rust-analyzer.showReferences",
|
||||
"rust-analyzer.gotoLocation",
|
||||
"editor.action.triggerParameterHints",
|
||||
]
|
||||
};
|
||||
capabilities.experimental = caps;
|
||||
}
|
||||
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
|
||||
|
Loading…
x
Reference in New Issue
Block a user