From c574895d146d8eb2b08f0b38d3fba2d2c5765585 Mon Sep 17 00:00:00 2001 From: "gcp-cherry-pick-bot[bot]" <98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:49:01 -0300 Subject: [PATCH] Fix bad unicode calculations in do_completion (cherry-pick #28259) (#28285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-picked Fix bad unicode calculations in do_completion (#28259) Co-authored-by: João Marcos Release Notes: - Fixed a panic with completions around non-ASCII code --------- Co-authored-by: João Marcos Co-authored-by: Conrad Irwin Co-authored-by: João Marcos --- crates/editor/src/editor.rs | 36 +++++++++++---------- crates/vim/src/normal/repeat.rs | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 79e513c706407eccc7a1e84b33151dcd30202015..77e3b8218d6c511f62007da75c60a201f73010e5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4510,14 +4510,17 @@ impl Editor { let lookahead = old_range .end .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer)); - let mut common_prefix_len = old_text - .bytes() - .zip(new_text.bytes()) - .take_while(|(a, b)| a == b) - .count(); + let mut common_prefix_len = 0; + for (a, b) in old_text.chars().zip(new_text.chars()) { + if a == b { + common_prefix_len += a.len_utf8(); + } else { + break; + } + } let snapshot = self.buffer.read(cx).snapshot(cx); - let mut range_to_replace: Option> = None; + let mut range_to_replace: Option> = None; let mut ranges = Vec::new(); let mut linked_edits = HashMap::<_, Vec<_>>::default(); for selection in &selections { @@ -4525,10 +4528,7 @@ impl Editor { let start = selection.start.saturating_sub(lookbehind); let end = selection.end + lookahead; if selection.id == newest_selection.id { - range_to_replace = Some( - ((start + common_prefix_len) as isize - selection.start as isize) - ..(end as isize - selection.start as isize), - ); + range_to_replace = Some(start + common_prefix_len..end); } ranges.push(start + common_prefix_len..end); } else { @@ -4536,12 +4536,7 @@ impl Editor { ranges.clear(); ranges.extend(selections.iter().map(|s| { if s.id == newest_selection.id { - range_to_replace = Some( - old_range.start.to_offset_utf16(&snapshot).0 as isize - - selection.start as isize - ..old_range.end.to_offset_utf16(&snapshot).0 as isize - - selection.start as isize, - ); + range_to_replace = Some(old_range.clone()); old_range.clone() } else { s.start..s.end @@ -4567,8 +4562,15 @@ impl Editor { } let text = &new_text[common_prefix_len..]; + let utf16_range_to_replace = range_to_replace.map(|range| { + let newest_selection = self.selections.newest::(cx).range(); + let selection_start_utf16 = newest_selection.start.0 as isize; + + range.start.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16 + ..range.end.to_offset_utf16(&snapshot).0 as isize - selection_start_utf16 + }); cx.emit(EditorEvent::InputHandled { - utf16_range_to_replace: range_to_replace, + utf16_range_to_replace, text: text.into(), }); diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index 62066882677a3679ec9a09c8bb3c57d76e4994df..3fa40029c9d28b1f28ad9d1c339f87d49610b1ea 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -482,6 +482,62 @@ mod test { ); } + #[gpui::test] + async fn test_repeat_completion_unicode_bug(cx: &mut gpui::TestAppContext) { + VimTestContext::init(cx); + let cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; + let mut cx = VimTestContext::new_with_lsp(cx, true); + + cx.set_state( + indoc! {" + ĩлˇк + ĩлк + "}, + Mode::Normal, + ); + + let mut request = cx.set_request_handler::( + move |_, params, _| async move { + let position = params.text_document_position.position; + let mut to_the_left = position; + to_the_left.character -= 2; + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { + label: "oops".to_string(), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(to_the_left, position), + new_text: "к!".to_string(), + })), + ..Default::default() + }, + ]))) + }, + ); + cx.simulate_keystrokes("i ."); + request.next().await; + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + cx.simulate_keystrokes("enter escape"); + cx.assert_state( + indoc! {" + ĩкˇ!к + ĩлк + "}, + Mode::Normal, + ); + } + #[gpui::test] async fn test_repeat_visual(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;