WIP

Nathan Sobo created

Change summary

crates/editor2/src/display_map.rs           | 106 +++++++-------
crates/editor2/src/display_map/wrap_map.rs  | 169 +++++++++++-----------
crates/gpui2/src/text_system/line.rs        |   5 
crates/gpui2/src/text_system/line_layout.rs |  22 ++
4 files changed, 165 insertions(+), 137 deletions(-)

Detailed changes

crates/editor2/src/display_map.rs 🔗

@@ -11,7 +11,7 @@ use crate::{
 pub use block_map::{BlockMap, BlockPoint};
 use collections::{BTreeMap, HashMap, HashSet};
 use fold_map::FoldMap;
-use gpui::{FontId, HighlightStyle, Hsla, Line, Model, ModelContext};
+use gpui::{Font, FontId, HighlightStyle, Hsla, Line, Model, ModelContext, Pixels};
 use inlay_map::InlayMap;
 use language::{
     language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
@@ -58,8 +58,8 @@ pub struct DisplayMap {
 impl DisplayMap {
     pub fn new(
         buffer: Model<MultiBuffer>,
-        font_id: FontId,
-        font_size: f32,
+        font: Font,
+        font_size: Pixels,
         wrap_width: Option<f32>,
         buffer_header_height: u8,
         excerpt_header_height: u8,
@@ -71,7 +71,7 @@ impl DisplayMap {
         let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
         let (fold_map, snapshot) = FoldMap::new(snapshot);
         let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
-        let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
+        let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
         let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
         cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
         DisplayMap {
@@ -239,7 +239,7 @@ impl DisplayMap {
         cleared
     }
 
-    pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
+    pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
         self.wrap_map
             .update(cx, |map, cx| map.set_font(font_id, font_size, cx))
     }
@@ -248,7 +248,7 @@ impl DisplayMap {
         self.fold_map.set_ellipses_color(color)
     }
 
-    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
+    pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
         self.wrap_map
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
@@ -558,62 +558,62 @@ impl DisplaySnapshot {
         &self,
         display_row: u32,
         TextLayoutDetails {
-            text_system: font_cache,
-            text_system: text_layout_cache,
+            text_system,
             editor_style,
         }: &TextLayoutDetails,
     ) -> Line {
-        let mut styles = Vec::new();
-        let mut line = String::new();
-        let mut ended_in_newline = false;
-
-        let range = display_row..display_row + 1;
-        for chunk in self.highlighted_chunks(range, false, editor_style) {
-            line.push_str(chunk.chunk);
-
-            let text_style = if let Some(style) = chunk.style {
-                editor_style
-                    .text
-                    .clone()
-                    .highlight(style, font_cache)
-                    .map(Cow::Owned)
-                    .unwrap_or_else(|_| Cow::Borrowed(&editor_style.text))
-            } else {
-                Cow::Borrowed(&editor_style.text)
-            };
-            ended_in_newline = chunk.chunk.ends_with("\n");
-
-            styles.push(
-                todo!(), // len: chunk.chunk.len(),
-                         // font_id: text_style.font_id,
-                         // color: text_style.color,
-                         // underline: text_style.underline,
-            );
-        }
-
-        // our pixel positioning logic assumes each line ends in \n,
-        // this is almost always true except for the last line which
-        // may have no trailing newline.
-        if !ended_in_newline && display_row == self.max_point().row() {
-            line.push_str("\n");
-
-            todo!();
-            // styles.push(RunStyle {
-            //     len: "\n".len(),
-            //     font_id: editor_style.text.font_id,
-            //     color: editor_style.text_color,
-            //     underline: editor_style.text.underline,
-            // });
-        }
-
-        text_layout_cache.layout_text(&line, editor_style.text.font_size, &styles, None)
+        todo!()
+        // let mut styles = Vec::new();
+        // let mut line = String::new();
+        // let mut ended_in_newline = false;
+
+        // let range = display_row..display_row + 1;
+        // for chunk in self.highlighted_chunks(range, false, editor_style) {
+        //     line.push_str(chunk.chunk);
+
+        //     let text_style = if let Some(style) = chunk.style {
+        //         editor_style
+        //             .text
+        //             .clone()
+        //             .highlight(style, text_system)
+        //             .map(Cow::Owned)
+        //             .unwrap_or_else(|_| Cow::Borrowed(&editor_style.text))
+        //     } else {
+        //         Cow::Borrowed(&editor_style.text)
+        //     };
+        //     ended_in_newline = chunk.chunk.ends_with("\n");
+
+        //     styles.push(
+        //         todo!(), // len: chunk.chunk.len(),
+        //                  // font_id: text_style.font_id,
+        //                  // color: text_style.color,
+        //                  // underline: text_style.underline,
+        //     );
+        // }
+
+        // // our pixel positioning logic assumes each line ends in \n,
+        // // this is almost always true except for the last line which
+        // // may have no trailing newline.
+        // if !ended_in_newline && display_row == self.max_point().row() {
+        //     line.push_str("\n");
+
+        //     todo!();
+        //     // styles.push(RunStyle {
+        //     //     len: "\n".len(),
+        //     //     font_id: editor_style.text.font_id,
+        //     //     color: editor_style.text_color,
+        //     //     underline: editor_style.text.underline,
+        //     // });
+        // }
+
+        // text_system.layout_text(&line, editor_style.text.font_size, &styles, None)
     }
 
     pub fn x_for_point(
         &self,
         display_point: DisplayPoint,
         text_layout_details: &TextLayoutDetails,
-    ) -> f32 {
+    ) -> Pixels {
         let layout_line = self.lay_out_line_for_row(display_point.row(), text_layout_details);
         layout_line.x_for_index(display_point.column() as usize)
     }

crates/editor2/src/display_map/wrap_map.rs 🔗

@@ -4,13 +4,14 @@ use super::{
     Highlights,
 };
 use crate::MultiBufferSnapshot;
-use gpui::{AppContext, FontId, LineWrapper, Model, ModelContext, Pixels, Task};
+use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
 use language::{Chunk, Point};
 use lazy_static::lazy_static;
 use smol::future::yield_now;
 use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
 use sum_tree::{Bias, Cursor, SumTree};
 use text::Patch;
+use util::ResultExt;
 
 pub use super::tab_map::TextSummary;
 pub type WrapEdit = text::Edit<u32>;
@@ -22,7 +23,7 @@ pub struct WrapMap {
     edits_since_sync: Patch<u32>,
     wrap_width: Option<Pixels>,
     background_task: Option<Task<()>>,
-    font: (FontId, Pixels),
+    font: (Font, Pixels),
 }
 
 #[derive(Clone)]
@@ -68,14 +69,14 @@ pub struct WrapBufferRows<'a> {
 impl WrapMap {
     pub fn new(
         tab_snapshot: TabSnapshot,
-        font_id: FontId,
-        font_size: f32,
-        wrap_width: Option<f32>,
+        font: Font,
+        font_size: Pixels,
+        wrap_width: Option<Pixels>,
         cx: &mut AppContext,
     ) -> (Model<Self>, WrapSnapshot) {
         let handle = cx.build_model(|cx| {
             let mut this = Self {
-                font: (font_id, font_size),
+                font: (font, font_size),
                 wrap_width: None,
                 pending_edits: Default::default(),
                 interpolated_edits: Default::default(),
@@ -115,14 +116,9 @@ impl WrapMap {
         (self.snapshot.clone(), mem::take(&mut self.edits_since_sync))
     }
 
-    pub fn set_font(
-        &mut self,
-        font_id: FontId,
-        font_size: f32,
-        cx: &mut ModelContext<Self>,
-    ) -> bool {
-        if (font_id, font_size) != self.font {
-            self.font = (font_id, font_size);
+    pub fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut ModelContext<Self>) -> bool {
+        if (font, font_size) != self.font {
+            self.font = (font, font_size);
             self.rewrap(cx);
             true
         } else {
@@ -151,28 +147,32 @@ impl WrapMap {
 
         if let Some(wrap_width) = self.wrap_width {
             let mut new_snapshot = self.snapshot.clone();
-            let font_cache = cx.font_cache().clone();
+            let mut edits = Patch::default();
+            let text_system = cx.text_system().clone();
             let (font_id, font_size) = self.font;
-            let task = cx.background().spawn(async move {
-                let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
-                let tab_snapshot = new_snapshot.tab_snapshot.clone();
-                let range = TabPoint::zero()..tab_snapshot.max_point();
-                let edits = new_snapshot
-                    .update(
-                        tab_snapshot,
-                        &[TabEdit {
-                            old: range.clone(),
-                            new: range.clone(),
-                        }],
-                        wrap_width,
-                        &mut line_wrapper,
-                    )
-                    .await;
+            let task = cx.background_executor().spawn(async move {
+                if let Some(mut line_wrapper) =
+                    text_system.line_wrapper(font_id, font_size).log_err()
+                {
+                    let tab_snapshot = new_snapshot.tab_snapshot.clone();
+                    let range = TabPoint::zero()..tab_snapshot.max_point();
+                    let edits = new_snapshot
+                        .update(
+                            tab_snapshot,
+                            &[TabEdit {
+                                old: range.clone(),
+                                new: range.clone(),
+                            }],
+                            wrap_width,
+                            &mut line_wrapper,
+                        )
+                        .await;
+                }
                 (new_snapshot, edits)
             });
 
             match cx
-                .background()
+                .background_executor()
                 .block_with_timeout(Duration::from_millis(5), task)
             {
                 Ok((snapshot, edits)) => {
@@ -235,23 +235,25 @@ impl WrapMap {
             if self.background_task.is_none() {
                 let pending_edits = self.pending_edits.clone();
                 let mut snapshot = self.snapshot.clone();
-                let font_cache = cx.font_cache().clone();
+                let text_system = cx.text_system().clone();
                 let (font_id, font_size) = self.font;
-                let update_task = cx.background().spawn(async move {
-                    let mut line_wrapper = font_cache.line_wrapper(font_id, font_size);
-
+                let update_task = cx.background_executor().spawn(async move {
                     let mut edits = Patch::default();
-                    for (tab_snapshot, tab_edits) in pending_edits {
-                        let wrap_edits = snapshot
-                            .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
-                            .await;
-                        edits = edits.compose(&wrap_edits);
+                    if let Some(mut line_wrapper) =
+                        text_system.line_wrapper(font_id, font_size).log_err()
+                    {
+                        for (tab_snapshot, tab_edits) in pending_edits {
+                            let wrap_edits = snapshot
+                                .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
+                                .await;
+                            edits = edits.compose(&wrap_edits);
+                        }
                     }
                     (snapshot, edits)
                 });
 
                 match cx
-                    .background()
+                    .background_executor()
                     .block_with_timeout(Duration::from_millis(1), update_task)
                 {
                     Ok((snapshot, output_edits)) => {
@@ -733,48 +735,49 @@ impl WrapSnapshot {
     }
 
     fn check_invariants(&self) {
-        #[cfg(test)]
-        {
-            assert_eq!(
-                TabPoint::from(self.transforms.summary().input.lines),
-                self.tab_snapshot.max_point()
-            );
-
-            {
-                let mut transforms = self.transforms.cursor::<()>().peekable();
-                while let Some(transform) = transforms.next() {
-                    if let Some(next_transform) = transforms.peek() {
-                        assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
-                    }
-                }
-            }
-
-            let text = language::Rope::from(self.text().as_str());
-            let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
-            let mut expected_buffer_rows = Vec::new();
-            let mut prev_tab_row = 0;
-            for display_row in 0..=self.max_point().row() {
-                let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
-                if tab_point.row() == prev_tab_row && display_row != 0 {
-                    expected_buffer_rows.push(None);
-                } else {
-                    expected_buffer_rows.push(input_buffer_rows.next().unwrap());
-                }
-
-                prev_tab_row = tab_point.row();
-                assert_eq!(self.line_len(display_row), text.line_len(display_row));
-            }
-
-            for start_display_row in 0..expected_buffer_rows.len() {
-                assert_eq!(
-                    self.buffer_rows(start_display_row as u32)
-                        .collect::<Vec<_>>(),
-                    &expected_buffer_rows[start_display_row..],
-                    "invalid buffer_rows({}..)",
-                    start_display_row
-                );
-            }
-        }
+        // todo!()
+        // #[cfg(test)]
+        // {
+        //     assert_eq!(
+        //         TabPoint::from(self.transforms.summary().input.lines),
+        //         self.tab_snapshot.max_point()
+        //     );
+
+        //     {
+        //         let mut transforms = self.transforms.cursor::<()>().peekable();
+        //         while let Some(transform) = transforms.next() {
+        //             if let Some(next_transform) = transforms.peek() {
+        //                 assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
+        //             }
+        //         }
+        //     }
+
+        //     let text = language::Rope::from(self.text().as_str());
+        //     let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
+        //     let mut expected_buffer_rows = Vec::new();
+        //     let mut prev_tab_row = 0;
+        //     for display_row in 0..=self.max_point().row() {
+        //         let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
+        //         if tab_point.row() == prev_tab_row && display_row != 0 {
+        //             expected_buffer_rows.push(None);
+        //         } else {
+        //             expected_buffer_rows.push(input_buffer_rows.next().unwrap());
+        //         }
+
+        //         prev_tab_row = tab_point.row();
+        //         assert_eq!(self.line_len(display_row), text.line_len(display_row));
+        //     }
+
+        //     for start_display_row in 0..expected_buffer_rows.len() {
+        //         assert_eq!(
+        //             self.buffer_rows(start_display_row as u32)
+        //                 .collect::<Vec<_>>(),
+        //             &expected_buffer_rows[start_display_row..],
+        //             "invalid buffer_rows({}..)",
+        //             start_display_row
+        //         );
+        //     }
+        // }
     }
 }
 

crates/gpui2/src/text_system/line.rs 🔗

@@ -2,6 +2,7 @@ use crate::{
     black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
     UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
 };
+use derive_more::{Deref, DerefMut};
 use smallvec::SmallVec;
 use std::sync::Arc;
 
@@ -12,8 +13,10 @@ pub struct DecorationRun {
     pub underline: Option<UnderlineStyle>,
 }
 
-#[derive(Clone, Default, Debug)]
+#[derive(Clone, Default, Debug, Deref, DerefMut)]
 pub struct Line {
+    #[deref]
+    #[deref_mut]
     pub(crate) layout: Arc<WrappedLineLayout>,
     pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
 }

crates/gpui2/src/text_system/line_layout.rs 🔗

@@ -48,6 +48,28 @@ impl LineLayout {
         }
     }
 
+    /// closest_index_for_x returns the character boundary closest to the given x coordinate
+    /// (e.g. to handle aligning up/down arrow keys)
+    pub fn closest_index_for_x(&self, x: Pixels) -> usize {
+        let mut prev_index = 0;
+        let mut prev_x = px(0.);
+
+        for run in self.runs.iter() {
+            for glyph in run.glyphs.iter() {
+                if glyph.position.x >= x {
+                    if glyph.position.x - x < x - prev_x {
+                        return glyph.index;
+                    } else {
+                        return prev_index;
+                    }
+                }
+                prev_index = glyph.index;
+                prev_x = glyph.position.x;
+            }
+        }
+        prev_index
+    }
+
     pub fn x_for_index(&self, index: usize) -> Pixels {
         for run in &self.runs {
             for glyph in &run.glyphs {