Compare commits

..

3 Commits

Author SHA1 Message Date
Joseph T. Lyons
8b51ebd016 zed 0.180.4 2025-04-08 10:45:14 -04:00
gcp-cherry-pick-bot[bot]
c574895d14 Fix bad unicode calculations in do_completion (cherry-pick #28259) (#28285)
Cherry-picked Fix bad unicode calculations in do_completion (#28259)

Co-authored-by: João Marcos <marcospb19@hotmail.com>

Release Notes:

- Fixed a panic with completions around non-ASCII code

---------

Co-authored-by: João Marcos <marcospb19@hotmail.com>

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: João Marcos <marcospb19@hotmail.com>
2025-04-07 20:49:01 -03:00
Ben Kunkle
41f744a32b jsx-tag-auto-close: Remove potential source of bugs and panics (#28119)
Switch to using anchors for storing edited ranges rather than offsets as
they have to be used with multiple buffer snapshots

Release Notes:

- N/A
2025-04-07 12:06:14 -07:00
5 changed files with 86 additions and 25 deletions

2
Cargo.lock generated
View File

@@ -17272,7 +17272,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.180.3"
version = "0.180.4"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -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<Range<isize>> = None;
let mut range_to_replace: Option<Range<usize>> = 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::<OffsetUtf16>(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(),
});

View File

@@ -29,7 +29,7 @@ const ALREADY_CLOSED_PARENT_ELEMENT_WALK_BACK_LIMIT: usize = 2;
pub(crate) fn should_auto_close(
buffer: &BufferSnapshot,
edited_ranges: &[Range<usize>],
edited_ranges: &[Range<Anchor>],
config: &JsxTagAutoCloseConfig,
) -> Option<Vec<JsxTagCompletionState>> {
let mut to_auto_edit = vec![];
@@ -37,6 +37,7 @@ pub(crate) fn should_auto_close(
let text = buffer
.text_for_range(edited_range.clone())
.collect::<String>();
let edited_range = edited_range.to_offset(&buffer);
if !text.ends_with(">") {
continue;
}
@@ -94,7 +95,7 @@ pub(crate) fn should_auto_close(
pub(crate) fn generate_auto_close_edits(
buffer: &BufferSnapshot,
ranges: &[Range<usize>],
ranges: &[Range<Anchor>],
config: &JsxTagAutoCloseConfig,
state: Vec<JsxTagCompletionState>,
) -> Result<Vec<(Range<Anchor>, String)>> {
@@ -381,7 +382,7 @@ pub(crate) fn handle_from(
struct JsxAutoCloseEditContext {
buffer: Entity<language::Buffer>,
config: language::JsxTagAutoCloseConfig,
edits: Vec<Range<usize>>,
edits: Vec<Range<Anchor>>,
}
let mut edit_contexts =
@@ -392,7 +393,10 @@ pub(crate) fn handle_from(
continue;
};
let snapshot = buffer.read(cx).snapshot();
for edit in buffer.read(cx).edits_since(&buffer_version_initial) {
for (edit, range) in buffer
.read(cx)
.anchored_edits_since::<usize>(&buffer_version_initial)
{
let Some(language) = snapshot.language_at(edit.new.end) else {
continue;
};
@@ -414,7 +418,7 @@ pub(crate) fn handle_from(
edits: vec![],
})
.edits
.push(edit.new);
.push(range);
}
}
@@ -448,7 +452,6 @@ pub(crate) fn handle_from(
};
let ensure_no_edits_since_start = || -> Option<()> {
// <div>wef,wefwef
let has_edits_since_start = this
.read_with(cx, |this, cx| {
this.buffer.read_with(cx, |buffer, cx| {

View File

@@ -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::<lsp::request::Completion, _, _>(
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;

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.180.3"
version = "0.180.4"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]