From ca1096de78fe219d58c651defcdf4bfc93f6d7e9 Mon Sep 17 00:00:00 2001 From: Anton Wetzel <59712243+antonWetzel@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:06:31 +0200 Subject: [PATCH] Replace tabs with spaces in raw elements. (#2042) --- crates/typst-library/src/text/raw.rs | 53 ++++++++++++++++++++++++++- tests/ref/text/raw-tabs.png | Bin 0 -> 3382 bytes tests/typ/text/raw-tabs.typ | 11 ++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/ref/text/raw-tabs.png create mode 100644 tests/typ/text/raw-tabs.typ diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index 1b4a42236..a9d404140 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -9,6 +9,7 @@ use typst::diag::FileError; use typst::eval::Bytes; use typst::syntax::{self, LinkedNode}; use typst::util::option_eq; +use unicode_segmentation::UnicodeSegmentation; use super::{ FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize, @@ -218,6 +219,21 @@ pub struct RawElem { #[internal] #[parse(theme_data.map(Some))] pub theme_data: Option, + + /// The size for a tab stop in spaces. A tab is replaced with enough spaces to + /// align with the next multiple of the size. + /// + /// ````example + /// #set raw(tab-size: 8) + /// ```tsv + /// Year Month Day + /// 2000 2 3 + /// 2001 2 1 + /// 2002 3 10 + /// ``` + /// ```` + #[default(2)] + pub tab_size: usize, } impl RawElem { @@ -247,7 +263,12 @@ impl Synthesize for RawElem { impl Show for RawElem { #[tracing::instrument(name = "RawElem::show", skip_all)] fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { - let text = self.text(); + let mut text = self.text(); + if text.contains('\t') { + let tab_size = RawElem::tab_size_in(styles); + text = align_tabs(&text, tab_size); + } + let lang = self .lang(styles) .as_ref() @@ -609,3 +630,33 @@ fn item( }, } } + +/// Replace tabs with spaces to align with multiples of `tab_size`. +fn align_tabs(text: &str, tab_size: usize) -> EcoString { + let replacement = " ".repeat(tab_size); + let divisor = tab_size.max(1); + let amount = text.chars().filter(|&c| c == '\t').count(); + + let mut res = EcoString::with_capacity(text.len() - amount + amount * tab_size); + let mut column = 0; + + for grapheme in text.graphemes(true) { + match grapheme { + "\t" => { + let required = tab_size - column % divisor; + res.push_str(&replacement[..required]); + column += required; + } + "\n" => { + res.push_str(grapheme); + column = 0; + } + _ => { + res.push_str(grapheme); + column += 1; + } + } + } + + res +} diff --git a/tests/ref/text/raw-tabs.png b/tests/ref/text/raw-tabs.png new file mode 100644 index 0000000000000000000000000000000000000000..cac265e982a5dacbf1ee13f6259115467e82d86f GIT binary patch literal 3382 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 literal 0 HcmV?d00001 diff --git a/tests/typ/text/raw-tabs.typ b/tests/typ/text/raw-tabs.typ new file mode 100644 index 000000000..d31326c0e --- /dev/null +++ b/tests/typ/text/raw-tabs.typ @@ -0,0 +1,11 @@ +// Test tabs in raw code. + +--- +#set raw(tab-size: 8) + +```tsv +Year Month Day +2000 2 3 +2001 2 1 +2002 3 10 +```