Auto indent on insert_at_line_start (#5837)

This commit is contained in:
Alex 2023-06-08 19:11:40 +02:00 committed by GitHub
parent e2a1678436
commit 993c68ad6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 11 deletions

View file

@ -2775,24 +2775,87 @@ fn last_picker(cx: &mut Context) {
}));
}
// I inserts at the first nonwhitespace character of each line with a selection
fn insert_at_line_start(cx: &mut Context) {
goto_first_nonwhitespace(cx);
enter_insert_mode(cx);
/// Fallback position to use for [`insert_with_indent`].
enum IndentFallbackPos {
LineStart,
LineEnd,
}
// A inserts at the end of each line with a selection
// `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, IndentFallbackPos::LineStart);
}
// `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,

View file

@ -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<ret>%<A-s>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<ret>%<A-s>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(())
}