Detailed changes
@@ -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<Self>, font_id: FontId, font_size: f32) -> LineWrapperHandle {
@@ -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<TextStyle> for HighlightStyle {
@@ -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<str>> {
+ 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<u32>,
+ ) -> 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<Theme> {
+ &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<f32> {
- 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<u32>,
- active_rows: &BTreeMap<u32, bool>,
- font_cache: &FontCache,
- layout_cache: &TextLayoutCache,
- theme: &Theme,
- ) -> Result<Vec<Option<text_layout::Line>>> {
- 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<u32>,
- style: &EditorStyle,
- font_cache: &FontCache,
- layout_cache: &TextLayoutCache,
- ) -> Result<Vec<text_layout::Line>> {
- 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<text_layout::Line> {
- 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| {
@@ -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<Buffer>,
@@ -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<u32>,
+ active_rows: &BTreeMap<u32, bool>,
+ snapshot: &Snapshot,
+ cx: &LayoutContext,
+ ) -> Vec<Option<text_layout::Line>> {
+ 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<u32>,
+ snapshot: &mut Snapshot,
+ cx: &LayoutContext,
+ ) -> Vec<text_layout::Line> {
+ 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<u32, bool>,
line_layouts: Vec<text_layout::Line>,
@@ -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);
+ }
+}