From e92990f4584a230e6dd112b387d4feb438604eda Mon Sep 17 00:00:00 2001 From: Daniel Ebert Date: Sat, 11 Feb 2023 16:28:09 +0100 Subject: [PATCH] Separate tab_width and indent_width: - tab_width is the width (in spaces) the `\t` character is rendered at - indent_width is the width (in spaces) of an indentation level Some functions previously incorrectly used tab_width for both. --- helix-core/src/indent.rs | 21 +++++++++++++++++---- helix-core/tests/indent.rs | 1 + helix-term/src/commands.rs | 25 +++++++++---------------- helix-term/src/commands/typed.rs | 14 +++++++------- helix-term/src/ui/document.rs | 2 +- helix-view/src/document.rs | 10 +++------- 6 files changed, 38 insertions(+), 35 deletions(-) diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index b56af0afb304e..08907846764a8 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -17,6 +17,12 @@ use crate::{ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct IndentStyle { pub unit: IndentUnit, + /// The width (in spaces) of a level of indentation. This is used: + /// - to insert the correct number of spaces, if unit == IndentUnit::Spaces + /// - to convert the number of spaces in an existing line into an indentation level + pub indent_width: usize, + /// The width that tabs are rendered at. This also determines how many + /// spaces a tab corresponds to when computing the indentation level for an existing line. pub tab_width: usize, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -38,11 +44,15 @@ impl IndentStyle { if indent.starts_with(' ') { IndentStyle { unit: IndentUnit::Spaces, - tab_width: indent.len(), + indent_width: indent.len(), + tab_width, } } else { IndentStyle { unit: IndentUnit::Tabs, + // TODO: Theoretically, one could define several tabs as an indent unit (which this code simply ignores). + // Supporting arbitrary indentation styles would require refactoring the `as_str` method though. + indent_width: tab_width, tab_width, } } @@ -50,7 +60,7 @@ impl IndentStyle { #[inline] pub fn as_str(&self) -> &'static str { - match (self.unit, self.tab_width) { + match (self.unit, self.indent_width) { (IndentUnit::Tabs, _) => "\t", (IndentUnit::Spaces, 1) => " ", (IndentUnit::Spaces, 2) => " ", @@ -182,11 +192,13 @@ pub fn auto_detect_indent_style(document_text: &Rope, tab_width: usize) -> Optio Some(match indent { 0 => IndentStyle { unit: IndentUnit::Tabs, + indent_width: tab_width, tab_width, }, _ => IndentStyle { unit: IndentUnit::Spaces, - tab_width: indent, + indent_width: indent, + tab_width, }, }) } else { @@ -206,7 +218,7 @@ pub fn indent_level_for_line(line: RopeSlice, indent_style: &IndentStyle) -> usi } } - len / indent_style.tab_width + len / indent_style.indent_width } /// Computes for node and all ancestors whether they are the first node on their line. @@ -781,6 +793,7 @@ mod test { fn test_indent_level() { let indent_style = IndentStyle { unit: IndentUnit::Spaces, + indent_width: 4, tab_width: 4, }; let line = Rope::from(" fn new"); // 8 spaces diff --git a/helix-core/tests/indent.rs b/helix-core/tests/indent.rs index c54170be0cabe..8d3d71938c836 100644 --- a/helix-core/tests/indent.rs +++ b/helix-core/tests/indent.rs @@ -51,6 +51,7 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) { &syntax, &IndentStyle { unit: helix_core::indent::IndentUnit::Spaces, + indent_width: 4, tab_width: 4, }, text, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 013e95ff1370f..5bb56b68c22b7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -879,7 +879,7 @@ fn align_selections(cx: &mut Context) { let text = doc.text().slice(..); let selection = doc.selection(view.id); - let tab_width = doc.tab_width(); + let tab_width = doc.indent_style.tab_width; let mut column_widths: Vec> = Vec::new(); let mut last_line = text.len_lines() + 1; let mut col = 0; @@ -1570,7 +1570,7 @@ fn copy_selection_on_line(cx: &mut Context, direction: Direction) { (range.head, range.anchor.saturating_sub(1)) }; - let tab_width = doc.tab_width(); + let tab_width = doc.indent_style.tab_width; let head_pos = visual_coords_at_pos(text, head, tab_width); let anchor_pos = visual_coords_at_pos(text, anchor, tab_width); @@ -3363,8 +3363,6 @@ pub mod insert { let count = cx.count(); let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); - let indent_unit = doc.indent_style.as_str(); - let tab_size = doc.tab_width(); let auto_pairs = doc.auto_pairs(cx.editor); let transaction = @@ -3385,18 +3383,13 @@ pub mod insert { None, ) } else { - let unit_len = indent_unit.chars().count(); - // NOTE: indent_unit always contains 'only spaces' or 'only tab' according to `IndentStyle` definition. - let unit_size = if indent_unit.starts_with('\t') { - tab_size * unit_len - } else { - unit_len - }; + let tab_width = doc.indent_style.tab_width; + let indent_width = doc.indent_style.indent_width; let width: usize = fragment .chars() .map(|ch| { if ch == '\t' { - tab_size + tab_width } else { // it can be none if it still meet control characters other than '\t' // here just set the width to 1 (or some value better?). @@ -3404,9 +3397,9 @@ pub mod insert { } }) .sum(); - let mut drop = width % unit_size; // round down to nearest unit + let mut drop = width % indent_width; // round down to nearest unit if drop == 0 { - drop = unit_size + drop = indent_width }; // if it's already at a unit, consume a whole unit let mut chars = fragment.chars().rev(); let mut start = pos; @@ -3947,8 +3940,8 @@ fn unindent(cx: &mut Context) { let (view, doc) = current!(cx.editor); let lines = get_lines(doc, view.id); let mut changes = Vec::with_capacity(lines.len()); - let tab_width = doc.tab_width(); - let indent_width = count * tab_width; + let tab_width = doc.indent_style.tab_width; + let indent_width = count * doc.indent_style.indent_width; for line_idx in lines { let line = doc.text().line(line_idx); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 89c0d564740e6..27e1d5d01b104 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -387,11 +387,13 @@ fn set_indent_style( IndentStyle { unit: Tabs, .. } => "tabs".to_owned(), IndentStyle { unit: Spaces, - tab_width: 1, + indent_width: 1, + .. } => "1 space".to_owned(), IndentStyle { unit: Spaces, - tab_width: n, + indent_width: n, + .. } if (2..=8).contains(&n) => format!("{} spaces", n), _ => unreachable!(), // Shouldn't happen. }); @@ -405,15 +407,13 @@ fn set_indent_style( doc.indent_style.unit = Tabs; } arg => { - let tab_width = arg + let new_width = arg .and_then(|arg| arg.parse::().ok()) .filter(|n| (1..=8).contains(n)) .context("invalid indent style")?; let doc = doc_mut!(cx.editor); - doc.indent_style = IndentStyle { - unit: Spaces, - tab_width, - }; + doc.indent_style.unit = Spaces; + doc.indent_style.indent_width = new_width; } } Ok(()) diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index ed4b1de90e913..04f584fc608f6 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -343,7 +343,7 @@ impl<'a> TextRenderer<'a> { characters: ws_chars, } = &editor_config.whitespace; - let tab_width = doc.tab_width(); + let tab_width = doc.indent_style.tab_width; let tab = if ws_render.tab() == WhitespaceRenderValue::All { std::iter::once(ws_chars.tab) .chain(std::iter::repeat(ws_chars.tabpad).take(tab_width - 1)) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 4773443cbb612..b781678c94585 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -40,6 +40,7 @@ const BUF_SIZE: usize = 8192; const DEFAULT_INDENT: IndentStyle = IndentStyle { unit: IndentUnit::Tabs, + indent_width: 4, tab_width: 4, }; @@ -514,7 +515,7 @@ impl Document { let request = language_server.text_document_formatting( self.identifier(), lsp::FormattingOptions { - tab_size: self.tab_width() as u32, + tab_size: self.indent_style.tab_width as u32, insert_spaces: self.indent_style.unit == IndentUnit::Spaces, ..Default::default() }, @@ -1129,11 +1130,6 @@ impl Document { self.syntax.as_ref() } - /// Tab size in columns. - pub fn tab_width(&self) -> usize { - self.indent_style.tab_width - } - pub fn changes(&self) -> &ChangeSet { &self.changes } @@ -1244,7 +1240,7 @@ impl Document { } let config = self.config.load(); let soft_wrap = &config.soft_wrap; - let tab_width = self.tab_width() as u16; + let tab_width = self.indent_style.tab_width as u16; TextFormat { soft_wrap: soft_wrap.enable && viewport_width > 10, tab_width,