From a731f8fb1ef0521b85f2a05a6ac0f45c21fccc08 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 2 Nov 2023 21:28:56 -0600 Subject: [PATCH] WIP --- crates/editor2/src/display_map.rs | 94 ++++++------ crates/editor2/src/display_map/wrap_map.rs | 161 ++++++++++---------- crates/gpui2/src/text_system/line.rs | 5 +- crates/gpui2/src/text_system/line_layout.rs | 22 +++ 4 files changed, 155 insertions(+), 127 deletions(-) diff --git a/crates/editor2/src/display_map.rs b/crates/editor2/src/display_map.rs index 94391f7cb5..0635154910 100644 --- a/crates/editor2/src/display_map.rs +++ b/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, - font_id: FontId, - font_size: f32, + font: Font, + font_size: Pixels, wrap_width: Option, 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) -> bool { + pub fn set_font(&self, font: Font, font_size: Pixels, cx: &mut ModelContext) -> 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, cx: &mut ModelContext) -> bool { + pub fn set_wrap_width(&self, width: Option, cx: &mut ModelContext) -> 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; + 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 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"); + // 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, - ); - } + // 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"); + // // 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, - // }); - } + // 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) + // 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) } diff --git a/crates/editor2/src/display_map/wrap_map.rs b/crates/editor2/src/display_map/wrap_map.rs index 06f06fb5c3..c7c55348b1 100644 --- a/crates/editor2/src/display_map/wrap_map.rs +++ b/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; @@ -22,7 +23,7 @@ pub struct WrapMap { edits_since_sync: Patch, wrap_width: Option, background_task: Option>, - 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, + font: Font, + font_size: Pixels, + wrap_width: Option, cx: &mut AppContext, ) -> (Model, 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, - ) -> 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) -> 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() - ); + // 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 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()); - } + // 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)); - } + // 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::>(), - &expected_buffer_rows[start_display_row..], - "invalid buffer_rows({}..)", - start_display_row - ); - } - } + // for start_display_row in 0..expected_buffer_rows.len() { + // assert_eq!( + // self.buffer_rows(start_display_row as u32) + // .collect::>(), + // &expected_buffer_rows[start_display_row..], + // "invalid buffer_rows({}..)", + // start_display_row + // ); + // } + // } } } diff --git a/crates/gpui2/src/text_system/line.rs b/crates/gpui2/src/text_system/line.rs index bfd1487d91..ad70a79bb1 100644 --- a/crates/gpui2/src/text_system/line.rs +++ b/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, } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, Deref, DerefMut)] pub struct Line { + #[deref] + #[deref_mut] pub(crate) layout: Arc, pub(crate) decorations: SmallVec<[DecorationRun; 32]>, } diff --git a/crates/gpui2/src/text_system/line_layout.rs b/crates/gpui2/src/text_system/line_layout.rs index 8ce859218b..7682aaa1b8 100644 --- a/crates/gpui2/src/text_system/line_layout.rs +++ b/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 {