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:
bors[bot] 2021-07-30 16:27:10 +00:00 committed by GitHub
commit 33f12a3608
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 127 additions and 124 deletions

View File

@ -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 {

View File

@ -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;
}

View File

@ -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>,
}

View File

@ -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;

View File

@ -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[];
}
```

View File

@ -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: `[]`)::
+
--

View File

@ -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": [],

View File

@ -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 {