diff --git a/gpui/src/font_cache.rs b/gpui/src/font_cache.rs index ddb7c9c28801ee490e03c8bd43c17189742dcf40..c0255a7af5f251b9828e4788dba6443f30efcc5b 100644 --- a/gpui/src/font_cache.rs +++ b/gpui/src/font_cache.rs @@ -141,8 +141,8 @@ impl FontCache { pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F { let bounding_box = self.metric(font_id, |m| m.bounding_box); - let width = self.scale_metric(bounding_box.width(), font_id, font_size); - let height = self.scale_metric(bounding_box.height(), font_id, font_size); + let width = bounding_box.width() * self.em_scale(font_id, font_size); + let height = bounding_box.height() * self.em_scale(font_id, font_size); vec2f(width, height) } @@ -154,28 +154,28 @@ impl FontCache { glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap(); bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap(); } - self.scale_metric(bounds.width(), font_id, font_size) + bounds.width() * self.em_scale(font_id, font_size) } pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 { let height = self.metric(font_id, |m| m.bounding_box.height()); - self.scale_metric(height, font_id, font_size) + (height * self.em_scale(font_id, font_size)).ceil() } pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 { - self.scale_metric(self.metric(font_id, |m| m.cap_height), font_id, font_size) + self.metric(font_id, |m| m.cap_height) * self.em_scale(font_id, font_size) } pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 { - self.scale_metric(self.metric(font_id, |m| m.ascent), font_id, font_size) + self.metric(font_id, |m| m.ascent) * self.em_scale(font_id, font_size) } pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 { - self.scale_metric(self.metric(font_id, |m| -m.descent), font_id, font_size) + self.metric(font_id, |m| -m.descent) * self.em_scale(font_id, font_size) } - pub fn scale_metric(&self, metric: f32, font_id: FontId, font_size: f32) -> f32 { - metric * font_size / self.metric(font_id, |m| m.units_per_em as f32) + pub fn em_scale(&self, font_id: FontId, font_size: f32) -> f32 { + font_size / self.metric(font_id, |m| m.units_per_em as f32) } pub fn line_wrapper(self: &Arc, font_id: FontId, font_size: f32) -> LineWrapperHandle { diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 9df36464f6c065206d4538129c3dbe1192281282..3ec8aad9626bf78e4beb79709b89e525be679ad9 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -127,6 +127,22 @@ impl TextStyle { } }) } + + pub fn line_height(&self, font_cache: &FontCache) -> f32 { + font_cache.line_height(self.font_id, self.font_size) + } + + pub fn em_width(&self, font_cache: &FontCache) -> f32 { + font_cache.em_width(self.font_id, self.font_size) + } + + pub fn descent(&self, font_cache: &FontCache) -> f32 { + font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache) + } + + fn em_scale(&self, font_cache: &FontCache) -> f32 { + font_cache.em_scale(self.font_id, self.font_size) + } } impl From for HighlightStyle { diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 3bdb200226f0d9222891c6e7220ca9e6a0cf440e..af7f0e4db339246a96a9e3e21dc48c19222b2ce3 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -4,7 +4,7 @@ mod element; pub mod movement; use crate::{ - settings::{HighlightId, Settings}, + settings::Settings, theme::Theme, time::ReplicaId, util::{post_inc, Bias}, @@ -17,15 +17,10 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, - color::Color, - font_cache::FamilyId, - fonts::{Properties as FontProperties, TextStyle}, - geometry::vector::Vector2F, - keymap::Binding, - text_layout::{self, RunStyle}, - AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle, - MutableAppContext, RenderContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle, + action, color::Color, font_cache::FamilyId, fonts::TextStyle, geometry::vector::Vector2F, + keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, + ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, + WeakViewHandle, }; use postage::watch; use serde::{Deserialize, Serialize}; @@ -34,8 +29,6 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, - collections::BTreeMap, - fmt::Write, iter::FromIterator, mem, ops::{Range, RangeInclusive}, @@ -2328,263 +2321,60 @@ impl Editor { } impl Snapshot { - pub fn scroll_position(&self) -> Vector2F { - compute_scroll_position( - &self.display_snapshot, - self.scroll_position, - &self.scroll_top_anchor, - ) + pub fn is_empty(&self) -> bool { + self.display_snapshot.is_empty() } - pub fn max_point(&self) -> DisplayPoint { - self.display_snapshot.max_point() + pub fn is_focused(&self) -> bool { + self.is_focused } - pub fn longest_row(&self) -> u32 { - self.display_snapshot.longest_row() + pub fn placeholder_text(&self) -> Option<&Arc> { + self.placeholder_text.as_ref() } - pub fn line_len(&self, display_row: u32) -> u32 { - self.display_snapshot.line_len(display_row) + pub fn buffer_row_count(&self) -> u32 { + self.display_snapshot.buffer_row_count() } - pub fn font_ascent(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - let ascent = font_cache.metric(font_id, |m| m.ascent); - font_cache.scale_metric(ascent, font_id, self.font_size) + pub fn buffer_rows(&self, start_row: u32) -> BufferRows { + self.display_snapshot.buffer_rows(start_row) } - pub fn font_descent(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - let descent = font_cache.metric(font_id, |m| m.descent); - font_cache.scale_metric(descent, font_id, self.font_size) + pub fn highlighted_chunks_for_rows( + &mut self, + display_rows: Range, + ) -> display_map::HighlightedChunks { + self.display_snapshot + .highlighted_chunks_for_rows(display_rows) } - pub fn line_height(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - font_cache.line_height(font_id, self.font_size).ceil() + pub fn theme(&self) -> &Arc { + &self.theme } - pub fn em_width(&self, font_cache: &FontCache) -> f32 { - let font_id = font_cache.default_font(self.font_family); - font_cache.em_width(font_id, self.font_size) + pub fn scroll_position(&self) -> Vector2F { + compute_scroll_position( + &self.display_snapshot, + self.scroll_position, + &self.scroll_top_anchor, + ) } - // TODO: Can we make this not return a result? - pub fn max_line_number_width( - &self, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - ) -> Result { - let font_size = self.font_size; - let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - let digit_count = (self.display_snapshot.buffer_row_count() as f32) - .log10() - .floor() as usize - + 1; - - Ok(layout_cache - .layout_str( - "1".repeat(digit_count).as_str(), - font_size, - &[( - digit_count, - RunStyle { - font_id, - color: Color::black(), - underline: false, - }, - )], - ) - .width()) + pub fn max_point(&self) -> DisplayPoint { + self.display_snapshot.max_point() } - pub fn layout_line_numbers( - &self, - rows: Range, - active_rows: &BTreeMap, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - theme: &Theme, - ) -> Result>> { - let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - - let mut layouts = Vec::with_capacity(rows.len()); - let mut line_number = String::new(); - for (ix, (buffer_row, soft_wrapped)) in self - .display_snapshot - .buffer_rows(rows.start) - .take((rows.end - rows.start) as usize) - .enumerate() - { - let display_row = rows.start + ix as u32; - let color = if active_rows.contains_key(&display_row) { - theme.editor.line_number_active - } else { - theme.editor.line_number - }; - if soft_wrapped { - layouts.push(None); - } else { - line_number.clear(); - write!(&mut line_number, "{}", buffer_row + 1).unwrap(); - layouts.push(Some(layout_cache.layout_str( - &line_number, - self.font_size, - &[( - line_number.len(), - RunStyle { - font_id, - color, - underline: false, - }, - )], - ))); - } - } - - Ok(layouts) + pub fn longest_row(&self) -> u32 { + self.display_snapshot.longest_row() } - pub fn layout_lines( - &mut self, - mut rows: Range, - style: &EditorStyle, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - ) -> Result> { - rows.end = cmp::min(rows.end, self.display_snapshot.max_point().row() + 1); - if rows.start >= rows.end { - return Ok(Vec::new()); - } - - // When the editor is empty and unfocused, then show the placeholder. - if self.display_snapshot.is_empty() && !self.is_focused { - let placeholder_lines = self - .placeholder_text - .as_ref() - .map_or("", AsRef::as_ref) - .split('\n') - .skip(rows.start as usize) - .take(rows.len()); - let font_id = font_cache - .select_font(self.font_family, &style.placeholder_text().font_properties)?; - return Ok(placeholder_lines - .into_iter() - .map(|line| { - layout_cache.layout_str( - line, - self.font_size, - &[( - line.len(), - RunStyle { - font_id, - color: style.placeholder_text().color, - underline: false, - }, - )], - ) - }) - .collect()); - } - - let mut prev_font_properties = FontProperties::new(); - let mut prev_font_id = font_cache - .select_font(self.font_family, &prev_font_properties) - .unwrap(); - - let mut layouts = Vec::with_capacity(rows.len()); - let mut line = String::new(); - let mut styles = Vec::new(); - let mut row = rows.start; - let mut line_exceeded_max_len = false; - let chunks = self - .display_snapshot - .highlighted_chunks_for_rows(rows.clone()); - - 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) { - for (ix, mut line_chunk) in chunk.split('\n').enumerate() { - if ix > 0 { - layouts.push(layout_cache.layout_str(&line, self.font_size, &styles)); - line.clear(); - styles.clear(); - row += 1; - line_exceeded_max_len = false; - if row == rows.end { - break 'outer; - } - } - - if !line_chunk.is_empty() && !line_exceeded_max_len { - let style = self - .theme - .syntax - .highlight_style(style_ix) - .unwrap_or(style.text.clone().into()); - // Avoid a lookup if the font properties match the previous ones. - let font_id = if style.font_properties == prev_font_properties { - prev_font_id - } else { - font_cache.select_font(self.font_family, &style.font_properties)? - }; - - if line.len() + line_chunk.len() > MAX_LINE_LEN { - let mut chunk_len = MAX_LINE_LEN - line.len(); - while !line_chunk.is_char_boundary(chunk_len) { - chunk_len -= 1; - } - line_chunk = &line_chunk[..chunk_len]; - line_exceeded_max_len = true; - } - - line.push_str(line_chunk); - styles.push(( - line_chunk.len(), - RunStyle { - font_id, - color: style.color, - underline: style.underline, - }, - )); - prev_font_id = font_id; - prev_font_properties = style.font_properties; - } - } - } - - Ok(layouts) + pub fn line_len(&self, display_row: u32) -> u32 { + self.display_snapshot.line_len(display_row) } - pub fn layout_line( - &self, - row: u32, - font_cache: &FontCache, - layout_cache: &TextLayoutCache, - ) -> Result { - let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - - let mut line = self.display_snapshot.line(row); - - if line.len() > MAX_LINE_LEN { - let mut len = MAX_LINE_LEN; - while !line.is_char_boundary(len) { - len -= 1; - } - line.truncate(len); - } - - Ok(layout_cache.layout_str( - &line, - self.font_size, - &[( - self.display_snapshot.line_len(row) as usize, - RunStyle { - font_id, - color: Color::black(), - underline: false, - }, - )], - )) + pub fn line(&self, display_row: u32) -> String { + self.display_snapshot.line(display_row) } pub fn prev_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) { @@ -2598,7 +2388,7 @@ impl Snapshot { impl EditorStyle { #[cfg(any(test, feature = "test-support"))] - pub fn test(font_cache: &FontCache) -> Self { + pub fn test(font_cache: &gpui::FontCache) -> Self { let font_family_name = Arc::from("Monaco"); let font_properties = Default::default(); let font_family_id = font_cache.load_family(&[&font_family_name]).unwrap(); @@ -2966,33 +2756,6 @@ mod tests { }); } - #[gpui::test] - fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) { - let layout_cache = TextLayoutCache::new(cx.platform().fonts()); - let font_cache = cx.font_cache().clone(); - - let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); - - let settings = settings::test(&cx).1; - let (_, editor) = cx.add_window(Default::default(), |cx| { - build_editor(buffer, settings.clone(), cx) - }); - - let layouts = editor.update(cx, |editor, cx| { - editor - .snapshot(cx) - .layout_line_numbers( - 0..6, - &Default::default(), - &font_cache, - &layout_cache, - &settings.borrow().theme, - ) - .unwrap() - }); - assert_eq!(layouts.len(), 6); - } - #[gpui::test] fn test_fold(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| { diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 16d4da79b4c6446fa02f42c10469297dc397ccb0..cdfc17f46c28f84c14336630142c501e2bb5835c 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -8,8 +8,8 @@ use gpui::{Entity, ModelContext, ModelHandle}; use postage::watch; use std::ops::Range; use tab_map::TabMap; -pub use wrap_map::BufferRows; use wrap_map::WrapMap; +pub use wrap_map::{BufferRows, HighlightedChunks}; pub struct DisplayMap { buffer: ModelHandle, diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 2dec968d78d67de9d9e30c20e6a7fd455915ab4a..0912265099191d2aacabaf95bfdaec7d0124c01a 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -1,7 +1,8 @@ use super::{ DisplayPoint, Editor, EditorMode, EditorStyle, Insert, Scroll, Select, SelectPhase, Snapshot, + MAX_LINE_LEN, }; -use crate::time::ReplicaId; +use crate::{theme::HighlightId, time::ReplicaId}; use gpui::{ color::Color, geometry::{ @@ -11,7 +12,7 @@ use gpui::{ }, json::{self, ToJson}, keymap::Keystroke, - text_layout::{self, TextLayoutCache}, + text_layout::{self, RunStyle, TextLayoutCache}, AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; @@ -20,6 +21,7 @@ use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, collections::{BTreeMap, HashMap}, + fmt::Write, ops::Range, }; @@ -376,6 +378,176 @@ impl EditorElement { cx.scene.pop_layer(); } + + fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 { + let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1; + + cx.text_layout_cache + .layout_str( + "1".repeat(digit_count).as_str(), + self.style.text.font_size, + &[( + digit_count, + RunStyle { + font_id: self.style.text.font_id, + color: Color::black(), + underline: false, + }, + )], + ) + .width() + } + + fn layout_line_numbers( + &self, + rows: Range, + active_rows: &BTreeMap, + snapshot: &Snapshot, + cx: &LayoutContext, + ) -> Vec> { + let mut layouts = Vec::with_capacity(rows.len()); + let mut line_number = String::new(); + for (ix, (buffer_row, soft_wrapped)) in snapshot + .buffer_rows(rows.start) + .take((rows.end - rows.start) as usize) + .enumerate() + { + let display_row = rows.start + ix as u32; + let color = if active_rows.contains_key(&display_row) { + self.style.line_number_active + } else { + self.style.line_number + }; + if soft_wrapped { + layouts.push(None); + } else { + line_number.clear(); + write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + layouts.push(Some(cx.text_layout_cache.layout_str( + &line_number, + self.style.text.font_size, + &[( + line_number.len(), + RunStyle { + font_id: self.style.text.font_id, + color, + underline: false, + }, + )], + ))); + } + } + + layouts + } + + fn layout_lines( + &mut self, + mut rows: Range, + snapshot: &mut Snapshot, + cx: &LayoutContext, + ) -> Vec { + rows.end = cmp::min(rows.end, snapshot.max_point().row() + 1); + if rows.start >= rows.end { + return Vec::new(); + } + + // When the editor is empty and unfocused, then show the placeholder. + if snapshot.is_empty() && !snapshot.is_focused() { + let placeholder_style = self.style.placeholder_text(); + let placeholder_text = snapshot.placeholder_text(); + let placeholder_lines = placeholder_text + .as_ref() + .map_or("", AsRef::as_ref) + .split('\n') + .skip(rows.start as usize) + .take(rows.len()); + return placeholder_lines + .map(|line| { + cx.text_layout_cache.layout_str( + line, + placeholder_style.font_size, + &[( + line.len(), + RunStyle { + font_id: placeholder_style.font_id, + color: placeholder_style.color, + underline: false, + }, + )], + ) + }) + .collect(); + } + + let mut prev_font_properties = self.style.text.font_properties.clone(); + let mut prev_font_id = self.style.text.font_id; + + let theme = snapshot.theme().clone(); + let mut layouts = Vec::with_capacity(rows.len()); + let mut line = String::new(); + let mut styles = Vec::new(); + let mut row = rows.start; + let mut line_exceeded_max_len = false; + let chunks = snapshot.highlighted_chunks_for_rows(rows.clone()); + + 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) { + for (ix, mut line_chunk) in chunk.split('\n').enumerate() { + if ix > 0 { + layouts.push(cx.text_layout_cache.layout_str( + &line, + self.style.text.font_size, + &styles, + )); + line.clear(); + styles.clear(); + row += 1; + line_exceeded_max_len = false; + if row == rows.end { + break 'outer; + } + } + + if !line_chunk.is_empty() && !line_exceeded_max_len { + let style = theme + .syntax + .highlight_style(style_ix) + .unwrap_or(self.style.text.clone().into()); + // Avoid a lookup if the font properties match the previous ones. + let font_id = if style.font_properties == prev_font_properties { + prev_font_id + } else { + cx.font_cache + .select_font(self.style.text.font_family_id, &style.font_properties) + .unwrap_or(self.style.text.font_id) + }; + + if line.len() + line_chunk.len() > MAX_LINE_LEN { + let mut chunk_len = MAX_LINE_LEN - line.len(); + while !line_chunk.is_char_boundary(chunk_len) { + chunk_len -= 1; + } + line_chunk = &line_chunk[..chunk_len]; + line_exceeded_max_len = true; + } + + line.push_str(line_chunk); + styles.push(( + line_chunk.len(), + RunStyle { + font_id, + color: style.color, + underline: style.underline, + }, + )); + prev_font_id = font_id; + prev_font_properties = style.font_properties; + } + } + } + + layouts + } } impl Element for EditorElement { @@ -392,30 +564,22 @@ impl Element for EditorElement { unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); } - let font_cache = &cx.font_cache; - let layout_cache = &cx.text_layout_cache; let snapshot = self.snapshot(cx.app); - let line_height = snapshot.line_height(font_cache); + let line_height = self.style.text.line_height(cx.font_cache); let gutter_padding; let gutter_width; if snapshot.mode == EditorMode::Full { - gutter_padding = snapshot.em_width(cx.font_cache); - match snapshot.max_line_number_width(cx.font_cache, cx.text_layout_cache) { - Err(error) => { - log::error!("error computing max line number width: {}", error); - return (size, None); - } - Ok(width) => gutter_width = width + gutter_padding * 2.0, - } + gutter_padding = self.style.text.em_width(cx.font_cache); + gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0; } else { gutter_padding = 0.0; gutter_width = 0.0 }; let text_width = size.x() - gutter_width; - let text_offset = vec2f(-snapshot.font_descent(cx.font_cache), 0.); - let em_width = snapshot.em_width(font_cache); + let text_offset = vec2f(-self.style.text.descent(cx.font_cache), 0.); + let em_width = self.style.text.em_width(cx.font_cache); let overscroll = vec2f(em_width, 0.); let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width; let snapshot = self.update_view(cx.app, |view, cx| { @@ -490,51 +654,18 @@ impl Element for EditorElement { }); let line_number_layouts = if snapshot.mode == EditorMode::Full { - let settings = self - .view - .upgrade(cx.app) - .unwrap() - .read(cx.app) - .settings - .borrow(); - match snapshot.layout_line_numbers( - start_row..end_row, - &active_rows, - cx.font_cache, - cx.text_layout_cache, - &settings.theme, - ) { - Err(error) => { - log::error!("error laying out line numbers: {}", error); - return (size, None); - } - Ok(layouts) => layouts, - } + self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx) } else { Vec::new() }; let mut max_visible_line_width = 0.0; - let line_layouts = match snapshot.layout_lines( - start_row..end_row, - &self.style, - font_cache, - layout_cache, - ) { - Err(error) => { - log::error!("error laying out lines: {}", error); - return (size, None); + let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); + for line in &line_layouts { + if line.width() > max_visible_line_width { + max_visible_line_width = line.width(); } - Ok(layouts) => { - for line in &layouts { - if line.width() > max_visible_line_width { - max_visible_line_width = line.width(); - } - } - - layouts - } - }; + } let mut layout = LayoutState { size, @@ -544,6 +675,7 @@ impl Element for EditorElement { overscroll, text_offset, snapshot, + style: self.style.clone(), active_rows, line_layouts, line_number_layouts, @@ -553,15 +685,18 @@ impl Element for EditorElement { max_visible_line_width, }; + let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x(); + let scroll_width = layout.scroll_width(cx.text_layout_cache); + let max_glyph_width = self.style.text.em_width(&cx.font_cache); self.update_view(cx.app, |view, cx| { - let clamped = view.clamp_scroll_left(layout.scroll_max(font_cache, layout_cache).x()); + let clamped = view.clamp_scroll_left(scroll_max); let autoscrolled; if autoscroll_horizontally { autoscrolled = view.autoscroll_horizontally( start_row, layout.text_size.x(), - layout.scroll_width(font_cache, layout_cache), - layout.snapshot.em_width(font_cache), + scroll_width, + max_glyph_width, &layout.line_layouts, cx, ); @@ -661,6 +796,7 @@ pub struct LayoutState { gutter_size: Vector2F, gutter_padding: f32, text_size: Vector2F, + style: EditorStyle, snapshot: Snapshot, active_rows: BTreeMap, line_layouts: Vec, @@ -674,20 +810,16 @@ pub struct LayoutState { } impl LayoutState { - fn scroll_width(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> f32 { + fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 { let row = self.snapshot.longest_row(); - let longest_line_width = self - .snapshot - .layout_line(row, font_cache, layout_cache) - .unwrap() - .width(); + let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width(); longest_line_width.max(self.max_visible_line_width) + self.overscroll.x() } fn scroll_max(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> Vector2F { let text_width = self.text_size.x(); - let scroll_width = self.scroll_width(font_cache, layout_cache); - let em_width = self.snapshot.em_width(font_cache); + let scroll_width = self.scroll_width(layout_cache); + let em_width = self.style.text.em_width(font_cache); let max_row = self.snapshot.max_point().row(); vec2f( @@ -695,6 +827,36 @@ impl LayoutState { max_row.saturating_sub(1) as f32, ) } + + pub fn layout_line( + &self, + row: u32, + snapshot: &Snapshot, + layout_cache: &TextLayoutCache, + ) -> text_layout::Line { + let mut line = snapshot.line(row); + + if line.len() > MAX_LINE_LEN { + let mut len = MAX_LINE_LEN; + while !line.is_char_boundary(len) { + len -= 1; + } + line.truncate(len); + } + + layout_cache.layout_str( + &line, + self.style.text.font_size, + &[( + snapshot.line_len(row) as usize, + RunStyle { + font_id: self.style.text.font_id, + color: Color::black(), + underline: false, + }, + )], + ) + } } pub struct PaintState { @@ -866,3 +1028,42 @@ fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { delta.powf(1.2) / 300.0 } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + editor::{Buffer, Editor, EditorStyle}, + settings, + test::sample_text, + }; + + #[gpui::test] + fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) { + let font_cache = cx.font_cache().clone(); + let settings = settings::test(&cx).1; + let style = EditorStyle::test(&font_cache); + + let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); + let (window_id, editor) = cx.add_window(Default::default(), |cx| { + Editor::for_buffer( + buffer, + settings.clone(), + { + let style = style.clone(); + move |_| style.clone() + }, + cx, + ) + }); + let element = EditorElement::new(editor.downgrade(), style); + + let layouts = editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let mut presenter = cx.build_presenter(window_id, 30.); + let mut layout_cx = presenter.build_layout_context(false, cx); + element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx) + }); + assert_eq!(layouts.len(), 6); + } +}