Add tests and fix bugs for editor indent/outdent commands w/ hard tabs

This commit is contained in:
Max Brunsfeld 2022-06-09 10:26:09 -07:00
parent 77b9ab0885
commit 7bb7187619
4 changed files with 127 additions and 29 deletions

View file

@ -3126,21 +3126,27 @@ impl Editor {
for selection in &mut selections { for selection in &mut selections {
let language_name = let language_name =
buffer.language_at(selection.start, cx).map(|l| l.name()); buffer.language_at(selection.start, cx).map(|l| l.name());
let tab_size = cx.global::<Settings>().tab_size(language_name.as_deref()); let settings = cx.global::<Settings>();
let tab_size = if settings.hard_tabs(language_name.as_deref()) {
IndentSize::tab()
} else {
let tab_size = settings.tab_size(language_name.as_deref());
let char_column = buffer let char_column = buffer
.read(cx) .read(cx)
.text_for_range(Point::new(selection.start.row, 0)..selection.start) .text_for_range(Point::new(selection.start.row, 0)..selection.start)
.flat_map(str::chars) .flat_map(str::chars)
.count(); .count();
let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size); let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
IndentSize::spaces(chars_to_next_tab_stop)
};
buffer.edit( buffer.edit(
[( [(
selection.start..selection.start, selection.start..selection.start,
" ".repeat(chars_to_next_tab_stop as usize), tab_size.chars().collect::<String>(),
)], )],
cx, cx,
); );
selection.start.column += chars_to_next_tab_stop; selection.start.column += tab_size.len;
selection.end = selection.start; selection.end = selection.start;
} }
}); });
@ -3161,7 +3167,14 @@ impl Editor {
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
for selection in &mut selections { for selection in &mut selections {
let language_name = buffer.language_at(selection.start, cx).map(|l| l.name()); let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
let tab_size = cx.global::<Settings>().tab_size(language_name.as_deref()); let settings = &cx.global::<Settings>();
let tab_size = settings.tab_size(language_name.as_deref());
let indent_kind = if settings.hard_tabs(language_name.as_deref()) {
IndentKind::Tab
} else {
IndentKind::Space
};
let mut start_row = selection.start.row; let mut start_row = selection.start.row;
let mut end_row = selection.end.row + 1; let mut end_row = selection.end.row + 1;
@ -3186,14 +3199,16 @@ impl Editor {
for row in start_row..end_row { for row in start_row..end_row {
let current_indent = snapshot.indent_size_for_line(row); let current_indent = snapshot.indent_size_for_line(row);
let indent_delta = match current_indent.kind { let indent_delta = match (current_indent.kind, indent_kind) {
IndentKind::Space => { (IndentKind::Space, IndentKind::Space) => {
let columns_to_next_tab_stop = let columns_to_next_tab_stop =
tab_size - (current_indent.len % tab_size); tab_size - (current_indent.len % tab_size);
IndentSize::spaces(columns_to_next_tab_stop) IndentSize::spaces(columns_to_next_tab_stop)
} }
IndentKind::Tab => IndentSize::tab(), (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
(_, IndentKind::Tab) => IndentSize::tab(),
}; };
let row_start = Point::new(row, 0); let row_start = Point::new(row, 0);
buffer.edit( buffer.edit(
[( [(
@ -7696,6 +7711,88 @@ mod tests {
four"}); four"});
} }
#[gpui::test]
async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
cx.update(|cx| {
cx.update_global::<Settings, _, _>(|settings, _| {
settings.hard_tabs = true;
});
});
// select two ranges on one line
cx.set_state(indoc! {"
[one} [two}
three
four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
\t[one} [two}
three
four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
\t\t[one} [two}
three
four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
\t[one} [two}
three
four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
[one} [two}
three
four"});
// select across a line ending
cx.set_state(indoc! {"
one two
t[hree
}four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
\tt[hree
}four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
\t\tt[hree
}four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
\tt[hree
}four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
t[hree
}four"});
// Ensure that indenting/outdenting works when the cursor is at column 0.
cx.set_state(indoc! {"
one two
|three
four"});
cx.assert_editor_state(indoc! {"
one two
|three
four"});
cx.update_editor(|e, cx| e.tab(&Tab, cx));
cx.assert_editor_state(indoc! {"
one two
\t|three
four"});
cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
cx.assert_editor_state(indoc! {"
one two
|three
four"});
}
#[gpui::test] #[gpui::test]
fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) { fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
cx.set_global( cx.set_global(

View file

@ -109,9 +109,10 @@ impl<'a> EditorTestContext<'a> {
self.editor.update(self.cx, update) self.editor.update(self.cx, update)
} }
pub fn editor_text(&mut self) -> String { pub fn buffer_text(&mut self) -> String {
self.editor self.editor.read_with(self.cx, |editor, cx| {
.update(self.cx, |editor, cx| editor.snapshot(cx).text()) editor.buffer.read(cx).snapshot(cx).text()
})
} }
pub fn simulate_keystroke(&mut self, keystroke_text: &str) { pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
@ -171,10 +172,10 @@ impl<'a> EditorTestContext<'a> {
&text, &text,
vec!['|'.into(), ('[', '}').into(), ('{', ']').into()], vec!['|'.into(), ('[', '}').into(), ('{', ']').into()],
); );
let editor_text = self.editor_text(); let buffer_text = self.buffer_text();
assert_eq!( assert_eq!(
editor_text, unmarked_text, buffer_text, unmarked_text,
"Unmarked text doesn't match editor text" "Unmarked text doesn't match buffer text"
); );
let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default(); let expected_empty_selections = selection_ranges.remove(&'|'.into()).unwrap_or_default();
@ -254,7 +255,7 @@ impl<'a> EditorTestContext<'a> {
let actual_selections = let actual_selections =
self.insert_markers(&empty_selections, &reverse_selections, &forward_selections); self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
let unmarked_text = self.editor_text(); let unmarked_text = self.buffer_text();
let all_eq: Result<(), SetEqError<String>> = let all_eq: Result<(), SetEqError<String>> =
set_eq!(expected_empty_selections, empty_selections) set_eq!(expected_empty_selections, empty_selections)
.map_err(|err| { .map_err(|err| {
@ -322,7 +323,7 @@ impl<'a> EditorTestContext<'a> {
reverse_selections: &Vec<Range<usize>>, reverse_selections: &Vec<Range<usize>>,
forward_selections: &Vec<Range<usize>>, forward_selections: &Vec<Range<usize>>,
) -> String { ) -> String {
let mut editor_text_with_selections = self.editor_text(); let mut editor_text_with_selections = self.buffer_text();
let mut selection_marks = BTreeMap::new(); let mut selection_marks = BTreeMap::new();
for range in empty_selections { for range in empty_selections {
selection_marks.insert(&range.start, '|'); selection_marks.insert(&range.start, '|');

View file

@ -2002,15 +2002,15 @@ impl BufferSnapshot {
pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize {
let mut result = IndentSize::spaces(0); let mut result = IndentSize::spaces(0);
for c in text.chars_at(Point::new(row, 0)) { for c in text.chars_at(Point::new(row, 0)) {
match (c, &result.kind) { let kind = match c {
(' ', IndentKind::Space) | ('\t', IndentKind::Tab) => result.len += 1, ' ' => IndentKind::Space,
('\t', IndentKind::Space) => { '\t' => IndentKind::Tab,
if result.len == 0 {
result = IndentSize::tab();
}
}
_ => break, _ => break,
};
if result.len == 0 {
result.kind = kind;
} }
result.len += 1;
} }
result result
} }

View file

@ -202,7 +202,7 @@ mod test {
cx.enable_vim(); cx.enable_vim();
assert_eq!(cx.mode(), Mode::Normal); assert_eq!(cx.mode(), Mode::Normal);
cx.simulate_keystrokes(["h", "h", "h", "l"]); cx.simulate_keystrokes(["h", "h", "h", "l"]);
assert_eq!(cx.editor_text(), "hjkl".to_owned()); assert_eq!(cx.buffer_text(), "hjkl".to_owned());
cx.assert_editor_state("h|jkl"); cx.assert_editor_state("h|jkl");
cx.simulate_keystrokes(["i", "T", "e", "s", "t"]); cx.simulate_keystrokes(["i", "T", "e", "s", "t"]);
cx.assert_editor_state("hTest|jkl"); cx.assert_editor_state("hTest|jkl");