From 56523b5775e780f2755e4683008d8b3c57f27de8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 1 Apr 2022 10:16:26 -0700 Subject: [PATCH] Allow applying code actions that use commands Co-Authored-By: Antonio Scandurra --- crates/lsp/src/lsp.rs | 3 +- crates/project/src/project.rs | 83 +++++++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 6d89b5e870..f5fc98640d 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -563,8 +563,9 @@ impl Drop for Subscription { } #[cfg(any(test, feature = "test-support"))] +#[derive(Clone)] pub struct FakeLanguageServer { - server: Arc, + pub server: Arc, notifications_rx: channel::Receiver<(String, String)>, } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e37124dda0..4225f9657f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -64,6 +64,7 @@ pub struct Project { HashMap<(WorktreeId, LanguageServerName), Task>>>, language_server_statuses: BTreeMap, language_server_settings: Arc>, + last_workspace_edits_by_language_server: HashMap, next_language_server_id: usize, client: Arc, next_entry_id: Arc, @@ -346,6 +347,7 @@ impl Project { language_servers: Default::default(), started_language_servers: Default::default(), language_server_statuses: Default::default(), + last_workspace_edits_by_language_server: Default::default(), language_server_settings: Default::default(), next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), @@ -433,6 +435,7 @@ impl Project { ) }) .collect(), + last_workspace_edits_by_language_server: Default::default(), next_language_server_id: 0, opened_buffers: Default::default(), buffer_snapshots: Default::default(), @@ -1736,18 +1739,21 @@ impl Project { } LanguageServerEvent::WorkspaceEdit(params) => { let transaction = Self::deserialize_workspace_edit( - this, + this.clone(), params.edit, - false, + true, adapter.clone(), language_server.clone(), cx, ) .await .log_err(); - - // Check if there is a code action currently running, using the state that is - // set in `start_code_action`. If so, then store the transaction for later use. + this.update(cx, |this, _| { + if let Some(transaction) = transaction { + this.last_workspace_edits_by_language_server + .insert(language_server_id, transaction); + } + }); } } } @@ -2740,7 +2746,6 @@ impl Project { ) .await } else if let Some(command) = action.lsp_action.command { - this.update(&mut cx, |this, _| this.start_code_action()); lang_server .request::(lsp::ExecuteCommandParams { command: command.command, @@ -2748,7 +2753,11 @@ impl Project { ..Default::default() }) .await?; - Ok(this.update(&mut cx, |this, cx| this.finish_code_action(cx))) + Ok(this.update(&mut cx, |this, _| { + this.last_workspace_edits_by_language_server + .remove(&lang_server.server_id()) + .unwrap_or_default() + })) } else { Ok(ProjectTransaction::default()) } @@ -2907,17 +2916,6 @@ impl Project { Ok(project_transaction) } - fn start_code_action(&mut self) { - // Set some state that will be read inside of `on_lsp_event` when handling a `WorkspaceEdit` - // event, and will cause the `ProjectTransaction` to be stored. - } - - fn finish_code_action(&mut self, cx: &mut ModelContext) -> ProjectTransaction { - // Retrieve all stored `ProjectTransactions` that have been received since `start_code_action` - // was called, and combine them together. - Default::default() - } - pub fn prepare_rename( &self, buffer: ModelHandle, @@ -6089,7 +6087,7 @@ mod tests { fs.insert_tree( "/dir", json!({ - "a.ts": "", + "a.ts": "a", }), ) .await; @@ -6116,7 +6114,7 @@ mod tests { let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx)); fake_server - .handle_request::(|params, _| async move { + .handle_request::(|_, _| async move { Ok(Some(vec![ lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { title: "The code action".into(), @@ -6144,12 +6142,51 @@ mod tests { |action, _| async move { Ok(action) }, ); fake_server - .handle_request::(move |params, cx| async move { - // fake_server.send(); - Ok(Some(json!(null))) + .handle_request::({ + let fake = fake_server.clone(); + move |params, _| { + assert_eq!(params.command, "_the/command"); + let fake = fake.clone(); + async move { + fake.server + .request::( + lsp::ApplyWorkspaceEditParams { + label: None, + edit: lsp::WorkspaceEdit { + changes: Some( + [( + lsp::Url::from_file_path("/dir/a.ts").unwrap(), + vec![lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 0), + ), + new_text: "X".into(), + }], + )] + .into_iter() + .collect(), + ), + ..Default::default() + }, + }, + ) + .await + .unwrap(); + Ok(Some(json!(null))) + } + } }) .next() .await; + + let transaction = apply.await.unwrap(); + assert!(transaction.0.contains_key(&buffer)); + buffer.update(cx, |buffer, cx| { + assert_eq!(buffer.text(), "Xa"); + buffer.undo(cx); + assert_eq!(buffer.text(), "a"); + }); } #[gpui::test]