aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-11-10 17:20:01 +0000
committerAleksey Kladov <[email protected]>2020-11-10 17:48:46 +0000
commit7d2eb000b078143e9fa6225d00ef52fc7c606fdf (patch)
tree580d90bd250ffcd4b1c66570e5601761e58d1057
parentada5a88f8fd0a79af7ad6e0411acc1cce9ef32d5 (diff)
Switch to upstream protocol for resolving code action
Note that we have to maintain custom implementation on the client side: I don't see how to marry bulitin resolve support with groups and snippets.
-rw-r--r--crates/rust-analyzer/src/caps.rs6
-rw-r--r--crates/rust-analyzer/src/config.rs11
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt2
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt2
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt2
-rw-r--r--crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt2
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs2
-rw-r--r--crates/rust-analyzer/src/handlers.rs23
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs35
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/to_proto.rs26
-rw-r--r--docs/dev/lsp-extensions.md26
-rw-r--r--editors/code/src/client.ts21
-rw-r--r--editors/code/src/commands.ts11
-rw-r--r--editors/code/src/lsp_ext.ts6
15 files changed, 86 insertions, 91 deletions
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index ff1ae9575..9a8870053 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -16,8 +16,6 @@ use serde_json::json;
16use crate::semantic_tokens; 16use crate::semantic_tokens;
17 17
18pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities { 18pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities {
19 let code_action_provider = code_action_capabilities(client_caps);
20
21 ServerCapabilities { 19 ServerCapabilities {
22 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { 20 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
23 open_close: Some(true), 21 open_close: Some(true),
@@ -49,7 +47,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
49 document_highlight_provider: Some(OneOf::Left(true)), 47 document_highlight_provider: Some(OneOf::Left(true)),
50 document_symbol_provider: Some(OneOf::Left(true)), 48 document_symbol_provider: Some(OneOf::Left(true)),
51 workspace_symbol_provider: Some(OneOf::Left(true)), 49 workspace_symbol_provider: Some(OneOf::Left(true)),
52 code_action_provider: Some(code_action_provider), 50 code_action_provider: Some(code_action_capabilities(client_caps)),
53 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), 51 code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
54 document_formatting_provider: Some(OneOf::Left(true)), 52 document_formatting_provider: Some(OneOf::Left(true)),
55 document_range_formatting_provider: None, 53 document_range_formatting_provider: None,
@@ -113,7 +111,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
113 CodeActionKind::REFACTOR_INLINE, 111 CodeActionKind::REFACTOR_INLINE,
114 CodeActionKind::REFACTOR_REWRITE, 112 CodeActionKind::REFACTOR_REWRITE,
115 ]), 113 ]),
116 resolve_provider: None, 114 resolve_provider: Some(true),
117 work_done_progress_options: Default::default(), 115 work_done_progress_options: Default::default(),
118 }) 116 })
119 }) 117 })
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 2ed6a0d82..9cc14fe82 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -144,7 +144,7 @@ pub struct ClientCapsConfig {
144 pub code_action_literals: bool, 144 pub code_action_literals: bool,
145 pub work_done_progress: bool, 145 pub work_done_progress: bool,
146 pub code_action_group: bool, 146 pub code_action_group: bool,
147 pub resolve_code_action: bool, 147 pub code_action_resolve: bool,
148 pub hover_actions: bool, 148 pub hover_actions: bool,
149 pub status_notification: bool, 149 pub status_notification: bool,
150 pub signature_help_label_offsets: bool, 150 pub signature_help_label_offsets: bool,
@@ -383,6 +383,14 @@ impl Config {
383 } 383 }
384 } 384 }
385 } 385 }
386
387 if let Some(code_action) = &doc_caps.code_action {
388 if let Some(resolve_support) = &code_action.resolve_support {
389 if resolve_support.properties.iter().any(|it| it == "edit") {
390 self.client_caps.code_action_resolve = true;
391 }
392 }
393 }
386 } 394 }
387 395
388 if let Some(window_caps) = caps.window.as_ref() { 396 if let Some(window_caps) = caps.window.as_ref() {
@@ -400,7 +408,6 @@ impl Config {
400 self.assist.allow_snippets(snippet_text_edit); 408 self.assist.allow_snippets(snippet_text_edit);
401 409
402 self.client_caps.code_action_group = get_bool("codeActionGroup"); 410 self.client_caps.code_action_group = get_bool("codeActionGroup");
403 self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
404 self.client_caps.hover_actions = get_bool("hoverActions"); 411 self.client_caps.hover_actions = get_bool("hoverActions");
405 self.client_caps.status_notification = get_bool("statusNotification"); 412 self.client_caps.status_notification = get_bool("statusNotification");
406 } 413 }
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
index 8dc53391e..a8d85f008 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
@@ -36,7 +36,6 @@
36 fixes: [ 36 fixes: [
37 CodeAction { 37 CodeAction {
38 title: "consider prefixing with an underscore", 38 title: "consider prefixing with an underscore",
39 id: None,
40 group: None, 39 group: None,
41 kind: Some( 40 kind: Some(
42 CodeActionKind( 41 CodeActionKind(
@@ -70,6 +69,7 @@
70 is_preferred: Some( 69 is_preferred: Some(
71 true, 70 true,
72 ), 71 ),
72 data: None,
73 }, 73 },
74 ], 74 ],
75 }, 75 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
index c8703194c..0b8937376 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
@@ -36,7 +36,6 @@
36 fixes: [ 36 fixes: [
37 CodeAction { 37 CodeAction {
38 title: "consider prefixing with an underscore", 38 title: "consider prefixing with an underscore",
39 id: None,
40 group: None, 39 group: None,
41 kind: Some( 40 kind: Some(
42 CodeActionKind( 41 CodeActionKind(
@@ -70,6 +69,7 @@
70 is_preferred: Some( 69 is_preferred: Some(
71 true, 70 true,
72 ), 71 ),
72 data: None,
73 }, 73 },
74 ], 74 ],
75 }, 75 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
index dc93227ad..7fa9dee62 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
@@ -36,7 +36,6 @@
36 fixes: [ 36 fixes: [
37 CodeAction { 37 CodeAction {
38 title: "consider prefixing with an underscore", 38 title: "consider prefixing with an underscore",
39 id: None,
40 group: None, 39 group: None,
41 kind: Some( 40 kind: Some(
42 CodeActionKind( 41 CodeActionKind(
@@ -70,6 +69,7 @@
70 is_preferred: Some( 69 is_preferred: Some(
71 true, 70 true,
72 ), 71 ),
72 data: None,
73 }, 73 },
74 ], 74 ],
75 }, 75 },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
index 81f752672..3c97b2084 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
@@ -51,7 +51,6 @@
51 fixes: [ 51 fixes: [
52 CodeAction { 52 CodeAction {
53 title: "return the expression directly", 53 title: "return the expression directly",
54 id: None,
55 group: None, 54 group: None,
56 kind: Some( 55 kind: Some(
57 CodeActionKind( 56 CodeActionKind(
@@ -98,6 +97,7 @@
98 is_preferred: Some( 97 is_preferred: Some(
99 true, 98 true,
100 ), 99 ),
100 data: None,
101 }, 101 },
102 ], 102 ],
103 }, 103 },
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index b949577c1..15145415b 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -110,7 +110,6 @@ fn map_rust_child_diagnostic(
110 } else { 110 } else {
111 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { 111 MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction {
112 title: rd.message.clone(), 112 title: rd.message.clone(),
113 id: None,
114 group: None, 113 group: None,
115 kind: Some(lsp_types::CodeActionKind::QUICKFIX), 114 kind: Some(lsp_types::CodeActionKind::QUICKFIX),
116 edit: Some(lsp_ext::SnippetWorkspaceEdit { 115 edit: Some(lsp_ext::SnippetWorkspaceEdit {
@@ -119,6 +118,7 @@ fn map_rust_child_diagnostic(
119 document_changes: None, 118 document_changes: None,
120 }), 119 }),
121 is_preferred: Some(true), 120 is_preferred: Some(true),
121 data: None,
122 }) 122 })
123 } 123 }
124} 124}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 049c583a4..95659b0db 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -806,11 +806,11 @@ fn handle_fixes(
806 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; 806 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
807 let action = lsp_ext::CodeAction { 807 let action = lsp_ext::CodeAction {
808 title: fix.label.to_string(), 808 title: fix.label.to_string(),
809 id: None,
810 group: None, 809 group: None,
811 kind: Some(CodeActionKind::QUICKFIX), 810 kind: Some(CodeActionKind::QUICKFIX),
812 edit: Some(edit), 811 edit: Some(edit),
813 is_preferred: Some(false), 812 is_preferred: Some(false),
813 data: None,
814 }; 814 };
815 res.push(action); 815 res.push(action);
816 } 816 }
@@ -852,11 +852,11 @@ pub(crate) fn handle_code_action(
852 852
853 handle_fixes(&snap, &params, &mut res)?; 853 handle_fixes(&snap, &params, &mut res)?;
854 854
855 if snap.config.client_caps.resolve_code_action { 855 if snap.config.client_caps.code_action_resolve {
856 for (index, assist) in 856 for (index, assist) in
857 snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() 857 snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
858 { 858 {
859 res.push(to_proto::unresolved_code_action(&snap, assist, index)?); 859 res.push(to_proto::unresolved_code_action(&snap, params.clone(), assist, index)?);
860 } 860 }
861 } else { 861 } else {
862 for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() { 862 for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() {
@@ -867,11 +867,16 @@ pub(crate) fn handle_code_action(
867 Ok(Some(res)) 867 Ok(Some(res))
868} 868}
869 869
870pub(crate) fn handle_resolve_code_action( 870pub(crate) fn handle_code_action_resolve(
871 mut snap: GlobalStateSnapshot, 871 mut snap: GlobalStateSnapshot,
872 params: lsp_ext::ResolveCodeActionParams, 872 mut code_action: lsp_ext::CodeAction,
873) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> { 873) -> Result<lsp_ext::CodeAction> {
874 let _p = profile::span("handle_resolve_code_action"); 874 let _p = profile::span("handle_code_action_resolve");
875 let params = match code_action.data.take() {
876 Some(it) => it,
877 None => Err("can't resolve code action without data")?,
878 };
879
875 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?; 880 let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
876 let line_index = snap.analysis.file_line_index(file_id)?; 881 let line_index = snap.analysis.file_line_index(file_id)?;
877 let range = from_proto::text_range(&line_index, params.code_action_params.range); 882 let range = from_proto::text_range(&line_index, params.code_action_params.range);
@@ -888,7 +893,9 @@ pub(crate) fn handle_resolve_code_action(
888 let index = index.parse::<usize>().unwrap(); 893 let index = index.parse::<usize>().unwrap();
889 let assist = &assists[index]; 894 let assist = &assists[index];
890 assert!(assist.assist.id.0 == id); 895 assert!(assist.assist.id.0 == id);
891 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit) 896 let edit = to_proto::resolved_code_action(&snap, assist.clone())?.edit;
897 code_action.edit = edit;
898 Ok(code_action)
892} 899}
893 900
894pub(crate) fn handle_code_lens( 901pub(crate) fn handle_code_lens(
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index f31f8d900..a7c3028e4 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -113,22 +113,6 @@ pub struct JoinLinesParams {
113 pub ranges: Vec<Range>, 113 pub ranges: Vec<Range>,
114} 114}
115 115
116pub enum ResolveCodeActionRequest {}
117
118impl Request for ResolveCodeActionRequest {
119 type Params = ResolveCodeActionParams;
120 type Result = Option<SnippetWorkspaceEdit>;
121 const METHOD: &'static str = "experimental/resolveCodeAction";
122}
123
124/// Params for the ResolveCodeActionRequest
125#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
126#[serde(rename_all = "camelCase")]
127pub struct ResolveCodeActionParams {
128 pub code_action_params: lsp_types::CodeActionParams,
129 pub id: String,
130}
131
132pub enum OnEnter {} 116pub enum OnEnter {}
133 117
134impl Request for OnEnter { 118impl Request for OnEnter {
@@ -265,13 +249,18 @@ impl Request for CodeActionRequest {
265 const METHOD: &'static str = "textDocument/codeAction"; 249 const METHOD: &'static str = "textDocument/codeAction";
266} 250}
267 251
252pub enum CodeActionResolveRequest {}
253impl Request for CodeActionResolveRequest {
254 type Params = CodeAction;
255 type Result = CodeAction;
256 const METHOD: &'static str = "codeAction/resolve";
257}
258
268#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] 259#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
269#[serde(rename_all = "camelCase")] 260#[serde(rename_all = "camelCase")]
270pub struct CodeAction { 261pub struct CodeAction {
271 pub title: String, 262 pub title: String,
272 #[serde(skip_serializing_if = "Option::is_none")] 263 #[serde(skip_serializing_if = "Option::is_none")]
273 pub id: Option<String>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub group: Option<String>, 264 pub group: Option<String>,
276 #[serde(skip_serializing_if = "Option::is_none")] 265 #[serde(skip_serializing_if = "Option::is_none")]
277 pub kind: Option<CodeActionKind>, 266 pub kind: Option<CodeActionKind>,
@@ -282,6 +271,16 @@ pub struct CodeAction {
282 pub edit: Option<SnippetWorkspaceEdit>, 271 pub edit: Option<SnippetWorkspaceEdit>,
283 #[serde(skip_serializing_if = "Option::is_none")] 272 #[serde(skip_serializing_if = "Option::is_none")]
284 pub is_preferred: Option<bool>, 273 pub is_preferred: Option<bool>,
274
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub data: Option<CodeActionData>,
277}
278
279#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct CodeActionData {
282 pub code_action_params: lsp_types::CodeActionParams,
283 pub id: String,
285} 284}
286 285
287#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] 286#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index d504572a8..6e6cac42e 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -435,7 +435,7 @@ impl GlobalState {
435 .on::<lsp_ext::Runnables>(handlers::handle_runnables) 435 .on::<lsp_ext::Runnables>(handlers::handle_runnables)
436 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints) 436 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)
437 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action) 437 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)
438 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action) 438 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover) 439 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) 440 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
441 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) 441 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index f8ecd8e83..4bdf4bf0f 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -735,16 +735,20 @@ pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
735 735
736pub(crate) fn unresolved_code_action( 736pub(crate) fn unresolved_code_action(
737 snap: &GlobalStateSnapshot, 737 snap: &GlobalStateSnapshot,
738 code_action_params: lsp_types::CodeActionParams,
738 assist: Assist, 739 assist: Assist,
739 index: usize, 740 index: usize,
740) -> Result<lsp_ext::CodeAction> { 741) -> Result<lsp_ext::CodeAction> {
741 let res = lsp_ext::CodeAction { 742 let res = lsp_ext::CodeAction {
742 title: assist.label.to_string(), 743 title: assist.label.to_string(),
743 id: Some(format!("{}:{}", assist.id.0, index.to_string())),
744 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0), 744 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
745 kind: Some(code_action_kind(assist.id.1)), 745 kind: Some(code_action_kind(assist.id.1)),
746 edit: None, 746 edit: None,
747 is_preferred: None, 747 is_preferred: None,
748 data: Some(lsp_ext::CodeActionData {
749 id: format!("{}:{}", assist.id.0, index.to_string()),
750 code_action_params,
751 }),
748 }; 752 };
749 Ok(res) 753 Ok(res)
750} 754}
@@ -754,13 +758,19 @@ pub(crate) fn resolved_code_action(
754 assist: ResolvedAssist, 758 assist: ResolvedAssist,
755) -> Result<lsp_ext::CodeAction> { 759) -> Result<lsp_ext::CodeAction> {
756 let change = assist.source_change; 760 let change = assist.source_change;
757 unresolved_code_action(snap, assist.assist, 0).and_then(|it| { 761 let res = lsp_ext::CodeAction {
758 Ok(lsp_ext::CodeAction { 762 edit: Some(snippet_workspace_edit(snap, change)?),
759 id: None, 763 title: assist.assist.label.to_string(),
760 edit: Some(snippet_workspace_edit(snap, change)?), 764 group: assist
761 ..it 765 .assist
762 }) 766 .group
763 }) 767 .filter(|_| snap.config.client_caps.code_action_group)
768 .map(|gr| gr.0),
769 kind: Some(code_action_kind(assist.assist.id.1)),
770 is_preferred: None,
771 data: None,
772 };
773 Ok(res)
764} 774}
765 775
766pub(crate) fn runnable( 776pub(crate) fn runnable(
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 780f5cb91..77d4e0ec9 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
1<!--- 1<!---
2lsp_ext.rs hash: 286f8bbac885531a 2lsp_ext.rs hash: 4f86fb54e4b2870e
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need to adjust this doc as well and ping this issue:
@@ -109,30 +109,6 @@ Invoking code action at this position will yield two code actions for importing
109* Is a fixed two-level structure enough? 109* Is a fixed two-level structure enough?
110* Should we devise a general way to encode custom interaction protocols for GUI refactorings? 110* Should we devise a general way to encode custom interaction protocols for GUI refactorings?
111 111
112## Lazy assists with `ResolveCodeAction`
113
114**Issue:** https://github.com/microsoft/language-server-protocol/issues/787
115
116**Client Capability** `{ "resolveCodeAction": boolean }`
117
118If this capability is set, the assists will be computed lazily. Thus `CodeAction` returned from the server will only contain `id` but not `edit` or `command` fields. The only exclusion from the rule is the diagnostic edits.
119
120After the client got the id, it should then call `experimental/resolveCodeAction` command on the server and provide the following payload:
121
122```typescript
123interface ResolveCodeActionParams {
124 id: string;
125 codeActionParams: lc.CodeActionParams;
126}
127```
128
129As a result of the command call the client will get the respective workspace edit (`lc.WorkspaceEdit`).
130
131### Unresolved Questions
132
133* Apply smarter filtering for ids?
134* Upon `resolveCodeAction` command only call the assits which should be resolved and not all of them?
135
136## Parent Module 112## Parent Module
137 113
138**Issue:** https://github.com/microsoft/language-server-protocol/issues/1002 114**Issue:** https://github.com/microsoft/language-server-protocol/issues/1002
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index d032b45b7..c9d032ead 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -4,6 +4,7 @@ import * as ra from '../src/lsp_ext';
4import * as Is from 'vscode-languageclient/lib/common/utils/is'; 4import * as Is from 'vscode-languageclient/lib/common/utils/is';
5import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens'; 5import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens';
6import { assert } from './util'; 6import { assert } from './util';
7import { WorkspaceEdit } from 'vscode';
7 8
8function renderCommand(cmd: ra.CommandLink) { 9function renderCommand(cmd: ra.CommandLink) {
9 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; 10 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
@@ -75,8 +76,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
75 return Promise.resolve(null); 76 return Promise.resolve(null);
76 }); 77 });
77 }, 78 },
78 // Using custom handling of CodeActions where each code action is resolved lazily 79 // Using custom handling of CodeActions to support action groups and snippet edits.
79 // That's why we are not waiting for any command or edits 80 // Note that this means we have to re-implement lazy edit resolving ourselves as well.
80 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { 81 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
81 const params: lc.CodeActionParams = { 82 const params: lc.CodeActionParams = {
82 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), 83 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
@@ -99,16 +100,15 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
99 const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind); 100 const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
100 const action = new vscode.CodeAction(item.title, kind); 101 const action = new vscode.CodeAction(item.title, kind);
101 const group = (item as any).group; 102 const group = (item as any).group;
102 const id = (item as any).id;
103 const resolveParams: ra.ResolveCodeActionParams = {
104 id: id,
105 codeActionParams: params
106 };
107 action.command = { 103 action.command = {
108 command: "rust-analyzer.resolveCodeAction", 104 command: "rust-analyzer.resolveCodeAction",
109 title: item.title, 105 title: item.title,
110 arguments: [resolveParams], 106 arguments: [item],
111 }; 107 };
108
109 // Set a dummy edit, so that VS Code doesn't try to resolve this.
110 action.edit = new WorkspaceEdit();
111
112 if (group) { 112 if (group) {
113 let entry = groups.get(group); 113 let entry = groups.get(group);
114 if (!entry) { 114 if (!entry) {
@@ -134,6 +134,10 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
134 return { label: item.title, arguments: item.command!!.arguments!![0] }; 134 return { label: item.title, arguments: item.command!!.arguments!![0] };
135 })], 135 })],
136 }; 136 };
137
138 // Set a dummy edit, so that VS Code doesn't try to resolve this.
139 action.edit = new WorkspaceEdit();
140
137 result[index] = action; 141 result[index] = action;
138 } 142 }
139 } 143 }
@@ -164,7 +168,6 @@ class ExperimentalFeatures implements lc.StaticFeature {
164 const caps: any = capabilities.experimental ?? {}; 168 const caps: any = capabilities.experimental ?? {};
165 caps.snippetTextEdit = true; 169 caps.snippetTextEdit = true;
166 caps.codeActionGroup = true; 170 caps.codeActionGroup = true;
167 caps.resolveCodeAction = true;
168 caps.hoverActions = true; 171 caps.hoverActions = true;
169 caps.statusNotification = true; 172 caps.statusNotification = true;
170 capabilities.experimental = caps; 173 capabilities.experimental = caps;
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 22509e874..cf34622c3 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -395,7 +395,7 @@ export function showReferences(ctx: Ctx): Cmd {
395} 395}
396 396
397export function applyActionGroup(_ctx: Ctx): Cmd { 397export function applyActionGroup(_ctx: Ctx): Cmd {
398 return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => { 398 return async (actions: { label: string; arguments: lc.CodeAction }[]) => {
399 const selectedAction = await vscode.window.showQuickPick(actions); 399 const selectedAction = await vscode.window.showQuickPick(actions);
400 if (!selectedAction) return; 400 if (!selectedAction) return;
401 vscode.commands.executeCommand( 401 vscode.commands.executeCommand(
@@ -442,12 +442,13 @@ export function openDocs(ctx: Ctx): Cmd {
442 442
443export function resolveCodeAction(ctx: Ctx): Cmd { 443export function resolveCodeAction(ctx: Ctx): Cmd {
444 const client = ctx.client; 444 const client = ctx.client;
445 return async (params: ra.ResolveCodeActionParams) => { 445 return async (params: lc.CodeAction) => {
446 const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); 446 params.command = undefined;
447 if (!item) { 447 const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params);
448 if (!item.edit) {
448 return; 449 return;
449 } 450 }
450 const edit = client.protocol2CodeConverter.asWorkspaceEdit(item); 451 const edit = client.protocol2CodeConverter.asWorkspaceEdit(item.edit);
451 await applySnippetWorkspaceEdit(edit); 452 await applySnippetWorkspaceEdit(edit);
452 }; 453 };
453} 454}
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index fc8e120b3..d320c3cd7 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -43,12 +43,6 @@ export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position
43 43
44export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule"); 44export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule");
45 45
46export interface ResolveCodeActionParams {
47 id: string;
48 codeActionParams: lc.CodeActionParams;
49}
50export const resolveCodeAction = new lc.RequestType<ResolveCodeActionParams, lc.WorkspaceEdit, unknown>('experimental/resolveCodeAction');
51
52export interface JoinLinesParams { 46export interface JoinLinesParams {
53 textDocument: lc.TextDocumentIdentifier; 47 textDocument: lc.TextDocumentIdentifier;
54 ranges: lc.Range[]; 48 ranges: lc.Range[];