From 32013b13e359ef55b57ff2a005b9bdc52e9692dc Mon Sep 17 00:00:00 2001 From: Sylvain Terrien Date: Mon, 23 Jun 2025 01:03:22 +0200 Subject: [PATCH] fix: alignment when indenting with spaces (#13498) Adjust the number of spaces inserted, so that indentation is aligned on columns. --- helix-term/src/commands.rs | 31 +++++++++--- helix-term/tests/test/commands/insert.rs | 56 ++++++++++++++++++++++ helix-term/tests/test/commands/movement.rs | 2 +- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 38e52e18c..119470239 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -4174,14 +4174,22 @@ pub mod insert { pub fn insert_tab(cx: &mut Context) { let (view, doc) = current!(cx.editor); - // TODO: round out to nearest indentation level (for example a line with 3 spaces should - // indent by one to reach 4 spaces). - let indent = Tendril::from(doc.indent_style.as_str()); - let transaction = Transaction::insert( + let transaction = Transaction::change( doc.text(), - &doc.selection(view.id).clone().cursors(doc.text().slice(..)), - indent, + doc.selection(view.id).ranges().iter().map(|range| { + let indent = if let IndentStyle::Spaces(indent_width) = doc.indent_style { + let line = range.cursor_line(doc.text().slice(..)); + let line_start = doc.text().line_to_char(line); + let offset = (range.head - line_start) % indent_width as usize; + + Tendril::from(doc.indent_style.as_str()).split_off(offset) + } else { + Tendril::from(doc.indent_style.as_str()) + }; + + (range.head, range.head, Some(indent)) + }), ); doc.apply(&transaction, view.id); } @@ -4867,7 +4875,16 @@ fn indent(cx: &mut Context) { return None; } let pos = doc.text().line_to_char(line); - Some((pos, pos, Some(indent.clone()))) + + let indent = if let IndentStyle::Spaces(indent_width) = doc.indent_style { + let line = doc.text().line(line); + let offset = line.first_non_whitespace_char().unwrap_or(0) % indent_width as usize; + indent.clone().split_off(offset) + } else { + indent.clone() + }; + + Some((pos, pos, Some(indent))) }), ); doc.apply(&transaction, view.id); diff --git a/helix-term/tests/test/commands/insert.rs b/helix-term/tests/test/commands/insert.rs index 9499868e2..7f00826bb 100644 --- a/helix-term/tests/test/commands/insert.rs +++ b/helix-term/tests/test/commands/insert.rs @@ -583,3 +583,59 @@ async fn test_jump_undo_redo() -> anyhow::Result<()> { .await?; Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_indent_with_spaces() -> anyhow::Result<()> { + let tests = vec![ + // at start of line + ( + indoc! {"\ + SELECT * + #[|FROM table]# + #(|WHERE condition)# + "}, + "i", + indoc! {"\ + SELECT * + #[|FROM table]# + #(|WHERE condition)# + "}, + ), + // in the middle of line + ( + indoc! {"\ + SELECT #[*|]# + FROM #(table|)# + WHERE #(condition|)# + "}, + "i", + indoc! {"\ + SELECT #[|*]# + FROM #(|table)# + WHERE #(|condition)# + "}, + ), + // indentation in normal mode + ( + indoc! {"\ + -- comment + #[|SELECT * + FROM table + WHERE condition]# + "}, + "", + indoc! {"\ + -- comment + #[|SELECT * + FROM table + WHERE condition]# + "}, + ), + ]; + + for test in tests { + test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?; + } + + Ok(()) +} diff --git a/helix-term/tests/test/commands/movement.rs b/helix-term/tests/test/commands/movement.rs index 5868fa494..c7e71ae92 100644 --- a/helix-term/tests/test/commands/movement.rs +++ b/helix-term/tests/test/commands/movement.rs @@ -437,7 +437,7 @@ async fn test_smart_tab_move_parent_node_end() -> anyhow::Result<()> { let result = if true { #[|\"yes\"\n]# } else { - \"no #(|\"\n)# + \"no #(|\"\n)# } } "},