Support expanding tabs

This commit is contained in:
Jeremy Soller 2024-06-06 21:01:46 -06:00
parent 56812a8348
commit 3c94352f3f
9 changed files with 82 additions and 25 deletions

View file

@ -210,6 +210,7 @@ pub struct Buffer {
redraw: bool, redraw: bool,
wrap: Wrap, wrap: Wrap,
monospace_width: Option<f32>, monospace_width: Option<f32>,
tab_width: u16,
/// Scratch buffer for shaping and laying out. /// Scratch buffer for shaping and laying out.
scratch: ShapeBuffer, scratch: ShapeBuffer,
@ -226,6 +227,7 @@ impl Clone for Buffer {
redraw: self.redraw, redraw: self.redraw,
wrap: self.wrap, wrap: self.wrap,
monospace_width: self.monospace_width, monospace_width: self.monospace_width,
tab_width: self.tab_width,
scratch: ShapeBuffer::default(), scratch: ShapeBuffer::default(),
} }
} }
@ -255,6 +257,7 @@ impl Buffer {
wrap: Wrap::WordOrGlyph, wrap: Wrap::WordOrGlyph,
scratch: ShapeBuffer::default(), scratch: ShapeBuffer::default(),
monospace_width: None, monospace_width: None,
tab_width: 8,
} }
} }
@ -294,6 +297,7 @@ impl Buffer {
self.width, self.width,
self.wrap, self.wrap,
self.monospace_width, self.monospace_width,
self.tab_width,
); );
} }
} }
@ -510,7 +514,7 @@ impl Buffer {
line_i: usize, line_i: usize,
) -> Option<&ShapeLine> { ) -> Option<&ShapeLine> {
let line = self.lines.get_mut(line_i)?; 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 /// Lay out the provided line index and return the result
@ -527,6 +531,7 @@ impl Buffer {
self.width, self.width,
self.wrap, self.wrap,
self.monospace_width, 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) /// Get the current buffer dimensions (width, height)
pub fn size(&self) -> (f32, f32) { pub fn size(&self) -> (f32, f32) {
(self.width, self.height) (self.width, self.height)

View file

@ -182,8 +182,8 @@ impl BufferLine {
} }
/// Shape line, will cache results /// Shape line, will cache results
pub fn shape(&mut self, font_system: &mut FontSystem) -> &ShapeLine { pub fn shape(&mut self, font_system: &mut FontSystem, tab_width: u16) -> &ShapeLine {
self.shape_in_buffer(&mut ShapeBuffer::default(), font_system) self.shape_in_buffer(&mut ShapeBuffer::default(), font_system, tab_width)
} }
/// Shape a line using a pre-existing shape buffer, will cache results /// Shape a line using a pre-existing shape buffer, will cache results
@ -191,6 +191,7 @@ impl BufferLine {
&mut self, &mut self,
scratch: &mut ShapeBuffer, scratch: &mut ShapeBuffer,
font_system: &mut FontSystem, font_system: &mut FontSystem,
tab_width: u16,
) -> &ShapeLine { ) -> &ShapeLine {
if self.shape_opt.is_none() { if self.shape_opt.is_none() {
self.shape_opt = Some(ShapeLine::new_in_buffer( self.shape_opt = Some(ShapeLine::new_in_buffer(
@ -199,6 +200,7 @@ impl BufferLine {
&self.text, &self.text,
&self.attrs_list, &self.attrs_list,
self.shaping, self.shaping,
tab_width,
)); ));
self.layout_opt = None; self.layout_opt = None;
} }
@ -218,6 +220,7 @@ impl BufferLine {
width: f32, width: f32,
wrap: Wrap, wrap: Wrap,
match_mono_width: Option<f32>, match_mono_width: Option<f32>,
tab_width: u16,
) -> &[LayoutLine] { ) -> &[LayoutLine] {
self.layout_in_buffer( self.layout_in_buffer(
&mut ShapeBuffer::default(), &mut ShapeBuffer::default(),
@ -226,6 +229,7 @@ impl BufferLine {
width, width,
wrap, wrap,
match_mono_width, match_mono_width,
tab_width,
) )
} }
@ -238,10 +242,11 @@ impl BufferLine {
width: f32, width: f32,
wrap: Wrap, wrap: Wrap,
match_mono_width: Option<f32>, match_mono_width: Option<f32>,
tab_width: u16,
) -> &[LayoutLine] { ) -> &[LayoutLine] {
if self.layout_opt.is_none() { if self.layout_opt.is_none() {
let align = self.align; 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); let mut layout = Vec::with_capacity(1);
shape.layout_to_buffer( shape.layout_to_buffer(
scratch, scratch,

View file

@ -24,7 +24,6 @@ pub struct Editor<'buffer> {
selection: Selection, selection: Selection,
cursor_moved: bool, cursor_moved: bool,
auto_indent: bool, auto_indent: bool,
tab_width: u16,
change: Option<Change>, change: Option<Change>,
} }
@ -38,7 +37,6 @@ impl<'buffer> Editor<'buffer> {
selection: Selection::None, selection: Selection::None,
cursor_moved: false, cursor_moved: false,
auto_indent: false, auto_indent: false,
tab_width: 4,
change: None, change: None,
} }
} }
@ -259,18 +257,11 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
} }
fn tab_width(&self) -> u16 { fn tab_width(&self) -> u16 {
self.tab_width self.with_buffer(|buffer| buffer.tab_width())
} }
fn set_tab_width(&mut self, tab_width: u16) { fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
// A tab width of 0 is not allowed self.with_buffer_mut(|buffer| buffer.set_tab_width(font_system, tab_width));
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 shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { 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 // 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 { for line_i in start.line..=end.line {
// Determine indexes of last indent and first character after whitespace // Determine indexes of last indent and first character after whitespace
let mut after_whitespace = 0; let mut after_whitespace = 0;
@ -745,7 +736,7 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
}; };
// For every line in selection // 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 { for line_i in start.line..=end.line {
// Determine indexes of last indent and first character after whitespace // Determine indexes of last indent and first character after whitespace
let mut last_indent = 0; let mut last_indent = 0;

View file

@ -282,7 +282,7 @@ pub trait Edit<'buffer> {
fn tab_width(&self) -> u16; fn tab_width(&self) -> u16;
/// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored /// 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 /// Shape lines until scroll, after adjusting scroll if the cursor moved
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool); 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 /// Shape lines until scroll, after adjusting scroll if the cursor moved
pub fn shape_as_needed(&mut self, prune: bool) { pub fn shape_as_needed(&mut self, prune: bool) {
self.inner.shape_as_needed(self.font_system, prune); self.inner.shape_as_needed(self.font_system, prune);

View file

@ -250,8 +250,8 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for SyntaxEditor<'syntax_system, 'bu
self.editor.tab_width() self.editor.tab_width()
} }
fn set_tab_width(&mut self, tab_width: u16) { fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
self.editor.set_tab_width(tab_width); self.editor.set_tab_width(font_system, tab_width);
} }
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {

View file

@ -550,8 +550,8 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer
self.editor.tab_width() self.editor.tab_width()
} }
fn set_tab_width(&mut self, tab_width: u16) { fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
self.editor.set_tab_width(tab_width); self.editor.set_tab_width(font_system, tab_width);
} }
fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) { fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {

View file

@ -1,5 +1,11 @@
#[cfg(not(feature = "std"))] #[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")] #[cfg(feature = "std")]
#[inline] #[inline]

View file

@ -763,6 +763,7 @@ impl ShapeLine {
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
shaping: Shaping, shaping: Shaping,
tab_width: u16,
) -> Self { ) -> Self {
Self::new_in_buffer( Self::new_in_buffer(
&mut ShapeBuffer::default(), &mut ShapeBuffer::default(),
@ -770,6 +771,7 @@ impl ShapeLine {
line, line,
attrs_list, attrs_list,
shaping, shaping,
tab_width,
) )
} }
@ -785,6 +787,7 @@ impl ShapeLine {
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
shaping: Shaping, shaping: Shaping,
tab_width: u16,
) -> Self { ) -> Self {
let mut spans = Vec::new(); 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 { Self {
rtl, rtl,
spans, spans,

View file

@ -21,7 +21,7 @@ fn stable_wrap() {
font_system.db_mut().load_font_data(font); font_system.db_mut().load_font_data(font);
let mut check_wrap = |text: &_, wrap, start_width| { 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 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); let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);