From b3afc959836a17c1f64f5c1784890ffef2ca5f47 Mon Sep 17 00:00:00 2001 From: CptPotato <3957610+CptPotato@users.noreply.github.com> Date: Sun, 5 Feb 2023 13:23:10 +0100 Subject: [PATCH 1/5] Auto indent on `insert_at_line_start` --- helix-term/src/commands.rs | 63 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 9859f64baada..f446cdcbba10 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -16,7 +16,9 @@ use helix_core::{ history::UndoKind, increment, indent, indent::IndentStyle, - line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, + line_ending::{ + get_line_ending_of_str, line_end_char_index, str_is_line_ending, + }, match_brackets, movement::{self, move_vertically_visual, Direction}, object, pos_at_coords, @@ -2756,9 +2758,66 @@ fn last_picker(cx: &mut Context) { } // I inserts at the first nonwhitespace character of each line with a selection +// If the line is empty, automatically indent fn insert_at_line_start(cx: &mut Context) { - goto_first_nonwhitespace(cx); enter_insert_mode(cx); + + let (view, doc) = current!(cx.editor); + + let text = doc.text().slice(..); + let contents = doc.text(); + let selection = doc.selection(view.id); + + let language_config = doc.language_config(); + let syntax = doc.syntax(); + let tab_width = doc.tab_width(); + + let mut ranges = SmallVec::with_capacity(selection.len()); + let mut offs = 0; + + let mut transaction = Transaction::change_by_selection(contents, selection, |range| { + let cursor_line = range.cursor_line(text); + let cursor_line_start = text.line_to_char(cursor_line); + + if line_end_char_index(&text, cursor_line) == cursor_line_start { + // line is empty => auto indent + let line_end_index = cursor_line_start; + + let indent = indent::indent_for_newline( + language_config, + syntax, + &doc.indent_style, + tab_width, + text, + cursor_line, + line_end_index, + cursor_line, + ); + + // calculate new selection ranges + let pos = offs + cursor_line_start; + let indent_width = indent.chars().count(); + ranges.push(Range::point(pos + indent_width)); + offs += indent_width; + + (line_end_index, line_end_index, Some(indent.into())) + } else { + // move cursor to the first non-whitespace character of the current line + let mut pos = cursor_line_start + offs; + let range = find_first_non_whitespace_char(text.line(cursor_line)) + .map(|ws_offset| { + pos += ws_offset; + range.put_cursor(text, pos, cx.editor.mode == Mode::Select) + }) + .unwrap_or(*range); + ranges.push(range); + + (cursor_line_start, cursor_line_start, None) + } + }); + + transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); + doc.apply(&transaction, view.id); } // A inserts at the end of each line with a selection From e5f2cbd2ff73577be28440c9b02018e00b8ff701 Mon Sep 17 00:00:00 2001 From: CptPotato <3957610+CptPotato@users.noreply.github.com> Date: Sun, 5 Feb 2023 16:45:33 +0100 Subject: [PATCH 2/5] Auto indent on insert_at_line_end --- helix-term/src/commands.rs | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f446cdcbba10..50e22c2ad9a2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -16,9 +16,7 @@ use helix_core::{ history::UndoKind, increment, indent, indent::IndentStyle, - line_ending::{ - get_line_ending_of_str, line_end_char_index, str_is_line_ending, - }, + line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, movement::{self, move_vertically_visual, Direction}, object, pos_at_coords, @@ -2760,6 +2758,26 @@ fn last_picker(cx: &mut Context) { // I inserts at the first nonwhitespace character of each line with a selection // If the line is empty, automatically indent fn insert_at_line_start(cx: &mut Context) { + insert_with_indent(cx, |cursor_line, cursor_line_start, text| { + find_first_non_whitespace_char(text.line(cursor_line)) + .map(|ws_offset| ws_offset + cursor_line_start) + }); +} + +// A inserts at the end of each line with a selection +// If the line is empty, automatically indent +fn insert_at_line_end(cx: &mut Context) { + insert_with_indent(cx, |cursor_line, _, text| { + Some(line_end_char_index(text, cursor_line)) + }); +} + +// Enter insert mode and auto-indent the current line if it is empty. +// If the line is not empty, move the cursor to the specified fallback position. +fn insert_with_indent( + cx: &mut Context, + cursor_fallback: impl Fn(usize, usize, &RopeSlice) -> Option, +) { enter_insert_mode(cx); let (view, doc) = current!(cx.editor); @@ -2802,13 +2820,9 @@ fn insert_at_line_start(cx: &mut Context) { (line_end_index, line_end_index, Some(indent.into())) } else { - // move cursor to the first non-whitespace character of the current line - let mut pos = cursor_line_start + offs; - let range = find_first_non_whitespace_char(text.line(cursor_line)) - .map(|ws_offset| { - pos += ws_offset; - range.put_cursor(text, pos, cx.editor.mode == Mode::Select) - }) + // move cursor to the fallback position + let range = cursor_fallback(cursor_line, cursor_line_start, &text) + .map(|pos| range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select)) .unwrap_or(*range); ranges.push(range); @@ -2820,20 +2834,6 @@ fn insert_at_line_start(cx: &mut Context) { doc.apply(&transaction, view.id); } -// A inserts at the end of each line with a selection -fn insert_at_line_end(cx: &mut Context) { - enter_insert_mode(cx); - let (view, doc) = current!(cx.editor); - - let selection = doc.selection(view.id).clone().transform(|range| { - let text = doc.text().slice(..); - let line = range.cursor_line(text); - let pos = line_end_char_index(&text, line); - Range::new(pos, pos) - }); - doc.set_selection(view.id, selection); -} - // Creates an LspCallback that waits for formatting changes to be computed. When they're done, // it applies them, but only if the doc hasn't changed. // From de8954a7ad36fcf920fae3af2611b43d5e8c03b3 Mon Sep 17 00:00:00 2001 From: CptPotato <3957610+CptPotato@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:59:26 +0100 Subject: [PATCH 3/5] fix insert_at_line_start --- helix-term/src/commands.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 50e22c2ad9a2..189111d40cbf 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2761,6 +2761,7 @@ fn insert_at_line_start(cx: &mut Context) { insert_with_indent(cx, |cursor_line, cursor_line_start, text| { find_first_non_whitespace_char(text.line(cursor_line)) .map(|ws_offset| ws_offset + cursor_line_start) + .unwrap_or(cursor_line_start) }); } @@ -2768,7 +2769,7 @@ fn insert_at_line_start(cx: &mut Context) { // If the line is empty, automatically indent fn insert_at_line_end(cx: &mut Context) { insert_with_indent(cx, |cursor_line, _, text| { - Some(line_end_char_index(text, cursor_line)) + line_end_char_index(text, cursor_line) }); } @@ -2776,7 +2777,7 @@ fn insert_at_line_end(cx: &mut Context) { // If the line is not empty, move the cursor to the specified fallback position. fn insert_with_indent( cx: &mut Context, - cursor_fallback: impl Fn(usize, usize, &RopeSlice) -> Option, + cursor_fallback: impl Fn(usize, usize, &RopeSlice) -> usize, ) { enter_insert_mode(cx); @@ -2821,10 +2822,8 @@ fn insert_with_indent( (line_end_index, line_end_index, Some(indent.into())) } else { // move cursor to the fallback position - let range = cursor_fallback(cursor_line, cursor_line_start, &text) - .map(|pos| range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select)) - .unwrap_or(*range); - ranges.push(range); + let pos = cursor_fallback(cursor_line, cursor_line_start, &text); + ranges.push(range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select)); (cursor_line_start, cursor_line_start, None) } From 75a2fa4ea0fc6daf514d37c943c4fba722be510f Mon Sep 17 00:00:00 2001 From: CptPotato <3957610+CptPotato@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:59:48 +0100 Subject: [PATCH 4/5] add test for insert_at_line_start/end --- helix-term/tests/test/commands.rs | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 52b123c7eb58..b13c37bcdcb6 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -426,3 +426,56 @@ async fn test_delete_char_forward() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_insert_with_indent() -> anyhow::Result<()> { + const INPUT: &str = "\ +#[f|]#n foo() { + if let Some(_) = None { + + } +\x20 +} + +fn bar() { + +}"; + + // insert_at_line_start + test(( + INPUT, + ":lang rust%I", + "\ +#[f|]#n foo() { + #(i|)#f let Some(_) = None { + #(\n|)#\ +\x20 #(}|)# +#(\x20|)# +#(}|)# +#(\n|)#\ +#(f|)#n bar() { + #(\n|)#\ +#(}|)#", + )) + .await?; + + // insert_at_line_end + test(( + INPUT, + ":lang rust%A", + "\ +fn foo() {#[\n|]#\ +\x20 if let Some(_) = None {#(\n|)#\ +\x20 #(\n|)#\ +\x20 }#(\n|)#\ +\x20#(\n|)#\ +}#(\n|)#\ +#(\n|)#\ +fn bar() {#(\n|)#\ +\x20 #(\n|)#\ +}#(|)#", + )) + .await?; + + Ok(()) +} From 50ba3070b6c22560156ed5e69352505aa08db539 Mon Sep 17 00:00:00 2001 From: CptPotato <3957610+CptPotato@users.noreply.github.com> Date: Thu, 8 Jun 2023 10:27:19 +0200 Subject: [PATCH 5/5] use fallback position enum --- helix-term/src/commands.rs | 39 +++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 189111d40cbf..58c560db5d24 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2755,30 +2755,27 @@ fn last_picker(cx: &mut Context) { })); } -// I inserts at the first nonwhitespace character of each line with a selection -// If the line is empty, automatically indent +/// Fallback position to use for [`insert_with_indent`]. +enum IndentFallbackPos { + LineStart, + LineEnd, +} + +// `I` inserts at the first nonwhitespace character of each line with a selection. +// If the line is empty, automatically indent. fn insert_at_line_start(cx: &mut Context) { - insert_with_indent(cx, |cursor_line, cursor_line_start, text| { - find_first_non_whitespace_char(text.line(cursor_line)) - .map(|ws_offset| ws_offset + cursor_line_start) - .unwrap_or(cursor_line_start) - }); + insert_with_indent(cx, IndentFallbackPos::LineStart); } -// A inserts at the end of each line with a selection -// If the line is empty, automatically indent +// `A` inserts at the end of each line with a selection. +// If the line is empty, automatically indent. fn insert_at_line_end(cx: &mut Context) { - insert_with_indent(cx, |cursor_line, _, text| { - line_end_char_index(text, cursor_line) - }); + insert_with_indent(cx, IndentFallbackPos::LineEnd); } // Enter insert mode and auto-indent the current line if it is empty. // If the line is not empty, move the cursor to the specified fallback position. -fn insert_with_indent( - cx: &mut Context, - cursor_fallback: impl Fn(usize, usize, &RopeSlice) -> usize, -) { +fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) { enter_insert_mode(cx); let (view, doc) = current!(cx.editor); @@ -2822,7 +2819,15 @@ fn insert_with_indent( (line_end_index, line_end_index, Some(indent.into())) } else { // move cursor to the fallback position - let pos = cursor_fallback(cursor_line, cursor_line_start, &text); + let pos = match cursor_fallback { + IndentFallbackPos::LineStart => { + find_first_non_whitespace_char(text.line(cursor_line)) + .map(|ws_offset| ws_offset + cursor_line_start) + .unwrap_or(cursor_line_start) + } + IndentFallbackPos::LineEnd => line_end_char_index(&text, cursor_line), + }; + ranges.push(range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select)); (cursor_line_start, cursor_line_start, None)