Auto indent on insert_at_line_start
(#5837)
This commit is contained in:
parent
e2a1678436
commit
993c68ad6f
2 changed files with 127 additions and 11 deletions
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue