diff options
author | Aleksey Kladov <[email protected]> | 2020-11-10 17:20:01 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-11-10 17:48:46 +0000 |
commit | 7d2eb000b078143e9fa6225d00ef52fc7c606fdf (patch) | |
tree | 580d90bd250ffcd4b1c66570e5601761e58d1057 | |
parent | ada5a88f8fd0a79af7ad6e0411acc1cce9ef32d5 (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.rs | 6 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 11 | ||||
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/to_proto.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 23 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 35 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 26 | ||||
-rw-r--r-- | docs/dev/lsp-extensions.md | 26 | ||||
-rw-r--r-- | editors/code/src/client.ts | 21 | ||||
-rw-r--r-- | editors/code/src/commands.ts | 11 | ||||
-rw-r--r-- | editors/code/src/lsp_ext.ts | 6 |
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; | |||
16 | use crate::semantic_tokens; | 16 | use crate::semantic_tokens; |
17 | 17 | ||
18 | pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities { | 18 | pub 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, ¶ms, &mut res)?; | 853 | handle_fixes(&snap, ¶ms, &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 | ||
870 | pub(crate) fn handle_resolve_code_action( | 870 | pub(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, ¶ms.code_action_params.text_document.uri)?; | 880 | let file_id = from_proto::file_id(&snap, ¶ms.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 | ||
894 | pub(crate) fn handle_code_lens( | 901 | pub(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 | ||
116 | pub enum ResolveCodeActionRequest {} | ||
117 | |||
118 | impl 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")] | ||
127 | pub struct ResolveCodeActionParams { | ||
128 | pub code_action_params: lsp_types::CodeActionParams, | ||
129 | pub id: String, | ||
130 | } | ||
131 | |||
132 | pub enum OnEnter {} | 116 | pub enum OnEnter {} |
133 | 117 | ||
134 | impl Request for OnEnter { | 118 | impl 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 | ||
252 | pub enum CodeActionResolveRequest {} | ||
253 | impl 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")] |
270 | pub struct CodeAction { | 261 | pub 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")] | ||
281 | pub 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 | ||
736 | pub(crate) fn unresolved_code_action( | 736 | pub(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 | ||
766 | pub(crate) fn runnable( | 776 | pub(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 | <!--- |
2 | lsp_ext.rs hash: 286f8bbac885531a | 2 | lsp_ext.rs hash: 4f86fb54e4b2870e |
3 | 3 | ||
4 | If you need to change the above hash to make the test pass, please check if you | 4 | If you need to change the above hash to make the test pass, please check if you |
5 | need to adjust this doc as well and ping this issue: | 5 | need 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 | |||
118 | If 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 | |||
120 | After the client got the id, it should then call `experimental/resolveCodeAction` command on the server and provide the following payload: | ||
121 | |||
122 | ```typescript | ||
123 | interface ResolveCodeActionParams { | ||
124 | id: string; | ||
125 | codeActionParams: lc.CodeActionParams; | ||
126 | } | ||
127 | ``` | ||
128 | |||
129 | As 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'; | |||
4 | import * as Is from 'vscode-languageclient/lib/common/utils/is'; | 4 | import * as Is from 'vscode-languageclient/lib/common/utils/is'; |
5 | import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens'; | 5 | import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens'; |
6 | import { assert } from './util'; | 6 | import { assert } from './util'; |
7 | import { WorkspaceEdit } from 'vscode'; | ||
7 | 8 | ||
8 | function renderCommand(cmd: ra.CommandLink) { | 9 | function 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 | ||
397 | export function applyActionGroup(_ctx: Ctx): Cmd { | 397 | export 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 | ||
443 | export function resolveCodeAction(ctx: Ctx): Cmd { | 443 | export 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 | ||
44 | export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule"); | 44 | export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule"); |
45 | 45 | ||
46 | export interface ResolveCodeActionParams { | ||
47 | id: string; | ||
48 | codeActionParams: lc.CodeActionParams; | ||
49 | } | ||
50 | export const resolveCodeAction = new lc.RequestType<ResolveCodeActionParams, lc.WorkspaceEdit, unknown>('experimental/resolveCodeAction'); | ||
51 | |||
52 | export interface JoinLinesParams { | 46 | export interface JoinLinesParams { |
53 | textDocument: lc.TextDocumentIdentifier; | 47 | textDocument: lc.TextDocumentIdentifier; |
54 | ranges: lc.Range[]; | 48 | ranges: lc.Range[]; |