Add tests and fix bugs for editor indent/outdent commands w/ hard tabs
This commit is contained in:
parent
77b9ab0885
commit
7bb7187619
4 changed files with 127 additions and 29 deletions
|
@ -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(
|
||||||
|
|
|
@ -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, '|');
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue