aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src')
-rw-r--r--helix-term/src/commands.rs83
1 files changed, 73 insertions, 10 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 99f50b04..99f27c00 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -2775,24 +2775,87 @@ fn last_picker(cx: &mut Context) {
}));
}
-// I inserts at the first nonwhitespace character of each line with a selection
+/// 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) {
- goto_first_nonwhitespace(cx);
- enter_insert_mode(cx);
+ insert_with_indent(cx, IndentFallbackPos::LineStart);
}
-// A inserts at the end of each line with a selection
+// `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, 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: IndentFallbackPos) {
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)
+ 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 fallback position
+ 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)
+ }
});
- doc.set_selection(view.id, selection);
+
+ transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
+ doc.apply(&transaction, view.id);
}
// Creates an LspCallback that waits for formatting changes to be computed. When they're done,