From 45aff05192975d4f0b613e12bb16262af9f7eea1 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 21:01:15 -0300 Subject: [PATCH] Fix bad unicode calculations in do_completion (cherry-pick #28259) (#28286) 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 db8772dab81d5c35e12a15e8615d8ea4d65a1f7a..7a73bec2feb9087fca302111ac72a70b459ca899 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4609,14 +4609,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 { @@ -4624,10 +4627,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 { @@ -4635,12 +4635,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 @@ -4666,8 +4661,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 1ffda0b9d9fc654ffcc79b55c906698b53a42a22..d396d0ae4d2aec7d1f16eadeaa9b301c4d821620 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;