vim: Respect auto_indent setting in o/O commands (#53620)
## Summary Closes #53570 - `o` and `O` in normal mode were unconditionally copying the current line's indentation into the new line, ignoring the `auto_indent` setting entirely - When `auto_indent: "none"` is set, new lines created by `o`/`O` now start at column 0 as expected - When `auto_indent` is `preserve_indent` or `syntax_aware`, behavior is unchanged The fix reads `language_settings_at` for the relevant row and splits edits into two paths: `editor.edit()` (no autoindent) for `None`, and `editor.edit_with_autoindent()` for everything else — mirroring the approach already used by the non-vim `Newline` action. ## Test plan - Added `test_o_auto_indent_none`: verifies `o`/`O` produce column-0 lines with `auto_indent: "none"`, including edge cases (first line, empty line) - Added `test_o_preserve_indent`: verifies `o`/`O` copy the current line's indentation with `auto_indent: "preserve_indent"` (regression guard) - Existing neovim-backed tests (`test_o`, `test_insert_line_above`, `test_o_comment`) continue to pass Release Notes: - Fixed vim `o`/`O` commands ignoring the `auto_indent: "none"` setting, causing new lines to inherit indentation instead of starting at column 0
This commit is contained in:
1 parent
d82b039e69
commit
58e4ec9c76
1 file changed
+114
-25
+114
-25
@@ -29,7 +29,7 @@ use editor::{Anchor, SelectionEffects};
|
||||
use editor::{Bias, ToPoint};
|
||||
use editor::{display_map::ToDisplayPoint, movement};
|
||||
use gpui::{Context, Window, actions};
|
||||
use language::{Point, SelectionGoal};
|
||||
use language::{AutoIndentMode, Point, SelectionGoal};
|
||||
use log::error;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
|
||||
@@ -729,19 +729,35 @@ impl Vim {
|
||||
.into_iter()
|
||||
.map(|selection| selection.start.row)
|
||||
.collect();
|
||||
let edits = selection_start_rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let indent = snapshot
|
||||
.indent_and_comment_for_line(MultiBufferRow(row), cx)
|
||||
.chars()
|
||||
.collect::<String>();
|
||||
|
||||
let start_of_line = Point::new(row, 0);
|
||||
(start_of_line..start_of_line, indent + "\n")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
let mut auto_indent_edits = Vec::new();
|
||||
let mut plain_edits = Vec::new();
|
||||
|
||||
for row in selection_start_rows {
|
||||
let auto_indent_mode = snapshot
|
||||
.language_settings_at(Point::new(row, 0), cx)
|
||||
.auto_indent;
|
||||
let indent = if auto_indent_mode == AutoIndentMode::None {
|
||||
String::new()
|
||||
} else {
|
||||
snapshot.indent_and_comment_for_line(MultiBufferRow(row), cx)
|
||||
};
|
||||
let start_of_line = Point::new(row, 0);
|
||||
let edit = (start_of_line..start_of_line, indent + "\n");
|
||||
if auto_indent_mode == AutoIndentMode::None {
|
||||
plain_edits.push(edit);
|
||||
} else {
|
||||
auto_indent_edits.push(edit);
|
||||
}
|
||||
}
|
||||
|
||||
if !plain_edits.is_empty() {
|
||||
editor.edit(plain_edits, cx);
|
||||
}
|
||||
if !auto_indent_edits.is_empty() {
|
||||
editor.edit_with_autoindent(auto_indent_edits, cx);
|
||||
}
|
||||
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let previous_line = map.start_of_relative_buffer_row(selection.start, -1);
|
||||
@@ -776,18 +792,28 @@ impl Vim {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let edits = selection_end_rows
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let indent = snapshot
|
||||
.indent_and_comment_for_line(MultiBufferRow(row), cx)
|
||||
.chars()
|
||||
.collect::<String>();
|
||||
|
||||
let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
|
||||
(end_of_line..end_of_line, "\n".to_string() + &indent)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut auto_indent_edits = Vec::new();
|
||||
let mut plain_edits = Vec::new();
|
||||
|
||||
for row in selection_end_rows {
|
||||
let auto_indent_mode = snapshot
|
||||
.language_settings_at(Point::new(row, 0), cx)
|
||||
.auto_indent;
|
||||
let indent = if auto_indent_mode == AutoIndentMode::None {
|
||||
String::new()
|
||||
} else {
|
||||
snapshot.indent_and_comment_for_line(MultiBufferRow(row), cx)
|
||||
};
|
||||
let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row)));
|
||||
let edit = (end_of_line..end_of_line, "\n".to_string() + &indent);
|
||||
if auto_indent_mode == AutoIndentMode::None {
|
||||
plain_edits.push(edit);
|
||||
} else {
|
||||
auto_indent_edits.push(edit);
|
||||
}
|
||||
}
|
||||
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.move_with(&mut |map, selection| {
|
||||
let current_line = if !selection.is_empty() && selection.end.column() == 0 {
|
||||
@@ -802,7 +828,13 @@ impl Vim {
|
||||
selection.collapse_to(insert_point, SelectionGoal::None)
|
||||
});
|
||||
});
|
||||
editor.edit_with_autoindent(edits, cx);
|
||||
|
||||
if !plain_edits.is_empty() {
|
||||
editor.edit(plain_edits, cx);
|
||||
}
|
||||
if !auto_indent_edits.is_empty() {
|
||||
editor.edit_with_autoindent(auto_indent_edits, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1140,6 +1172,7 @@ mod test {
|
||||
state::Mode::{self},
|
||||
test::{NeovimBackedTestContext, VimTestContext},
|
||||
};
|
||||
use language;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_h(cx: &mut gpui::TestAppContext) {
|
||||
@@ -2099,6 +2132,62 @@ mod test {
|
||||
cx.shared_state().await.assert_eq("// hello\n// ˇ\n// x\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_o_auto_indent_none(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings(cx, |s| {
|
||||
s.project.all_languages.defaults.auto_indent = Some(language::AutoIndentMode::None);
|
||||
});
|
||||
});
|
||||
|
||||
// o: new line below starts at column 0 regardless of current indentation
|
||||
cx.set_state(" let xˇ = 1;", Mode::Normal);
|
||||
cx.simulate_keystrokes("o");
|
||||
cx.assert_state(" let x = 1;\nˇ", Mode::Insert);
|
||||
|
||||
// O: new line above starts at column 0 regardless of current indentation
|
||||
cx.set_state(" let xˇ = 1;", Mode::Normal);
|
||||
cx.simulate_keystrokes("shift-o");
|
||||
cx.assert_state("ˇ\n let x = 1;", Mode::Insert);
|
||||
|
||||
// o on the first line: no crash and column 0
|
||||
cx.set_state("ˇfoo", Mode::Normal);
|
||||
cx.simulate_keystrokes("o");
|
||||
cx.assert_state("foo\nˇ", Mode::Insert);
|
||||
|
||||
// O on the first line: no crash and column 0
|
||||
cx.set_state("ˇfoo", Mode::Normal);
|
||||
cx.simulate_keystrokes("shift-o");
|
||||
cx.assert_state("ˇ\nfoo", Mode::Insert);
|
||||
|
||||
// o on an already-empty line: stays at column 0
|
||||
cx.set_state("fooˇ\n\nbar", Mode::Normal);
|
||||
cx.simulate_keystrokes("j o");
|
||||
cx.assert_state("foo\n\nˇ\nbar", Mode::Insert);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_o_preserve_indent(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings(cx, |s| {
|
||||
s.project.all_languages.defaults.auto_indent =
|
||||
Some(language::AutoIndentMode::PreserveIndent);
|
||||
});
|
||||
});
|
||||
|
||||
// o: new line below copies current line's indentation
|
||||
cx.set_state(" let xˇ = 1;", Mode::Normal);
|
||||
cx.simulate_keystrokes("o");
|
||||
cx.assert_state(" let x = 1;\n ˇ", Mode::Insert);
|
||||
|
||||
// O: new line above copies current line's indentation
|
||||
cx.set_state(" let xˇ = 1;", Mode::Normal);
|
||||
cx.simulate_keystrokes("shift-o");
|
||||
cx.assert_state(" ˇ\n let x = 1;", Mode::Insert);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_yank_line_with_trailing_newline(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
Reference in new issue
Block a user