agent_ui: Improve message editor history navigation (#35532)

- We no longer move through history if a message has been edited by the
user -
It is possible to navigate back down to an empty message

Co-authored-by: Cole Miller <cole@zed.dev>

Release Notes:

- N/A

Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
Ben Brandt 2025-08-02 23:49:22 +02:00 committed by GitHub
parent f4391ed631
commit 986e3e7cbc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -31,7 +31,7 @@ use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
use parking_lot::Mutex;
use project::Project;
use settings::Settings as _;
use text::Anchor;
use text::{Anchor, BufferSnapshot};
use theme::ThemeSettings;
use ui::{Disclosure, Divider, DividerColor, KeyBinding, Tooltip, prelude::*};
use util::ResultExt;
@ -61,7 +61,7 @@ pub struct AcpThreadView {
thread_state: ThreadState,
diff_editors: HashMap<EntityId, Entity<Editor>>,
message_editor: Entity<Editor>,
message_set_from_history: bool,
message_set_from_history: Option<BufferSnapshot>,
_message_editor_subscription: Subscription,
mention_set: Arc<Mutex<MentionSet>>,
notifications: Vec<WindowHandle<AgentNotification>>,
@ -144,14 +144,28 @@ impl AcpThreadView {
editor
});
let message_editor_subscription = cx.subscribe(&message_editor, |this, _, event, _| {
if let editor::EditorEvent::BufferEdited = &event {
if !this.message_set_from_history {
this.message_history.borrow_mut().reset_position();
let message_editor_subscription =
cx.subscribe(&message_editor, |this, editor, event, cx| {
if let editor::EditorEvent::BufferEdited = &event {
let buffer = editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.unwrap()
.read(cx)
.snapshot();
if let Some(message) = this.message_set_from_history.clone()
&& message.version() != buffer.version()
{
this.message_set_from_history = None;
}
if this.message_set_from_history.is_none() {
this.message_history.borrow_mut().reset_position();
}
}
this.message_set_from_history = false;
}
});
});
let mention_set = mention_set.clone();
@ -178,7 +192,7 @@ impl AcpThreadView {
project: project.clone(),
thread_state: Self::initial_state(agent, workspace, project, window, cx),
message_editor,
message_set_from_history: false,
message_set_from_history: None,
_message_editor_subscription: message_editor_subscription,
mention_set,
notifications: Vec::new(),
@ -424,11 +438,21 @@ impl AcpThreadView {
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.message_set_from_history.is_none() && !self.message_editor.read(cx).is_empty(cx) {
self.message_editor.update(cx, |editor, cx| {
editor.move_up(&Default::default(), window, cx);
});
return;
}
self.message_set_from_history = Self::set_draft_message(
self.message_editor.clone(),
self.mention_set.clone(),
self.project.clone(),
self.message_history.borrow_mut().prev(),
self.message_history
.borrow_mut()
.prev()
.map(|blocks| blocks.as_slice()),
window,
cx,
);
@ -440,14 +464,35 @@ impl AcpThreadView {
window: &mut Window,
cx: &mut Context<Self>,
) {
self.message_set_from_history = Self::set_draft_message(
if self.message_set_from_history.is_none() {
self.message_editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), window, cx);
});
return;
}
let mut message_history = self.message_history.borrow_mut();
let next_history = message_history.next();
let set_draft_message = Self::set_draft_message(
self.message_editor.clone(),
self.mention_set.clone(),
self.project.clone(),
self.message_history.borrow_mut().next(),
Some(
next_history
.map(|blocks| blocks.as_slice())
.unwrap_or_else(|| &[]),
),
window,
cx,
);
// If we reset the text to an empty string because we ran out of history,
// we don't want to mark it as coming from the history
self.message_set_from_history = if next_history.is_some() {
set_draft_message
} else {
None
};
}
fn open_agent_diff(&mut self, _: &OpenAgentDiff, window: &mut Window, cx: &mut Context<Self>) {
@ -481,15 +526,13 @@ impl AcpThreadView {
message_editor: Entity<Editor>,
mention_set: Arc<Mutex<MentionSet>>,
project: Entity<Project>,
message: Option<&Vec<acp::ContentBlock>>,
message: Option<&[acp::ContentBlock]>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
) -> Option<BufferSnapshot> {
cx.notify();
let Some(message) = message else {
return false;
};
let message = message?;
let mut text = String::new();
let mut mentions = Vec::new();
@ -553,7 +596,8 @@ impl AcpThreadView {
}
}
true
let snapshot = snapshot.as_singleton().unwrap().2.clone();
Some(snapshot.text)
}
fn handle_thread_event(