Paint editor background

Antonio Scandurra created

Change summary

crates/editor2/src/editor.rs        |   10 
crates/editor2/src/element.rs       | 2048 ++++++++++++++++--------------
crates/gpui2/src/color.rs           |    9 
crates/gpui2/src/geometry.rs        |    9 
crates/theme2/src/colors.rs         |    5 
crates/theme2/src/default_colors.rs |   10 
6 files changed, 1,112 insertions(+), 979 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -36,7 +36,7 @@ pub use element::{
 use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    div, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
+    div, px, AnyElement, AppContext, BackgroundExecutor, Context, Div, Element, EventEmitter,
     FocusHandle, Hsla, Model, Pixels, Render, Styled, Subscription, Task, TextStyle, View,
     ViewContext, VisualContext, WeakView, WindowContext,
 };
@@ -597,12 +597,11 @@ pub enum SoftWrap {
 
 #[derive(Clone)]
 pub struct EditorStyle {
+    pub background: Hsla,
     pub local_player: PlayerColor,
     pub text: TextStyle,
     pub line_height_scalar: f32,
-    // pub placeholder_text: Option<TextStyle>,
-    //  pub theme: theme::Editor,
-    pub theme_id: usize,
+    pub scrollbar_width: Pixels,
 }
 
 type CompletionId = usize;
@@ -9327,10 +9326,11 @@ impl Render for Editor {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         EditorElement::new(EditorStyle {
+            background: cx.theme().colors().editor,
             local_player: cx.theme().players().local(),
             text: cx.text_style(),
             line_height_scalar: 1.,
-            theme_id: 0,
+            scrollbar_width: px(12.),
         })
     }
 }

crates/editor2/src/element.rs 🔗

@@ -8,15 +8,23 @@ use crate::{
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
-    black, point, px, relative, size, AnyElement, Bounds, Element, Hsla, Line, Pixels, Size, Style,
-    TextRun, TextStyle, TextSystem, ViewContext, WindowContext,
+    black, hsla, point, px, relative, size, transparent_black, AnyElement, BorrowWindow, Bounds,
+    ContentMask, Corners, Edges, Element, Hsla, Line, Pixels, Size, Style, TextRun, TextStyle,
+    TextSystem, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
 use multi_buffer::Anchor;
 use settings::Settings;
 use smallvec::SmallVec;
-use std::{borrow::Cow, cmp, fmt::Write, iter, ops::Range, sync::Arc};
+use std::{
+    borrow::Cow,
+    cmp::{self, Ordering},
+    fmt::Write,
+    iter,
+    ops::Range,
+    sync::Arc,
+};
 use sum_tree::Bias;
 use theme::{ActiveTheme, PlayerColor};
 use workspace::item::Item;
@@ -368,7 +376,7 @@ impl EditorElement {
     //         let mut scroll_delta = gpui::Point<Pixels>::zero();
 
     //         let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
-    //         let top = text_bounds.origin_y() + vertical_margin;
+    //         let top = text_bounds.origin.y + vertical_margin;
     //         let bottom = text_bounds.lower_left().y() - vertical_margin;
     //         if position.y() < top {
     //             scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
@@ -378,7 +386,7 @@ impl EditorElement {
     //         }
 
     //         let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
-    //         let left = text_bounds.origin_x() + horizontal_margin;
+    //         let left = text_bounds.origin.x + horizontal_margin;
     //         let right = text_bounds.upper_right().x() - horizontal_margin;
     //         if position.x() < left {
     //             scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
@@ -476,122 +484,129 @@ impl EditorElement {
     //         position_map.snapshot.ongoing_scroll.filter(&mut delta)
     //     } else {
     //         //Not trackpad
-    //         delta *= vec2f(max_glyph_width, line_height);
+    //         delta *= point(max_glyph_width, line_height);
     //         None //Resets ongoing scroll
     //     };
 
     //     let scroll_position = position_map.snapshot.scroll_position();
     //     let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
     //     let y = (scroll_position.y() * line_height - delta.y()) / line_height;
-    //     let scroll_position = vec2f(x, y).clamp(gpui::Point<Pixels>::zero(), position_map.scroll_max);
+    //     let scroll_position = point(x, y).clamp(gpui::Point<Pixels>::zero(), position_map.scroll_max);
     //     editor.scroll(scroll_position, axis, cx);
 
     //     true
     // }
 
-    // fn paint_background(
-    //     &self,
-    //     gutter_bounds: Bounds<Pixels>,
-    //     text_bounds: Bounds<Pixels>,
-    //     layout: &LayoutState,
-    //     cx: &mut ViewContext<Editor>,
-    // ) {
-    //     let bounds = gutter_bounds.union_rect(text_bounds);
-    //     let scroll_top =
-    //         layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
-    //     cx.scene().push_quad(Quad {
-    //         bounds: gutter_bounds,
-    //         background: Some(self.style.gutter_background),
-    //         border: Border::new(0., Color::transparent_black()).into(),
-    //         corner_radii: Default::default(),
-    //     });
-    //     cx.scene().push_quad(Quad {
-    //         bounds: text_bounds,
-    //         background: Some(self.style.background),
-    //         border: Border::new(0., Color::transparent_black()).into(),
-    //         corner_radii: Default::default(),
-    //     });
+    fn paint_background(
+        &self,
+        gutter_bounds: Bounds<Pixels>,
+        text_bounds: Bounds<Pixels>,
+        layout: &LayoutState,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let bounds = gutter_bounds.union(&text_bounds);
+        let scroll_top =
+            layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
+        let gutter_bg = cx.theme().colors().editor_gutter;
+        cx.paint_quad(
+            gutter_bounds,
+            Corners::default(),
+            gutter_bg,
+            Edges::default(),
+            transparent_black(),
+        );
+        cx.paint_quad(
+            text_bounds,
+            Corners::default(),
+            self.style.background,
+            Edges::default(),
+            transparent_black(),
+        );
 
-    //     if let EditorMode::Full = layout.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;
-    //             while active_rows.peek().map_or(false, |r| {
-    //                 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
-    //             }) {
-    //                 active_rows.next().unwrap();
-    //                 end_row += 1;
-    //             }
+        if let EditorMode::Full = layout.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;
+                while active_rows.peek().map_or(false, |r| {
+                    *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
+                }) {
+                    active_rows.next().unwrap();
+                    end_row += 1;
+                }
 
-    //             if !contains_non_empty_selection {
-    //                 let origin = vec2f(
-    //                     bounds.origin_x(),
-    //                     bounds.origin_y() + (layout.position_map.line_height * *start_row as f32)
-    //                         - scroll_top,
-    //                 );
-    //                 let size = vec2f(
-    //                     bounds.width(),
-    //                     layout.position_map.line_height * (end_row - start_row + 1) as f32,
-    //                 );
-    //                 cx.scene().push_quad(Quad {
-    //                     bounds: Bounds<Pixels>::new(origin, size),
-    //                     background: Some(self.style.active_line_background),
-    //                     border: Border::default().into(),
-    //                     corner_radii: Default::default(),
-    //                 });
-    //             }
-    //         }
+                if !contains_non_empty_selection {
+                    let origin = point(
+                        bounds.origin.x,
+                        bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
+                            - scroll_top,
+                    );
+                    let size = size(
+                        bounds.size.width,
+                        layout.position_map.line_height * (end_row - start_row + 1) as f32,
+                    );
+                    let active_line_bg = cx.theme().colors().editor_active_line;
+                    cx.paint_quad(
+                        Bounds { origin, size },
+                        Corners::default(),
+                        active_line_bg,
+                        Edges::default(),
+                        transparent_black(),
+                    );
+                }
+            }
 
-    //         if let Some(highlighted_rows) = &layout.highlighted_rows {
-    //             let origin = vec2f(
-    //                 bounds.origin_x(),
-    //                 bounds.origin_y()
-    //                     + (layout.position_map.line_height * highlighted_rows.start as f32)
-    //                     - scroll_top,
-    //             );
-    //             let size = vec2f(
-    //                 bounds.width(),
-    //                 layout.position_map.line_height * highlighted_rows.len() as f32,
-    //             );
-    //             cx.scene().push_quad(Quad {
-    //                 bounds: Bounds<Pixels>::new(origin, size),
-    //                 background: Some(self.style.highlighted_line_background),
-    //                 border: Border::default().into(),
-    //                 corner_radii: Default::default(),
-    //             });
-    //         }
+            if let Some(highlighted_rows) = &layout.highlighted_rows {
+                let origin = point(
+                    bounds.origin.x,
+                    bounds.origin.y
+                        + (layout.position_map.line_height * highlighted_rows.start as f32)
+                        - scroll_top,
+                );
+                let size = size(
+                    bounds.size.width,
+                    layout.position_map.line_height * highlighted_rows.len() as f32,
+                );
+                let highlighted_line_bg = cx.theme().colors().editor_highlighted_line;
+                cx.paint_quad(
+                    Bounds { origin, size },
+                    Corners::default(),
+                    highlighted_line_bg,
+                    Edges::default(),
+                    transparent_black(),
+                );
+            }
 
-    //         let scroll_left =
-    //             layout.position_map.snapshot.scroll_position().x() * layout.position_map.em_width;
+            let scroll_left =
+                layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
 
-    //         for (wrap_position, active) in layout.wrap_guides.iter() {
-    //             let x =
-    //                 (text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.)
-    //                     - scroll_left;
+            for (wrap_position, active) in layout.wrap_guides.iter() {
+                let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
+                    - scroll_left;
 
-    //             if x < text_bounds.origin_x()
-    //                 || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
-    //             {
-    //                 continue;
-    //             }
+                if x < text_bounds.origin.x
+                    || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
+                {
+                    continue;
+                }
 
-    //             let color = if *active {
-    //                 self.style.active_wrap_guide
-    //             } else {
-    //                 self.style.wrap_guide
-    //             };
-    //             cx.scene().push_quad(Quad {
-    //                 bounds: Bounds<Pixels>::new(
-    //                     vec2f(x, text_bounds.origin_y()),
-    //                     vec2f(1., text_bounds.height()),
-    //                 ),
-    //                 background: Some(color),
-    //                 border: Border::new(0., Color::transparent_black()).into(),
-    //                 corner_radii: Default::default(),
-    //             });
-    //         }
-    //     }
-    // }
+                let color = if *active {
+                    cx.theme().colors().editor_active_wrap_guide
+                } else {
+                    cx.theme().colors().editor_wrap_guide
+                };
+                cx.paint_quad(
+                    Bounds {
+                        origin: point(x, text_bounds.origin.y),
+                        size: size(px(1.), text_bounds.size.height),
+                    },
+                    Corners::default(),
+                    color,
+                    Edges::default(),
+                    transparent_black(),
+                );
+            }
+        }
+    }
 
     // fn paint_gutter(
     //     &mut self,
@@ -618,7 +633,7 @@ impl EditorElement {
     //     for (ix, line) in layout.line_number_layouts.iter().enumerate() {
     //         if let Some(line) = line {
     //             let line_origin = bounds.origin()
-    //                 + vec2f(
+    //                 + point(
     //                     bounds.width() - line.width() - layout.gutter_padding,
     //                     ix as f32 * line_height - (scroll_top % line_height),
     //                 );
@@ -629,11 +644,11 @@ impl EditorElement {
 
     //     for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
     //         if let Some(indicator) = fold_indicator.as_mut() {
-    //             let position = vec2f(
+    //             let position = point(
     //                 bounds.width() - layout.gutter_padding,
     //                 ix as f32 * line_height - (scroll_top % line_height),
     //             );
-    //             let centering_offset = vec2f(
+    //             let centering_offset = point(
     //                 (layout.gutter_padding + layout.gutter_margin - indicator.size().x()) / 2.,
     //                 (line_height - indicator.size().y()) / 2.,
     //             );
@@ -649,7 +664,7 @@ impl EditorElement {
     //         let mut y = *row as f32 * line_height - scroll_top;
     //         x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
     //         y += (line_height - indicator.size().y()) / 2.;
-    //         indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx);
+    //         indicator.paint(bounds.origin() + point(x, y), visible_bounds, editor, cx);
     //     }
     // }
 
@@ -668,11 +683,11 @@ impl EditorElement {
     //                 let end_y = start_y + line_height;
 
     //                 let width = diff_style.removed_width_em * line_height;
-    //                 let highlight_origin = bounds.origin() + vec2f(-width, start_y);
-    //                 let highlight_size = vec2f(width * 2., end_y - start_y);
+    //                 let highlight_origin = bounds.origin() + point(-width, start_y);
+    //                 let highlight_size = point(width * 2., end_y - start_y);
     //                 let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
 
-    //                 cx.scene().push_quad(Quad {
+    //                 cx.paint_quad(Quad {
     //                     bounds: highlight_bounds,
     //                     background: Some(diff_style.modified),
     //                     border: Border::new(0., Color::transparent_black()).into(),
@@ -701,11 +716,11 @@ impl EditorElement {
     //                 let end_y = start_y + line_height;
 
     //                 let width = diff_style.removed_width_em * line_height;
-    //                 let highlight_origin = bounds.origin() + vec2f(-width, start_y);
-    //                 let highlight_size = vec2f(width * 2., end_y - start_y);
+    //                 let highlight_origin = bounds.origin() + point(-width, start_y);
+    //                 let highlight_size = point(width * 2., end_y - start_y);
     //                 let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
 
-    //                 cx.scene().push_quad(Quad {
+    //                 cx.paint_quad(Quad {
     //                     bounds: highlight_bounds,
     //                     background: Some(diff_style.deleted),
     //                     border: Border::new(0., Color::transparent_black()).into(),
@@ -723,11 +738,11 @@ impl EditorElement {
     //         let end_y = end_row as f32 * line_height - scroll_top;
 
     //         let width = diff_style.width_em * line_height;
-    //         let highlight_origin = bounds.origin() + vec2f(-width, start_y);
-    //         let highlight_size = vec2f(width * 2., end_y - start_y);
+    //         let highlight_origin = bounds.origin() + point(-width, start_y);
+    //         let highlight_size = point(width * 2., end_y - start_y);
     //         let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
 
-    //         cx.scene().push_quad(Quad {
+    //         cx.paint_quad(Quad {
     //             bounds: highlight_bounds,
     //             background: Some(color),
     //             border: Border::new(0., Color::transparent_black()).into(),
@@ -750,7 +765,7 @@ impl EditorElement {
     //     let scroll_top = scroll_position.y() * layout.position_map.line_height;
     //     let max_glyph_width = layout.position_map.em_width;
     //     let scroll_left = scroll_position.x() * max_glyph_width;
-    //     let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
+    //     let content_origin = bounds.origin() + point(layout.gutter_margin, 0.);
     //     let line_end_overshoot = 0.15 * layout.position_map.line_height;
     //     let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
 
@@ -899,15 +914,15 @@ impl EditorElement {
     //                     let y = cursor_position.row() as f32 * layout.position_map.line_height
     //                         - scroll_top;
     //                     if selection.is_newest {
-    //                         editor.pixel_position_of_newest_cursor = Some(vec2f(
-    //                             bounds.origin_x() + x + block_width / 2.,
-    //                             bounds.origin_y() + y + layout.position_map.line_height / 2.,
+    //                         editor.pixel_position_of_newest_cursor = Some(point(
+    //                             bounds.origin.x + x + block_width / 2.,
+    //                             bounds.origin.y + y + layout.position_map.line_height / 2.,
     //                         ));
     //                     }
     //                     cursors.push(Cursor {
     //                         color: selection_style.cursor,
     //                         block_width,
-    //                         origin: vec2f(x, y),
+    //                         origin: point(x, y),
     //                         line_height: layout.position_map.line_height,
     //                         shape: selection.cursor_shape,
     //                         block_text,
@@ -947,7 +962,7 @@ impl EditorElement {
     //             &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
     //         let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
     //         let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
-    //         let mut list_origin = content_origin + vec2f(x, y);
+    //         let mut list_origin = content_origin + point(x, y);
     //         let list_width = context_menu.size().x();
     //         let list_height = context_menu.size().y();
 
@@ -963,7 +978,7 @@ impl EditorElement {
 
     //         context_menu.paint(
     //             list_origin,
-    //             Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+    //             Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
     //             editor,
     //             cx,
     //         );
@@ -988,14 +1003,14 @@ impl EditorElement {
     //         // Compute Hovered Point
     //         let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
     //         let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
-    //         let hovered_point = content_origin + vec2f(x, y);
+    //         let hovered_point = content_origin + point(x, y);
 
     //         if hovered_point.y() - height_to_reserve > 0.0 {
     //             // There is enough space above. Render popovers above the hovered point
     //             let mut current_y = hovered_point.y();
     //             for hover_popover in hover_popovers {
     //                 let size = hover_popover.size();
-    //                 let mut popover_origin = vec2f(hovered_point.x(), current_y - size.y());
+    //                 let mut popover_origin = point(hovered_point.x(), current_y - size.y());
 
     //                 let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
     //                 if x_out_of_bounds < 0.0 {
@@ -1004,7 +1019,7 @@ impl EditorElement {
 
     //                 hover_popover.paint(
     //                     popover_origin,
-    //                     Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+    //                     Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
     //                     editor,
     //                     cx,
     //                 );
@@ -1016,7 +1031,7 @@ impl EditorElement {
     //             let mut current_y = hovered_point.y() + layout.position_map.line_height;
     //             for hover_popover in hover_popovers {
     //                 let size = hover_popover.size();
-    //                 let mut popover_origin = vec2f(hovered_point.x(), current_y);
+    //                 let mut popover_origin = point(hovered_point.x(), current_y);
 
     //                 let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
     //                 if x_out_of_bounds < 0.0 {
@@ -1025,7 +1040,7 @@ impl EditorElement {
 
     //                 hover_popover.paint(
     //                     popover_origin,
-    //                     Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+    //                     Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
     //                     editor,
     //                     cx,
     //                 );
@@ -1040,9 +1055,9 @@ impl EditorElement {
     //     cx.scene().pop_layer();
     // }
 
-    // fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> f32 {
-    //     bounds.max_x() - self.style.theme.scrollbar.width
-    // }
+    fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
+        bounds.upper_right().x - self.style.scrollbar_width
+    }
 
     // fn paint_scrollbar(
     //     &mut self,
@@ -1082,11 +1097,11 @@ impl EditorElement {
 
     //     let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
     //     let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
-    //     let track_bounds = Bounds<Pixels>::from_points(vec2f(left, top), vec2f(right, bottom));
-    //     let thumb_bounds = Bounds<Pixels>::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
+    //     let track_bounds = Bounds<Pixels>::from_points(point(left, top), point(right, bottom));
+    //     let thumb_bounds = Bounds<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
 
     //     if layout.show_scrollbars {
-    //         cx.scene().push_quad(Quad {
+    //         cx.paint_quad(Quad {
     //             bounds: track_bounds,
     //             border: style.track.border.into(),
     //             background: style.track.background_color,
@@ -1114,9 +1129,9 @@ impl EditorElement {
     //                 if end_y - start_y < 1. {
     //                     end_y = start_y + 1.;
     //                 }
-    //                 let bounds = Bounds<Pixels>::from_points(vec2f(left, start_y), vec2f(right, end_y));
+    //                 let bounds = Bounds<Pixels>::from_points(point(left, start_y), point(right, end_y));
 
-    //                 cx.scene().push_quad(Quad {
+    //                 cx.paint_quad(Quad {
     //                     bounds,
     //                     background: Some(color),
     //                     border: border.into(),
@@ -1158,7 +1173,7 @@ impl EditorElement {
     //                 if end_y - start_y < 1. {
     //                     end_y = start_y + 1.;
     //                 }
-    //                 let bounds = Bounds<Pixels>::from_points(vec2f(left, start_y), vec2f(right, end_y));
+    //                 let bounds = Bounds<Pixels>::from_points(point(left, start_y), point(right, end_y));
 
     //                 let color = match hunk.status() {
     //                     DiffHunkStatus::Added => diff_style.inserted,
@@ -1176,7 +1191,7 @@ impl EditorElement {
     //                     left: true,
     //                 };
 
-    //                 cx.scene().push_quad(Quad {
+    //                 cx.paint_quad(Quad {
     //                     bounds,
     //                     background: Some(color),
     //                     border: border.into(),
@@ -1185,7 +1200,7 @@ impl EditorElement {
     //             }
     //         }
 
-    //         cx.scene().push_quad(Quad {
+    //         cx.paint_quad(Quad {
     //             bounds: thumb_bounds,
     //             border: style.thumb.border.into(),
     //             background: style.thumb.background_color,
@@ -1316,12 +1331,12 @@ impl EditorElement {
 
     //     for block in &mut layout.blocks {
     //         let mut origin = bounds.origin()
-    //             + vec2f(
+    //             + point(
     //                 0.,
     //                 block.row as f32 * layout.position_map.line_height - scroll_top,
     //             );
     //         if !matches!(block.style, BlockStyle::Sticky) {
-    //             origin += vec2f(-scroll_left, 0.);
+    //             origin += point(-scroll_left, 0.);
     //         }
     //         block.element.paint(origin, visible_bounds, editor, cx);
     //     }
@@ -1561,920 +1576,1005 @@ impl EditorElement {
         }
     }
 
-    // #[allow(clippy::too_many_arguments)]
-    // fn layout_blocks(
-    //     &mut self,
-    //     rows: Range<u32>,
-    //     snapshot: &EditorSnapshot,
-    //     editor_width: f32,
-    //     scroll_width: f32,
-    //     gutter_padding: f32,
-    //     gutter_width: f32,
-    //     em_width: f32,
-    //     text_x: f32,
-    //     line_height: f32,
-    //     style: &EditorStyle,
-    //     line_layouts: &[LineWithInvisibles],
-    //     editor: &mut Editor,
-    //     cx: &mut ViewContext<Editor>,
-    // ) -> (f32, Vec<BlockLayout>) {
-    //     let mut block_id = 0;
-    //     let scroll_x = snapshot.scroll_anchor.offset.x();
-    //     let (fixed_blocks, non_fixed_blocks) = snapshot
-    //         .blocks_in_range(rows.clone())
-    //         .partition::<Vec<_>, _>(|(_, block)| match block {
-    //             TransformBlock::ExcerptHeader { .. } => false,
-    //             TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
-    //         });
-    //     let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
-    //         let mut element = match block {
-    //             TransformBlock::Custom(block) => {
-    //                 let align_to = block
-    //                     .position()
-    //                     .to_point(&snapshot.buffer_snapshot)
-    //                     .to_display_point(snapshot);
-    //                 let anchor_x = text_x
-    //                     + if rows.contains(&align_to.row()) {
-    //                         line_layouts[(align_to.row() - rows.start) as usize]
-    //                             .line
-    //                             .x_for_index(align_to.column() as usize)
-    //                     } else {
-    //                         layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
-    //                             .x_for_index(align_to.column() as usize)
-    //                     };
+    fn compute_layout(
+        &mut self,
+        editor: &mut Editor,
+        cx: &mut ViewContext<'_, Editor>,
+        bounds: Bounds<Pixels>,
+    ) -> LayoutState {
+        // let mut size = constraint.max;
+        // if size.x().is_infinite() {
+        //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
+        // }
 
-    //                 block.render(&mut BlockContext {
-    //                     view_context: cx,
-    //                     anchor_x,
-    //                     gutter_padding,
-    //                     line_height,
-    //                     scroll_x,
-    //                     gutter_width,
-    //                     em_width,
-    //                     block_id,
-    //                 })
-    //             }
-    //             TransformBlock::ExcerptHeader {
-    //                 id,
-    //                 buffer,
-    //                 range,
-    //                 starts_new_buffer,
-    //                 ..
-    //             } => {
-    //                 let tooltip_style = theme::current(cx).tooltip.clone();
-    //                 let include_root = editor
-    //                     .project
-    //                     .as_ref()
-    //                     .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
-    //                     .unwrap_or_default();
-    //                 let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
-    //                     let jump_path = ProjectPath {
-    //                         worktree_id: file.worktree_id(cx),
-    //                         path: file.path.clone(),
-    //                     };
-    //                     let jump_anchor = range
-    //                         .primary
-    //                         .as_ref()
-    //                         .map_or(range.context.start, |primary| primary.start);
-    //                     let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
+        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;
 
-    //                     enum JumpIcon {}
-    //                     MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
-    //                         let style = style.jump_icon.style_for(state);
-    //                         Svg::new("icons/arrow_up_right.svg")
-    //                             .with_color(style.color)
-    //                             .constrained()
-    //                             .with_width(style.icon_width)
-    //                             .aligned()
-    //                             .contained()
-    //                             .with_style(style.container)
-    //                             .constrained()
-    //                             .with_width(style.button_width)
-    //                             .with_height(style.button_width)
-    //                     })
-    //                     .with_cursor_style(CursorStyle::PointingHand)
-    //                     .on_click(MouseButton::Left, move |_, editor, cx| {
-    //                         if let Some(workspace) = editor
-    //                             .workspace
-    //                             .as_ref()
-    //                             .and_then(|(workspace, _)| workspace.upgrade(cx))
-    //                         {
-    //                             workspace.update(cx, |workspace, cx| {
-    //                                 Editor::jump(
-    //                                     workspace,
-    //                                     jump_path.clone(),
-    //                                     jump_position,
-    //                                     jump_anchor,
-    //                                     cx,
-    //                                 );
-    //                             });
-    //                         }
-    //                     })
-    //                     .with_tooltip::<JumpIcon>(
-    //                         (*id).into(),
-    //                         "Jump to Buffer".to_string(),
-    //                         Some(Box::new(crate::OpenExcerpts)),
-    //                         tooltip_style.clone(),
-    //                         cx,
-    //                     )
-    //                     .aligned()
-    //                     .flex_float()
-    //                 });
+        let gutter_padding;
+        let gutter_width;
+        let gutter_margin;
+        if snapshot.show_gutter {
+            let descent = cx.text_system().descent(font_id, font_size).unwrap();
 
-    //                 if *starts_new_buffer {
-    //                     let editor_font_size = style.text.font_size;
-    //                     let style = &style.diagnostic_path_header;
-    //                     let font_size = (style.text_scale_factor * editor_font_size).round();
+            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 path = buffer.resolve_file_path(cx, include_root);
-    //                     let mut filename = None;
-    //                     let mut parent_path = None;
-    //                     // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
-    //                     if let Some(path) = path {
-    //                         filename = path.file_name().map(|f| f.to_string_lossy().to_string());
-    //                         parent_path =
-    //                             path.parent().map(|p| p.to_string_lossy().to_string() + "/");
-    //                     }
+        let text_width = bounds.size.width - gutter_width;
+        let overscroll = size(em_width, px(0.));
+        let snapshot = {
+            editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
 
-    //                     Flex::row()
-    //                         .with_child(
-    //                             Label::new(
-    //                                 filename.unwrap_or_else(|| "untitled".to_string()),
-    //                                 style.filename.text.clone().with_font_size(font_size),
-    //                             )
-    //                             .contained()
-    //                             .with_style(style.filename.container)
-    //                             .aligned(),
-    //                         )
-    //                         .with_children(parent_path.map(|path| {
-    //                             Label::new(path, style.path.text.clone().with_font_size(font_size))
-    //                                 .contained()
-    //                                 .with_style(style.path.container)
-    //                                 .aligned()
-    //                         }))
-    //                         .with_children(jump_icon)
-    //                         .contained()
-    //                         .with_style(style.container)
-    //                         .with_padding_left(gutter_padding)
-    //                         .with_padding_right(gutter_padding)
-    //                         .expanded()
-    //                         .into_any_named("path header block")
-    //                 } else {
-    //                     let text_style = style.text.clone();
-    //                     Flex::row()
-    //                         .with_child(Label::new("⋯", text_style))
-    //                         .with_children(jump_icon)
-    //                         .contained()
-    //                         .with_padding_left(gutter_padding)
-    //                         .with_padding_right(gutter_padding)
-    //                         .expanded()
-    //                         .into_any_named("collapsed context")
-    //                 }
-    //             }
-    //         };
+            let editor_width = text_width - gutter_margin - overscroll.width - 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),
+            };
 
-    //         element.layout(
-    //             SizeConstraint {
-    //                 min: gpui::Point<Pixels>::zero(),
-    //                 max: vec2f(width, block.height() as f32 * line_height),
-    //             },
-    //             editor,
-    //             cx,
-    //         );
-    //         element
-    //     };
+            if editor.set_wrap_width(Some(wrap_width), cx) {
+                editor.snapshot(cx)
+            } else {
+                snapshot
+            }
+        };
 
-    //     let mut fixed_block_max_width = 0f32;
-    //     let mut blocks = Vec::new();
-    //     for (row, block) in fixed_blocks {
-    //         let element = render_block(block, f32::INFINITY, block_id);
-    //         block_id += 1;
-    //         fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
-    //         blocks.push(BlockLayout {
-    //             row,
-    //             element,
-    //             style: BlockStyle::Fixed,
-    //         });
-    //     }
-    //     for (row, block) in non_fixed_blocks {
-    //         let style = match block {
-    //             TransformBlock::Custom(block) => block.style(),
-    //             TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
-    //         };
-    //         let width = match style {
-    //             BlockStyle::Sticky => editor_width,
-    //             BlockStyle::Flex => editor_width
-    //                 .max(fixed_block_max_width)
-    //                 .max(gutter_width + scroll_width),
-    //             BlockStyle::Fixed => unreachable!(),
-    //         };
-    //         let element = render_block(block, width, block_id);
-    //         block_id += 1;
-    //         blocks.push(BlockLayout {
-    //             row,
-    //             element,
-    //             style,
-    //         });
-    //     }
-    //     (
-    //         scroll_width.max(fixed_block_max_width - gutter_width),
-    //         blocks,
-    //     )
-    // }
-}
+        let wrap_guides = editor
+            .wrap_guides(cx)
+            .iter()
+            .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
+            .collect::<SmallVec<[_; 2]>>();
 
-#[derive(Debug)]
-pub struct LineWithInvisibles {
-    pub line: Line,
-    invisibles: Vec<Invisible>,
-}
+        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 {
+            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);
 
-impl LineWithInvisibles {
-    fn from_chunks<'a>(
-        chunks: impl Iterator<Item = HighlightedChunk<'a>>,
-        text_style: &TextStyle,
-        max_line_len: usize,
-        max_line_count: usize,
-        line_number_layouts: &[Option<Line>],
-        editor_mode: EditorMode,
-        cx: &WindowContext,
-    ) -> Vec<Self> {
-        let mut layouts = Vec::with_capacity(max_line_count);
-        let mut line = String::new();
-        let mut invisibles = Vec::new();
-        let mut styles = Vec::new();
-        let mut non_whitespace_added = false;
-        let mut row = 0;
-        let mut line_exceeded_max_len = false;
-        let font_size = text_style.font_size * cx.rem_size();
+        let autoscroll_horizontally =
+            editor.autoscroll_vertically(bounds.size.height, line_height, cx);
+        let mut snapshot = editor.snapshot(cx);
 
-        for highlighted_chunk in chunks.chain([HighlightedChunk {
-            chunk: "\n",
-            style: None,
-            is_tab: false,
-        }]) {
-            for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
-                if ix > 0 {
-                    let layout = cx
-                        .text_system()
-                        .layout_text(&line, font_size, &styles, None);
-                    layouts.push(Self {
-                        line: layout.unwrap().pop().unwrap(),
-                        invisibles: invisibles.drain(..).collect(),
-                    });
+        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();
 
-                    line.clear();
-                    styles.clear();
-                    row += 1;
-                    line_exceeded_max_len = false;
-                    non_whitespace_added = false;
-                    if row == max_line_count {
-                        return layouts;
-                    }
-                }
+        // 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);
 
-                if !line_chunk.is_empty() && !line_exceeded_max_len {
-                    let text_style = if let Some(style) = highlighted_chunk.style {
-                        text_style
-                            .clone()
-                            .highlight(style)
-                            .map(Cow::Owned)
-                            .unwrap_or_else(|_| Cow::Borrowed(text_style))
-                    } else {
-                        Cow::Borrowed(text_style)
-                    };
+        let start_anchor = if start_row == 0 {
+            Anchor::min()
+        } else {
+            snapshot
+                .buffer_snapshot
+                .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
+        };
+        let end_anchor = if end_row > max_row {
+            Anchor::max()
+        } else {
+            snapshot
+                .buffer_snapshot
+                .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
+        };
 
-                    if line.len() + line_chunk.len() > max_line_len {
-                        let mut chunk_len = max_line_len - line.len();
-                        while !line_chunk.is_char_boundary(chunk_len) {
-                            chunk_len -= 1;
-                        }
-                        line_chunk = &line_chunk[..chunk_len];
-                        line_exceeded_max_len = true;
-                    }
+        let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
+        let mut active_rows = BTreeMap::new();
+        let mut fold_ranges = Vec::new();
+        let is_singleton = editor.is_singleton(cx);
 
-                    styles.push(TextRun {
-                        len: line_chunk.len(),
-                        font: text_style.font(),
-                        color: text_style.color,
-                        underline: text_style.underline,
-                    });
+        let highlighted_rows = editor.highlighted_rows();
+        let highlighted_ranges = editor.background_highlights_in_range(
+            start_anchor..end_anchor,
+            &snapshot.display_snapshot,
+            cx.theme().colors(),
+        );
 
-                    if editor_mode == EditorMode::Full {
-                        // Line wrap pads its contents with fake whitespaces,
-                        // avoid printing them
-                        let inside_wrapped_string = line_number_layouts
-                            .get(row)
-                            .and_then(|layout| layout.as_ref())
-                            .is_none();
-                        if highlighted_chunk.is_tab {
-                            if non_whitespace_added || !inside_wrapped_string {
-                                invisibles.push(Invisible::Tab {
-                                    line_start_offset: line.len(),
-                                });
-                            }
-                        } else {
-                            invisibles.extend(
-                                line_chunk
-                                    .chars()
-                                    .enumerate()
-                                    .filter(|(_, line_char)| {
-                                        let is_whitespace = line_char.is_whitespace();
-                                        non_whitespace_added |= !is_whitespace;
-                                        is_whitespace
-                                            && (non_whitespace_added || !inside_wrapped_string)
-                                    })
-                                    .map(|(whitespace_index, _)| Invisible::Whitespace {
-                                        line_offset: line.len() + whitespace_index,
-                                    }),
-                            )
-                        }
-                    }
+        fold_ranges.extend(
+            snapshot
+                .folds_in_range(start_anchor..end_anchor)
+                .map(|anchor| {
+                    let start = anchor.start.to_point(&snapshot.buffer_snapshot);
+                    (
+                        start.row,
+                        start.to_display_point(&snapshot.display_snapshot)
+                            ..anchor.end.to_display_point(&snapshot),
+                    )
+                }),
+        );
 
-                    line.push_str(line_chunk);
-                }
-            }
-        }
+        let mut newest_selection_head = None;
 
-        layouts
-    }
+        if editor.show_local_selections {
+            let mut local_selections: Vec<Selection<Point>> = editor
+                .selections
+                .disjoint_in_range(start_anchor..end_anchor, cx);
+            local_selections.extend(editor.selections.pending(cx));
+            let mut layouts = Vec::new();
+            let newest = editor.selections.newest(cx);
+            for selection in local_selections.drain(..) {
+                let is_empty = selection.start == selection.end;
+                let is_newest = selection == newest;
 
-    fn draw(
-        &self,
-        layout: &LayoutState,
-        row: u32,
-        scroll_top: Pixels,
-        content_origin: gpui::Point<Pixels>,
-        scroll_left: Pixels,
-        visible_text_bounds: Bounds<Pixels>,
-        whitespace_setting: ShowWhitespaceSetting,
-        selection_ranges: &[Range<DisplayPoint>],
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let line_height = layout.position_map.line_height;
-        let line_y = line_height * row as f32 - scroll_top;
-
-        self.line.paint(
-            content_origin + gpui::point(-scroll_left, line_y),
-            line_height,
-            cx,
-        );
+                let layout = SelectionLayout::new(
+                    selection,
+                    editor.selections.line_mode,
+                    editor.cursor_shape,
+                    &snapshot.display_snapshot,
+                    is_newest,
+                    true,
+                );
+                if is_newest {
+                    newest_selection_head = Some(layout.head);
+                }
 
-        self.draw_invisibles(
-            &selection_ranges,
-            layout,
-            content_origin,
-            scroll_left,
-            line_y,
-            row,
-            line_height,
-            whitespace_setting,
-            cx,
-        );
-    }
+                for row in cmp::max(layout.active_rows.start, start_row)
+                    ..=cmp::min(layout.active_rows.end, end_row)
+                {
+                    let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
+                    *contains_non_empty_selection |= !is_empty;
+                }
+                layouts.push(layout);
+            }
 
-    fn draw_invisibles(
-        &self,
-        selection_ranges: &[Range<DisplayPoint>],
-        layout: &LayoutState,
-        content_origin: gpui::Point<Pixels>,
-        scroll_left: Pixels,
-        line_y: Pixels,
-        row: u32,
-        line_height: Pixels,
-        whitespace_setting: ShowWhitespaceSetting,
-        cx: &mut ViewContext<Editor>,
-    ) {
-        let allowed_invisibles_regions = match whitespace_setting {
-            ShowWhitespaceSetting::None => return,
-            ShowWhitespaceSetting::Selection => Some(selection_ranges),
-            ShowWhitespaceSetting::All => None,
-        };
+            selections.push((style.local_player, layouts));
+        }
 
-        for invisible in &self.invisibles {
-            let (&token_offset, invisible_symbol) = match invisible {
-                Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
-                Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
-            };
+        if let Some(collaboration_hub) = &editor.collaboration_hub {
+            // When following someone, render the local selections in their color.
+            if let Some(leader_id) = editor.leader_peer_id {
+                if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
+                    if let Some(participant_index) = collaboration_hub
+                        .user_participant_indices(cx)
+                        .get(&collaborator.user_id)
+                    {
+                        if let Some((local_selection_style, _)) = selections.first_mut() {
+                            *local_selection_style = cx
+                                .theme()
+                                .players()
+                                .color_for_participant(participant_index.0);
+                        }
+                    }
+                }
+            }
 
-            let x_offset = self.line.x_for_index(token_offset);
-            let invisible_offset = (layout.position_map.em_width - invisible_symbol.width())
-                .max(Pixels::from(0.0))
-                / 2.0;
-            let origin =
-                content_origin + gpui::point(-scroll_left + x_offset + invisible_offset, line_y);
+            let mut remote_selections = HashMap::default();
+            for selection in snapshot.remote_selections_in_range(
+                &(start_anchor..end_anchor),
+                collaboration_hub.as_ref(),
+                cx,
+            ) {
+                let selection_style = if let Some(participant_index) = selection.participant_index {
+                    cx.theme()
+                        .players()
+                        .color_for_participant(participant_index.0)
+                } else {
+                    cx.theme().players().absent()
+                };
 
-            if let Some(allowed_regions) = allowed_invisibles_regions {
-                let invisible_point = DisplayPoint::new(row, token_offset as u32);
-                if !allowed_regions
-                    .iter()
-                    .any(|region| region.start <= invisible_point && invisible_point < region.end)
-                {
+                // Don't re-render the leader's selections, since the local selections
+                // match theirs.
+                if Some(selection.peer_id) == editor.leader_peer_id {
                     continue;
                 }
+
+                remote_selections
+                    .entry(selection.replica_id)
+                    .or_insert((selection_style, Vec::new()))
+                    .1
+                    .push(SelectionLayout::new(
+                        selection.selection,
+                        selection.line_mode,
+                        selection.cursor_shape,
+                        &snapshot.display_snapshot,
+                        false,
+                        false,
+                    ));
             }
-            invisible_symbol.paint(origin, line_height, cx);
+
+            selections.extend(remote_selections.into_values());
         }
-    }
-}
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum Invisible {
-    Tab { line_start_offset: usize },
-    Whitespace { line_offset: usize },
-}
+        let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
+        let show_scrollbars = match scrollbar_settings.show {
+            ShowScrollbar::Auto => {
+                // Git
+                (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
+                        ||
+                        // Selections
+                        (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
+                        // Scrollmanager
+                        || editor.scroll_manager.scrollbars_visible()
+            }
+            ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
+            ShowScrollbar::Always => true,
+            ShowScrollbar::Never => false,
+        };
 
-impl Element<Editor> for EditorElement {
-    type ElementState = ();
+        let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
+            .into_iter()
+            .map(|(id, fold)| {
+                todo!("folds!")
+                // let color = self
+                //     .style
+                //     .folds
+                //     .ellipses
+                //     .background
+                //     .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
+                //     .color;
 
-    fn id(&self) -> Option<gpui::ElementId> {
-        None
-    }
+                // (id, fold, color)
+            })
+            .collect();
 
-    fn initialize(
-        &mut self,
-        view_state: &mut Editor,
-        element_state: Option<Self::ElementState>,
-        cx: &mut gpui::ViewContext<Editor>,
-    ) -> Self::ElementState {
-        ()
-    }
+        let head_for_relative = newest_selection_head.unwrap_or_else(|| {
+            let newest = editor.selections.newest::<Point>(cx);
+            SelectionLayout::new(
+                newest,
+                editor.selections.line_mode,
+                editor.cursor_shape,
+                &snapshot.display_snapshot,
+                true,
+                true,
+            )
+            .head
+        });
 
-    fn layout(
-        &mut self,
-        view_state: &mut Editor,
-        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();
-        cx.request_layout(&style, None)
-    }
+        let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
+            start_row..end_row,
+            &active_rows,
+            head_for_relative,
+            is_singleton,
+            &snapshot,
+            cx,
+        );
 
-    fn paint(
-        &mut self,
-        bounds: Bounds<gpui::Pixels>,
-        editor: &mut Editor,
-        element_state: &mut Self::ElementState,
-        cx: &mut gpui::ViewContext<Editor>,
-    ) {
-        // let mut size = constraint.max;
-        // if size.x().is_infinite() {
-        //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-        // }
+        let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
 
-        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 scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
 
-        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 mut max_visible_line_width = Pixels::ZERO;
+        let line_layouts =
+            self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
+        for line_with_invisibles in &line_layouts {
+            if line_with_invisibles.line.width() > max_visible_line_width {
+                max_visible_line_width = line_with_invisibles.line.width();
+            }
+        }
 
-            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 longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
+            .unwrap()
+            .width();
+        let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
+        // todo!("blocks")
+        // let (scroll_width, blocks) = self.layout_blocks(
+        //     start_row..end_row,
+        //     &snapshot,
+        //     size.x(),
+        //     scroll_width,
+        //     gutter_padding,
+        //     gutter_width,
+        //     em_width,
+        //     gutter_width + gutter_margin,
+        //     line_height,
+        //     &style,
+        //     &line_layouts,
+        //     editor,
+        //     cx,
+        // );
 
-        let text_width = bounds.size.width - gutter_width;
-        let overscroll = size(em_width, px(0.));
-        let snapshot = {
-            editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
+        let scroll_max = point(
+            f32::from((scroll_width - text_size.width) / em_width).max(0.0),
+            max_row as f32,
+        );
 
-            let editor_width = text_width - gutter_margin - overscroll.width - 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),
-            };
+        let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
 
-            if editor.set_wrap_width(Some(wrap_width), cx) {
-                editor.snapshot(cx)
-            } else {
-                snapshot
-            }
+        let autoscrolled = if autoscroll_horizontally {
+            editor.autoscroll_horizontally(
+                start_row,
+                text_size.width,
+                scroll_width,
+                em_width,
+                &line_layouts,
+                cx,
+            )
+        } else {
+            false
         };
 
-        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)))
+        if clamped || autoscrolled {
+            snapshot = editor.snapshot(cx);
         }
-        // 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();
+        // todo!("context menu")
+        // let mut context_menu = None;
+        // let mut code_actions_indicator = None;
+        // if let Some(newest_selection_head) = newest_selection_head {
+        //     if (start_row..end_row).contains(&newest_selection_head.row()) {
+        //         if editor.context_menu_visible() {
+        //             context_menu =
+        //                 editor.render_context_menu(newest_selection_head, style.clone(), cx);
+        //         }
 
-        // 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);
+        //         let active = matches!(
+        //             editor.context_menu.read().as_ref(),
+        //             Some(crate::ContextMenu::CodeActions(_))
+        //         );
 
-        let start_anchor = if start_row == 0 {
-            Anchor::min()
-        } else {
-            snapshot
-                .buffer_snapshot
-                .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
-        };
-        let end_anchor = if end_row > max_row {
-            Anchor::max()
-        } else {
-            snapshot
-                .buffer_snapshot
-                .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
-        };
+        //         code_actions_indicator = editor
+        //             .render_code_actions_indicator(&style, active, cx)
+        //             .map(|indicator| (newest_selection_head.row(), indicator));
+        //     }
+        // }
 
-        let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
-        let mut active_rows = BTreeMap::new();
-        let mut fold_ranges = Vec::new();
-        let is_singleton = editor.is_singleton(cx);
+        let visible_rows = start_row..start_row + line_layouts.len() as u32;
+        // todo!("hover")
+        // let mut hover = editor.hover_state.render(
+        //     &snapshot,
+        //     &style,
+        //     visible_rows,
+        //     editor.workspace.as_ref().map(|(w, _)| w.clone()),
+        //     cx,
+        // );
+        // let mode = editor.mode;
 
-        let highlighted_rows = editor.highlighted_rows();
-        let highlighted_ranges = editor.background_highlights_in_range(
-            start_anchor..end_anchor,
-            &snapshot.display_snapshot,
-            cx.theme().colors(),
-        );
+        // todo!("fold_indicators")
+        // let mut fold_indicators = editor.render_fold_indicators(
+        //     fold_statuses,
+        //     &style,
+        //     editor.gutter_hovered,
+        //     line_height,
+        //     gutter_margin,
+        //     cx,
+        // );
 
-        fold_ranges.extend(
-            snapshot
-                .folds_in_range(start_anchor..end_anchor)
-                .map(|anchor| {
-                    let start = anchor.start.to_point(&snapshot.buffer_snapshot);
-                    (
-                        start.row,
-                        start.to_display_point(&snapshot.display_snapshot)
-                            ..anchor.end.to_display_point(&snapshot),
-                    )
-                }),
-        );
+        // todo!("context_menu")
+        // if let Some((_, context_menu)) = context_menu.as_mut() {
+        //     context_menu.layout(
+        //         SizeConstraint {
+        //             min: gpui::Point<Pixels>::zero(),
+        //             max: point(
+        //                 cx.window_size().x() * 0.7,
+        //                 (12. * line_height).min((size.y() - line_height) / 2.),
+        //             ),
+        //         },
+        //         editor,
+        //         cx,
+        //     );
+        // }
 
-        let mut newest_selection_head = None;
+        // todo!("code actions")
+        // if let Some((_, indicator)) = code_actions_indicator.as_mut() {
+        //     indicator.layout(
+        //         SizeConstraint::strict_along(
+        //             Axis::Vertical,
+        //             line_height * style.code_actions.vertical_scale,
+        //         ),
+        //         editor,
+        //         cx,
+        //     );
+        // }
 
-        if editor.show_local_selections {
-            let mut local_selections: Vec<Selection<Point>> = editor
-                .selections
-                .disjoint_in_range(start_anchor..end_anchor, cx);
-            local_selections.extend(editor.selections.pending(cx));
-            let mut layouts = Vec::new();
-            let newest = editor.selections.newest(cx);
-            for selection in local_selections.drain(..) {
-                let is_empty = selection.start == selection.end;
-                let is_newest = selection == newest;
+        // todo!("fold indicators")
+        // for fold_indicator in fold_indicators.iter_mut() {
+        //     if let Some(indicator) = fold_indicator.as_mut() {
+        //         indicator.layout(
+        //             SizeConstraint::strict_along(
+        //                 Axis::Vertical,
+        //                 line_height * style.code_actions.vertical_scale,
+        //             ),
+        //             editor,
+        //             cx,
+        //         );
+        //     }
+        // }
 
-                let layout = SelectionLayout::new(
-                    selection,
-                    editor.selections.line_mode,
-                    editor.cursor_shape,
-                    &snapshot.display_snapshot,
-                    is_newest,
-                    true,
-                );
-                if is_newest {
-                    newest_selection_head = Some(layout.head);
-                }
+        // todo!("hover popovers")
+        // if let Some((_, hover_popovers)) = hover.as_mut() {
+        //     for hover_popover in hover_popovers.iter_mut() {
+        //         hover_popover.layout(
+        //             SizeConstraint {
+        //                 min: gpui::Point<Pixels>::zero(),
+        //                 max: point(
+        //                     (120. * em_width) // Default size
+        //                         .min(size.x() / 2.) // Shrink to half of the editor width
+        //                         .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
+        //                     (16. * line_height) // Default size
+        //                         .min(size.y() / 2.) // Shrink to half of the editor height
+        //                         .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
+        //                 ),
+        //             },
+        //             editor,
+        //             cx,
+        //         );
+        //     }
+        // }
 
-                for row in cmp::max(layout.active_rows.start, start_row)
-                    ..=cmp::min(layout.active_rows.end, end_row)
-                {
-                    let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
-                    *contains_non_empty_selection |= !is_empty;
+        let invisible_symbol_font_size = font_size / 2.;
+        let tab_invisible = cx
+            .text_system()
+            .layout_text(
+                "→",
+                invisible_symbol_font_size,
+                &[TextRun {
+                    len: "→".len(),
+                    font: self.style.text.font(),
+                    color: cx.theme().colors().editor_invisible,
+                    underline: None,
+                }],
+                None,
+            )
+            .unwrap()
+            .pop()
+            .unwrap();
+        let space_invisible = cx
+            .text_system()
+            .layout_text(
+                "•",
+                invisible_symbol_font_size,
+                &[TextRun {
+                    len: "•".len(),
+                    font: self.style.text.font(),
+                    color: cx.theme().colors().editor_invisible,
+                    underline: None,
+                }],
+                None,
+            )
+            .unwrap()
+            .pop()
+            .unwrap();
+
+        LayoutState {
+            mode: editor_mode,
+            position_map: Arc::new(PositionMap {
+                size: bounds.size,
+                scroll_max,
+                line_layouts,
+                line_height,
+                em_width,
+                em_advance,
+                snapshot,
+            }),
+            visible_display_row_range: start_row..end_row,
+            wrap_guides,
+            gutter_size,
+            gutter_padding,
+            text_size,
+            scrollbar_row_range,
+            show_scrollbars,
+            is_singleton,
+            max_row,
+            gutter_margin,
+            active_rows,
+            highlighted_rows,
+            highlighted_ranges,
+            fold_ranges,
+            line_number_layouts,
+            display_hunks,
+            // blocks,
+            selections,
+            // context_menu,
+            // code_actions_indicator,
+            // fold_indicators,
+            tab_invisible,
+            space_invisible,
+            // hover_popovers: hover,
+        }
+    }
+
+    // #[allow(clippy::too_many_arguments)]
+    // fn layout_blocks(
+    //     &mut self,
+    //     rows: Range<u32>,
+    //     snapshot: &EditorSnapshot,
+    //     editor_width: f32,
+    //     scroll_width: f32,
+    //     gutter_padding: f32,
+    //     gutter_width: f32,
+    //     em_width: f32,
+    //     text_x: f32,
+    //     line_height: f32,
+    //     style: &EditorStyle,
+    //     line_layouts: &[LineWithInvisibles],
+    //     editor: &mut Editor,
+    //     cx: &mut ViewContext<Editor>,
+    // ) -> (f32, Vec<BlockLayout>) {
+    //     let mut block_id = 0;
+    //     let scroll_x = snapshot.scroll_anchor.offset.x();
+    //     let (fixed_blocks, non_fixed_blocks) = snapshot
+    //         .blocks_in_range(rows.clone())
+    //         .partition::<Vec<_>, _>(|(_, block)| match block {
+    //             TransformBlock::ExcerptHeader { .. } => false,
+    //             TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
+    //         });
+    //     let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
+    //         let mut element = match block {
+    //             TransformBlock::Custom(block) => {
+    //                 let align_to = block
+    //                     .position()
+    //                     .to_point(&snapshot.buffer_snapshot)
+    //                     .to_display_point(snapshot);
+    //                 let anchor_x = text_x
+    //                     + if rows.contains(&align_to.row()) {
+    //                         line_layouts[(align_to.row() - rows.start) as usize]
+    //                             .line
+    //                             .x_for_index(align_to.column() as usize)
+    //                     } else {
+    //                         layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
+    //                             .x_for_index(align_to.column() as usize)
+    //                     };
+
+    //                 block.render(&mut BlockContext {
+    //                     view_context: cx,
+    //                     anchor_x,
+    //                     gutter_padding,
+    //                     line_height,
+    //                     scroll_x,
+    //                     gutter_width,
+    //                     em_width,
+    //                     block_id,
+    //                 })
+    //             }
+    //             TransformBlock::ExcerptHeader {
+    //                 id,
+    //                 buffer,
+    //                 range,
+    //                 starts_new_buffer,
+    //                 ..
+    //             } => {
+    //                 let tooltip_style = theme::current(cx).tooltip.clone();
+    //                 let include_root = editor
+    //                     .project
+    //                     .as_ref()
+    //                     .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
+    //                     .unwrap_or_default();
+    //                 let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
+    //                     let jump_path = ProjectPath {
+    //                         worktree_id: file.worktree_id(cx),
+    //                         path: file.path.clone(),
+    //                     };
+    //                     let jump_anchor = range
+    //                         .primary
+    //                         .as_ref()
+    //                         .map_or(range.context.start, |primary| primary.start);
+    //                     let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
+
+    //                     enum JumpIcon {}
+    //                     MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
+    //                         let style = style.jump_icon.style_for(state);
+    //                         Svg::new("icons/arrow_up_right.svg")
+    //                             .with_color(style.color)
+    //                             .constrained()
+    //                             .with_width(style.icon_width)
+    //                             .aligned()
+    //                             .contained()
+    //                             .with_style(style.container)
+    //                             .constrained()
+    //                             .with_width(style.button_width)
+    //                             .with_height(style.button_width)
+    //                     })
+    //                     .with_cursor_style(CursorStyle::PointingHand)
+    //                     .on_click(MouseButton::Left, move |_, editor, cx| {
+    //                         if let Some(workspace) = editor
+    //                             .workspace
+    //                             .as_ref()
+    //                             .and_then(|(workspace, _)| workspace.upgrade(cx))
+    //                         {
+    //                             workspace.update(cx, |workspace, cx| {
+    //                                 Editor::jump(
+    //                                     workspace,
+    //                                     jump_path.clone(),
+    //                                     jump_position,
+    //                                     jump_anchor,
+    //                                     cx,
+    //                                 );
+    //                             });
+    //                         }
+    //                     })
+    //                     .with_tooltip::<JumpIcon>(
+    //                         (*id).into(),
+    //                         "Jump to Buffer".to_string(),
+    //                         Some(Box::new(crate::OpenExcerpts)),
+    //                         tooltip_style.clone(),
+    //                         cx,
+    //                     )
+    //                     .aligned()
+    //                     .flex_float()
+    //                 });
+
+    //                 if *starts_new_buffer {
+    //                     let editor_font_size = style.text.font_size;
+    //                     let style = &style.diagnostic_path_header;
+    //                     let font_size = (style.text_scale_factor * editor_font_size).round();
+
+    //                     let path = buffer.resolve_file_path(cx, include_root);
+    //                     let mut filename = None;
+    //                     let mut parent_path = None;
+    //                     // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
+    //                     if let Some(path) = path {
+    //                         filename = path.file_name().map(|f| f.to_string_lossy().to_string());
+    //                         parent_path =
+    //                             path.parent().map(|p| p.to_string_lossy().to_string() + "/");
+    //                     }
+
+    //                     Flex::row()
+    //                         .with_child(
+    //                             Label::new(
+    //                                 filename.unwrap_or_else(|| "untitled".to_string()),
+    //                                 style.filename.text.clone().with_font_size(font_size),
+    //                             )
+    //                             .contained()
+    //                             .with_style(style.filename.container)
+    //                             .aligned(),
+    //                         )
+    //                         .with_children(parent_path.map(|path| {
+    //                             Label::new(path, style.path.text.clone().with_font_size(font_size))
+    //                                 .contained()
+    //                                 .with_style(style.path.container)
+    //                                 .aligned()
+    //                         }))
+    //                         .with_children(jump_icon)
+    //                         .contained()
+    //                         .with_style(style.container)
+    //                         .with_padding_left(gutter_padding)
+    //                         .with_padding_right(gutter_padding)
+    //                         .expanded()
+    //                         .into_any_named("path header block")
+    //                 } else {
+    //                     let text_style = style.text.clone();
+    //                     Flex::row()
+    //                         .with_child(Label::new("⋯", text_style))
+    //                         .with_children(jump_icon)
+    //                         .contained()
+    //                         .with_padding_left(gutter_padding)
+    //                         .with_padding_right(gutter_padding)
+    //                         .expanded()
+    //                         .into_any_named("collapsed context")
+    //                 }
+    //             }
+    //         };
+
+    //         element.layout(
+    //             SizeConstraint {
+    //                 min: gpui::Point<Pixels>::zero(),
+    //                 max: point(width, block.height() as f32 * line_height),
+    //             },
+    //             editor,
+    //             cx,
+    //         );
+    //         element
+    //     };
+
+    //     let mut fixed_block_max_width = 0f32;
+    //     let mut blocks = Vec::new();
+    //     for (row, block) in fixed_blocks {
+    //         let element = render_block(block, f32::INFINITY, block_id);
+    //         block_id += 1;
+    //         fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
+    //         blocks.push(BlockLayout {
+    //             row,
+    //             element,
+    //             style: BlockStyle::Fixed,
+    //         });
+    //     }
+    //     for (row, block) in non_fixed_blocks {
+    //         let style = match block {
+    //             TransformBlock::Custom(block) => block.style(),
+    //             TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
+    //         };
+    //         let width = match style {
+    //             BlockStyle::Sticky => editor_width,
+    //             BlockStyle::Flex => editor_width
+    //                 .max(fixed_block_max_width)
+    //                 .max(gutter_width + scroll_width),
+    //             BlockStyle::Fixed => unreachable!(),
+    //         };
+    //         let element = render_block(block, width, block_id);
+    //         block_id += 1;
+    //         blocks.push(BlockLayout {
+    //             row,
+    //             element,
+    //             style,
+    //         });
+    //     }
+    //     (
+    //         scroll_width.max(fixed_block_max_width - gutter_width),
+    //         blocks,
+    //     )
+    // }
+}
+
+#[derive(Debug)]
+pub struct LineWithInvisibles {
+    pub line: Line,
+    invisibles: Vec<Invisible>,
+}
+
+impl LineWithInvisibles {
+    fn from_chunks<'a>(
+        chunks: impl Iterator<Item = HighlightedChunk<'a>>,
+        text_style: &TextStyle,
+        max_line_len: usize,
+        max_line_count: usize,
+        line_number_layouts: &[Option<Line>],
+        editor_mode: EditorMode,
+        cx: &WindowContext,
+    ) -> Vec<Self> {
+        let mut layouts = Vec::with_capacity(max_line_count);
+        let mut line = String::new();
+        let mut invisibles = Vec::new();
+        let mut styles = Vec::new();
+        let mut non_whitespace_added = false;
+        let mut row = 0;
+        let mut line_exceeded_max_len = false;
+        let font_size = text_style.font_size * cx.rem_size();
+
+        for highlighted_chunk in chunks.chain([HighlightedChunk {
+            chunk: "\n",
+            style: None,
+            is_tab: false,
+        }]) {
+            for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
+                if ix > 0 {
+                    let layout = cx
+                        .text_system()
+                        .layout_text(&line, font_size, &styles, None);
+                    layouts.push(Self {
+                        line: layout.unwrap().pop().unwrap(),
+                        invisibles: invisibles.drain(..).collect(),
+                    });
+
+                    line.clear();
+                    styles.clear();
+                    row += 1;
+                    line_exceeded_max_len = false;
+                    non_whitespace_added = false;
+                    if row == max_line_count {
+                        return layouts;
+                    }
                 }
-                layouts.push(layout);
-            }
 
-            selections.push((style.local_player, layouts));
-        }
+                if !line_chunk.is_empty() && !line_exceeded_max_len {
+                    let text_style = if let Some(style) = highlighted_chunk.style {
+                        text_style
+                            .clone()
+                            .highlight(style)
+                            .map(Cow::Owned)
+                            .unwrap_or_else(|_| Cow::Borrowed(text_style))
+                    } else {
+                        Cow::Borrowed(text_style)
+                    };
 
-        if let Some(collaboration_hub) = &editor.collaboration_hub {
-            // When following someone, render the local selections in their color.
-            if let Some(leader_id) = editor.leader_peer_id {
-                if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
-                    if let Some(participant_index) = collaboration_hub
-                        .user_participant_indices(cx)
-                        .get(&collaborator.user_id)
-                    {
-                        if let Some((local_selection_style, _)) = selections.first_mut() {
-                            *local_selection_style = cx
-                                .theme()
-                                .players()
-                                .color_for_participant(participant_index.0);
+                    if line.len() + line_chunk.len() > max_line_len {
+                        let mut chunk_len = max_line_len - line.len();
+                        while !line_chunk.is_char_boundary(chunk_len) {
+                            chunk_len -= 1;
                         }
+                        line_chunk = &line_chunk[..chunk_len];
+                        line_exceeded_max_len = true;
                     }
-                }
-            }
 
-            let mut remote_selections = HashMap::default();
-            for selection in snapshot.remote_selections_in_range(
-                &(start_anchor..end_anchor),
-                collaboration_hub.as_ref(),
-                cx,
-            ) {
-                let selection_style = if let Some(participant_index) = selection.participant_index {
-                    cx.theme()
-                        .players()
-                        .color_for_participant(participant_index.0)
-                } else {
-                    cx.theme().players().absent()
-                };
+                    styles.push(TextRun {
+                        len: line_chunk.len(),
+                        font: text_style.font(),
+                        color: text_style.color,
+                        underline: text_style.underline,
+                    });
 
-                // Don't re-render the leader's selections, since the local selections
-                // match theirs.
-                if Some(selection.peer_id) == editor.leader_peer_id {
-                    continue;
-                }
+                    if editor_mode == EditorMode::Full {
+                        // Line wrap pads its contents with fake whitespaces,
+                        // avoid printing them
+                        let inside_wrapped_string = line_number_layouts
+                            .get(row)
+                            .and_then(|layout| layout.as_ref())
+                            .is_none();
+                        if highlighted_chunk.is_tab {
+                            if non_whitespace_added || !inside_wrapped_string {
+                                invisibles.push(Invisible::Tab {
+                                    line_start_offset: line.len(),
+                                });
+                            }
+                        } else {
+                            invisibles.extend(
+                                line_chunk
+                                    .chars()
+                                    .enumerate()
+                                    .filter(|(_, line_char)| {
+                                        let is_whitespace = line_char.is_whitespace();
+                                        non_whitespace_added |= !is_whitespace;
+                                        is_whitespace
+                                            && (non_whitespace_added || !inside_wrapped_string)
+                                    })
+                                    .map(|(whitespace_index, _)| Invisible::Whitespace {
+                                        line_offset: line.len() + whitespace_index,
+                                    }),
+                            )
+                        }
+                    }
 
-                remote_selections
-                    .entry(selection.replica_id)
-                    .or_insert((selection_style, Vec::new()))
-                    .1
-                    .push(SelectionLayout::new(
-                        selection.selection,
-                        selection.line_mode,
-                        selection.cursor_shape,
-                        &snapshot.display_snapshot,
-                        false,
-                        false,
-                    ));
+                    line.push_str(line_chunk);
+                }
             }
-
-            selections.extend(remote_selections.into_values());
         }
 
-        let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
-        let show_scrollbars = match scrollbar_settings.show {
-            ShowScrollbar::Auto => {
-                // Git
-                (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
-                        ||
-                        // Selections
-                        (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
-                        // Scrollmanager
-                        || editor.scroll_manager.scrollbars_visible()
-            }
-            ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
-            ShowScrollbar::Always => true,
-            ShowScrollbar::Never => false,
-        };
-
-        let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
-            .into_iter()
-            .map(|(id, fold)| {
-                todo!("folds!")
-                // let color = self
-                //     .style
-                //     .folds
-                //     .ellipses
-                //     .background
-                //     .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
-                //     .color;
-
-                // (id, fold, color)
-            })
-            .collect();
+        layouts
+    }
 
-        let head_for_relative = newest_selection_head.unwrap_or_else(|| {
-            let newest = editor.selections.newest::<Point>(cx);
-            SelectionLayout::new(
-                newest,
-                editor.selections.line_mode,
-                editor.cursor_shape,
-                &snapshot.display_snapshot,
-                true,
-                true,
-            )
-            .head
-        });
+    fn draw(
+        &self,
+        layout: &LayoutState,
+        row: u32,
+        scroll_top: Pixels,
+        content_origin: gpui::Point<Pixels>,
+        scroll_left: Pixels,
+        visible_text_bounds: Bounds<Pixels>,
+        whitespace_setting: ShowWhitespaceSetting,
+        selection_ranges: &[Range<DisplayPoint>],
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let line_height = layout.position_map.line_height;
+        let line_y = line_height * row as f32 - scroll_top;
 
-        let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
-            start_row..end_row,
-            &active_rows,
-            head_for_relative,
-            is_singleton,
-            &snapshot,
+        self.line.paint(
+            content_origin + gpui::point(-scroll_left, line_y),
+            line_height,
             cx,
         );
 
-        let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
-
-        let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
-
-        let mut max_visible_line_width = Pixels::ZERO;
-        let line_layouts =
-            self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
-        for line_with_invisibles in &line_layouts {
-            if line_with_invisibles.line.width() > max_visible_line_width {
-                max_visible_line_width = line_with_invisibles.line.width();
-            }
-        }
-
-        let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
-            .unwrap()
-            .width();
-        let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
-        // todo!("blocks")
-        // let (scroll_width, blocks) = self.layout_blocks(
-        //     start_row..end_row,
-        //     &snapshot,
-        //     size.x(),
-        //     scroll_width,
-        //     gutter_padding,
-        //     gutter_width,
-        //     em_width,
-        //     gutter_width + gutter_margin,
-        //     line_height,
-        //     &style,
-        //     &line_layouts,
-        //     editor,
-        //     cx,
-        // );
-
-        let scroll_max = point(
-            f32::from((scroll_width - text_size.width) / em_width).max(0.0),
-            max_row as f32,
+        self.draw_invisibles(
+            &selection_ranges,
+            layout,
+            content_origin,
+            scroll_left,
+            line_y,
+            row,
+            line_height,
+            whitespace_setting,
+            cx,
         );
+    }
 
-        let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
-
-        let autoscrolled = if autoscroll_horizontally {
-            editor.autoscroll_horizontally(
-                start_row,
-                text_size.width,
-                scroll_width,
-                em_width,
-                &line_layouts,
-                cx,
-            )
-        } else {
-            false
+    fn draw_invisibles(
+        &self,
+        selection_ranges: &[Range<DisplayPoint>],
+        layout: &LayoutState,
+        content_origin: gpui::Point<Pixels>,
+        scroll_left: Pixels,
+        line_y: Pixels,
+        row: u32,
+        line_height: Pixels,
+        whitespace_setting: ShowWhitespaceSetting,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let allowed_invisibles_regions = match whitespace_setting {
+            ShowWhitespaceSetting::None => return,
+            ShowWhitespaceSetting::Selection => Some(selection_ranges),
+            ShowWhitespaceSetting::All => None,
         };
 
-        if clamped || autoscrolled {
-            snapshot = editor.snapshot(cx);
-        }
-
-        // todo!("context menu")
-        // let mut context_menu = None;
-        // let mut code_actions_indicator = None;
-        // if let Some(newest_selection_head) = newest_selection_head {
-        //     if (start_row..end_row).contains(&newest_selection_head.row()) {
-        //         if editor.context_menu_visible() {
-        //             context_menu =
-        //                 editor.render_context_menu(newest_selection_head, style.clone(), cx);
-        //         }
+        for invisible in &self.invisibles {
+            let (&token_offset, invisible_symbol) = match invisible {
+                Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
+                Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
+            };
 
-        //         let active = matches!(
-        //             editor.context_menu.read().as_ref(),
-        //             Some(crate::ContextMenu::CodeActions(_))
-        //         );
+            let x_offset = self.line.x_for_index(token_offset);
+            let invisible_offset = (layout.position_map.em_width - invisible_symbol.width())
+                .max(Pixels::from(0.0))
+                / 2.0;
+            let origin =
+                content_origin + gpui::point(-scroll_left + x_offset + invisible_offset, line_y);
 
-        //         code_actions_indicator = editor
-        //             .render_code_actions_indicator(&style, active, cx)
-        //             .map(|indicator| (newest_selection_head.row(), indicator));
-        //     }
-        // }
+            if let Some(allowed_regions) = allowed_invisibles_regions {
+                let invisible_point = DisplayPoint::new(row, token_offset as u32);
+                if !allowed_regions
+                    .iter()
+                    .any(|region| region.start <= invisible_point && invisible_point < region.end)
+                {
+                    continue;
+                }
+            }
+            invisible_symbol.paint(origin, line_height, cx);
+        }
+    }
+}
 
-        let visible_rows = start_row..start_row + line_layouts.len() as u32;
-        // todo!("hover")
-        // let mut hover = editor.hover_state.render(
-        //     &snapshot,
-        //     &style,
-        //     visible_rows,
-        //     editor.workspace.as_ref().map(|(w, _)| w.clone()),
-        //     cx,
-        // );
-        // let mode = editor.mode;
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum Invisible {
+    Tab { line_start_offset: usize },
+    Whitespace { line_offset: usize },
+}
 
-        // todo!("fold_indicators")
-        // let mut fold_indicators = editor.render_fold_indicators(
-        //     fold_statuses,
-        //     &style,
-        //     editor.gutter_hovered,
-        //     line_height,
-        //     gutter_margin,
-        //     cx,
-        // );
+impl Element<Editor> for EditorElement {
+    type ElementState = ();
 
-        // todo!("context_menu")
-        // if let Some((_, context_menu)) = context_menu.as_mut() {
-        //     context_menu.layout(
-        //         SizeConstraint {
-        //             min: gpui::Point<Pixels>::zero(),
-        //             max: vec2f(
-        //                 cx.window_size().x() * 0.7,
-        //                 (12. * line_height).min((size.y() - line_height) / 2.),
-        //             ),
-        //         },
-        //         editor,
-        //         cx,
-        //     );
-        // }
+    fn id(&self) -> Option<gpui::ElementId> {
+        None
+    }
 
-        // todo!("code actions")
-        // if let Some((_, indicator)) = code_actions_indicator.as_mut() {
-        //     indicator.layout(
-        //         SizeConstraint::strict_along(
-        //             Axis::Vertical,
-        //             line_height * style.code_actions.vertical_scale,
-        //         ),
-        //         editor,
-        //         cx,
-        //     );
-        // }
+    fn initialize(
+        &mut self,
+        view_state: &mut Editor,
+        element_state: Option<Self::ElementState>,
+        cx: &mut gpui::ViewContext<Editor>,
+    ) -> Self::ElementState {
+        ()
+    }
 
-        // todo!("fold indicators")
-        // for fold_indicator in fold_indicators.iter_mut() {
-        //     if let Some(indicator) = fold_indicator.as_mut() {
-        //         indicator.layout(
-        //             SizeConstraint::strict_along(
-        //                 Axis::Vertical,
-        //                 line_height * style.code_actions.vertical_scale,
-        //             ),
-        //             editor,
-        //             cx,
-        //         );
-        //     }
-        // }
+    fn layout(
+        &mut self,
+        view_state: &mut Editor,
+        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();
+        cx.request_layout(&style, None)
+    }
 
-        // todo!("hover popovers")
-        // if let Some((_, hover_popovers)) = hover.as_mut() {
-        //     for hover_popover in hover_popovers.iter_mut() {
-        //         hover_popover.layout(
-        //             SizeConstraint {
-        //                 min: gpui::Point<Pixels>::zero(),
-        //                 max: vec2f(
-        //                     (120. * em_width) // Default size
-        //                         .min(size.x() / 2.) // Shrink to half of the editor width
-        //                         .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
-        //                     (16. * line_height) // Default size
-        //                         .min(size.y() / 2.) // Shrink to half of the editor height
-        //                         .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
-        //                 ),
-        //             },
-        //             editor,
-        //             cx,
-        //         );
-        //     }
-        // }
+    fn paint(
+        &mut self,
+        bounds: Bounds<gpui::Pixels>,
+        editor: &mut Editor,
+        element_state: &mut Self::ElementState,
+        cx: &mut gpui::ViewContext<Editor>,
+    ) {
+        let layout = self.compute_layout(editor, cx, bounds);
+        cx.with_content_mask(ContentMask { bounds }, |cx| {
+            let gutter_bounds = Bounds {
+                origin: bounds.origin,
+                size: layout.gutter_size,
+            };
+            let text_bounds = Bounds {
+                origin: gutter_bounds.upper_right(),
+                size: layout.text_size,
+            };
 
-        // let invisible_symbol_font_size = self.style.text.font_size / 2.0;
-        // let invisible_symbol_style = RunStyle {
-        //     color: self.style.whitespace,
-        //     font_id: self.style.text.font_id,
-        //     underline: Default::default(),
-        // };
-        //
+            self.paint_background(gutter_bounds, text_bounds, &layout, cx);
+        });
     }
 }
 

crates/gpui2/src/color.rs 🔗

@@ -176,6 +176,15 @@ pub fn black() -> Hsla {
     }
 }
 
+pub fn transparent_black() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 0.,
+        a: 0.,
+    }
+}
+
 pub fn white() -> Hsla {
     Hsla {
         h: 0.,

crates/gpui2/src/geometry.rs 🔗

@@ -492,6 +492,15 @@ where
 impl<T: Clone + Default + Debug + Copy> Copy for Edges<T> {}
 
 impl<T: Clone + Default + Debug> Edges<T> {
+    pub fn all(value: T) -> Self {
+        Self {
+            top: value.clone(),
+            right: value.clone(),
+            bottom: value.clone(),
+            left: value,
+        }
+    }
+
     pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Edges<U>
     where
         U: Clone + Default + Debug,

crates/theme2/src/colors.rs 🔗

@@ -103,10 +103,15 @@ pub struct ThemeColors {
     pub tab_inactive: Hsla,
     pub tab_active: Hsla,
     pub editor: Hsla,
+    pub editor_gutter: Hsla,
     pub editor_subheader: Hsla,
     pub editor_active_line: Hsla,
+    pub editor_highlighted_line: Hsla,
     pub editor_line_number: Hsla,
     pub editor_active_line_number: Hsla,
+    pub editor_invisible: Hsla,
+    pub editor_wrap_guide: Hsla,
+    pub editor_active_wrap_guide: Hsla,
 }
 
 #[derive(Refineable, Clone)]

crates/theme2/src/default_colors.rs 🔗

@@ -238,10 +238,15 @@ impl ThemeColors {
             tab_active: neutral().light().step_1(),
             tab_inactive: neutral().light().step_2(),
             editor: neutral().light().step_1(),
+            editor_gutter: neutral().light().step_1(), // todo!("pick the right colors")
             editor_subheader: neutral().light().step_2(),
             editor_active_line: neutral().light_alpha().step_3(),
             editor_line_number: neutral().light_alpha().step_3(), // todo!("pick the right colors")
             editor_active_line_number: neutral().light_alpha().step_3(), // todo!("pick the right colors")
+            editor_highlighted_line: neutral().light_alpha().step_4(), // todo!("pick the right colors")
+            editor_invisible: neutral().light_alpha().step_4(), // todo!("pick the right colors")
+            editor_wrap_guide: neutral().light_alpha().step_4(), // todo!("pick the right colors")
+            editor_active_wrap_guide: neutral().light_alpha().step_4(), // todo!("pick the right colors")
         }
     }
 
@@ -285,10 +290,15 @@ impl ThemeColors {
             tab_active: neutral().dark().step_1(),
             tab_inactive: neutral().dark().step_2(),
             editor: neutral().dark().step_1(),
+            editor_gutter: neutral().dark().step_1(), // todo!("pick the right colors")
             editor_subheader: neutral().dark().step_2(),
             editor_active_line: neutral().dark_alpha().step_3(),
             editor_line_number: neutral().dark_alpha().step_3(), // todo!("pick the right colors")
             editor_active_line_number: neutral().dark_alpha().step_3(), // todo!("pick the right colors")
+            editor_highlighted_line: neutral().dark_alpha().step_4(), // todo!("pick the right colors")
+            editor_invisible: neutral().dark_alpha().step_4(), // todo!("pick the right colors")
+            editor_wrap_guide: neutral().dark_alpha().step_4(), // todo!("pick the right colors")
+            editor_active_wrap_guide: neutral().dark_alpha().step_4(), // todo!("pick the right colors")
         }
     }
 }