From f912c545e772bfac23bf48d315d81de0aa22b785 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 31 Dec 2024 02:50:57 +0200 Subject: [PATCH] Deduplicate edits from WorkspaceEdit LSP responses (#22512) Closes https://github.com/zed-industries/zed/issues/21515 Release Notes: - Fixed zls renames applying duplicate edits --- crates/editor/src/editor_tests.rs | 58 ++++++++++++++++++++++++++++++- crates/project/src/lsp_store.rs | 19 +++++++--- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 7377b6809b71a..5670f8e9d773f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -11260,7 +11260,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) { }, ..Default::default() }, - Some(tree_sitter_rust::LANGUAGE.into()), + Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()), ))); update_test_language_settings(cx, |settings| { settings.defaults.prettier = Some(PrettierSettings { @@ -14732,6 +14732,62 @@ fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) { } } +#[gpui::test] +async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await; + + cx.set_state(indoc! {" + struct Fˇoo {} + "}); + + cx.update_editor(|editor, cx| { + let highlight_range = Point::new(0, 7)..Point::new(0, 10); + let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx)); + editor.highlight_background::( + &[highlight_range], + |c| c.editor_document_highlight_read_background, + cx, + ); + }); + + cx.update_editor(|e, cx| e.rename(&Rename, cx)) + .expect("Rename was not started") + .await + .expect("Rename failed"); + let mut rename_handler = + cx.handle_request::(move |url, _, _| async move { + let edit = lsp::TextEdit { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 7, + }, + end: lsp::Position { + line: 0, + character: 10, + }, + }, + new_text: "FooRenamed".to_string(), + }; + Ok(Some(lsp::WorkspaceEdit::new( + // Specify the same edit twice + std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))), + ))) + }); + cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx)) + .expect("Confirm rename was not started") + .await + .expect("Confirm rename failed"); + rename_handler.next().await.unwrap(); + cx.run_until_parked(); + + // Despite two edits, only one is actually applied as those are identical + cx.assert_editor_state(indoc! {" + struct FooRenamedˇ {} + "}); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(DisplayRow(row as u32), column as u32); point..point diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 940ddce2d0943..e06858eeda798 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -2353,8 +2353,16 @@ impl LocalLspStore { let (mut edits, mut snippet_edits) = (vec![], vec![]); for edit in op.edits { match edit { - Edit::Plain(edit) => edits.push(edit), - Edit::Annotated(edit) => edits.push(edit.text_edit), + Edit::Plain(edit) => { + if !edits.contains(&edit) { + edits.push(edit) + } + } + Edit::Annotated(edit) => { + if !edits.contains(&edit.text_edit) { + edits.push(edit.text_edit) + } + } Edit::Snippet(edit) => { let Ok(snippet) = Snippet::parse(&edit.snippet.value) else { @@ -2365,10 +2373,13 @@ impl LocalLspStore { snippet_edits.push((edit.range, snippet)); } else { // Since this buffer is not focused, apply a normal edit. - edits.push(TextEdit { + let new_edit = TextEdit { range: edit.range, new_text: snippet.text, - }); + }; + if !edits.contains(&new_edit) { + edits.push(new_edit); + } } } }