From bc5e58ecc03249d5eb993f3ef007b04fb0fcd909 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:45:35 -0400 Subject: [PATCH] agent_panel: Fix mention pasting crash (#58375) This crash occurred because metion crease were using raw text ranges that were precomputed before calling editor.edit(), and didn't take into account that editor normalizes text inserted The fix is normalizing the text first, then using the normalize text range for the crease Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fix a crash that could occur when adding a crease to the agent panel --- crates/agent_ui/src/message_editor.rs | 40 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index ab084d46a6..0c048dd455 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -37,6 +37,7 @@ use project::{ use rope::Point; use settings::Settings; use std::{cmp::min, fmt::Write, ops::Range, rc::Rc, sync::Arc}; +use text::LineEnding; use theme_settings::ThemeSettings; use ui::{ContextMenu, prelude::*}; use util::paths::PathStyle; @@ -1719,11 +1720,15 @@ impl MessageEditor { let path_style = workspace.read(cx).project().read(cx).path_style(cx); let mut text = String::new(); let mut mentions = Vec::new(); + let append_normalized = |text: &mut String, mut segment: String| { + LineEnding::normalize(&mut segment); + text.push_str(&segment); + }; for chunk in message { match chunk { acp::ContentBlock::Text(text_content) => { - text.push_str(&text_content.text); + append_normalized(&mut text, text_content.text); } acp::ContentBlock::Resource(acp::EmbeddedResource { resource: acp::EmbeddedResourceResource::TextResourceContents(resource), @@ -1734,7 +1739,7 @@ impl MessageEditor { continue; }; let start = text.len(); - write!(&mut text, "{}", mention_uri.as_link()).ok(); + append_normalized(&mut text, mention_uri.as_link().to_string()); let end = text.len(); mentions.push(( start..end, @@ -1750,7 +1755,7 @@ impl MessageEditor { MentionUri::parse(&resource.uri, path_style).log_err() { let start = text.len(); - write!(&mut text, "{}", mention_uri.as_link()).ok(); + append_normalized(&mut text, mention_uri.as_link().to_string()); let end = text.len(); mentions.push((start..end, mention_uri, Mention::Link)); } @@ -1776,7 +1781,7 @@ impl MessageEditor { continue; }; let start = text.len(); - write!(&mut text, "{}", mention_uri.as_link()).ok(); + append_normalized(&mut text, mention_uri.as_link().to_string()); let end = text.len(); mentions.push(( start..end, @@ -5296,6 +5301,33 @@ mod tests { assert!(!message_editor.update(cx, |editor, cx| editor.is_empty(cx))); } + #[gpui::test] + async fn test_set_message_normalizes_crlf_before_mention(cx: &mut TestAppContext) { + init_test(cx); + let (message_editor, cx) = setup_message_editor(cx).await; + + message_editor.update_in(cx, |editor, window, cx| { + editor.set_message( + vec![ + acp::ContentBlock::Text(acp::TextContent::new("before\r\n".to_string())), + acp::ContentBlock::ResourceLink(acp::ResourceLink::new( + "file.txt", + "file:///project/file.txt", + )), + ], + window, + cx, + ); + }); + + let text = message_editor.update(cx, |editor, cx| editor.text(cx)); + assert_eq!(text, "before\n[@file.txt](file:///project/file.txt)"); + + let mention_uris = + message_editor.update(cx, |editor, cx| editor.mention_set.read(cx).mentions()); + assert_eq!(mention_uris.len(), 1); + } + #[gpui::test] async fn test_set_message_replaces_existing_content(cx: &mut TestAppContext) { init_test(cx);