From 3c94352f3f5e7eddc4f1073833011f72fb0dcb89 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 6 Jun 2024 21:01:46 -0600 Subject: [PATCH] Support expanding tabs --- src/buffer.rs | 31 ++++++++++++++++++++++++++++++- src/buffer_line.rs | 11 ++++++++--- src/edit/editor.rs | 19 +++++-------------- src/edit/mod.rs | 7 ++++++- src/edit/syntect.rs | 4 ++-- src/edit/vi.rs | 4 ++-- src/math.rs | 8 +++++++- src/shape.rs | 21 +++++++++++++++++++++ tests/wrap_stability.rs | 2 +- 9 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 80904f2..714cd23 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -210,6 +210,7 @@ pub struct Buffer { redraw: bool, wrap: Wrap, monospace_width: Option, + tab_width: u16, /// Scratch buffer for shaping and laying out. scratch: ShapeBuffer, @@ -226,6 +227,7 @@ impl Clone for Buffer { redraw: self.redraw, wrap: self.wrap, monospace_width: self.monospace_width, + tab_width: self.tab_width, scratch: ShapeBuffer::default(), } } @@ -255,6 +257,7 @@ impl Buffer { wrap: Wrap::WordOrGlyph, scratch: ShapeBuffer::default(), monospace_width: None, + tab_width: 8, } } @@ -294,6 +297,7 @@ impl Buffer { self.width, self.wrap, self.monospace_width, + self.tab_width, ); } } @@ -510,7 +514,7 @@ impl Buffer { line_i: usize, ) -> Option<&ShapeLine> { let line = self.lines.get_mut(line_i)?; - Some(line.shape_in_buffer(&mut self.scratch, font_system)) + Some(line.shape_in_buffer(&mut self.scratch, font_system, self.tab_width)) } /// Lay out the provided line index and return the result @@ -527,6 +531,7 @@ impl Buffer { self.width, self.wrap, self.monospace_width, + self.tab_width, )) } @@ -576,6 +581,30 @@ impl Buffer { } } + /// Get the current `tab_width` + pub fn tab_width(&self) -> u16 { + self.tab_width + } + + /// Set tab width (number of spaces between tab stops) + pub fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) { + // A tab width of 0 is not allowed + if tab_width == 0 { + return; + } + if tab_width != self.tab_width { + self.tab_width = tab_width; + // Shaping must be reset when tab width is changed + for line in self.lines.iter_mut() { + if line.text().contains('\t') { + line.reset_shaping(); + } + } + self.redraw = true; + self.shape_until_scroll(font_system, false); + } + } + /// Get the current buffer dimensions (width, height) pub fn size(&self) -> (f32, f32) { (self.width, self.height) diff --git a/src/buffer_line.rs b/src/buffer_line.rs index d5a432d..f028bf7 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -182,8 +182,8 @@ impl BufferLine { } /// Shape line, will cache results - pub fn shape(&mut self, font_system: &mut FontSystem) -> &ShapeLine { - self.shape_in_buffer(&mut ShapeBuffer::default(), font_system) + pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine { + self.shape_in_buffer(&mut ShapeBuffer::default(), font_system, tab_width) } /// Shape a line using a pre-existing shape buffer, will cache results @@ -191,6 +191,7 @@ impl BufferLine { &mut self, scratch: &mut ShapeBuffer, font_system: &mut FontSystem, + tab_width: u16, ) -> &ShapeLine { if self.shape_opt.is_none() { self.shape_opt = Some(ShapeLine::new_in_buffer( @@ -199,6 +200,7 @@ impl BufferLine { &self.text, &self.attrs_list, self.shaping, + tab_width, )); self.layout_opt = None; } @@ -218,6 +220,7 @@ impl BufferLine { width: f32, wrap: Wrap, match_mono_width: Option, + tab_width: u16, ) -> &[LayoutLine] { self.layout_in_buffer( &mut ShapeBuffer::default(), @@ -226,6 +229,7 @@ impl BufferLine { width, wrap, match_mono_width, + tab_width, ) } @@ -238,10 +242,11 @@ impl BufferLine { width: f32, wrap: Wrap, match_mono_width: Option, + tab_width: u16, ) -> &[LayoutLine] { if self.layout_opt.is_none() { let align = self.align; - let shape = self.shape_in_buffer(scratch, font_system); + let shape = self.shape_in_buffer(scratch, font_system, tab_width); let mut layout = Vec::with_capacity(1); shape.layout_to_buffer( scratch, diff --git a/src/edit/editor.rs b/src/edit/editor.rs index a604529..d2d6c04 100644 --- a/src/edit/editor.rs +++ b/src/edit/editor.rs @@ -24,7 +24,6 @@ pub struct Editor<'buffer> { selection: Selection, cursor_moved: bool, auto_indent: bool, - tab_width: u16, change: Option, } @@ -38,7 +37,6 @@ impl<'buffer> Editor<'buffer> { selection: Selection::None, cursor_moved: false, auto_indent: false, - tab_width: 4, change: None, } } @@ -259,18 +257,11 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> { } fn tab_width(&self) -> u16 { - self.tab_width + self.with_buffer(|buffer| buffer.tab_width()) } - fn set_tab_width(&mut self, tab_width: u16) { - // A tab width of 0 is not allowed - if tab_width == 0 { - return; - } - if self.tab_width != tab_width { - self.tab_width = tab_width; - self.with_buffer_mut(|buffer| buffer.set_redraw(true)); - } + fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) { + self.with_buffer_mut(|buffer| buffer.set_tab_width(font_system, tab_width)); } fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { @@ -682,7 +673,7 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> { }; // For every line in selection - let tab_width: usize = self.tab_width.into(); + let tab_width: usize = self.tab_width().into(); for line_i in start.line..=end.line { // Determine indexes of last indent and first character after whitespace let mut after_whitespace = 0; @@ -745,7 +736,7 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> { }; // For every line in selection - let tab_width: usize = self.tab_width.into(); + let tab_width: usize = self.tab_width().into(); for line_i in start.line..=end.line { // Determine indexes of last indent and first character after whitespace let mut last_indent = 0; diff --git a/src/edit/mod.rs b/src/edit/mod.rs index 76bff0e..61a8f44 100644 --- a/src/edit/mod.rs +++ b/src/edit/mod.rs @@ -282,7 +282,7 @@ pub trait Edit<'buffer> { fn tab_width(&self) -> u16; /// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored - fn set_tab_width(&mut self, tab_width: u16); + fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16); /// Shape lines until scroll, after adjusting scroll if the cursor moved fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool); @@ -336,6 +336,11 @@ impl<'font_system, 'buffer, E: Edit<'buffer>> BorrowedWithFontSystem<'font_syste }) } + /// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored + pub fn set_tab_width(&mut self, tab_width: u16) { + self.inner.set_tab_width(self.font_system, tab_width); + } + /// Shape lines until scroll, after adjusting scroll if the cursor moved pub fn shape_as_needed(&mut self, prune: bool) { self.inner.shape_as_needed(self.font_system, prune); diff --git a/src/edit/syntect.rs b/src/edit/syntect.rs index bddaad5..5ad7b0a 100644 --- a/src/edit/syntect.rs +++ b/src/edit/syntect.rs @@ -250,8 +250,8 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for SyntaxEditor<'syntax_system, 'bu self.editor.tab_width() } - fn set_tab_width(&mut self, tab_width: u16) { - self.editor.set_tab_width(tab_width); + fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) { + self.editor.set_tab_width(font_system, tab_width); } fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { diff --git a/src/edit/vi.rs b/src/edit/vi.rs index 92b0480..334540c 100644 --- a/src/edit/vi.rs +++ b/src/edit/vi.rs @@ -550,8 +550,8 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer self.editor.tab_width() } - fn set_tab_width(&mut self, tab_width: u16) { - self.editor.set_tab_width(tab_width); + fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) { + self.editor.set_tab_width(font_system, tab_width); } fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { diff --git a/src/math.rs b/src/math.rs index 92185f9..126cbf7 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,5 +1,11 @@ #[cfg(not(feature = "std"))] -pub use libm::{roundf, truncf}; +pub use libm::{floorf, roundf, truncf}; + +#[cfg(feature = "std")] +#[inline] +pub fn floorf(x: f32) -> f32 { + x.floor() +} #[cfg(feature = "std")] #[inline] diff --git a/src/shape.rs b/src/shape.rs index 8d69800..119e87d 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -763,6 +763,7 @@ impl ShapeLine { line: &str, attrs_list: &AttrsList, shaping: Shaping, + tab_width: u16, ) -> Self { Self::new_in_buffer( &mut ShapeBuffer::default(), @@ -770,6 +771,7 @@ impl ShapeLine { line, attrs_list, shaping, + tab_width, ) } @@ -785,6 +787,7 @@ impl ShapeLine { line: &str, attrs_list: &AttrsList, shaping: Shaping, + tab_width: u16, ) -> Self { let mut spans = Vec::new(); @@ -844,6 +847,24 @@ impl ShapeLine { )); } + // Adjust for tabs + let mut x = 0.0; + for span in spans.iter_mut() { + for word in span.words.iter_mut() { + for glyph in word.glyphs.iter_mut() { + if &line[glyph.start..glyph.end] == "\t" { + //TODO: better fallback for width + let space_x_advance = + glyph.font_monospace_em_width.unwrap_or(glyph.x_advance); + let tab_x_advance = (tab_width as f32) * space_x_advance; + let tab_stop = (math::floorf(x / tab_x_advance) + 1.0) * tab_x_advance; + glyph.x_advance = tab_stop - x; + } + x += glyph.x_advance; + } + } + } + Self { rtl, spans, diff --git a/tests/wrap_stability.rs b/tests/wrap_stability.rs index c05d659..382fc25 100644 --- a/tests/wrap_stability.rs +++ b/tests/wrap_stability.rs @@ -21,7 +21,7 @@ fn stable_wrap() { font_system.db_mut().load_font_data(font); let mut check_wrap = |text: &_, wrap, start_width| { - let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced); + let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced, 8); let layout_unbounded = line.layout(font_size, start_width, wrap, Some(Align::Left), None); let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);