From da82eec4cb00fde584a7a0153bea36193a4fe980 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 6 Oct 2025 10:39:51 +0200 Subject: [PATCH] editor: Fix utf8 boundary panic in `process_completion_for_edit` (#39561) Fixes ZED-1WH Release Notes: - Fixed panic when requesting completions after a multibyte character --- crates/editor/src/editor.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bfa83c62f1008651facef1ba157c9b3e95fd8e7b..7b987a6962ad9b02b2beafa92a744112c8244afc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -185,7 +185,7 @@ use std::{ time::{Duration, Instant}, }; use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables}; -use text::{BufferId, FromAnchor, OffsetUtf16, Rope}; +use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _}; use theme::{ ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings, observe_buffer_font_size_adjustment, @@ -10459,10 +10459,10 @@ impl Editor { } let buffer = display_map.buffer_snapshot(); - let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer); + let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer); let edit_end = if buffer.max_point().row >= rows.end.0 { // If there's a line after the range, delete the \n from the end of the row range - Point::new(rows.end.0, 0).to_offset(buffer) + ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer) } else { // If there isn't a line after the range, delete the \n from the line before the // start of the row range @@ -12263,7 +12263,7 @@ impl Editor { } let start = Point::new(start_row, 0); - let start_offset = start.to_offset(&buffer); + let start_offset = ToOffset::to_offset(&start, &buffer); let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row))); let selection_text = buffer.text_for_range(start..end).collect::(); let mut first_line_delimiter = None; @@ -21930,12 +21930,16 @@ fn process_completion_for_edit( let buffer = buffer.read(cx); let buffer_snapshot = buffer.snapshot(); let (snippet, new_text) = if completion.is_snippet() { + let mut snippet_source = completion.new_text.clone(); // Workaround for typescript language server issues so that methods don't expand within // strings and functions with type expressions. The previous point is used because the query // for function identifier doesn't match when the cursor is immediately after. See PR #30312 - let mut snippet_source = completion.new_text.clone(); - let mut previous_point = text::ToPoint::to_point(cursor_position, buffer); - previous_point.column = previous_point.column.saturating_sub(1); + let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot); + let previous_point = if previous_point.column > 0 { + cursor_position.to_previous_offset(&buffer_snapshot) + } else { + cursor_position.to_offset(&buffer_snapshot) + }; if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) && scope.prefers_label_for_snippet_in_completion() && let Some(label) = completion.label()