agent: Fix long previous user message double scroll (#33056)

Danilo Leal created

Previously, if editing a long previous user message in the thread, you'd
have a double scroll situation because the editor used in that case had
its max number of lines capped. To solve that, I made the `max_lines` in
the editor `AutoHeight` mode optional, allowing me to not pass any
arbitrary number to the previous user message editor, and ultimately,
solving the double scroll problem by not having any scroll at all.

Release Notes:

- agent: Fixed double scroll that happened when editing a long previous
user message.

@ConradIrwin adding you as a reviewer as I'm touching editor code
here... want to be careful. :)

Change summary

crates/agent/src/active_thread.rs        |  5 +--
crates/agent/src/inline_prompt_editor.rs |  4 +-
crates/agent/src/message_editor.rs       |  8 +++---
crates/editor/src/editor.rs              | 27 +++++++++++++++++++++++--
crates/editor/src/element.rs             | 19 ++++++++++++-----
crates/git_ui/src/git_panel.rs           |  2 
crates/repl/src/notebook/cell.rs         |  2 
7 files changed, 47 insertions(+), 20 deletions(-)

Detailed changes

crates/agent/src/active_thread.rs 🔗

@@ -59,7 +59,6 @@ use zed_llm_client::CompletionIntent;
 
 const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
 const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
-const EDIT_PREVIOUS_MESSAGE_MAX_LINES: usize = 6;
 
 pub struct ActiveThread {
     context_store: Entity<ContextStore>,
@@ -1330,7 +1329,7 @@ impl ActiveThread {
             self.thread_store.downgrade(),
             self.text_thread_store.downgrade(),
             EDIT_PREVIOUS_MESSAGE_MIN_LINES,
-            EDIT_PREVIOUS_MESSAGE_MAX_LINES,
+            None,
             window,
             cx,
         );
@@ -1695,7 +1694,7 @@ impl ActiveThread {
             let mut editor = Editor::new(
                 editor::EditorMode::AutoHeight {
                     min_lines: 1,
-                    max_lines: 4,
+                    max_lines: Some(4),
                 },
                 buffer,
                 None,

crates/agent/src/inline_prompt_editor.rs 🔗

@@ -870,7 +870,7 @@ impl PromptEditor<BufferCodegen> {
             let mut editor = Editor::new(
                 EditorMode::AutoHeight {
                     min_lines: 1,
-                    max_lines: Self::MAX_LINES as usize,
+                    max_lines: Some(Self::MAX_LINES as usize),
                 },
                 prompt_buffer,
                 None,
@@ -1049,7 +1049,7 @@ impl PromptEditor<TerminalCodegen> {
             let mut editor = Editor::new(
                 EditorMode::AutoHeight {
                     min_lines: 1,
-                    max_lines: Self::MAX_LINES as usize,
+                    max_lines: Some(Self::MAX_LINES as usize),
                 },
                 prompt_buffer,
                 None,

crates/agent/src/message_editor.rs 🔗

@@ -89,7 +89,7 @@ pub(crate) fn create_editor(
     thread_store: WeakEntity<ThreadStore>,
     text_thread_store: WeakEntity<TextThreadStore>,
     min_lines: usize,
-    max_lines: usize,
+    max_lines: Option<usize>,
     window: &mut Window,
     cx: &mut App,
 ) -> Entity<Editor> {
@@ -107,7 +107,7 @@ pub(crate) fn create_editor(
         let mut editor = Editor::new(
             editor::EditorMode::AutoHeight {
                 min_lines,
-                max_lines,
+                max_lines: max_lines,
             },
             buffer,
             None,
@@ -163,7 +163,7 @@ impl MessageEditor {
             thread_store.clone(),
             text_thread_store.clone(),
             MIN_EDITOR_LINES,
-            MAX_EDITOR_LINES,
+            Some(MAX_EDITOR_LINES),
             window,
             cx,
         );
@@ -261,7 +261,7 @@ impl MessageEditor {
             } else {
                 editor.set_mode(EditorMode::AutoHeight {
                     min_lines: MIN_EDITOR_LINES,
-                    max_lines: MAX_EDITOR_LINES,
+                    max_lines: Some(MAX_EDITOR_LINES),
                 })
             }
         });

crates/editor/src/editor.rs 🔗

@@ -487,7 +487,7 @@ pub enum EditorMode {
     },
     AutoHeight {
         min_lines: usize,
-        max_lines: usize,
+        max_lines: Option<usize>,
     },
     Full {
         /// When set to `true`, the editor will scale its UI elements with the buffer font size.
@@ -1650,7 +1650,28 @@ impl Editor {
         Self::new(
             EditorMode::AutoHeight {
                 min_lines,
-                max_lines,
+                max_lines: Some(max_lines),
+            },
+            buffer,
+            None,
+            window,
+            cx,
+        )
+    }
+
+    /// Creates a new auto-height editor with a minimum number of lines but no maximum.
+    /// The editor grows as tall as needed to fit its content.
+    pub fn auto_height_unbounded(
+        min_lines: usize,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Self {
+        let buffer = cx.new(|cx| Buffer::local("", cx));
+        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
+        Self::new(
+            EditorMode::AutoHeight {
+                min_lines,
+                max_lines: None,
             },
             buffer,
             None,
@@ -22718,7 +22739,7 @@ impl BreakpointPromptEditor {
             let mut prompt = Editor::new(
                 EditorMode::AutoHeight {
                     min_lines: 1,
-                    max_lines: Self::MAX_LINES as usize,
+                    max_lines: Some(Self::MAX_LINES as usize),
                 },
                 buffer,
                 None,

crates/editor/src/element.rs 🔗

@@ -9985,7 +9985,7 @@ pub fn register_action<T: Action>(
 fn compute_auto_height_layout(
     editor: &mut Editor,
     min_lines: usize,
-    max_lines: usize,
+    max_lines: Option<usize>,
     max_line_number_width: Pixels,
     known_dimensions: Size<Option<Pixels>>,
     available_width: AvailableSpace,
@@ -10031,11 +10031,18 @@ fn compute_auto_height_layout(
     }
 
     let scroll_height = (snapshot.max_point().row().next_row().0 as f32) * line_height;
-    let height = scroll_height
-        .max(line_height * min_lines as f32)
-        .min(line_height * max_lines as f32);
 
-    Some(size(width, height))
+    let min_height = line_height * min_lines as f32;
+    let content_height = scroll_height.max(min_height);
+
+    let final_height = if let Some(max_lines) = max_lines {
+        let max_height = line_height * max_lines as f32;
+        content_height.min(max_height)
+    } else {
+        content_height
+    };
+
+    Some(size(width, final_height))
 }
 
 #[cfg(test)]
@@ -10337,7 +10344,7 @@ mod tests {
             EditorMode::SingleLine { auto_width: false },
             EditorMode::AutoHeight {
                 min_lines: 1,
-                max_lines: 100,
+                max_lines: Some(100),
             },
         ] {
             for show_line_numbers in [true, false] {

crates/git_ui/src/git_panel.rs 🔗

@@ -379,7 +379,7 @@ pub(crate) fn commit_message_editor(
     let mut commit_editor = Editor::new(
         EditorMode::AutoHeight {
             min_lines: 1,
-            max_lines,
+            max_lines: Some(max_lines),
         },
         buffer,
         None,

crates/repl/src/notebook/cell.rs 🔗

@@ -179,7 +179,7 @@ impl Cell {
                     let mut editor = Editor::new(
                         EditorMode::AutoHeight {
                             min_lines: 1,
-                            max_lines: 1024,
+                            max_lines: Some(1024),
                         },
                         multi_buffer,
                         None,