Start rendering highlighted text and line numbers via the wrap map

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

zed/src/editor.rs                      |  10 +
zed/src/editor/display_map.rs          |  15 ++-
zed/src/editor/display_map/fold_map.rs |  14 +-
zed/src/editor/display_map/tab_map.rs  |  18 ++-
zed/src/editor/display_map/wrap_map.rs | 128 ++++++++++++++++++++++++++-
zed/src/editor/element.rs              |   2 
6 files changed, 161 insertions(+), 26 deletions(-)

Detailed changes

zed/src/editor.rs πŸ”—

@@ -21,7 +21,7 @@ use gpui::{
     WeakViewHandle,
 };
 use parking_lot::Mutex;
-use postage::watch;
+use postage::{prelude::Stream, watch};
 use serde::{Deserialize, Serialize};
 use smallvec::SmallVec;
 use smol::Timer;
@@ -413,6 +413,14 @@ impl Editor {
         let display_map =
             DisplayMap::new(buffer.clone(), settings.borrow().clone(), None, cx.as_ref());
 
+        let mut notifications = display_map.notifications();
+        cx.spawn(|this, mut cx| async move {
+            while notifications.recv().await.is_some() {
+                this.update(&mut cx, |_, cx| cx.notify());
+            }
+        })
+        .detach();
+
         let mut next_selection_id = 0;
         let selection_set_id = buffer.update(cx, |buffer, cx| {
             buffer.add_selection_set(

zed/src/editor/display_map.rs πŸ”—

@@ -4,10 +4,11 @@ mod wrap_map;
 
 use super::{buffer, Anchor, Bias, Buffer, Point, Settings, ToOffset, ToPoint};
 use fold_map::FoldMap;
-pub use fold_map::InputRows;
 use gpui::{AppContext, ModelHandle};
+use postage::prelude::Stream;
 use std::ops::Range;
 use tab_map::TabMap;
+pub use wrap_map::BufferRows;
 use wrap_map::WrapMap;
 
 pub struct DisplayMap {
@@ -76,6 +77,10 @@ impl DisplayMap {
     pub fn set_wrap_width(&self, width: Option<f32>) {
         self.wrap_map.set_wrap_width(width);
     }
+
+    pub fn notifications(&self) -> impl Stream<Item = ()> {
+        self.wrap_map.notifications()
+    }
 }
 
 pub struct DisplayMapSnapshot {
@@ -86,8 +91,8 @@ pub struct DisplayMapSnapshot {
 }
 
 impl DisplayMapSnapshot {
-    pub fn buffer_rows(&self, start_row: u32) -> InputRows {
-        self.folds_snapshot.input_rows(start_row)
+    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
+        self.wraps_snapshot.buffer_rows(start_row)
     }
 
     pub fn max_point(&self) -> DisplayPoint {
@@ -98,8 +103,8 @@ impl DisplayMapSnapshot {
         self.wraps_snapshot.chunks_at(point.0)
     }
 
-    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> tab_map::HighlightedChunks {
-        self.tabs_snapshot.highlighted_chunks_for_rows(rows)
+    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> wrap_map::HighlightedChunks {
+        self.wraps_snapshot.highlighted_chunks_for_rows(rows)
     }
 
     pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {

zed/src/editor/display_map/fold_map.rs πŸ”—

@@ -491,7 +491,7 @@ impl Snapshot {
         (line_end - line_start) as u32
     }
 
-    pub fn input_rows(&self, start_row: u32) -> InputRows {
+    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
         if start_row > self.transforms.summary().output.lines.row {
             panic!("invalid display row {}", start_row);
         }
@@ -499,7 +499,7 @@ impl Snapshot {
         let output_point = OutputPoint::new(start_row, 0);
         let mut cursor = self.transforms.cursor();
         cursor.seek(&output_point, Bias::Left, &());
-        InputRows {
+        BufferRows {
             output_point,
             cursor,
         }
@@ -880,12 +880,12 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
     }
 }
 
-pub struct InputRows<'a> {
+pub struct BufferRows<'a> {
     cursor: Cursor<'a, Transform, OutputPoint, InputPoint>,
     output_point: OutputPoint,
 }
 
-impl<'a> Iterator for InputRows<'a> {
+impl<'a> Iterator for BufferRows<'a> {
     type Item = u32;
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -1450,7 +1450,7 @@ mod tests {
                         .to_output_point(InputPoint::new(*input_row, 0))
                         .row();
                     assert_eq!(
-                        snapshot.input_rows(output_row).collect::<Vec<_>>(),
+                        snapshot.buffer_rows(output_row).collect::<Vec<_>>(),
                         expected_input_rows[idx..],
                     );
                 }
@@ -1544,8 +1544,8 @@ mod tests {
 
         let (snapshot, _) = map.read(cx.as_ref());
         assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee\nffffff\n");
-        assert_eq!(snapshot.input_rows(0).collect::<Vec<_>>(), [0, 3, 5, 6]);
-        assert_eq!(snapshot.input_rows(3).collect::<Vec<_>>(), [6]);
+        assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), [0, 3, 5, 6]);
+        assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [6]);
     }
 
     impl FoldMap {

zed/src/editor/display_map/tab_map.rs πŸ”—

@@ -1,7 +1,7 @@
 use parking_lot::Mutex;
 
 use super::fold_map::{
-    Chunks as InputChunks, Edit as InputEdit, HighlightedChunks as InputHighlightedChunks,
+    self, Chunks as InputChunks, Edit as InputEdit, HighlightedChunks as InputHighlightedChunks,
     OutputOffset as InputOffset, OutputPoint as InputPoint, Snapshot as InputSnapshot,
 };
 use crate::{editor::rope, settings::StyleId, util::Bias};
@@ -94,13 +94,15 @@ impl Snapshot {
         }
     }
 
-    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
-        let start = self.input.to_output_offset(InputPoint::new(rows.start, 0));
-        let end = self
+    pub fn highlighted_chunks(&mut self, range: Range<OutputPoint>) -> HighlightedChunks {
+        let input_start = self
             .input
-            .to_output_offset(InputPoint::new(rows.end, 0).min(self.input.max_point()));
+            .to_output_offset(self.to_input_point(range.start, Bias::Left).0);
+        let input_end = self
+            .input
+            .to_output_offset(self.to_input_point(range.end, Bias::Left).0);
         HighlightedChunks {
-            input_chunks: self.input.highlighted_chunks(start..end),
+            input_chunks: self.input.highlighted_chunks(input_start..input_end),
             column: 0,
             tab_size: self.tab_size,
             chunk: "",
@@ -108,6 +110,10 @@ impl Snapshot {
         }
     }
 
+    pub fn buffer_rows(&self, row: u32) -> fold_map::BufferRows {
+        self.input.buffer_rows(row)
+    }
+
     #[cfg(test)]
     pub fn text(&self) -> String {
         self.chunks_at(Default::default()).collect()

zed/src/editor/display_map/wrap_map.rs πŸ”—

@@ -1,13 +1,17 @@
-use super::tab_map::{
-    self, Edit as InputEdit, OutputPoint as InputPoint, Snapshot as InputSnapshot, TextSummary,
+use super::{
+    fold_map,
+    tab_map::{
+        self, Edit as InputEdit, OutputPoint as InputPoint, Snapshot as InputSnapshot, TextSummary,
+    },
 };
 use crate::{
-    editor::{Editor, Point},
+    editor::Point,
+    settings::StyleId,
     sum_tree::{self, Cursor, SumTree},
     util::Bias,
     Settings,
 };
-use gpui::{AppContext, FontCache, FontSystem, Task, ViewContext};
+use gpui::{AppContext, FontCache, FontSystem, Task};
 use parking_lot::Mutex;
 use postage::{
     prelude::{Sink, Stream},
@@ -139,10 +143,41 @@ impl Snapshot {
         }
     }
 
+    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
+        let output_start = OutputPoint::new(rows.start, 0);
+        let output_end = OutputPoint::new(rows.end, 0);
+        let mut transforms = self.transforms.cursor::<OutputPoint, InputPoint>();
+        transforms.seek(&output_start, Bias::Right, &());
+        let input_start =
+            InputPoint(transforms.sum_start().0 + (output_start.0 - transforms.seek_start().0));
+        let input_end = self.to_input_point(output_end).min(self.input.max_point());
+        HighlightedChunks {
+            input_chunks: self.input.highlighted_chunks(input_start..input_end),
+            input_position: input_start,
+            style_id: StyleId::default(),
+            input_chunk: "",
+            transforms,
+        }
+    }
+
     pub fn max_point(&self) -> OutputPoint {
         self.to_output_point(self.input.max_point())
     }
 
+    pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
+        let mut transforms = self.transforms.cursor::<OutputPoint, InputPoint>();
+        transforms.seek(&OutputPoint::new(start_row, 0), Bias::Right, &());
+        let input_row = transforms.sum_start().row();
+        let mut input_buffer_rows = self.input.buffer_rows(start_row);
+        let input_buffer_row = input_buffer_rows.next().unwrap();
+        BufferRows {
+            transforms,
+            input_row,
+            input_buffer_row,
+            input_buffer_rows,
+        }
+    }
+
     pub fn to_input_point(&self, point: OutputPoint) -> InputPoint {
         let mut cursor = self.transforms.cursor::<OutputPoint, InputPoint>();
         cursor.seek(&point, Bias::Right, &());
@@ -167,6 +202,21 @@ pub struct Chunks<'a> {
     transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
 }
 
+pub struct HighlightedChunks<'a> {
+    input_chunks: tab_map::HighlightedChunks<'a>,
+    input_chunk: &'a str,
+    style_id: StyleId,
+    input_position: InputPoint,
+    transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
+}
+
+pub struct BufferRows<'a> {
+    input_buffer_rows: fold_map::BufferRows<'a>,
+    input_row: u32,
+    input_buffer_row: u32,
+    transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
+}
+
 impl<'a> Iterator for Chunks<'a> {
     type Item = &'a str;
 
@@ -205,6 +255,65 @@ impl<'a> Iterator for Chunks<'a> {
     }
 }
 
+impl<'a> Iterator for HighlightedChunks<'a> {
+    type Item = (&'a str, StyleId);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let transform = self.transforms.item()?;
+        if let Some(display_text) = transform.display_text {
+            self.transforms.next(&());
+            return Some((display_text, self.style_id));
+        }
+
+        if self.input_chunk.is_empty() {
+            let (chunk, style_id) = self.input_chunks.next().unwrap();
+            self.input_chunk = chunk;
+            self.style_id = style_id;
+        }
+
+        let mut input_len = 0;
+        let transform_end = self.transforms.sum_end(&());
+        for c in self.input_chunk.chars() {
+            let char_len = c.len_utf8();
+            input_len += char_len;
+            if c == '\n' {
+                *self.input_position.row_mut() += 1;
+                *self.input_position.column_mut() = 0;
+            } else {
+                *self.input_position.column_mut() += char_len as u32;
+            }
+
+            if self.input_position >= transform_end {
+                self.transforms.next(&());
+                break;
+            }
+        }
+
+        let (prefix, suffix) = self.input_chunk.split_at(input_len);
+        self.input_chunk = suffix;
+        Some((prefix, self.style_id))
+    }
+}
+
+impl<'a> Iterator for BufferRows<'a> {
+    type Item = u32;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let result = self.input_buffer_row;
+        if self.input_row + 1 < self.transforms.sum_end(&()).row() {
+            self.input_row += 1;
+            self.input_buffer_row = self.input_buffer_rows.next().unwrap();
+        } else {
+            self.transforms.seek_forward(
+                &OutputPoint::new(self.transforms.seek_start().row() + 1, 0),
+                Bias::Right,
+                &(),
+            );
+        }
+        Some(result)
+    }
+}
+
 struct State {
     snapshot: Snapshot,
     pending_edits: VecDeque<(InputSnapshot, Vec<InputEdit>)>,
@@ -222,7 +331,7 @@ impl WrapMap {
         input: InputSnapshot,
         settings: Settings,
         wrap_width: Option<f32>,
-        cx: &mut ViewContext<Editor>,
+        cx: &AppContext,
     ) -> Self {
         let font_cache = cx.font_cache().clone();
         let font_system = cx.platform().fonts();
@@ -252,7 +361,9 @@ impl WrapMap {
 
     pub fn sync(&self, input: InputSnapshot, edits: Vec<InputEdit>, cx: &AppContext) -> Snapshot {
         let mut background_snapshot = self.background_snapshot.clone();
-        let mut snapshot = self.background_snapshot.borrow().clone();
+        let mut snapshot = background_snapshot.borrow().clone();
+
+        log::info!("sync version: {:?}", snapshot.input.version());
 
         if !edits.is_empty() {
             self.background_changes_tx
@@ -295,6 +406,10 @@ impl WrapMap {
             .try_send(Change::Width(width))
             .unwrap();
     }
+
+    pub fn notifications(&self) -> impl Stream<Item = ()> {
+        self.background_snapshot.clone().map(|_| ())
+    }
 }
 
 struct BackgroundWrapper {
@@ -362,6 +477,7 @@ impl BackgroundWrapper {
                     }
                 }
             };
+
             if snapshot_tx.send(self.snapshot.clone()).await.is_err() {
                 break;
             }

zed/src/editor/element.rs πŸ”—

@@ -338,7 +338,6 @@ impl Element for EditorElement {
         }
 
         let view = self.view(app);
-        view.set_width(size.x());
 
         let font_cache = &cx.font_cache;
         let layout_cache = &cx.text_layout_cache;
@@ -362,6 +361,7 @@ impl Element for EditorElement {
 
         let gutter_size = vec2f(gutter_width, size.y());
         let text_size = size - vec2f(gutter_width, 0.0);
+        view.set_width(text_size.x());
 
         let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, app);