acp: Don't cancel editing when scrolling message out of view (#37020)

Antonio Scandurra and Bennet Bo Fenner created

Release Notes:

- agent: Fixed a bug that canceled editing when scrolling the user
message out of view.

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>

Change summary

crates/agent_ui/src/acp/entry_view_state.rs | 11 +++++++++--
crates/agent_ui/src/acp/thread_view.rs      | 14 +++++++++++---
2 files changed, 20 insertions(+), 5 deletions(-)

Detailed changes

crates/agent_ui/src/acp/entry_view_state.rs 🔗

@@ -6,8 +6,8 @@ use agent2::HistoryStore;
 use collections::HashMap;
 use editor::{Editor, EditorMode, MinimapVisibility};
 use gpui::{
-    AnyEntity, App, AppContext as _, Entity, EntityId, EventEmitter, Focusable, ScrollHandle,
-    TextStyleRefinement, WeakEntity, Window,
+    AnyEntity, App, AppContext as _, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
+    ScrollHandle, TextStyleRefinement, WeakEntity, Window,
 };
 use language::language_settings::SoftWrap;
 use project::Project;
@@ -247,6 +247,13 @@ pub enum Entry {
 }
 
 impl Entry {
+    pub fn focus_handle(&self, cx: &App) -> Option<FocusHandle> {
+        match self {
+            Self::UserMessage(editor) => Some(editor.read(cx).focus_handle(cx)),
+            Self::AssistantMessage(_) | Self::Content(_) => None,
+        }
+    }
+
     pub fn message_editor(&self) -> Option<&Entity<MessageEditor>> {
         match self {
             Self::UserMessage(editor) => Some(editor),

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -479,11 +479,14 @@ impl AcpThreadView {
                             .set(thread.read(cx).prompt_capabilities());
 
                         let count = thread.read(cx).entries().len();
-                        this.list_state.splice(0..0, count);
                         this.entry_view_state.update(cx, |view_state, cx| {
                             for ix in 0..count {
                                 view_state.sync_entry(ix, &thread, window, cx);
                             }
+                            this.list_state.splice_focusable(
+                                0..0,
+                                (0..count).map(|ix| view_state.entry(ix)?.focus_handle(cx)),
+                            );
                         });
 
                         if let Some(resume) = resume_thread {
@@ -1116,9 +1119,14 @@ impl AcpThreadView {
                 let len = thread.read(cx).entries().len();
                 let index = len - 1;
                 self.entry_view_state.update(cx, |view_state, cx| {
-                    view_state.sync_entry(index, thread, window, cx)
+                    view_state.sync_entry(index, thread, window, cx);
+                    self.list_state.splice_focusable(
+                        index..index,
+                        [view_state
+                            .entry(index)
+                            .and_then(|entry| entry.focus_handle(cx))],
+                    );
                 });
-                self.list_state.splice(index..index, 1);
             }
             AcpThreadEvent::EntryUpdated(index) => {
                 self.entry_view_state.update(cx, |view_state, cx| {