From ea49e02d20947e8c9beae2e6393ec0fe4ee7df22 Mon Sep 17 00:00:00 2001 From: Callum Oakley Date: Sun, 6 Aug 2023 10:42:31 +0100 Subject: [PATCH] workaround for textwrap's broken tab handling while reflowing --- helix-core/src/wrap.rs | 47 ++++++++++++++++++++++++++++++-- helix-term/src/commands/typed.rs | 7 ++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/helix-core/src/wrap.rs b/helix-core/src/wrap.rs index 2ba8d173ee1d..19030f1a9458 100644 --- a/helix-core/src/wrap.rs +++ b/helix-core/src/wrap.rs @@ -1,7 +1,50 @@ +use once_cell::sync::Lazy; +use regex::{Captures, Regex}; use smartstring::{LazyCompact, SmartString}; +use crate::indent::IndentStyle; + +static LEADING_TABS: Lazy = Lazy::new(|| Regex::new(r"(?m)^\t+").unwrap()); +static LEADING_SPACES: Lazy = Lazy::new(|| Regex::new(r"(?m)^ +").unwrap()); + /// Given a slice of text, return the text re-wrapped to fit it /// within the given width. -pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString { - textwrap::refill(text, text_width).into() +pub fn reflow_hard_wrap( + text: &str, + text_width: usize, + indent_style: IndentStyle, + tab_width: usize, +) -> SmartString { + if indent_style == IndentStyle::Tabs { + // textwrap doesn't handle tabs correctly (see + // ). So as a + // workaround, expand leading tabs before wrapping and change them back + // afterwards. + // + // If/when is merged + // upstream we can remove this workaround. + let text = LEADING_TABS.replace_all(text, |captures: &Captures| { + " ".repeat(captures[0].len() * tab_width) + }); + let text = textwrap::refill(&text, text_width); + LEADING_SPACES + .replace_all(&text, |captures: &Captures| { + "\t".repeat(captures[0].len() / tab_width) + }) + .into() + } else { + textwrap::refill(text, text_width).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reflow_hard_wrap_tabs() { + let text = "\t\t// The quick brown fox jumps over\n\t\t// the lazy dog.\n"; + let expected = "\t\t// The quick brown fox jumps\n\t\t// over the lazy dog.\n"; + assert_eq!(reflow_hard_wrap(text, 40, IndentStyle::Tabs, 4), expected); + } } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 67640f79764f..74a0a965d245 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1993,7 +1993,12 @@ fn reflow( let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(rope, selection, |range| { let fragment = range.fragment(rope.slice(..)); - let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, text_width); + let reflowed_text = helix_core::wrap::reflow_hard_wrap( + &fragment, + text_width, + doc.indent_style, + doc.tab_width(), + ); (range.from(), range.to(), Some(reflowed_text)) });