Allow editor to be created in auto-height mode

Antonio Scandurra created

Change summary

gpui/src/presenter.rs     |  7 ++++
zed/src/chat_panel.rs     | 16 +++++++--
zed/src/editor.rs         | 66 +++++++++++++++++++++++++++++-----------
zed/src/editor/element.rs | 20 ++++++++---
4 files changed, 81 insertions(+), 28 deletions(-)

Detailed changes

gpui/src/presenter.rs 🔗

@@ -325,6 +325,13 @@ impl SizeConstraint {
             Axis::Vertical => self.max.y(),
         }
     }
+
+    pub fn min_along(&self, axis: Axis) -> f32 {
+        match axis {
+            Axis::Horizontal => self.min.x(),
+            Axis::Vertical => self.min.y(),
+        }
+    }
 }
 
 impl ToJson for SizeConstraint {

zed/src/chat_panel.rs 🔗

@@ -1,14 +1,18 @@
 use crate::{
     channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
+    editor::Editor,
     Settings,
 };
-use gpui::{elements::*, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext};
+use gpui::{
+    elements::*, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, ViewHandle,
+};
 use postage::watch;
 
 pub struct ChatPanel {
     channel_list: ModelHandle<ChannelList>,
     active_channel: Option<(ModelHandle<Channel>, Subscription)>,
     messages: ListState,
+    input_editor: ViewHandle<Editor>,
     settings: watch::Receiver<Settings>,
 }
 
@@ -20,10 +24,12 @@ impl ChatPanel {
         settings: watch::Receiver<Settings>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
+        let input_editor = cx.add_view(|cx| Editor::auto_height(settings.clone(), cx));
         let mut this = Self {
             channel_list,
-            messages: ListState::new(Vec::new()),
             active_channel: None,
+            messages: ListState::new(Vec::new()),
+            input_editor,
             settings,
         };
 
@@ -81,7 +87,7 @@ impl ChatPanel {
     }
 
     fn render_active_channel_messages(&self) -> ElementBox {
-        Expanded::new(0.8, List::new(self.messages.clone()).boxed()).boxed()
+        Expanded::new(1., List::new(self.messages.clone()).boxed()).boxed()
     }
 
     fn render_message(&self, message: &ChannelMessage) -> ElementBox {
@@ -99,7 +105,9 @@ impl ChatPanel {
     }
 
     fn render_input_box(&self) -> ElementBox {
-        Empty::new().boxed()
+        ConstrainedBox::new(ChildView::new(self.input_editor.id()).boxed())
+            .with_max_height(100.)
+            .boxed()
     }
 }
 

zed/src/editor.rs 🔗

@@ -43,7 +43,7 @@ const MAX_LINE_LEN: usize = 1024;
 action!(Cancel);
 action!(Backspace);
 action!(Delete);
-action!(Newline);
+action!(Newline, bool);
 action!(Insert, String);
 action!(DeleteLine);
 action!(DeleteToPreviousWordBoundary);
@@ -102,7 +102,8 @@ pub fn init(cx: &mut MutableAppContext) {
         Binding::new("ctrl-h", Backspace, Some("BufferView")),
         Binding::new("delete", Delete, Some("BufferView")),
         Binding::new("ctrl-d", Delete, Some("BufferView")),
-        Binding::new("enter", Newline, Some("BufferView")),
+        Binding::new("enter", Newline(false), Some("BufferView")),
+        Binding::new("alt-enter", Newline(true), Some("BufferView")),
         Binding::new("tab", Insert("\t".into()), Some("BufferView")),
         Binding::new("ctrl-shift-K", DeleteLine, Some("BufferView")),
         Binding::new(
@@ -268,6 +269,12 @@ pub enum SelectPhase {
     End,
 }
 
+enum EditorMode {
+    SingleLine,
+    AutoHeight,
+    Full,
+}
+
 pub struct Editor {
     handle: WeakViewHandle<Self>,
     buffer: ModelHandle<Buffer>,
@@ -285,12 +292,13 @@ pub struct Editor {
     cursors_visible: bool,
     blink_epoch: usize,
     blinking_paused: bool,
-    single_line: bool,
+    mode: EditorMode,
 }
 
 pub struct Snapshot {
     pub display_snapshot: DisplayMapSnapshot,
     pub gutter_visible: bool,
+    pub auto_height: bool,
     pub theme: Arc<Theme>,
     pub font_family: FamilyId,
     pub font_size: f32,
@@ -313,7 +321,14 @@ impl Editor {
     pub fn single_line(settings: watch::Receiver<Settings>, cx: &mut ViewContext<Self>) -> Self {
         let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
         let mut view = Self::for_buffer(buffer, settings, cx);
-        view.single_line = true;
+        view.mode = EditorMode::SingleLine;
+        view
+    }
+
+    pub fn auto_height(settings: watch::Receiver<Settings>, cx: &mut ViewContext<Self>) -> Self {
+        let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
+        let mut view = Self::for_buffer(buffer, settings, cx);
+        view.mode = EditorMode::AutoHeight;
         view
     }
 
@@ -359,7 +374,7 @@ impl Editor {
             cursors_visible: false,
             blink_epoch: 0,
             blinking_paused: false,
-            single_line: false,
+            mode: EditorMode::Full,
         }
     }
 
@@ -376,7 +391,8 @@ impl Editor {
 
         Snapshot {
             display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
-            gutter_visible: !self.single_line,
+            gutter_visible: matches!(self.mode, EditorMode::Full),
+            auto_height: matches!(self.mode, EditorMode::AutoHeight),
             scroll_position: self.scroll_position,
             scroll_top_anchor: self.scroll_top_anchor.clone(),
             theme: settings.theme.clone(),
@@ -413,10 +429,15 @@ impl Editor {
         line_height: f32,
         cx: &mut ViewContext<Self>,
     ) -> bool {
+        let visible_lines = viewport_height / line_height;
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut scroll_position =
             compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor);
-        let max_scroll_top = display_map.max_point().row().saturating_sub(1) as f32;
+        let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight) {
+            (display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
+        } else {
+            display_map.max_point().row().saturating_sub(1) as f32
+        };
         if scroll_position.y() > max_scroll_top {
             scroll_position.set_y(max_scroll_top);
             self.set_scroll_position(scroll_position, cx);
@@ -428,7 +449,6 @@ impl Editor {
             return false;
         }
 
-        let visible_lines = viewport_height / line_height;
         let first_cursor_top = self
             .selections(cx)
             .first()
@@ -445,9 +465,13 @@ impl Editor {
             .row() as f32
             + 1.0;
 
-        let margin = ((visible_lines - (last_cursor_bottom - first_cursor_top)) / 2.0)
-            .floor()
-            .min(3.0);
+        let margin = if matches!(self.mode, EditorMode::AutoHeight) {
+            0.
+        } else {
+            ((visible_lines - (last_cursor_bottom - first_cursor_top)) / 2.0)
+                .floor()
+                .min(3.0)
+        };
         if margin < 0.0 {
             return false;
         }
@@ -695,11 +719,17 @@ impl Editor {
         self.end_transaction(cx);
     }
 
-    fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
-        if self.single_line {
-            cx.propagate_action();
-        } else {
-            self.insert(&Insert("\n".into()), cx);
+    fn newline(&mut self, Newline(insert_newline): &Newline, cx: &mut ViewContext<Self>) {
+        match self.mode {
+            EditorMode::SingleLine => cx.propagate_action(),
+            EditorMode::AutoHeight => {
+                if *insert_newline {
+                    self.insert(&Insert("\n".into()), cx);
+                } else {
+                    cx.propagate_action();
+                }
+            }
+            EditorMode::Full => self.insert(&Insert("\n".into()), cx),
         }
     }
 
@@ -1276,7 +1306,7 @@ impl Editor {
 
     pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        if self.single_line {
+        if matches!(self.mode, EditorMode::SingleLine) {
             cx.propagate_action();
         } else {
             let mut selections = self.selections(cx.as_ref()).to_vec();
@@ -1317,7 +1347,7 @@ impl Editor {
     }
 
     pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
-        if self.single_line {
+        if matches!(self.mode, EditorMode::SingleLine) {
             cx.propagate_action();
         } else {
             let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));

zed/src/editor/element.rs 🔗

@@ -1,4 +1,4 @@
-use super::{DisplayPoint, Editor, Select, SelectPhase, Snapshot, Insert, Scroll};
+use super::{DisplayPoint, Editor, EditorMode, Insert, Scroll, Select, SelectPhase, Snapshot};
 use crate::time::ReplicaId;
 use gpui::{
     color::Color,
@@ -9,8 +9,8 @@ use gpui::{
     },
     json::{self, ToJson},
     text_layout::{self, TextLayoutCache},
-    AppContext, Border, Element, Event, EventContext, FontCache, LayoutContext, MutableAppContext,
-    PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
+    AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext,
+    MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
 };
 use json::json;
 use smallvec::SmallVec;
@@ -204,7 +204,7 @@ impl EditorElement {
             corner_radius: 0.,
         });
 
-        if !editor.single_line {
+        if let EditorMode::Full = editor.mode {
             let mut active_rows = layout.active_rows.iter().peekable();
             while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
                 let mut end_row = *start_row;
@@ -409,8 +409,16 @@ impl Element for EditorElement {
                 snapshot
             }
         });
-        if size.y().is_infinite() {
-            size.set_y((snapshot.max_point().row() + 1) as f32 * line_height);
+
+        let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
+        if snapshot.auto_height {
+            size.set_y(
+                scroll_height
+                    .min(constraint.max_along(Axis::Vertical))
+                    .max(constraint.min_along(Axis::Vertical)),
+            )
+        } else if size.y().is_infinite() {
+            size.set_y(scroll_height);
         }
         let gutter_size = vec2f(gutter_width, size.y());
         let text_size = vec2f(text_width, size.y());