From 9e40b49e5ee761825bd07fc64e02531d1a389e69 Mon Sep 17 00:00:00 2001 From: VAWVAW Date: Tue, 17 Jun 2025 01:30:07 +0000 Subject: [PATCH] Fix display of tabs in code blocks (#463) --- docs/iamb.5 | 4 ++++ src/config.rs | 4 ++++ src/message/html.rs | 21 ++++++++++++++++++--- src/message/printer.rs | 10 +++++++++- src/tests.rs | 1 + 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/docs/iamb.5 b/docs/iamb.5 index 26fc1b7..56b0b32 100644 --- a/docs/iamb.5 +++ b/docs/iamb.5 @@ -231,6 +231,10 @@ Possible values are Specify the width of the column where usernames are displayed in a room. Usernames that are too long are truncated. Defaults to 30. + +.It Sy tabstop +Number of spaces that a counts for. +Defaults to 4. .El .Ss Example 1: Avoid showing Emojis (useful for terminals w/o support) diff --git a/src/config.rs b/src/config.rs index b712c73..1438ed7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -574,6 +574,7 @@ pub struct TunableValues { pub image_preview: Option, pub user_gutter_width: usize, pub external_edit_file_suffix: String, + pub tabstop: usize, } #[derive(Clone, Default, Deserialize)] @@ -600,6 +601,7 @@ pub struct Tunables { pub image_preview: Option, pub user_gutter_width: Option, pub external_edit_file_suffix: Option, + pub tabstop: Option, } impl Tunables { @@ -632,6 +634,7 @@ impl Tunables { external_edit_file_suffix: self .external_edit_file_suffix .or(other.external_edit_file_suffix), + tabstop: self.tabstop.or(other.tabstop), } } @@ -660,6 +663,7 @@ impl Tunables { external_edit_file_suffix: self .external_edit_file_suffix .unwrap_or_else(|| ".md".to_string()), + tabstop: self.tabstop.unwrap_or(4), } } } diff --git a/src/message/html.rs b/src/message/html.rs index 82aac65..6d4c44e 100644 --- a/src/message/html.rs +++ b/src/message/html.rs @@ -1415,13 +1415,14 @@ pub mod tests { let s = concat!( "
",
             "fn hello() -> usize {\n",
+            " \t// weired\n",
             "    return 5;\n",
             "}\n",
             "
\n" ); let tree = parse_matrix_html(s); let text = tree.to_text(25, Style::default(), true, &settings); - assert_eq!(text.lines.len(), 5); + assert_eq!(text.lines.len(), 6); assert_eq!( text.lines[0], Line::from(vec![ @@ -1452,6 +1453,20 @@ pub mod tests { ); assert_eq!( text.lines[2], + Line::from(vec![ + Span::raw(line::VERTICAL), + Span::raw(" "), + Span::raw(" "), + Span::raw("/"), + Span::raw("/"), + Span::raw(" "), + Span::raw("weired"), + Span::raw(" "), + Span::raw(line::VERTICAL) + ]) + ); + assert_eq!( + text.lines[3], Line::from(vec![ Span::raw(line::VERTICAL), Span::raw(" "), @@ -1464,7 +1479,7 @@ pub mod tests { ]) ); assert_eq!( - text.lines[3], + text.lines[4], Line::from(vec![ Span::raw(line::VERTICAL), Span::raw("}"), @@ -1473,7 +1488,7 @@ pub mod tests { ]) ); assert_eq!( - text.lines[4], + text.lines[5], Line::from(vec![ Span::raw(line::BOTTOM_LEFT), Span::raw(line::HORIZONTAL.repeat(23)), diff --git a/src/message/printer.rs b/src/message/printer.rs index d2a2dd0..3418752 100644 --- a/src/message/printer.rs +++ b/src/message/printer.rs @@ -216,6 +216,8 @@ impl<'a> TextPrinter<'a> { return; } + let tabstop = self.settings().tunables.tabstop; + for mut word in UnicodeSegmentation::split_word_bounds(s) { if let "\n" | "\r\n" = word { if self.literal { @@ -232,11 +234,17 @@ impl<'a> TextPrinter<'a> { continue; } - let cow = if self.emoji_shortcodes() { + let mut cow = if self.emoji_shortcodes() { Cow::Owned(replace_emojis_in_str(word)) } else { Cow::Borrowed(word) }; + + if cow == "\t" { + let tablen = tabstop - (self.curr_width % tabstop); + cow = Cow::Owned(" ".repeat(tablen)); + } + let sw = UnicodeWidthStr::width(cow.as_ref()); if sw > self.width { diff --git a/src/tests.rs b/src/tests.rs index 52cb859..3e3f825 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -198,6 +198,7 @@ pub fn mock_tunables() -> TunableValues { }, image_preview: None, user_gutter_width: 30, + tabstop: 4, } }