From b1735ea375df91dd1ebfc2b1143463021c2715d7 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:05:30 +0000 Subject: [PATCH] agent_ui: Fix panic when inserting context prefix with multi-byte characters (#48179) (cherry-pick to preview) (#48180) Cherry-pick of #48179 to preview ---- Closes ZED-4R9 Introduced in https://github.com/zed-industries/zed/pull/47768 The `insert_context_prefix` function was using byte offsets to check if the prefix already exists at the cursor. This caused a panic with multi-byte characters like emojis. Now uses character counts instead. Release Notes: - Fixed a crash in the Agent Panel when inserting context mentions with emojis in the message editor. Co-authored-by: Smit Barmase --- crates/agent_ui/src/acp/message_editor.rs | 98 ++++++++++++++++++++--- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index ecaf947963fb589ffb3013898ff9299d89856395..9304b60801e00b09c338b3a948be6bafa36c5b19 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -701,16 +701,12 @@ impl MessageEditor { let snapshot = editor.display_snapshot(cx); let cursor = editor.selections.newest::(&snapshot).head(); let offset = cursor.to_offset(&snapshot); - if offset.0 >= prefix.len() { - let start_offset = MultiBufferOffset(offset.0 - prefix.len()); - let buffer_snapshot = snapshot.buffer_snapshot(); - let text = buffer_snapshot - .text_for_range(start_offset..offset) - .collect::(); - text == prefix - } else { - false - } + let buffer_snapshot = snapshot.buffer_snapshot(); + let prefix_char_count = prefix.chars().count(); + buffer_snapshot + .reversed_chars_at(offset) + .take(prefix_char_count) + .eq(prefix.chars().rev()) }; if menu_is_open && has_prefix { @@ -3111,4 +3107,86 @@ mod tests { }) }); } + + #[gpui::test] + async fn test_insert_context_with_multibyte_characters(cx: &mut TestAppContext) { + init_test(cx); + + let app_state = cx.update(AppState::test); + + cx.update(|cx| { + editor::init(cx); + workspace::init(app_state.clone(), cx); + }); + + app_state + .fs + .as_fake() + .insert_tree(path!("/dir"), json!({})) + .await; + + let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await; + let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let workspace = window.root(cx).unwrap(); + + let mut cx = VisualTestContext::from_window(*window, cx); + + let thread_store = cx.new(|cx| ThreadStore::new(cx)); + let history = cx + .update(|window, cx| cx.new(|cx| crate::acp::AcpThreadHistory::new(None, window, cx))); + + let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| { + let workspace_handle = cx.weak_entity(); + let message_editor = cx.new(|cx| { + MessageEditor::new_with_cache( + workspace_handle, + project.downgrade(), + Some(thread_store), + history.downgrade(), + None, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + "Test Agent".into(), + "Test", + EditorMode::AutoHeight { + max_lines: None, + min_lines: 1, + }, + window, + cx, + ) + }); + workspace.active_pane().update(cx, |pane, cx| { + pane.add_item( + Box::new(cx.new(|_| MessageEditorItem(message_editor.clone()))), + true, + true, + None, + window, + cx, + ); + }); + message_editor.read(cx).focus_handle(cx).focus(window, cx); + let editor = message_editor.read(cx).editor().clone(); + (message_editor, editor) + }); + + editor.update_in(&mut cx, |editor, window, cx| { + editor.set_text("😄😄", window, cx); + }); + + cx.run_until_parked(); + + message_editor.update_in(&mut cx, |message_editor, window, cx| { + message_editor.insert_context_type("file", window, cx); + }); + + cx.run_until_parked(); + + editor.update(&mut cx, |editor, cx| { + assert_eq!(editor.text(cx), "😄😄@file"); + }); + } }