Start restructuring WrapMap with simpler concurrency

Max Brunsfeld and Nathan Sobo created

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

Change summary

gpui/src/executor.rs                       |  16 
zed/src/editor.rs                          |   4 
zed/src/editor/display_map.rs              |   9 
zed/src/editor/display_map/line_wrapper.rs | 108 ++
zed/src/editor/display_map/wrap_map.rs     | 920 +++++++++--------------
zed/src/editor/element.rs                  |   2 
6 files changed, 494 insertions(+), 565 deletions(-)

Detailed changes

gpui/src/executor.rs 🔗

@@ -385,14 +385,14 @@ impl Background {
         }
     }
 
-    pub fn block_on<F, T>(&self, timeout: Duration, future: F) -> Option<T>
+    pub fn block_with_timeout<F, T>(&self, timeout: Duration, mut future: F) -> Result<T, F>
     where
         T: 'static,
-        F: Future<Output = T>,
+        F: 'static + Unpin + Future<Output = T>,
     {
-        match self {
+        let output = match self {
             Self::Production { .. } => {
-                smol::block_on(async move { util::timeout(timeout, future).await.ok() })
+                smol::block_on(util::timeout(timeout, Pin::new(&mut future))).ok()
             }
             Self::Deterministic(executor) => {
                 let max_ticks = {
@@ -400,8 +400,14 @@ impl Background {
                     let range = state.block_on_ticks.clone();
                     state.rng.gen_range(range)
                 };
-                executor.block_on(max_ticks, future)
+                executor.block_on(max_ticks, Pin::new(&mut future))
             }
+        };
+
+        if let Some(output) = output {
+            Ok(output)
+        } else {
+            Err(future)
         }
     }
 

zed/src/editor.rs 🔗

@@ -2198,8 +2198,8 @@ impl Editor {
         font_cache.em_width(font_id, settings.buffer_font_size)
     }
 
-    pub fn set_wrap_width(&self, width: f32) {
-        self.display_map.set_wrap_width(Some(width));
+    pub fn set_wrap_width(&self, width: f32, cx: &AppContext) {
+        self.display_map.set_wrap_width(Some(width), cx);
     }
 
     // TODO: Can we make this not return a result?

zed/src/editor/display_map.rs 🔗

@@ -1,4 +1,5 @@
 mod fold_map;
+mod line_wrapper;
 mod tab_map;
 mod wrap_map;
 
@@ -74,8 +75,8 @@ impl DisplayMap {
         self.wrap_map.sync(snapshot, edits, cx);
     }
 
-    pub fn set_wrap_width(&self, width: Option<f32>) {
-        self.wrap_map.set_wrap_width(width);
+    pub fn set_wrap_width(&self, width: Option<f32>, cx: &AppContext) {
+        self.wrap_map.set_wrap_width(width, cx);
     }
 
     pub fn notifications(&self) -> impl Stream<Item = ()> {
@@ -212,11 +213,11 @@ impl DisplayMapSnapshot {
 }
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct DisplayPoint(wrap_map::OutputPoint);
+pub struct DisplayPoint(wrap_map::WrapPoint);
 
 impl DisplayPoint {
     pub fn new(row: u32, column: u32) -> Self {
-        Self(wrap_map::OutputPoint::new(row, column))
+        Self(wrap_map::WrapPoint::new(row, column))
     }
 
     pub fn zero() -> Self {

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

@@ -0,0 +1,108 @@
+use crate::Settings;
+use gpui::{fonts::FontId, FontCache, FontSystem};
+use parking_lot::Mutex;
+use std::{collections::HashMap, sync::Arc};
+
+pub struct LineWrapper {
+    font_system: Arc<dyn FontSystem>,
+    font_cache: Arc<FontCache>,
+    font_id: FontId,
+    font_size: f32,
+    cached_ascii_char_widths: Mutex<[f32; 128]>,
+    cached_other_char_widths: Mutex<HashMap<char, f32>>,
+}
+
+impl LineWrapper {
+    pub fn new(
+        font_system: Arc<dyn FontSystem>,
+        font_cache: Arc<FontCache>,
+        settings: Settings,
+    ) -> Self {
+        let font_id = font_cache
+            .select_font(settings.buffer_font_family, &Default::default())
+            .unwrap();
+        let font_size = settings.buffer_font_size;
+        Self {
+            font_cache,
+            font_system,
+            font_id,
+            font_size,
+            cached_ascii_char_widths: Mutex::new([f32::NAN; 128]),
+            cached_other_char_widths: Mutex::new(HashMap::new()),
+        }
+    }
+
+    pub fn wrap_line_with_shaping(&self, line: &str, wrap_width: f32) -> Vec<usize> {
+        self.font_system
+            .wrap_line(line, self.font_id, self.font_size, wrap_width)
+    }
+
+    pub fn wrap_line_without_shaping(&self, line: &str, wrap_width: f32) -> Vec<usize> {
+        let mut width = 0.0;
+        let mut result = Vec::new();
+        let mut last_boundary_ix = 0;
+        let mut last_boundary_width = 0.0;
+        let mut prev_c = '\0';
+        for (ix, c) in line.char_indices() {
+            if self.is_boundary(prev_c, c) {
+                last_boundary_ix = ix;
+                last_boundary_width = width;
+            }
+
+            let char_width = self.width_for_char(c);
+            width += char_width;
+            if width > wrap_width {
+                if last_boundary_ix > 0 {
+                    result.push(last_boundary_ix);
+                    width -= last_boundary_width;
+                    last_boundary_ix = 0;
+                } else {
+                    result.push(ix);
+                    width = char_width;
+                }
+            }
+            prev_c = c;
+        }
+        result
+    }
+
+    fn is_boundary(&self, prev: char, next: char) -> bool {
+        if prev == ' ' || next == ' ' {
+            return true;
+        }
+        false
+    }
+
+    fn width_for_char(&self, c: char) -> f32 {
+        if (c as u32) < 128 {
+            let mut cached_ascii_char_widths = self.cached_ascii_char_widths.lock();
+            let mut width = cached_ascii_char_widths[c as usize];
+            if width.is_nan() {
+                width = self.compute_width_for_char(c);
+                cached_ascii_char_widths[c as usize] = width;
+            }
+            width
+        } else {
+            let mut cached_other_char_widths = self.cached_other_char_widths.lock();
+            let mut width = cached_other_char_widths
+                .get(&c)
+                .copied()
+                .unwrap_or(f32::NAN);
+            if width.is_nan() {
+                width = self.compute_width_for_char(c);
+                cached_other_char_widths.insert(c, width);
+            }
+            width
+        }
+    }
+
+    fn compute_width_for_char(&self, c: char) -> f32 {
+        self.font_system
+            .layout_line(
+                &c.to_string(),
+                self.font_size,
+                &[(1, self.font_id, Default::default())],
+            )
+            .width
+    }
+}

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

@@ -1,7 +1,8 @@
 use super::{
     fold_map,
+    line_wrapper::LineWrapper,
     tab_map::{
-        self, Edit as InputEdit, OutputPoint as InputPoint, Snapshot as InputSnapshot, TextSummary,
+        self, Edit as TabEdit, OutputPoint as TabPoint, Snapshot as TabSnapshot, TextSummary,
     },
 };
 use crate::{
@@ -11,100 +12,257 @@ use crate::{
     util::Bias,
     Settings,
 };
-use gpui::{fonts::FontId, AppContext, FontCache, FontSystem, Task};
+use gpui::{executor::Background, AppContext, Task};
 use parking_lot::Mutex;
-use postage::{
-    prelude::{Sink, Stream},
-    watch,
-};
-use smol::channel;
-use std::{
-    collections::{HashMap, VecDeque},
-    ops::Range,
-    sync::Arc,
-    time::Duration,
-};
+use postage::{prelude::Stream, sink::Sink, watch};
+use smol::future::yield_now;
+use std::{collections::VecDeque, ops::Range, sync::Arc, time::Duration};
+
+#[derive(Clone)]
+pub struct WrapMap(Arc<Mutex<WrapMapState>>);
+
+struct WrapMapState {
+    snapshot: Snapshot,
+    pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
+    wrap_width: Option<f32>,
+    background_task: Option<Task<()>>,
+    updates: (watch::Sender<()>, watch::Receiver<()>),
+    line_wrapper: Arc<LineWrapper>,
+}
+
+#[derive(Clone)]
+pub struct Snapshot {
+    tab_snapshot: TabSnapshot,
+    transforms: SumTree<Transform>,
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct Transform {
+    summary: TransformSummary,
+    display_text: Option<&'static str>,
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct TransformSummary {
+    input: TextSummary,
+    output: TextSummary,
+}
 
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
-pub struct OutputPoint(super::Point);
+pub struct WrapPoint(super::Point);
 
-impl OutputPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(super::Point::new(row, column))
-    }
+pub struct Chunks<'a> {
+    input_chunks: tab_map::Chunks<'a>,
+    input_chunk: &'a str,
+    input_position: TabPoint,
+    transforms: Cursor<'a, Transform, WrapPoint, TabPoint>,
+}
 
-    pub fn zero() -> Self {
-        Self::new(0, 0)
-    }
+pub struct HighlightedChunks<'a> {
+    input_chunks: tab_map::HighlightedChunks<'a>,
+    input_chunk: &'a str,
+    style_id: StyleId,
+    output_position: WrapPoint,
+    max_output_row: u32,
+    transforms: Cursor<'a, Transform, WrapPoint, TabPoint>,
+}
 
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
+pub struct BufferRows<'a> {
+    input_buffer_rows: fold_map::BufferRows<'a>,
+    input_buffer_row: u32,
+    output_row: u32,
+    max_output_row: u32,
+    transforms: Cursor<'a, Transform, WrapPoint, TabPoint>,
+}
 
-    pub fn column(self) -> u32 {
-        self.0.column
+impl WrapMap {
+    pub fn new(
+        tab_snapshot: TabSnapshot,
+        settings: Settings,
+        wrap_width: Option<f32>,
+        cx: &AppContext,
+    ) -> Self {
+        let this = Self(Arc::new(Mutex::new(WrapMapState {
+            background_task: None,
+            wrap_width: None,
+            updates: watch::channel(),
+            pending_edits: Default::default(),
+            snapshot: Snapshot::new(tab_snapshot),
+            line_wrapper: Arc::new(LineWrapper::new(
+                cx.platform().fonts(),
+                cx.font_cache().clone(),
+                settings,
+            )),
+        })));
+        this.set_wrap_width(wrap_width, cx);
+        this
     }
 
-    pub fn row_mut(&mut self) -> &mut u32 {
-        &mut self.0.row
+    pub fn notifications(&self) -> impl Stream<Item = ()> {
+        self.0.lock().updates.1.clone()
     }
 
-    pub fn column_mut(&mut self) -> &mut u32 {
-        &mut self.0.column
+    pub fn sync(
+        &self,
+        tab_snapshot: TabSnapshot,
+        edits: Vec<TabEdit>,
+        cx: &AppContext,
+    ) -> Snapshot {
+        self.0.lock().pending_edits.push_back((tab_snapshot, edits));
+        self.flush_edits(cx.background());
+        self.0.lock().snapshot.clone()
+    }
+
+    pub fn set_wrap_width(&self, wrap_width: Option<f32>, cx: &AppContext) {
+        let mut state = self.0.lock();
+        if wrap_width == state.wrap_width {
+            return;
+        }
+
+        state.background_task.take();
+
+        if let Some(wrap_width) = wrap_width {
+            let mut new_snapshot = state.snapshot.clone();
+            let line_wrapper = state.line_wrapper.clone();
+            let task = cx.background().spawn(async move {
+                let tab_snapshot = new_snapshot.tab_snapshot.clone();
+                let range = TabPoint::zero()..tab_snapshot.max_point();
+                new_snapshot
+                    .update(
+                        tab_snapshot,
+                        &[TabEdit {
+                            old_lines: range.clone(),
+                            new_lines: range.clone(),
+                        }],
+                        wrap_width,
+                        line_wrapper.as_ref(),
+                    )
+                    .await;
+                new_snapshot
+            });
+
+            let executor = cx.background();
+            match executor.block_with_timeout(Duration::from_millis(5), task) {
+                Ok(snapshot) => state.snapshot = snapshot,
+                Err(wrap_task) => {
+                    let this = self.clone();
+                    let exec = executor.clone();
+                    state.background_task = Some(executor.spawn(async move {
+                        this.0.lock().snapshot = wrap_task.await;
+                        this.flush_edits(&exec);
+                        this.0.lock().updates.0.blocking_send(()).ok();
+                    }));
+                }
+            }
+        }
     }
-}
 
-#[derive(Clone)]
-pub struct Snapshot {
-    transforms: SumTree<Transform>,
-    input: InputSnapshot,
+    fn flush_edits(&self, executor: &Arc<Background>) {
+        let mut state = self.0.lock();
+
+        while let Some((tab_snapshot, _)) = state.pending_edits.front() {
+            if tab_snapshot.version() <= state.snapshot.tab_snapshot.version() {
+                state.pending_edits.pop_front();
+            } else {
+                break;
+            }
+        }
+
+        if state.pending_edits.is_empty() {
+            return;
+        }
+
+        if let Some(wrap_width) = state.wrap_width {
+            if state.background_task.is_none() {
+                let pending_edits = state.pending_edits.clone();
+                let mut snapshot = state.snapshot.clone();
+                let line_wrapper = state.line_wrapper.clone();
+
+                let update_task = executor.spawn(async move {
+                    for (tab_snapshot, edits) in pending_edits {
+                        snapshot
+                            .update(tab_snapshot, &edits, wrap_width, &line_wrapper)
+                            .await;
+                    }
+                    snapshot
+                });
+
+                match executor.block_with_timeout(Duration::from_micros(500), update_task) {
+                    Ok(snapshot) => {
+                        state.snapshot = snapshot;
+                    }
+                    Err(update_task) => {
+                        let this = self.clone();
+                        let exec = executor.clone();
+                        state.background_task = Some(executor.spawn(async move {
+                            this.0.lock().snapshot = update_task.await;
+                            this.flush_edits(&exec);
+                            this.0.lock().updates.0.blocking_send(()).ok();
+                        }));
+                    }
+                }
+            }
+        }
+
+        while let Some((tab_snapshot, _)) = state.pending_edits.front() {
+            if tab_snapshot.version() <= state.snapshot.tab_snapshot.version() {
+                state.pending_edits.pop_front();
+            } else {
+                break;
+            }
+        }
+
+        for (tab_snapshot, edits) in state.pending_edits.clone() {
+            state.snapshot.interpolate(tab_snapshot, &edits);
+        }
+    }
 }
 
 impl Snapshot {
-    fn new(input: InputSnapshot) -> Self {
+    fn new(tab_snapshot: TabSnapshot) -> Self {
+        let extent = tab_snapshot.text_summary();
         Self {
             transforms: SumTree::from_item(
                 Transform {
                     summary: TransformSummary {
-                        input: input.text_summary(),
-                        output: input.text_summary(),
+                        input: extent.clone(),
+                        output: extent.clone(),
                     },
                     display_text: None,
                 },
                 &(),
             ),
-            input,
+            tab_snapshot,
         }
     }
 
-    fn interpolate(&mut self, new_snapshot: InputSnapshot, edits: &[InputEdit]) {
+    fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) {
         let mut new_transforms;
         if edits.is_empty() {
             new_transforms = self.transforms.clone();
         } else {
-            let mut old_cursor = self.transforms.cursor::<InputPoint, ()>();
+            let mut old_cursor = self.transforms.cursor::<TabPoint, ()>();
             let mut edits = edits.into_iter().peekable();
             new_transforms =
                 old_cursor.slice(&edits.peek().unwrap().old_lines.start, Bias::Right, &());
 
             while let Some(edit) = edits.next() {
-                if edit.new_lines.start > InputPoint::from(new_transforms.summary().input.lines) {
-                    let summary = new_snapshot.text_summary_for_range(
-                        InputPoint::from(new_transforms.summary().input.lines)
-                            ..edit.new_lines.start,
+                if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) {
+                    let summary = new_tab_snapshot.text_summary_for_range(
+                        TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start,
                     );
                     new_transforms.push_or_extend(Transform::isomorphic(summary));
                 }
 
                 new_transforms.push_or_extend(Transform::isomorphic(
-                    new_snapshot.text_summary_for_range(edit.new_lines.clone()),
+                    new_tab_snapshot.text_summary_for_range(edit.new_lines.clone()),
                 ));
 
                 old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &());
                 if let Some(next_edit) = edits.peek() {
                     if next_edit.old_lines.start > old_cursor.seek_end(&()) {
                         if old_cursor.seek_end(&()) > edit.old_lines.end {
-                            let summary = self.input.text_summary_for_range(
+                            let summary = self.tab_snapshot.text_summary_for_range(
                                 edit.old_lines.end..old_cursor.seek_end(&()),
                             );
                             new_transforms.push_or_extend(Transform::isomorphic(summary));
@@ -118,7 +276,7 @@ impl Snapshot {
                 } else {
                     if old_cursor.seek_end(&()) > edit.old_lines.end {
                         let summary = self
-                            .input
+                            .tab_snapshot
                             .text_summary_for_range(edit.old_lines.end..old_cursor.seek_end(&()));
                         new_transforms.push_or_extend(Transform::isomorphic(summary));
                     }
@@ -129,15 +287,144 @@ impl Snapshot {
         }
 
         self.transforms = new_transforms;
-        self.input = new_snapshot;
+        self.tab_snapshot = new_tab_snapshot;
     }
 
-    pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
-        let mut transforms = self.transforms.cursor::<OutputPoint, InputPoint>();
+    async fn update(
+        &mut self,
+        new_tab_snapshot: TabSnapshot,
+        edits: &[TabEdit],
+        wrap_width: f32,
+        line_wrapper: &LineWrapper,
+    ) {
+        #[derive(Debug)]
+        struct RowEdit {
+            old_rows: Range<u32>,
+            new_rows: Range<u32>,
+        }
+
+        let mut edits = edits.into_iter().peekable();
+        let mut row_edits = Vec::new();
+        while let Some(edit) = edits.next() {
+            let mut row_edit = RowEdit {
+                old_rows: edit.old_lines.start.row()..edit.old_lines.end.row() + 1,
+                new_rows: edit.new_lines.start.row()..edit.new_lines.end.row() + 1,
+            };
+
+            while let Some(next_edit) = edits.peek() {
+                if next_edit.old_lines.start.row() <= row_edit.old_rows.end {
+                    row_edit.old_rows.end = next_edit.old_lines.end.row() + 1;
+                    row_edit.new_rows.end = next_edit.new_lines.end.row() + 1;
+                    edits.next();
+                } else {
+                    break;
+                }
+            }
+
+            row_edits.push(row_edit);
+        }
+
+        let mut new_transforms;
+        if row_edits.is_empty() {
+            new_transforms = self.transforms.clone();
+        } else {
+            let mut row_edits = row_edits.into_iter().peekable();
+            let mut old_cursor = self.transforms.cursor::<TabPoint, ()>();
+
+            new_transforms = old_cursor.slice(
+                &TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
+                Bias::Right,
+                &(),
+            );
+
+            while let Some(edit) = row_edits.next() {
+                if edit.new_rows.start > new_transforms.summary().input.lines.row {
+                    let summary = new_tab_snapshot.text_summary_for_range(
+                        TabPoint::new(new_transforms.summary().input.lines.row, 0)
+                            ..TabPoint::new(edit.new_rows.start, 0),
+                    );
+                    new_transforms.push_or_extend(Transform::isomorphic(summary));
+                }
+
+                let mut line = String::new();
+                let mut remaining = None;
+                let mut chunks = new_tab_snapshot.chunks_at(TabPoint::new(edit.new_rows.start, 0));
+                for _ in edit.new_rows.start..edit.new_rows.end {
+                    while let Some(chunk) = remaining.take().or_else(|| chunks.next()) {
+                        if let Some(ix) = chunk.find('\n') {
+                            line.push_str(&chunk[..ix + 1]);
+                            remaining = Some(&chunk[ix + 1..]);
+                            break;
+                        } else {
+                            line.push_str(chunk)
+                        }
+                    }
+
+                    if line.is_empty() {
+                        break;
+                    }
+
+                    let mut prev_boundary_ix = 0;
+                    for boundary_ix in line_wrapper.wrap_line_without_shaping(&line, wrap_width) {
+                        let wrapped = &line[prev_boundary_ix..boundary_ix];
+                        new_transforms
+                            .push_or_extend(Transform::isomorphic(TextSummary::from(wrapped)));
+                        new_transforms.push_or_extend(Transform::newline());
+                        prev_boundary_ix = boundary_ix;
+                    }
+
+                    if prev_boundary_ix < line.len() {
+                        new_transforms.push_or_extend(Transform::isomorphic(TextSummary::from(
+                            &line[prev_boundary_ix..],
+                        )));
+                    }
+
+                    line.clear();
+                    yield_now().await;
+                }
+
+                old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &());
+                if let Some(next_edit) = row_edits.peek() {
+                    if next_edit.old_rows.start > old_cursor.seek_end(&()).row() {
+                        if old_cursor.seek_end(&()) > TabPoint::new(edit.old_rows.end, 0) {
+                            let summary = self.tab_snapshot.text_summary_for_range(
+                                TabPoint::new(edit.old_rows.end, 0)..old_cursor.seek_end(&()),
+                            );
+                            new_transforms.push_or_extend(Transform::isomorphic(summary));
+                        }
+                        old_cursor.next(&());
+                        new_transforms.push_tree(
+                            old_cursor.slice(
+                                &TabPoint::new(next_edit.old_rows.start, 0),
+                                Bias::Right,
+                                &(),
+                            ),
+                            &(),
+                        );
+                    }
+                } else {
+                    if old_cursor.seek_end(&()) > TabPoint::new(edit.old_rows.end, 0) {
+                        let summary = self.tab_snapshot.text_summary_for_range(
+                            TabPoint::new(edit.old_rows.end, 0)..old_cursor.seek_end(&()),
+                        );
+                        new_transforms.push_or_extend(Transform::isomorphic(summary));
+                    }
+                    old_cursor.next(&());
+                    new_transforms.push_tree(old_cursor.suffix(&()), &());
+                }
+            }
+        }
+
+        self.transforms = new_transforms;
+        self.tab_snapshot = new_tab_snapshot;
+    }
+
+    pub fn chunks_at(&self, point: WrapPoint) -> Chunks {
+        let mut transforms = self.transforms.cursor::<WrapPoint, TabPoint>();
         transforms.seek(&point, Bias::Right, &());
         let input_position =
-            InputPoint(transforms.sum_start().0 + (point.0 - transforms.seek_start().0));
-        let input_chunks = self.input.chunks_at(input_position);
+            TabPoint(transforms.sum_start().0 + (point.0 - transforms.seek_start().0));
+        let input_chunks = self.tab_snapshot.chunks_at(input_position);
         Chunks {
             input_chunks,
             transforms,
@@ -147,15 +434,17 @@ 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>();
+        let output_start = WrapPoint::new(rows.start, 0);
+        let output_end = WrapPoint::new(rows.end, 0);
+        let mut transforms = self.transforms.cursor::<WrapPoint, TabPoint>();
         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());
+            TabPoint(transforms.sum_start().0 + (output_start.0 - transforms.seek_start().0));
+        let input_end = self
+            .to_input_point(output_end)
+            .min(self.tab_snapshot.max_point());
         HighlightedChunks {
-            input_chunks: self.input.highlighted_chunks(input_start..input_end),
+            input_chunks: self.tab_snapshot.highlighted_chunks(input_start..input_end),
             input_chunk: "",
             style_id: StyleId::default(),
             output_position: output_start,
@@ -164,13 +453,13 @@ impl Snapshot {
         }
     }
 
-    pub fn max_point(&self) -> OutputPoint {
-        self.to_output_point(self.input.max_point())
+    pub fn max_point(&self) -> WrapPoint {
+        self.to_output_point(self.tab_snapshot.max_point())
     }
 
     pub fn line_len(&self, row: u32) -> u32 {
         let mut len = 0;
-        for chunk in self.chunks_at(OutputPoint::new(row, 0)) {
+        for chunk in self.chunks_at(WrapPoint::new(row, 0)) {
             if let Some(newline_ix) = chunk.find('\n') {
                 len += newline_ix;
                 break;
@@ -186,10 +475,10 @@ impl Snapshot {
     }
 
     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 mut transforms = self.transforms.cursor::<WrapPoint, TabPoint>();
+        transforms.seek(&WrapPoint::new(start_row, 0), Bias::Right, &());
         let input_row = transforms.sum_start().row() + (start_row - transforms.seek_start().row());
-        let mut input_buffer_rows = self.input.buffer_rows(input_row);
+        let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
         let input_buffer_row = input_buffer_rows.next().unwrap();
         BufferRows {
             transforms,
@@ -200,21 +489,21 @@ impl Snapshot {
         }
     }
 
-    pub fn to_input_point(&self, point: OutputPoint) -> InputPoint {
-        let mut cursor = self.transforms.cursor::<OutputPoint, InputPoint>();
+    pub fn to_input_point(&self, point: WrapPoint) -> TabPoint {
+        let mut cursor = self.transforms.cursor::<WrapPoint, TabPoint>();
         cursor.seek(&point, Bias::Right, &());
-        InputPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
+        TabPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
     }
 
-    pub fn to_output_point(&self, point: InputPoint) -> OutputPoint {
-        let mut cursor = self.transforms.cursor::<InputPoint, OutputPoint>();
+    pub fn to_output_point(&self, point: TabPoint) -> WrapPoint {
+        let mut cursor = self.transforms.cursor::<TabPoint, WrapPoint>();
         cursor.seek(&point, Bias::Right, &());
-        OutputPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
+        WrapPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
     }
 
-    pub fn clip_point(&self, mut point: OutputPoint, bias: Bias) -> OutputPoint {
+    pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
         if bias == Bias::Left {
-            let mut cursor = self.transforms.cursor::<OutputPoint, ()>();
+            let mut cursor = self.transforms.cursor::<WrapPoint, ()>();
             cursor.seek(&point, Bias::Right, &());
             let transform = cursor.item().expect("invalid point");
             if !transform.is_isomorphic() {
@@ -222,34 +511,13 @@ impl Snapshot {
             }
         }
 
-        self.to_output_point(self.input.clip_point(self.to_input_point(point), bias))
+        self.to_output_point(
+            self.tab_snapshot
+                .clip_point(self.to_input_point(point), bias),
+        )
     }
 }
 
-pub struct Chunks<'a> {
-    input_chunks: tab_map::Chunks<'a>,
-    input_chunk: &'a str,
-    input_position: InputPoint,
-    transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
-}
-
-pub struct HighlightedChunks<'a> {
-    input_chunks: tab_map::HighlightedChunks<'a>,
-    input_chunk: &'a str,
-    style_id: StyleId,
-    output_position: OutputPoint,
-    max_output_row: u32,
-    transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
-}
-
-pub struct BufferRows<'a> {
-    input_buffer_rows: fold_map::BufferRows<'a>,
-    input_buffer_row: u32,
-    output_row: u32,
-    max_output_row: u32,
-    transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
-}
-
 impl<'a> Iterator for Chunks<'a> {
     type Item = &'a str;
 
@@ -344,7 +612,7 @@ impl<'a> Iterator for BufferRows<'a> {
         let buffer_row = self.input_buffer_row;
         self.output_row += 1;
         self.transforms
-            .seek_forward(&OutputPoint::new(self.output_row, 0), Bias::Left, &());
+            .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left, &());
         if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
             self.input_buffer_row = self.input_buffer_rows.next().unwrap();
         }
@@ -353,460 +621,6 @@ impl<'a> Iterator for BufferRows<'a> {
     }
 }
 
-struct State {
-    snapshot: Snapshot,
-    pending_edits: VecDeque<(InputSnapshot, Vec<InputEdit>)>,
-    last_background_output_version: usize,
-}
-
-pub struct WrapMap {
-    state: Mutex<State>,
-    notifications_rx: watch::Receiver<()>,
-    background_changes_tx: channel::Sender<Change>,
-    background_state: Arc<Mutex<BackgroundState>>,
-    _background_task: Task<()>,
-}
-
-impl WrapMap {
-    pub fn new(
-        input: InputSnapshot,
-        settings: Settings,
-        wrap_width: Option<f32>,
-        cx: &AppContext,
-    ) -> Self {
-        let snapshot = Snapshot::new(input.clone());
-        let (mut wrapper, background_state, notifications_rx) = BackgroundWrapper::new(
-            snapshot.clone(),
-            settings,
-            wrap_width,
-            cx.font_cache().clone(),
-            cx.platform().fonts(),
-        );
-        let (background_changes_tx, background_changes_rx) = channel::unbounded();
-        let background_task = cx.background().spawn(async move {
-            wrapper.run(input, background_changes_rx).await;
-        });
-
-        Self {
-            state: Mutex::new(State {
-                snapshot,
-                pending_edits: VecDeque::new(),
-                last_background_output_version: 0,
-            }),
-            background_changes_tx,
-            background_state,
-            _background_task: background_task,
-            notifications_rx,
-        }
-    }
-
-    pub fn sync(&self, input: InputSnapshot, edits: Vec<InputEdit>, cx: &AppContext) -> Snapshot {
-        let mut state = &mut *self.state.lock();
-        let mut background_state = self.background_state.lock();
-
-        let block = if input.version() > state.snapshot.input.version() {
-            self.background_changes_tx
-                .try_send(Change::Input {
-                    snapshot: input.clone(),
-                    edits: edits.clone(),
-                })
-                .unwrap();
-            state.pending_edits.push_back((input.clone(), edits));
-            true
-        } else {
-            background_state.is_wrapping
-        };
-
-        println!("will block? {}", block);
-        let mut updated_from_background = false;
-        if block {
-            let (sync_tx, sync_rx) = channel::unbounded();
-            background_state.sync_tx = Some(sync_tx);
-            drop(background_state);
-            cx.background().block_on(Duration::from_micros(500), async {
-                loop {
-                    sync_rx.recv().await.unwrap();
-                    let background_state = self.background_state.lock();
-                    state.snapshot = background_state.snapshot.clone();
-                    state.last_background_output_version = background_state.output_version;
-                    updated_from_background = true;
-                    if state.snapshot.input.version() == input.version() {
-                        break;
-                    }
-                }
-            });
-        } else {
-            drop(background_state);
-        }
-
-        {
-            let mut background_state = self.background_state.lock();
-            background_state.sync_tx = None;
-            if background_state.output_version > state.last_background_output_version {
-                println!("last chance to update");
-                state.snapshot = background_state.snapshot.clone();
-                state.last_background_output_version = background_state.output_version;
-                updated_from_background = true;
-            }
-        }
-
-        println!("updated from background? {}", updated_from_background);
-        if updated_from_background {
-            while let Some((pending_input, _)) = state.pending_edits.front() {
-                if pending_input.version() <= state.snapshot.input.version() {
-                    state.pending_edits.pop_front();
-                } else {
-                    break;
-                }
-            }
-
-            for (input, edits) in &state.pending_edits {
-                state.snapshot.interpolate(input.clone(), &edits);
-            }
-        }
-
-        state.snapshot.clone()
-    }
-
-    pub fn set_wrap_width(&self, width: Option<f32>) {
-        self.background_changes_tx
-            .try_send(Change::Width(width))
-            .unwrap();
-    }
-
-    pub fn notifications(&self) -> impl Stream<Item = ()> {
-        self.notifications_rx.clone()
-    }
-}
-
-struct BackgroundWrapper {
-    wrap_width: Option<f32>,
-    state: Arc<Mutex<BackgroundState>>,
-    snapshot: Snapshot,
-    notifications_tx: watch::Sender<()>,
-    line_wrapper: LineWrapper,
-}
-
-struct BackgroundState {
-    is_wrapping: bool,
-    sync_tx: Option<channel::Sender<()>>,
-    snapshot: Snapshot,
-    output_version: usize,
-}
-
-struct LineWrapper {
-    font_system: Arc<dyn FontSystem>,
-    font_cache: Arc<FontCache>,
-    font_id: FontId,
-    font_size: f32,
-    cached_ascii_char_widths: [f32; 128],
-    cached_other_char_widths: HashMap<char, f32>,
-}
-
-enum Change {
-    Input {
-        snapshot: InputSnapshot,
-        edits: Vec<tab_map::Edit>,
-    },
-    Width(Option<f32>),
-}
-
-impl BackgroundWrapper {
-    fn new(
-        snapshot: Snapshot,
-        settings: Settings,
-        wrap_width: Option<f32>,
-        font_cache: Arc<FontCache>,
-        font_system: Arc<dyn FontSystem>,
-    ) -> (Self, Arc<Mutex<BackgroundState>>, watch::Receiver<()>) {
-        let (notifications_tx, notifications_rx) = watch::channel();
-        let state = Arc::new(Mutex::new(BackgroundState {
-            is_wrapping: false,
-            sync_tx: None,
-            snapshot: snapshot.clone(),
-            output_version: 0,
-        }));
-        let wrapper = Self {
-            wrap_width,
-            state: state.clone(),
-            snapshot,
-            line_wrapper: LineWrapper::new(font_system, font_cache, settings),
-            notifications_tx,
-        };
-        (wrapper, state, notifications_rx)
-    }
-
-    async fn run(&mut self, input: InputSnapshot, changes_rx: channel::Receiver<Change>) {
-        let edit = InputEdit {
-            old_lines: Default::default()..input.max_point(),
-            new_lines: Default::default()..input.max_point(),
-        };
-        self.sync(input, vec![edit]);
-
-        while let Ok(change) = changes_rx.recv().await {
-            match change {
-                Change::Input { snapshot, edits } => {
-                    self.sync(snapshot, edits);
-                }
-                Change::Width(wrap_width) => {
-                    if self.wrap_width != wrap_width {
-                        self.wrap_width = wrap_width;
-                        let edit = InputEdit {
-                            old_lines: Default::default()..self.snapshot.input.max_point(),
-                            new_lines: Default::default()..self.snapshot.input.max_point(),
-                        };
-                        self.sync(self.snapshot.input.clone(), vec![edit]);
-                    }
-                }
-            };
-        }
-    }
-
-    fn sync(&mut self, new_snapshot: InputSnapshot, edits: Vec<InputEdit>) {
-        #[derive(Debug)]
-        struct RowEdit {
-            old_rows: Range<u32>,
-            new_rows: Range<u32>,
-        }
-
-        self.state.lock().is_wrapping = true;
-
-        let mut edits = edits.into_iter().peekable();
-        let mut row_edits = Vec::new();
-        while let Some(edit) = edits.next() {
-            let mut row_edit = RowEdit {
-                old_rows: edit.old_lines.start.row()..edit.old_lines.end.row() + 1,
-                new_rows: edit.new_lines.start.row()..edit.new_lines.end.row() + 1,
-            };
-
-            while let Some(next_edit) = edits.peek() {
-                if next_edit.old_lines.start.row() <= row_edit.old_rows.end {
-                    row_edit.old_rows.end = next_edit.old_lines.end.row() + 1;
-                    row_edit.new_rows.end = next_edit.new_lines.end.row() + 1;
-                    edits.next();
-                } else {
-                    break;
-                }
-            }
-
-            row_edits.push(row_edit);
-        }
-
-        let mut new_transforms;
-        if row_edits.is_empty() {
-            new_transforms = self.snapshot.transforms.clone();
-        } else {
-            let mut row_edits = row_edits.into_iter().peekable();
-            let mut old_cursor = self.snapshot.transforms.cursor::<InputPoint, ()>();
-
-            new_transforms = old_cursor.slice(
-                &InputPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
-                Bias::Right,
-                &(),
-            );
-
-            while let Some(edit) = row_edits.next() {
-                if edit.new_rows.start > new_transforms.summary().input.lines.row {
-                    let summary = new_snapshot.text_summary_for_range(
-                        InputPoint::new(new_transforms.summary().input.lines.row, 0)
-                            ..InputPoint::new(edit.new_rows.start, 0),
-                    );
-                    new_transforms.push_or_extend(Transform::isomorphic(summary));
-                }
-
-                let mut input_row = edit.new_rows.start;
-                let mut line = String::new();
-                let mut remaining = None;
-                let mut chunks = new_snapshot.chunks_at(InputPoint::new(input_row, 0));
-                loop {
-                    while let Some(chunk) = remaining.take().or_else(|| chunks.next()) {
-                        if let Some(ix) = chunk.find('\n') {
-                            line.push_str(&chunk[..ix + 1]);
-                            remaining = Some(&chunk[ix + 1..]);
-                            break;
-                        } else {
-                            line.push_str(chunk)
-                        }
-                    }
-
-                    if line.is_empty() {
-                        break;
-                    }
-
-                    let mut prev_boundary_ix = 0;
-                    if let Some(wrap_width) = self.wrap_width {
-                        for boundary_ix in self
-                            .line_wrapper
-                            .wrap_line_without_shaping(&line, wrap_width)
-                        {
-                            let wrapped = &line[prev_boundary_ix..boundary_ix];
-                            new_transforms
-                                .push_or_extend(Transform::isomorphic(TextSummary::from(wrapped)));
-                            new_transforms.push_or_extend(Transform::newline());
-                            prev_boundary_ix = boundary_ix;
-                        }
-                    }
-
-                    if prev_boundary_ix < line.len() {
-                        new_transforms.push_or_extend(Transform::isomorphic(TextSummary::from(
-                            &line[prev_boundary_ix..],
-                        )));
-                    }
-
-                    line.clear();
-                    input_row += 1;
-                    if input_row == edit.new_rows.end {
-                        break;
-                    }
-                }
-
-                old_cursor.seek_forward(&InputPoint::new(edit.old_rows.end, 0), Bias::Right, &());
-                if let Some(next_edit) = row_edits.peek() {
-                    if next_edit.old_rows.start > old_cursor.seek_end(&()).row() {
-                        if old_cursor.seek_end(&()) > InputPoint::new(edit.old_rows.end, 0) {
-                            let summary = self.snapshot.input.text_summary_for_range(
-                                InputPoint::new(edit.old_rows.end, 0)..old_cursor.seek_end(&()),
-                            );
-                            new_transforms.push_or_extend(Transform::isomorphic(summary));
-                        }
-                        old_cursor.next(&());
-                        new_transforms.push_tree(
-                            old_cursor.slice(
-                                &InputPoint::new(next_edit.old_rows.start, 0),
-                                Bias::Right,
-                                &(),
-                            ),
-                            &(),
-                        );
-                    }
-                } else {
-                    if old_cursor.seek_end(&()) > InputPoint::new(edit.old_rows.end, 0) {
-                        let summary = self.snapshot.input.text_summary_for_range(
-                            InputPoint::new(edit.old_rows.end, 0)..old_cursor.seek_end(&()),
-                        );
-                        new_transforms.push_or_extend(Transform::isomorphic(summary));
-                    }
-                    old_cursor.next(&());
-                    new_transforms.push_tree(old_cursor.suffix(&()), &());
-                }
-            }
-        }
-
-        self.snapshot.transforms = new_transforms;
-        self.snapshot.input = new_snapshot;
-
-        let mut state = self.state.lock();
-        state.output_version += 1;
-        state.is_wrapping = false;
-        state.snapshot = self.snapshot.clone();
-        if let Some(sync_tx) = state.sync_tx.as_ref() {
-            let _ = sync_tx.try_send(());
-        } else {
-            let _ = self.notifications_tx.try_send(());
-        }
-    }
-}
-
-impl LineWrapper {
-    fn new(
-        font_system: Arc<dyn FontSystem>,
-        font_cache: Arc<FontCache>,
-        settings: Settings,
-    ) -> Self {
-        let font_id = font_cache
-            .select_font(settings.buffer_font_family, &Default::default())
-            .unwrap();
-        let font_size = settings.buffer_font_size;
-        Self {
-            font_cache,
-            font_system,
-            font_id,
-            font_size,
-            cached_ascii_char_widths: [f32::NAN; 128],
-            cached_other_char_widths: HashMap::new(),
-        }
-    }
-
-    fn wrap_line_with_shaping(&mut self, line: &str, wrap_width: f32) -> Vec<usize> {
-        self.font_system
-            .wrap_line(line, self.font_id, self.font_size, wrap_width)
-    }
-
-    fn wrap_line_without_shaping(&mut self, line: &str, wrap_width: f32) -> Vec<usize> {
-        let mut width = 0.0;
-        let mut result = Vec::new();
-        let mut last_boundary_ix = 0;
-        let mut last_boundary_width = 0.0;
-        let mut prev_c = '\0';
-        for (ix, c) in line.char_indices() {
-            if self.is_boundary(prev_c, c) {
-                last_boundary_ix = ix;
-                last_boundary_width = width;
-            }
-
-            let char_width = self.width_for_char(c);
-            width += char_width;
-            if width > wrap_width {
-                if last_boundary_ix > 0 {
-                    result.push(last_boundary_ix);
-                    width -= last_boundary_width;
-                    last_boundary_ix = 0;
-                } else {
-                    result.push(ix);
-                    width = char_width;
-                }
-            }
-            prev_c = c;
-        }
-        result
-    }
-
-    fn is_boundary(&self, prev: char, next: char) -> bool {
-        if prev == ' ' || next == ' ' {
-            return true;
-        }
-        false
-    }
-
-    fn width_for_char(&mut self, c: char) -> f32 {
-        if (c as u32) < 128 {
-            let mut width = self.cached_ascii_char_widths[c as usize];
-            if width.is_nan() {
-                width = self.compute_width_for_char(c);
-                self.cached_ascii_char_widths[c as usize] = width;
-            }
-            width
-        } else {
-            let mut width = self
-                .cached_other_char_widths
-                .get(&c)
-                .copied()
-                .unwrap_or(f32::NAN);
-            if width.is_nan() {
-                width = self.compute_width_for_char(c);
-                self.cached_other_char_widths.insert(c, width);
-            }
-            width
-        }
-    }
-
-    fn compute_width_for_char(&self, c: char) -> f32 {
-        self.font_system
-            .layout_line(
-                &c.to_string(),
-                self.font_size,
-                &[(1, self.font_id, Default::default())],
-            )
-            .width
-    }
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct Transform {
-    summary: TransformSummary,
-    display_text: Option<&'static str>,
-}
-
 impl Transform {
     fn isomorphic(summary: TextSummary) -> Self {
         Self {

zed/src/editor/element.rs 🔗

@@ -366,7 +366,7 @@ impl Element for EditorElement {
         let wrap_width = text_size.x() - text_offset.x() - overscroll.x();
         // TODO: Core text doesn't seem to be keeping our lines below the specified wrap width. Find out why.
         let wrap_width = wrap_width - em_width;
-        view.set_wrap_width(wrap_width);
+        view.set_wrap_width(wrap_width, app);
 
         let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, app);