WIP: start on editor element

Antonio Scandurra created

Change summary

crates/editor2/src/editor.rs            |  92 +++++++------
crates/editor2/src/element.rs           | 181 +++++++++++++++++++++-----
crates/editor2/src/scroll.rs            |  28 ++--
crates/editor2/src/scroll/autoscroll.rs |   6 
crates/gpui2/src/geometry.rs            |   6 
5 files changed, 217 insertions(+), 96 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -37,8 +37,8 @@ use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
-    FocusHandle, Hsla, Model, Pixels, Render, Subscription, Task, TextStyle, View, ViewContext,
-    VisualContext, WeakView, WindowContext,
+    FocusHandle, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -68,6 +68,7 @@ use scroll::{
 use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
+use smallvec::SmallVec;
 use std::{
     any::TypeId,
     borrow::Cow,
@@ -8347,51 +8348,51 @@ impl Editor {
     //             .text()
     //     }
 
-    //     pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
-    //         let mut wrap_guides = smallvec::smallvec![];
+    pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
+        let mut wrap_guides = smallvec::smallvec![];
 
-    //         if self.show_wrap_guides == Some(false) {
-    //             return wrap_guides;
-    //         }
+        if self.show_wrap_guides == Some(false) {
+            return wrap_guides;
+        }
 
-    //         let settings = self.buffer.read(cx).settings_at(0, cx);
-    //         if settings.show_wrap_guides {
-    //             if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
-    //                 wrap_guides.push((soft_wrap as usize, true));
-    //             }
-    //             wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
-    //         }
+        let settings = self.buffer.read(cx).settings_at(0, cx);
+        if settings.show_wrap_guides {
+            if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
+                wrap_guides.push((soft_wrap as usize, true));
+            }
+            wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
+        }
 
-    //         wrap_guides
-    //     }
+        wrap_guides
+    }
 
-    //     pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
-    //         let settings = self.buffer.read(cx).settings_at(0, cx);
-    //         let mode = self
-    //             .soft_wrap_mode_override
-    //             .unwrap_or_else(|| settings.soft_wrap);
-    //         match mode {
-    //             language_settings::SoftWrap::None => SoftWrap::None,
-    //             language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
-    //             language_settings::SoftWrap::PreferredLineLength => {
-    //                 SoftWrap::Column(settings.preferred_line_length)
-    //             }
-    //         }
-    //     }
+    pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
+        let settings = self.buffer.read(cx).settings_at(0, cx);
+        let mode = self
+            .soft_wrap_mode_override
+            .unwrap_or_else(|| settings.soft_wrap);
+        match mode {
+            language_settings::SoftWrap::None => SoftWrap::None,
+            language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
+            language_settings::SoftWrap::PreferredLineLength => {
+                SoftWrap::Column(settings.preferred_line_length)
+            }
+        }
+    }
 
-    //     pub fn set_soft_wrap_mode(
-    //         &mut self,
-    //         mode: language_settings::SoftWrap,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.soft_wrap_mode_override = Some(mode);
-    //         cx.notify();
-    //     }
+    pub fn set_soft_wrap_mode(
+        &mut self,
+        mode: language_settings::SoftWrap,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.soft_wrap_mode_override = Some(mode);
+        cx.notify();
+    }
 
-    //     pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut AppContext) -> bool {
-    //         self.display_map
-    //             .update(cx, |map, cx| map.set_wrap_width(width, cx))
-    //     }
+    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))
+    }
 
     //     pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, cx: &mut ViewContext<Self>) {
     //         if self.soft_wrap_mode_override.is_some() {
@@ -9321,11 +9322,14 @@ impl EventEmitter for Editor {
 }
 
 impl Render for Editor {
-    type Element = Div<Self>;
+    type Element = EditorElement;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        // todo!()
-        div()
+        EditorElement::new(EditorStyle {
+            text: cx.text_style(),
+            line_height_scalar: 1.,
+            theme_id: 0,
+        })
     }
 }
 

crates/editor2/src/element.rs 🔗

@@ -3,17 +3,18 @@ use super::{
 };
 use crate::{
     display_map::{BlockStyle, DisplaySnapshot},
-    EditorStyle,
+    EditorMode, EditorStyle, SoftWrap,
 };
 use anyhow::Result;
 use gpui::{
-    black, px, relative, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style, TextRun,
-    TextSystem,
+    black, point, px, relative, size, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style,
+    TextRun, TextSystem, ViewContext,
 };
 use language::{CursorShape, Selection};
 use smallvec::SmallVec;
-use std::{ops::Range, sync::Arc};
+use std::{cmp, ops::Range, sync::Arc};
 use sum_tree::Bias;
+use theme::ActiveTheme;
 
 enum FoldMarkers {}
 
@@ -1321,29 +1322,31 @@ impl EditorElement {
     //     }
     // }
 
-    // fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> f32 {
-    //     let style = &self.style;
-
-    //     cx.text_layout_cache()
-    //         .layout_str(
-    //             " ".repeat(column).as_str(),
-    //             style.text.font_size,
-    //             &[(
-    //                 column,
-    //                 RunStyle {
-    //                     font_id: style.text.font_id,
-    //                     color: Color::black(),
-    //                     underline: Default::default(),
-    //                 },
-    //             )],
-    //         )
-    //         .width()
-    // }
+    fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
+        let style = &self.style;
+        let font_size = style.text.font_size * cx.rem_size();
+        let layout = cx
+            .text_system()
+            .layout_text(
+                " ".repeat(column).as_str(),
+                font_size,
+                &[TextRun {
+                    len: column,
+                    font: style.text.font(),
+                    color: Hsla::default(),
+                    underline: None,
+                }],
+                None,
+            )
+            .unwrap();
+
+        layout[0].width
+    }
 
-    // fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
-    //     let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
-    //     self.column_pixels(digit_count, cx)
-    // }
+    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {
+        let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
+        self.column_pixels(digit_count, cx)
+    }
 
     //Folds contained in a hunk are ignored apart from shrinking visual size
     //If a fold contains any hunks then that fold line is marked as modified
@@ -2002,6 +2005,7 @@ impl Element<Editor> for EditorElement {
         element_state: &mut Self::ElementState,
         cx: &mut gpui::ViewContext<Editor>,
     ) -> gpui::LayoutId {
+        let rem_size = cx.rem_size();
         let mut style = Style::default();
         style.size.width = relative(1.).into();
         style.size.height = relative(1.).into();
@@ -2011,18 +2015,125 @@ impl Element<Editor> for EditorElement {
     fn paint(
         &mut self,
         bounds: Bounds<gpui::Pixels>,
-        view_state: &mut Editor,
+        editor: &mut Editor,
         element_state: &mut Self::ElementState,
         cx: &mut gpui::ViewContext<Editor>,
     ) {
-        let text_style = cx.text_style();
-
-        let layout_text = cx.text_system().layout_text(
-            "hello world",
-            text_style.font_size * cx.rem_size(),
-            &[text_style.to_run("hello world".len())],
-            None,
-        );
+        // 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();
+        let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+        let font_size = style.text.font_size * cx.rem_size();
+        let line_height = (font_size * style.line_height_scalar).round();
+        let em_width = cx
+            .text_system()
+            .typographic_bounds(font_id, font_size, 'm')
+            .unwrap()
+            .size
+            .width;
+        let em_advance = cx
+            .text_system()
+            .advance(font_id, font_size, 'm')
+            .unwrap()
+            .width;
+
+        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 = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
+            gutter_margin = -descent;
+        } else {
+            gutter_padding = px(0.0);
+            gutter_width = px(0.0);
+            gutter_margin = px(0.0);
+        };
+
+        let text_width = bounds.size.width - gutter_width;
+        let overscroll = point(em_width, px(0.));
+        let snapshot = {
+            editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
+
+            let editor_width = text_width - gutter_margin - overscroll.x - em_width;
+            let wrap_width = match editor.soft_wrap_mode(cx) {
+                SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
+                SoftWrap::EditorWidth => editor_width,
+                SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
+            };
+
+            if editor.set_wrap_width(Some(wrap_width), cx) {
+                editor.snapshot(cx)
+            } else {
+                snapshot
+            }
+        };
+
+        let wrap_guides = editor
+            .wrap_guides(cx)
+            .iter()
+            .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
+            .collect::<SmallVec<[_; 2]>>();
+
+        let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
+        // todo!("this should happen during layout")
+        if let EditorMode::AutoHeight { max_lines } = snapshot.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 = snapshot.mode {
+            todo!()
+            //     size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
+        }
+        // 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);
+
+        let autoscroll_horizontally =
+            editor.autoscroll_vertically(bounds.size.height, line_height, cx);
+        let mut snapshot = editor.snapshot(cx);
+
+        let scroll_position = snapshot.scroll_position();
+        // The scroll position is a fractional point, the whole number of which represents
+        // the top of the window in terms of display rows.
+        let start_row = scroll_position.y as u32;
+        let height_in_lines = f32::from(bounds.size.height / line_height);
+        let max_row = snapshot.max_point().row();
+
+        // Add 1 to ensure selections bleed off screen
+        let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
+
+        dbg!(start_row..end_row);
+        // let text_style = cx.text_style();
+        // let layout_text = cx.text_system().layout_text(
+        //     "hello world",
+        //     text_style.font_size * cx.rem_size(),
+        //     &[text_style.to_run("hello world".len())],
+        //     None,
+        // );
+        // let line_height = text_style
+        //     .line_height
+        //     .to_pixels(text_style.font_size.into(), cx.rem_size());
+
+        // layout_text.unwrap()[0]
+        //     .paint(bounds.origin, line_height, cx)
+        //     .unwrap();
     }
 }
 

crates/editor2/src/scroll.rs 🔗

@@ -303,20 +303,20 @@ impl Editor {
         self.scroll_manager.visible_line_count
     }
 
-    //     pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
-    //         let opened_first_time = self.scroll_manager.visible_line_count.is_none();
-    //         self.scroll_manager.visible_line_count = Some(lines);
-    //         if opened_first_time {
-    //             cx.spawn(|editor, mut cx| async move {
-    //                 editor
-    //                     .update(&mut cx, |editor, cx| {
-    //                         editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
-    //                     })
-    //                     .ok()
-    //             })
-    //             .detach()
-    //         }
-    //     }
+    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
+        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
+        self.scroll_manager.visible_line_count = Some(lines);
+        if opened_first_time {
+            cx.spawn(|editor, mut cx| async move {
+                editor
+                    .update(&mut cx, |editor, cx| {
+                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
+                    })
+                    .ok()
+            })
+            .detach()
+        }
+    }
 
     pub fn set_scroll_position(
         &mut self,

crates/editor2/src/scroll/autoscroll.rs 🔗

@@ -48,11 +48,11 @@ impl AutoscrollStrategy {
 impl Editor {
     pub fn autoscroll_vertically(
         &mut self,
-        viewport_height: f32,
-        line_height: f32,
+        viewport_height: Pixels,
+        line_height: Pixels,
         cx: &mut ViewContext<Editor>,
     ) -> bool {
-        let visible_lines = viewport_height / line_height;
+        let visible_lines = f32::from(viewport_height / line_height);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
         let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {

crates/gpui2/src/geometry.rs 🔗

@@ -825,6 +825,12 @@ impl From<Pixels> for u32 {
     }
 }
 
+impl From<u32> for Pixels {
+    fn from(pixels: u32) -> Self {
+        Pixels(pixels as f32)
+    }
+}
+
 impl From<Pixels> for usize {
     fn from(pixels: Pixels) -> Self {
         pixels.0 as usize