From 31bb2de87b98233321d8aef0189cd29dc366bc69 Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 4 Aug 2022 01:36:33 +0200 Subject: [PATCH] Make WidthDB tab-width-aware --- src/buffer.rs | 15 +++++---------- src/frame.rs | 31 +++++++++---------------------- src/terminal.rs | 4 ++-- src/widthdb.rs | 38 +++++++++++++++++++++++++++----------- src/wrap.rs | 8 ++------ 5 files changed, 45 insertions(+), 51 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index e25035a..9345e8c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -4,7 +4,6 @@ use crossterm::style::ContentStyle; use crate::styled::Styled; use crate::widthdb::WidthDB; -use crate::wrap; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Size { @@ -331,7 +330,7 @@ impl Buffer { } } - pub fn write(&mut self, widthdb: &mut WidthDB, tab_width: u8, pos: Pos, styled: &Styled) { + pub fn write(&mut self, widthdb: &mut WidthDB, pos: Pos, styled: &Styled) { let frame = self.current_frame(); let (xrange, yrange) = match frame.legal_ranges() { Some(ranges) => ranges, @@ -348,18 +347,14 @@ impl Buffer { let x = pos.x + col as i32; let g = *styled_grapheme.content(); let style = *styled_grapheme.style(); + let width = widthdb.grapheme_width(g, col); + col += width as usize; if g == "\t" { - let width = wrap::tab_width_at_column(tab_width, col); - col += width as usize; for dx in 0..width { self.write_grapheme(&xrange, x + dx as i32, y, width, " ", style); } - } else { - let width = widthdb.grapheme_width(g); - col += width as usize; - if width > 0 { - self.write_grapheme(&xrange, x, y, width, g, style); - } + } else if width > 0 { + self.write_grapheme(&xrange, x, y, width, g, style); } } } diff --git a/src/frame.rs b/src/frame.rs index 2943c8e..ae0c364 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -6,23 +6,11 @@ use crate::styled::Styled; use crate::widthdb::WidthDB; use crate::wrap; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Frame { pub(crate) widthdb: WidthDB, pub(crate) buffer: Buffer, cursor: Option, - pub(crate) tab_width: u8, -} - -impl Default for Frame { - fn default() -> Self { - Self { - widthdb: Default::default(), - buffer: Default::default(), - cursor: None, - tab_width: 8, - } - } } impl Frame { @@ -62,30 +50,29 @@ impl Frame { /// Determine the width of a grapheme. /// + /// If the grapheme is a tab, the column is used to determine its width. + /// /// If the width has not been measured yet, it is estimated using the /// Unicode Standard Annex #11. - pub fn grapheme_width(&mut self, grapheme: &str) -> u8 { - self.widthdb.grapheme_width(grapheme) + pub fn grapheme_width(&mut self, grapheme: &str, col: usize) -> u8 { + self.widthdb.grapheme_width(grapheme, col) } /// Determine the width of a string based on its graphemes. /// + /// If a grapheme is a tab, its column is used to determine its width. + /// /// If the width of a grapheme has not been measured yet, it is estimated /// using the Unicode Standard Annex #11. pub fn width(&mut self, s: &str) -> usize { self.widthdb.width(s) } - pub fn tab_width_at_column(&self, col: usize) -> u8 { - wrap::tab_width_at_column(self.tab_width, col) - } - pub fn wrap(&mut self, text: &str, width: usize) -> Vec { - wrap::wrap(&mut self.widthdb, self.tab_width, text, width) + wrap::wrap(&mut self.widthdb, text, width) } pub fn write>(&mut self, pos: Pos, styled: S) { - self.buffer - .write(&mut self.widthdb, self.tab_width, pos, &styled.into()); + self.buffer.write(&mut self.widthdb, pos, &styled.into()); } } diff --git a/src/terminal.rs b/src/terminal.rs index f7e1d05..8307b47 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -60,11 +60,11 @@ impl Terminal { } pub fn set_tab_width(&mut self, tab_width: u8) { - self.frame.tab_width = tab_width; + self.frame.widthdb.tab_width = tab_width; } pub fn tab_width(&self) -> u8 { - self.frame.tab_width + self.frame.widthdb.tab_width } pub fn set_measuring(&mut self, active: bool) { diff --git a/src/widthdb.rs b/src/widthdb.rs index 9072995..00a5995 100644 --- a/src/widthdb.rs +++ b/src/widthdb.rs @@ -9,20 +9,42 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; /// Measures and stores the with (in terminal coordinates) of graphemes. -#[derive(Debug, Default)] +#[derive(Debug)] pub struct WidthDB { pub active: bool, known: HashMap, requested: HashSet, + pub(crate) tab_width: u8, +} + +impl Default for WidthDB { + fn default() -> Self { + Self { + active: false, + known: Default::default(), + requested: Default::default(), + tab_width: 8, + } + } } impl WidthDB { + /// Determine the width of a tab character starting at the specified column. + fn tab_width_at_column(&self, col: usize) -> u8 { + self.tab_width - (col % self.tab_width as usize) as u8 + } + /// Determine the width of a grapheme. /// + /// If the grapheme is a tab, the column is used to determine its width. + /// /// If the width has not been measured yet, it is estimated using the /// Unicode Standard Annex #11. - pub fn grapheme_width(&mut self, grapheme: &str) -> u8 { + pub fn grapheme_width(&mut self, grapheme: &str, col: usize) -> u8 { assert_eq!(Some(grapheme), grapheme.graphemes(true).next()); + if grapheme == "\t" { + return self.tab_width_at_column(col); + } if !self.active { return grapheme.width() as u8; } @@ -36,20 +58,14 @@ impl WidthDB { /// Determine the width of a string based on its graphemes. /// + /// If a grapheme is a tab, its column is used to determine its width. + /// /// If the width of a grapheme has not been measured yet, it is estimated /// using the Unicode Standard Annex #11. pub fn width(&mut self, s: &str) -> usize { - if !self.active { - return s.width(); - } let mut total: usize = 0; for grapheme in s.graphemes(true) { - total += if let Some(width) = self.known.get(grapheme) { - (*width).into() - } else { - self.requested.insert(grapheme.to_string()); - grapheme.width() - }; + total += self.grapheme_width(grapheme, total) as usize; } total } diff --git a/src/wrap.rs b/src/wrap.rs index ac9964c..21e1814 100644 --- a/src/wrap.rs +++ b/src/wrap.rs @@ -5,11 +5,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::widthdb::WidthDB; -pub fn tab_width_at_column(tab_width: u8, col: usize) -> u8 { - tab_width - (col % tab_width as usize) as u8 -} - -pub fn wrap(widthdb: &mut WidthDB, tab_width: u8, text: &str, width: usize) -> Vec { +pub fn wrap(widthdb: &mut WidthDB, text: &str, width: usize) -> Vec { let mut breaks = vec![]; let mut break_options = unicode_linebreak::linebreaks(text).peekable(); @@ -54,7 +50,7 @@ pub fn wrap(widthdb: &mut WidthDB, tab_width: u8, text: &str, width: usize) -> V // Calculate widths after current grapheme let g_is_whitespace = g.chars().all(|c| c.is_whitespace()); let g_width = if g == "\t" { - tab_width_at_column(tab_width, current_width) as usize + widthdb.tab_width_at_column(current_width) as usize } else { widthdb.grapheme_width(g) as usize };