WIP

Antonio Scandurra created

Change summary

crates/editor2/src/editor.rs                        |  37 ++--
crates/editor2/src/element.rs                       | 135 ++++++++++----
crates/storybook2/src/stories.rs                    |   2 
crates/storybook2/src/stories/auto_height_editor.rs |  34 +++
crates/storybook2/src/story_selector.rs             |   2 
5 files changed, 155 insertions(+), 55 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -1734,21 +1734,11 @@ impl Editor {
     //         Self::new(EditorMode::Full, buffer, None, field_editor_style, cx)
     //     }
 
-    //     pub fn auto_height(
-    //         max_lines: usize,
-    //         field_editor_style: Option<Arc<GetFieldEditorTheme>>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Self {
-    //         let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new()));
-    //         let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
-    //         Self::new(
-    //             EditorMode::AutoHeight { max_lines },
-    //             buffer,
-    //             None,
-    //             field_editor_style,
-    //             cx,
-    //         )
-    //     }
+    pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
+        let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+        let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+        Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
+    }
 
     pub fn for_buffer(
         buffer: Model<Buffer>,
@@ -2908,6 +2898,7 @@ impl Editor {
     }
 
     pub fn newline(&mut self, _: &Newline, cx: &mut ViewContext<Self>) {
+        dbg!("!!!!!!!!!!");
         self.transact(cx, |this, cx| {
             let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
                 let selections = this.selections.all::<usize>(cx);
@@ -8374,6 +8365,18 @@ impl Editor {
         cx.notify();
     }
 
+    pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
+        let rem_size = cx.rem_size();
+        self.display_map.update(cx, |map, cx| {
+            map.set_font(
+                style.text.font(),
+                style.text.font_size.to_pixels(rem_size),
+                cx,
+            )
+        });
+        self.style = Some(style);
+    }
+
     pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
         self.display_map
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
@@ -9397,7 +9400,7 @@ impl Render for Editor {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let settings = ThemeSettings::get_global(cx);
         let text_style = match self.mode {
-            EditorMode::SingleLine => TextStyle {
+            EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
                 color: cx.theme().colors().text,
                 font_family: settings.ui_font.family.clone(),
                 font_features: settings.ui_font.features,
@@ -9410,8 +9413,6 @@ impl Render for Editor {
                 white_space: WhiteSpace::Normal,
             },
 
-            EditorMode::AutoHeight { max_lines } => todo!(),
-
             EditorMode::Full => TextStyle {
                 color: cx.theme().colors().text,
                 font_family: settings.buffer_font.family.clone(),

crates/editor2/src/element.rs 🔗

@@ -20,9 +20,9 @@ use crate::{
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
-    div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
-    BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
-    ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
+    div, point, px, relative, size, transparent_black, Action, AnyElement, AsyncWindowContext,
+    AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
+    ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
     IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
     ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
     StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
@@ -1662,11 +1662,6 @@ impl EditorElement {
         cx: &mut WindowContext,
     ) -> LayoutState {
         self.editor.update(cx, |editor, cx| {
-            // let mut size = constraint.max;
-            // if size.x.is_infinite() {
-            //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-            // }
-
             let snapshot = editor.snapshot(cx);
             let style = self.style.clone();
 
@@ -1702,6 +1697,7 @@ impl EditorElement {
             };
 
             editor.gutter_width = gutter_width;
+
             let text_width = bounds.size.width - gutter_width;
             let overscroll = size(em_width, px(0.));
             let snapshot = {
@@ -1714,6 +1710,8 @@ impl EditorElement {
                     SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
                 };
 
+                dbg!(bounds.size.width, gutter_width, gutter_margin, overscroll.width, em_width, em_advance, wrap_width);
+                println!("setting wrap width during paint: {wrap_width:?}");
                 if editor.set_wrap_width(Some(wrap_width), cx) {
                     editor.snapshot(cx)
                 } else {
@@ -1728,25 +1726,6 @@ impl EditorElement {
                 .collect::<SmallVec<[_; 2]>>();
 
             let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
-            // todo!("this should happen during layout")
-            let editor_mode = snapshot.mode;
-            if let EditorMode::AutoHeight { max_lines } = editor_mode {
-                todo!()
-                //     size.set_y(
-                //         scroll_height
-                //             .min(constraint.max_along(Axis::Vertical))
-                //             .max(constraint.min_along(Axis::Vertical))
-                //             .max(line_height)
-                //             .min(line_height * max_lines as f32),
-                //     )
-            } else if let EditorMode::SingleLine = editor_mode {
-                bounds.size.height = line_height.min(bounds.size.height);
-            }
-            // todo!()
-            // else if size.y.is_infinite() {
-            //     //     size.set_y(scroll_height);
-            // }
-            //
             let gutter_size = size(gutter_width, bounds.size.height);
             let text_size = size(text_width, bounds.size.height);
 
@@ -2064,7 +2043,7 @@ impl EditorElement {
                 .unwrap();
 
             LayoutState {
-                mode: editor_mode,
+                mode: snapshot.mode,
                 position_map: Arc::new(PositionMap {
                     size: bounds.size,
                     scroll_position: point(
@@ -2617,19 +2596,44 @@ impl Element for EditorElement {
         cx: &mut gpui::WindowContext,
     ) -> (gpui::LayoutId, Self::State) {
         self.editor.update(cx, |editor, cx| {
-            editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
+            editor.set_style(self.style.clone(), cx);
 
-            let rem_size = cx.rem_size();
-            let mut style = Style::default();
-            style.size.width = relative(1.).into();
-            style.size.height = match editor.mode {
+            let layout_id = match editor.mode {
                 EditorMode::SingleLine => {
-                    self.style.text.line_height_in_pixels(cx.rem_size()).into()
+                    let rem_size = cx.rem_size();
+                    let mut style = Style::default();
+                    style.size.width = relative(1.).into();
+                    style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
+                    cx.request_layout(&style, None)
+                }
+                EditorMode::AutoHeight { max_lines } => {
+                    let editor_handle = cx.view().clone();
+                    let max_line_number_width =
+                        self.max_line_number_width(&editor.snapshot(cx), cx);
+                    cx.request_measured_layout(
+                        Style::default(),
+                        move |known_dimensions, available_space, cx| {
+                            editor_handle
+                                .update(cx, |editor, cx| {
+                                    dbg!(compute_auto_height_layout(
+                                        editor,
+                                        max_lines,
+                                        max_line_number_width,
+                                        known_dimensions,
+                                        cx,
+                                    ))
+                                })
+                                .unwrap_or_default()
+                        },
+                    )
+                }
+                EditorMode::Full => {
+                    let mut style = Style::default();
+                    style.size.width = relative(1.).into();
+                    style.size.height = relative(1.).into();
+                    cx.request_layout(&style, None)
                 }
-                EditorMode::AutoHeight { .. } => todo!(),
-                EditorMode::Full => relative(1.).into(),
             };
-            let layout_id = cx.request_layout(&style, None);
 
             (layout_id, ())
         })
@@ -4134,3 +4138,60 @@ pub fn register_action<T: Action>(
         }
     })
 }
+
+fn compute_auto_height_layout(
+    editor: &mut Editor,
+    max_lines: usize,
+    max_line_number_width: Pixels,
+    known_dimensions: Size<Option<Pixels>>,
+    cx: &mut ViewContext<Editor>,
+) -> Option<Size<Pixels>> {
+    let mut width = known_dimensions.width?;
+    if let Some(height) = known_dimensions.height {
+        return Some(size(width, height));
+    }
+
+    let style = editor.style.as_ref().unwrap();
+    let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+    let font_size = style.text.font_size.to_pixels(cx.rem_size());
+    let line_height = style.text.line_height_in_pixels(cx.rem_size());
+    let em_width = cx
+        .text_system()
+        .typographic_bounds(font_id, font_size, 'm')
+        .unwrap()
+        .size
+        .width;
+
+    let mut snapshot = editor.snapshot(cx);
+    let gutter_padding;
+    let gutter_width;
+    let gutter_margin;
+    if snapshot.show_gutter {
+        let descent = cx.text_system().descent(font_id, font_size).unwrap();
+        let gutter_padding_factor = 3.5;
+        gutter_padding = (em_width * gutter_padding_factor).round();
+        gutter_width = max_line_number_width + gutter_padding * 2.0;
+        gutter_margin = -descent;
+    } else {
+        gutter_padding = Pixels::ZERO;
+        gutter_width = Pixels::ZERO;
+        gutter_margin = Pixels::ZERO;
+    };
+
+    editor.gutter_width = gutter_width;
+    let text_width = width - gutter_width;
+    let overscroll = size(em_width, px(0.));
+
+    let editor_width = text_width - gutter_margin - overscroll.width - em_width;
+    println!("setting wrap width during layout: {editor_width:?}");
+    if editor.set_wrap_width(Some(editor_width), cx) {
+        snapshot = editor.snapshot(cx);
+    }
+
+    let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
+    let height = scroll_height
+        .max(line_height)
+        .min(line_height * max_lines as f32);
+
+    Some(size(width, height))
+}

crates/storybook2/src/stories.rs 🔗

@@ -1,3 +1,4 @@
+mod auto_height_editor;
 mod focus;
 mod kitchen_sink;
 mod picker;
@@ -5,6 +6,7 @@ mod scroll;
 mod text;
 mod z_index;
 
+pub use auto_height_editor::*;
 pub use focus::*;
 pub use kitchen_sink::*;
 pub use picker::*;

crates/storybook2/src/stories/auto_height_editor.rs 🔗

@@ -0,0 +1,34 @@
+use editor::Editor;
+use gpui::{
+    div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext,
+    WindowContext,
+};
+
+pub struct AutoHeightEditorStory {
+    editor: View<Editor>,
+}
+
+impl AutoHeightEditorStory {
+    pub fn new(cx: &mut WindowContext) -> View<Self> {
+        cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
+        cx.build_view(|cx| Self {
+            editor: cx.build_view(|cx| {
+                let mut editor = Editor::auto_height(3, cx);
+                editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+                editor
+            }),
+        })
+    }
+}
+
+impl Render for AutoHeightEditorStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        div()
+            .size_full()
+            .bg(white())
+            .text_sm()
+            .child(div().w_32().bg(gpui::black()).child(self.editor.clone()))
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -12,6 +12,7 @@ use ui::prelude::*;
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 pub enum ComponentStory {
+    AutoHeightEditor,
     Avatar,
     Button,
     Checkbox,
@@ -33,6 +34,7 @@ pub enum ComponentStory {
 impl ComponentStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
+            Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
             Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(),
             Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
             Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),