Re-enable `wrap_map` module and fix compile errors

Antonio Scandurra and Nathan Sobo created

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

Change summary

zed/src/editor/display_map.rs          |   2 
zed/src/editor/display_map/fold_map.rs |  12 
zed/src/editor/display_map/tab_map.rs  | 115 +++++++++--
zed/src/editor/display_map/wrap_map.rs | 284 +++++++++++++++------------
4 files changed, 256 insertions(+), 157 deletions(-)

Detailed changes

zed/src/editor/display_map.rs 🔗

@@ -1,6 +1,6 @@
 mod fold_map;
 mod tab_map;
-// mod wrap_map;
+mod wrap_map;
 
 use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint};
 use fold_map::FoldMap;

zed/src/editor/display_map/fold_map.rs 🔗

@@ -483,14 +483,14 @@ impl Snapshot {
         summary
     }
 
-    pub fn len(&self) -> usize {
-        self.transforms.summary().output.bytes
+    pub fn len(&self) -> OutputOffset {
+        OutputOffset(self.transforms.summary().output.bytes)
     }
 
     pub fn line_len(&self, row: u32) -> u32 {
         let line_start = self.to_output_offset(OutputPoint::new(row, 0)).0;
         let line_end = if row >= self.max_point().row() {
-            self.len()
+            self.len().0
         } else {
             self.to_output_offset(OutputPoint::new(row + 1, 0)).0 - 1
         };
@@ -1442,8 +1442,10 @@ mod tests {
                 }
 
                 for _ in 0..5 {
-                    let offset = snapshot
-                        .clip_offset(OutputOffset(rng.gen_range(0..=snapshot.len())), Bias::Right);
+                    let offset = snapshot.clip_offset(
+                        OutputOffset(rng.gen_range(0..=snapshot.len().0)),
+                        Bias::Right,
+                    );
                     assert_eq!(
                         snapshot.chunks_at(offset).collect::<String>(),
                         &expected_text[offset.0..],

zed/src/editor/display_map/tab_map.rs 🔗

@@ -47,6 +47,35 @@ pub struct Snapshot {
 }
 
 impl Snapshot {
+    pub fn text_summary(&self) -> TextSummary {
+        // TODO: expand tabs on first and last line, ignoring the longest row.
+        let summary = self.input.text_summary();
+        TextSummary {
+            lines: summary.lines,
+            first_line_chars: summary.first_line_chars,
+            last_line_chars: summary.last_line_chars,
+            longest_row: summary.longest_row,
+            longest_row_chars: summary.longest_row_chars,
+        }
+    }
+
+    pub fn text_summary_for_rows(&self, rows: Range<u32>) -> TextSummary {
+        // TODO: expand tabs on first and last line, ignoring the longest row.
+        let range = InputPoint::new(rows.start, 0)..InputPoint::new(rows.end, 0);
+        let summary = self.input.text_summary_for_range(range);
+        TextSummary {
+            lines: summary.lines,
+            first_line_chars: summary.first_line_chars,
+            last_line_chars: summary.last_line_chars,
+            longest_row: summary.longest_row,
+            longest_row_chars: summary.longest_row_chars,
+        }
+    }
+
+    pub fn version(&self) -> usize {
+        self.input.version
+    }
+
     pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
         let (point, expanded_char_column, to_next_stop) = self.to_input_point(point, Bias::Left);
         let fold_chunks = self.input.chunks_at(self.input.to_output_offset(point));
@@ -73,6 +102,15 @@ impl Snapshot {
         }
     }
 
+    #[cfg(test)]
+    pub fn text(&self) -> String {
+        self.chunks_at(Default::default()).collect()
+    }
+
+    pub fn len(&self) -> OutputOffset {
+        self.to_output_offset(self.input.len())
+    }
+
     pub fn line_len(&self, row: u32) -> u32 {
         self.to_output_point(InputPoint::new(row, self.input.line_len(row)))
             .column()
@@ -184,27 +222,7 @@ impl Snapshot {
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct OutputOffset(usize);
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Edit {
-    pub old_bytes: Range<OutputOffset>,
-    pub new_bytes: Range<OutputOffset>,
-}
-
-impl Edit {
-    pub fn delta(&self) -> isize {
-        self.inserted_bytes() as isize - self.deleted_bytes() as isize
-    }
-
-    pub fn deleted_bytes(&self) -> usize {
-        self.old_bytes.end.0 - self.old_bytes.start.0
-    }
-
-    pub fn inserted_bytes(&self) -> usize {
-        self.new_bytes.end.0 - self.new_bytes.start.0
-    }
-}
+pub struct OutputOffset(pub usize);
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 pub struct OutputPoint(super::Point);
@@ -235,6 +253,61 @@ impl OutputPoint {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Edit {
+    pub old_bytes: Range<OutputOffset>,
+    pub new_bytes: Range<OutputOffset>,
+}
+
+impl Edit {
+    pub fn delta(&self) -> isize {
+        self.inserted_bytes() as isize - self.deleted_bytes() as isize
+    }
+
+    pub fn deleted_bytes(&self) -> usize {
+        self.old_bytes.end.0 - self.old_bytes.start.0
+    }
+
+    pub fn inserted_bytes(&self) -> usize {
+        self.new_bytes.end.0 - self.new_bytes.start.0
+    }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct TextSummary {
+    pub lines: super::Point,
+    pub first_line_chars: u32,
+    pub last_line_chars: u32,
+    pub longest_row: u32,
+    pub longest_row_chars: u32,
+}
+
+impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
+    fn add_assign(&mut self, other: &'a Self) {
+        let joined_chars = self.last_line_chars + other.first_line_chars;
+        if joined_chars > self.longest_row_chars {
+            self.longest_row = self.lines.row;
+            self.longest_row_chars = joined_chars;
+        }
+        if other.longest_row_chars > self.longest_row_chars {
+            self.longest_row = self.lines.row + other.longest_row;
+            self.longest_row_chars = other.longest_row_chars;
+        }
+
+        if self.lines.row == 0 {
+            self.first_line_chars += other.first_line_chars;
+        }
+
+        if other.lines.row == 0 {
+            self.last_line_chars += other.first_line_chars;
+        } else {
+            self.last_line_chars = other.last_line_chars;
+        }
+
+        self.lines += &other.lines;
+    }
+}
+
 // Handles a tab width <= 16
 const SPACES: &'static str = "                ";
 

zed/src/editor/display_map/wrap_map.rs 🔗

@@ -1,24 +1,22 @@
-use std::sync::Arc;
-
+use super::tab_map::{
+    Edit as InputEdit, OutputOffset as InputOffset, Snapshot as InputSnapshot, TextSummary,
+};
 use crate::{
-    editor::{
-        display_map::fold_map::{self, DisplayOffset, FoldedPoint},
-        Point, TextSummary,
-    },
+    editor::Point,
     sum_tree::{self, SumTree},
-    util::Bias,
 };
 use gpui::{font_cache::FamilyId, AppContext, FontCache, FontSystem, Task};
 use parking_lot::Mutex;
 use postage::{prelude::Sink, watch};
 use smol::channel;
+use std::sync::Arc;
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct WrappedPoint(Point);
+pub struct OutputPoint(super::Point);
 
-impl WrappedPoint {
+impl OutputPoint {
     pub fn new(row: u32, column: u32) -> Self {
-        Self(Point::new(row, column))
+        Self(super::Point::new(row, column))
     }
 
     pub fn zero() -> Self {
@@ -42,13 +40,32 @@ impl WrappedPoint {
     }
 }
 
-#[derive(Clone, Default)]
+#[derive(Clone)]
 pub struct Snapshot {
     transforms: SumTree<Transform>,
-    folds_snapshot: fold_map::Snapshot,
+    input: InputSnapshot,
     version: usize,
 }
 
+impl Snapshot {
+    fn new(input: InputSnapshot) -> Self {
+        Self {
+            transforms: SumTree::from_item(
+                Transform {
+                    summary: TransformSummary {
+                        input: input.text_summary(),
+                        output: input.text_summary(),
+                    },
+                    display_text: None,
+                },
+                &(),
+            ),
+            version: input.version(),
+            input,
+        }
+    }
+}
+
 struct State {
     snapshot: Snapshot,
     interpolated_version: usize,
@@ -63,38 +80,26 @@ pub struct Config {
 
 pub struct WrapMap {
     state: Mutex<State>,
-    edits_tx: channel::Sender<(fold_map::Snapshot, Vec<fold_map::Edit>)>,
+    edits_tx: channel::Sender<(InputSnapshot, Vec<InputEdit>)>,
     background_snapshots: watch::Receiver<Snapshot>,
     _background_task: Task<()>,
 }
 
 impl WrapMap {
-    pub fn new(folds_snapshot: fold_map::Snapshot, config: Config, cx: &AppContext) -> Self {
+    pub fn new(input: InputSnapshot, config: Config, cx: &AppContext) -> Self {
         let font_cache = cx.font_cache().clone();
         let font_system = cx.platform().fonts();
-        let snapshot = Snapshot {
-            transforms: SumTree::from_item(
-                Transform {
-                    summary: TransformSummary {
-                        folded: folds_snapshot.text_summary(),
-                        wrapped: folds_snapshot.text_summary(),
-                    },
-                    display_text: None,
-                },
-                &(),
-            ),
-            folds_snapshot,
-            version: folds_snapshot.version,
-        };
+        let snapshot = Snapshot::new(input.clone());
         let (background_snapshots_tx, background_snapshots_rx) =
             watch::channel_with(snapshot.clone());
         let (edits_tx, edits_rx) = channel::unbounded();
-        let background_task = cx.background().spawn(async move {
-            let mut wrapper = BackgroundWrapper::new(config, font_cache, font_system);
-            wrapper
-                .run(folds_snapshot, edits_rx, background_snapshots_tx)
-                .await;
-        });
+        let background_task = {
+            let snapshot = snapshot.clone();
+            cx.background().spawn(async move {
+                let mut wrapper = BackgroundWrapper::new(snapshot, config, font_cache, font_system);
+                wrapper.run(input, edits_rx, background_snapshots_tx).await;
+            })
+        };
 
         Self {
             state: Mutex::new(State {
@@ -107,9 +112,9 @@ impl WrapMap {
         }
     }
 
-    pub fn sync(&self, folds_snapshot: fold_map::Snapshot, edits: Vec<fold_map::Edit>) -> Snapshot {
+    pub fn sync(&self, input: InputSnapshot, edits: Vec<InputEdit>) -> Snapshot {
         // TODO: interpolate
-        self.edits_tx.try_send((folds_snapshot, edits)).unwrap();
+        self.edits_tx.try_send((input, edits)).unwrap();
         self.state.lock().snapshot.clone()
     }
 }
@@ -122,24 +127,29 @@ struct BackgroundWrapper {
 }
 
 impl BackgroundWrapper {
-    fn new(config: Config, font_cache: Arc<FontCache>, font_system: Arc<dyn FontSystem>) -> Self {
+    fn new(
+        snapshot: Snapshot,
+        config: Config,
+        font_cache: Arc<FontCache>,
+        font_system: Arc<dyn FontSystem>,
+    ) -> Self {
         Self {
             config,
             font_cache,
             font_system,
-            snapshot: Snapshot::default(),
+            snapshot,
         }
     }
 
     async fn run(
         &mut self,
-        snapshot: fold_map::Snapshot,
-        edits_rx: channel::Receiver<(fold_map::Snapshot, Vec<fold_map::Edit>)>,
+        snapshot: InputSnapshot,
+        edits_rx: channel::Receiver<(InputSnapshot, Vec<InputEdit>)>,
         mut snapshots_tx: watch::Sender<Snapshot>,
     ) {
-        let edit = fold_map::Edit {
-            old_bytes: DisplayOffset(0)..DisplayOffset(0),
-            new_bytes: DisplayOffset(0)..DisplayOffset(snapshot.len()),
+        let edit = InputEdit {
+            old_bytes: InputOffset(0)..InputOffset(0),
+            new_bytes: InputOffset(0)..snapshot.len(),
         };
         self.sync(snapshot, vec![edit]);
         if snapshots_tx.send(self.snapshot.clone()).await.is_err() {
@@ -154,7 +164,7 @@ impl BackgroundWrapper {
         }
     }
 
-    fn sync(&mut self, snapshot: fold_map::Snapshot, edits: Vec<fold_map::Edit>) {
+    fn sync(&mut self, snapshot: InputSnapshot, edits: Vec<InputEdit>) {
         let font_id = self
             .font_cache
             .select_font(self.config.font_family, &Default::default());
@@ -163,76 +173,76 @@ impl BackgroundWrapper {
 
         let mut new_transforms = SumTree::new();
         {
-            let mut old_cursor = self.snapshot.transforms.cursor::<DisplayPoint, ()>();
-            let mut position = DisplayPoint::zero();
-            for edit in edits {
-                let old_start = DisplayPoint::new(
-                    edit.old_bytes.start.to_display_point(&self.snapshot).row(),
-                    0,
-                );
-                let old_end = DisplayPoint::new(
-                    edit.old_bytes.end.to_display_point(&self.snapshot).row() + 1,
-                    0,
-                );
-                let new_start =
-                    DisplayPoint::new(edit.new_bytes.start.to_display_point(&snapshot).row(), 0);
-                let new_end =
-                    DisplayPoint::new(edit.new_bytes.end.to_display_point(&snapshot).row() + 1, 0);
-
-                if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
-                    old_cursor.next(&());
-                }
-
-                let prefix = old_cursor.slice(&old_start, Bias::Right, &());
-                new_transforms.push_tree(prefix, &());
-                new_transforms.push(
-                    Transform::isomorphic(
-                        self.snapshot
-                            .folds_snapshot
-                            .text_summary_for_range(position..old_start),
-                    ),
-                    &(),
-                );
-
-                let mut row = new_start.row();
-                let mut line = String::new();
-                'outer: for chunk in snapshot.chunks_at(snapshot.to_display_offset(new_start)) {
-                    for (ix, line_chunk) in chunk.split('\n').enumerate() {
-                        if ix > 0 {
-                            let mut prev_boundary_ix = 0;
-                            for boundary_ix in self
-                                .font_system
-                                .wrap_line(&line, font_id, font_size, wrap_width)
-                            {
-                                let wrapped = &line[prev_boundary_ix..boundary_ix];
-                                new_transforms
-                                    .push(Transform::isomorphic(TextSummary::from(wrapped)));
-                                new_transforms.push(Transform::newline());
-                                prev_boundary_ix = boundary_ix;
-                            }
-
-                            line.clear();
-                            row += 1;
-                            if row == new_end.row() {
-                                break 'outer;
-                            }
-                        }
-
-                        line.push_str(line_chunk);
-                    }
-                }
-
-                old_cursor.seek_forward(&old_end, Bias::Right, &());
-                position = old_end;
-            }
-
-            if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
-                old_cursor.next(&());
-            }
+            // let mut old_cursor = self.snapshot.transforms.cursor::<DisplayPoint, ()>();
+            // let mut position = DisplayPoint::zero();
+            // for edit in edits {
+            //     let old_start = DisplayPoint::new(
+            //         edit.old_bytes.start.to_display_point(&self.snapshot).row(),
+            //         0,
+            //     );
+            //     let old_end = DisplayPoint::new(
+            //         edit.old_bytes.end.to_display_point(&self.snapshot).row() + 1,
+            //         0,
+            //     );
+            //     let new_start =
+            //         DisplayPoint::new(edit.new_bytes.start.to_display_point(&snapshot).row(), 0);
+            //     let new_end =
+            //         DisplayPoint::new(edit.new_bytes.end.to_display_point(&snapshot).row() + 1, 0);
+
+            //     if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
+            //         old_cursor.next(&());
+            //     }
+
+            //     let prefix = old_cursor.slice(&old_start, Bias::Right, &());
+            //     new_transforms.push_tree(prefix, &());
+            //     new_transforms.push(
+            //         Transform::isomorphic(
+            //             self.snapshot
+            //                 .folds_snapshot
+            //                 .text_summary_for_range(position..old_start),
+            //         ),
+            //         &(),
+            //     );
+
+            //     let mut row = new_start.row();
+            //     let mut line = String::new();
+            //     'outer: for chunk in snapshot.chunks_at(snapshot.to_display_offset(new_start)) {
+            //         for (ix, line_chunk) in chunk.split('\n').enumerate() {
+            //             if ix > 0 {
+            //                 let mut prev_boundary_ix = 0;
+            //                 for boundary_ix in self
+            //                     .font_system
+            //                     .wrap_line(&line, font_id, font_size, wrap_width)
+            //                 {
+            //                     let wrapped = &line[prev_boundary_ix..boundary_ix];
+            //                     new_transforms
+            //                         .push(Transform::isomorphic(TextSummary::from(wrapped)));
+            //                     new_transforms.push(Transform::newline());
+            //                     prev_boundary_ix = boundary_ix;
+            //                 }
+
+            //                 line.clear();
+            //                 row += 1;
+            //                 if row == new_end.row() {
+            //                     break 'outer;
+            //                 }
+            //             }
+
+            //             line.push_str(line_chunk);
+            //         }
+            //     }
+
+            //     old_cursor.seek_forward(&old_end, Bias::Right, &());
+            //     position = old_end;
+            // }
+
+            // if position > old_cursor.seek_start() && old_start >= old_cursor.seek_end(&()) {
+            //     old_cursor.next(&());
+            // }
         }
 
         self.snapshot.transforms = new_transforms;
-        self.snapshot.version = snapshot.version;
+        self.snapshot.version = snapshot.version();
     }
 }
 
@@ -246,8 +256,8 @@ impl Transform {
     fn isomorphic(summary: TextSummary) -> Self {
         Self {
             summary: TransformSummary {
-                folded: summary.clone(),
-                wrapped: summary,
+                input: summary.clone(),
+                output: summary,
             },
             display_text: None,
         }
@@ -256,8 +266,14 @@ impl Transform {
     fn newline() -> Self {
         Self {
             summary: TransformSummary {
-                folded: TextSummary::default(),
-                wrapped: TextSummary::from("\n"),
+                input: TextSummary::default(),
+                output: TextSummary {
+                    lines: Point::new(1, 0),
+                    first_line_chars: 0,
+                    last_line_chars: 0,
+                    longest_row: 0,
+                    longest_row_chars: 0,
+                },
             },
             display_text: Some("\n"),
         }
@@ -274,22 +290,22 @@ impl sum_tree::Item for Transform {
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
 struct TransformSummary {
-    folded: TextSummary,
-    wrapped: TextSummary,
+    input: TextSummary,
+    output: TextSummary,
 }
 
 impl sum_tree::Summary for TransformSummary {
     type Context = ();
 
     fn add_summary(&mut self, other: &Self, _: &()) {
-        self.folded += &other.folded;
-        self.wrapped += &other.wrapped;
+        self.input += &other.input;
+        self.output += &other.output;
     }
 }
 
 impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
     fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
-        *self += &summary.folded.lines;
+        *self += &summary.input.lines;
     }
 }
 
@@ -297,12 +313,14 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
 mod tests {
     use super::*;
     use crate::{
-        editor::{display_map::fold_map::FoldMap, Buffer},
+        editor::{
+            display_map::{fold_map::FoldMap, tab_map::TabMap},
+            Buffer,
+        },
         util::RandomCharIter,
     };
     use rand::prelude::*;
     use std::env;
-    use Bias::{Left, Right};
 
     #[gpui::test]
     fn test_random_wraps(cx: &mut gpui::MutableAppContext) {
@@ -329,7 +347,9 @@ mod tests {
                 Buffer::new(0, text, cx)
             });
             let fold_map = FoldMap::new(buffer.clone(), cx.as_ref());
-            let (snapshot, _) = fold_map.read(cx.as_ref());
+            let (folds_snapshot, edits) = fold_map.read(cx.as_ref());
+            let tab_map = TabMap::new(folds_snapshot.clone(), rng.gen_range(1..=4));
+            let (tabs_snapshot, _) = tab_map.sync(folds_snapshot, edits);
             let font_cache = cx.font_cache().clone();
             let font_system = cx.platform().fonts();
             let config = Config {
@@ -340,16 +360,20 @@ mod tests {
             let font_id = font_cache
                 .select_font(config.font_family, &Default::default())
                 .unwrap();
-            let mut wrapper =
-                BackgroundWrapper::new(config.clone(), font_cache.clone(), font_system.clone());
-            let edit = fold_map::Edit {
-                old_bytes: DisplayOffset(0)..DisplayOffset(0),
-                new_bytes: DisplayOffset(0)..DisplayOffset(snapshot.len()),
+            let mut wrapper = BackgroundWrapper::new(
+                Snapshot::new(tabs_snapshot.clone()),
+                config.clone(),
+                font_cache.clone(),
+                font_system.clone(),
+            );
+            let edit = InputEdit {
+                old_bytes: InputOffset(0)..InputOffset(0),
+                new_bytes: InputOffset(0)..tabs_snapshot.len(),
             };
-            wrapper.sync(snapshot.clone(), vec![edit]);
+            wrapper.sync(tabs_snapshot.clone(), vec![edit]);
 
             let mut expected_text = String::new();
-            for line in snapshot.text().lines() {
+            for line in tabs_snapshot.text().lines() {
                 let mut prev_ix = 0;
                 for ix in font_system.wrap_line(line, font_id, 14.0, config.wrap_width) {
                     expected_text.push_str(&line[prev_ix..ix]);