Detailed changes
@@ -5,8 +5,8 @@ pub mod movement;
mod multi_buffer;
pub mod selections_collection;
-#[cfg(test)]
-mod test;
+#[cfg(any(test, feature = "test-support"))]
+pub mod test;
use aho_corasick::AhoCorasick;
use anyhow::Result;
@@ -6017,7 +6017,9 @@ pub fn styled_runs_for_code_label<'a>(
#[cfg(test)]
mod tests {
- use crate::test::{assert_text_with_selections, select_ranges};
+ use crate::test::{
+ assert_text_with_selections, build_editor, select_ranges, EditorTestContext,
+ };
use super::*;
use gpui::{
@@ -7289,117 +7291,62 @@ mod tests {
}
#[gpui::test]
- fn test_indent_outdent(cx: &mut gpui::MutableAppContext) {
- cx.set_global(Settings::test(cx));
- let buffer = MultiBuffer::build_simple(
- indoc! {"
- one two
- three
- four"},
- cx,
- );
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
- view.update(cx, |view, cx| {
- // two selections on the same line
- select_ranges(
- view,
- indoc! {"
- [one] [two]
- three
- four"},
- cx,
- );
-
- // indent from mid-tabstop to full tabstop
- view.tab(&Tab, cx);
- assert_text_with_selections(
- view,
- indoc! {"
- [one] [two]
- three
- four"},
- cx,
- );
-
- // outdent from 1 tabstop to 0 tabstops
- view.tab_prev(&TabPrev, cx);
- assert_text_with_selections(
- view,
- indoc! {"
- [one] [two]
- three
- four"},
- cx,
- );
-
- // select across line ending
- select_ranges(
- view,
- indoc! {"
- one two
- t[hree
- ] four"},
- cx,
- );
+ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
+ let mut cx = EditorTestContext::new(cx).await;
- // indent and outdent affect only the preceding line
- view.tab(&Tab, cx);
- assert_text_with_selections(
- view,
- indoc! {"
- one two
- t[hree
- ] four"},
- cx,
- );
- view.tab_prev(&TabPrev, cx);
- assert_text_with_selections(
- view,
- indoc! {"
- one two
- t[hree
- ] four"},
- cx,
- );
-
- // Ensure that indenting/outdenting works when the cursor is at column 0.
- select_ranges(
- view,
- indoc! {"
- one two
- []three
- four"},
- cx,
- );
- view.tab(&Tab, cx);
- assert_text_with_selections(
- view,
- indoc! {"
- one two
- []three
- four"},
- cx,
- );
+ cx.set_state(indoc! {"
+ [one} [two}
+ three
+ four"});
+ cx.update_editor(|e, cx| e.tab(&Tab, cx));
+ cx.assert_editor_state(indoc! {"
+ [one} [two}
+ three
+ four"});
- select_ranges(
- view,
- indoc! {"
- one two
- [] three
- four"},
- cx,
- );
- view.tab_prev(&TabPrev, cx);
- assert_text_with_selections(
- view,
- indoc! {"
- one two
- []three
- four"},
- cx,
- );
- });
+ cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+ cx.assert_editor_state(indoc! {"
+ [one} [two}
+ three
+ four"});
+
+ // select across 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
+ t[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.update_editor(|e, cx| e.tab(&Tab, cx));
+ cx.assert_editor_state(indoc! {"
+ one two
+ |three
+ four"});
+
+ cx.set_state(indoc! {"
+ one two
+ | three
+ four"});
+ cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
+ cx.assert_editor_state(indoc! {"
+ one two
+ |three
+ four"});
}
#[gpui::test]
@@ -7508,73 +7455,74 @@ mod tests {
}
#[gpui::test]
- fn test_backspace(cx: &mut gpui::MutableAppContext) {
- cx.set_global(Settings::test(cx));
- let (_, view) = cx.add_window(Default::default(), |cx| {
- build_editor(MultiBuffer::build_simple("", cx), cx)
- });
-
- view.update(cx, |view, cx| {
- view.set_text("one two three\nfour five six\nseven eight nine\nten\n", cx);
- view.change_selections(None, cx, |s| {
- s.select_display_ranges([
- // an empty selection - the preceding character is deleted
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
- // one character selected - it is deleted
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
- // a line suffix selected - it is deleted
- DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
- ])
- });
- view.backspace(&Backspace, cx);
- assert_eq!(view.text(cx), "oe two three\nfou five six\nseven ten\n");
-
- view.set_text(" one\n two\n three\n four", cx);
- view.change_selections(None, cx, |s| {
- s.select_display_ranges([
- // cursors at the the end of leading indent - last indent is deleted
- DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
- DisplayPoint::new(1, 8)..DisplayPoint::new(1, 8),
- // cursors inside leading indent - overlapping indent deletions are coalesced
- DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
- DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
- DisplayPoint::new(2, 6)..DisplayPoint::new(2, 6),
- // cursor at the beginning of a line - preceding newline is deleted
- DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
- // selection inside leading indent - only the selected character is deleted
- DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3),
- ])
- });
- view.backspace(&Backspace, cx);
- assert_eq!(view.text(cx), "one\n two\n three four");
- });
+ async fn test_backspace(cx: &mut gpui::TestAppContext) {
+ let mut cx = EditorTestContext::new(cx).await;
+ // Basic backspace
+ cx.set_state(indoc! {"
+ on|e two three
+ fou[r} five six
+ seven {eight nine
+ ]ten"});
+ cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+ cx.assert_editor_state(indoc! {"
+ o|e two three
+ fou| five six
+ seven |ten"});
+
+ // Test backspace inside and around indents
+ cx.set_state(indoc! {"
+ zero
+ |one
+ |two
+ | | | three
+ | | four"});
+ cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+ cx.assert_editor_state(indoc! {"
+ zero
+ |one
+ |two
+ | three| four"});
+
+ // Test backspace with line_mode set to true
+ cx.update_editor(|e, _| e.selections.line_mode = true);
+ cx.set_state(indoc! {"
+ The |quick |brown
+ fox jumps over
+ the lazy dog
+ |The qu[ick b}rown"});
+ cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+ cx.assert_editor_state(indoc! {"
+ |
+ fox jumps over
+ the lazy dog|"});
}
#[gpui::test]
- fn test_delete(cx: &mut gpui::MutableAppContext) {
- cx.set_global(Settings::test(cx));
- let buffer =
- MultiBuffer::build_simple("one two three\nfour five six\nseven eight nine\nten\n", cx);
- let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
-
- view.update(cx, |view, cx| {
- view.change_selections(None, cx, |s| {
- s.select_display_ranges([
- // an empty selection - the following character is deleted
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
- // one character selected - it is deleted
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
- // a line suffix selected - it is deleted
- DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
- ])
- });
- view.delete(&Delete, cx);
- });
-
- assert_eq!(
- buffer.read(cx).read(cx).text(),
- "on two three\nfou five six\nseven ten\n"
- );
+ async fn test_delete(cx: &mut gpui::TestAppContext) {
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.set_state(indoc! {"
+ on|e two three
+ fou[r} five six
+ seven {eight nine
+ ]ten"});
+ cx.update_editor(|e, cx| e.delete(&Delete, cx));
+ cx.assert_editor_state(indoc! {"
+ on| two three
+ fou| five six
+ seven |ten"});
+
+ // Test backspace with line_mode set to true
+ cx.update_editor(|e, _| e.selections.line_mode = true);
+ cx.set_state(indoc! {"
+ The |quick |brown
+ fox {jum]ps over|
+ the lazy dog
+ |The qu[ick b}rown"});
+ cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+ cx.assert_editor_state(indoc! {"
+ |
+ the lazy dog|"});
}
#[gpui::test]
@@ -9795,10 +9743,6 @@ mod tests {
point..point
}
- fn build_editor(buffer: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
- Editor::new(EditorMode::Full, buffer, None, None, None, cx)
- }
-
fn assert_selection_ranges(
marked_text: &str,
selection_marker_pairs: Vec<(char, char)>,
@@ -1,9 +1,20 @@
-use gpui::ViewContext;
-use util::test::{marked_text, marked_text_ranges};
+use std::ops::{Deref, DerefMut, Range};
+
+use indoc::indoc;
+
+use collections::BTreeMap;
+use gpui::{keymap::Keystroke, ModelHandle, ViewContext, ViewHandle};
+use itertools::{Either, Itertools};
+use language::Selection;
+use settings::Settings;
+use util::{
+ set_eq,
+ test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError},
+};
use crate::{
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
- DisplayPoint, Editor, MultiBuffer,
+ Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer,
};
#[cfg(test)]
@@ -56,3 +67,298 @@ pub fn assert_text_with_selections(
assert_eq!(editor.text(cx), unmarked_text);
assert_eq!(editor.selections.ranges(cx), text_ranges);
}
+
+pub(crate) fn build_editor(
+ buffer: ModelHandle<MultiBuffer>,
+ cx: &mut ViewContext<Editor>,
+) -> Editor {
+ Editor::new(EditorMode::Full, buffer, None, None, None, cx)
+}
+
+pub struct EditorTestContext<'a> {
+ pub cx: &'a mut gpui::TestAppContext,
+ pub window_id: usize,
+ pub editor: ViewHandle<Editor>,
+}
+
+impl<'a> EditorTestContext<'a> {
+ pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
+ let (window_id, editor) = cx.update(|cx| {
+ cx.set_global(Settings::test(cx));
+ crate::init(cx);
+
+ let (window_id, editor) = cx.add_window(Default::default(), |cx| {
+ build_editor(MultiBuffer::build_simple("", cx), cx)
+ });
+
+ editor.update(cx, |_, cx| cx.focus_self());
+
+ (window_id, editor)
+ });
+
+ Self {
+ cx,
+ window_id,
+ editor,
+ }
+ }
+
+ pub fn update_editor<F, T>(&mut self, update: F) -> T
+ where
+ F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
+ {
+ self.editor.update(self.cx, update)
+ }
+
+ pub fn editor_text(&mut self) -> String {
+ self.editor
+ .update(self.cx, |editor, cx| editor.snapshot(cx).text())
+ }
+
+ pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
+ let keystroke = Keystroke::parse(keystroke_text).unwrap();
+ let input = if keystroke.modified() {
+ None
+ } else {
+ Some(keystroke.key.clone())
+ };
+ self.cx
+ .dispatch_keystroke(self.window_id, keystroke, input, false);
+ }
+
+ pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
+ for keystroke_text in keystroke_texts.into_iter() {
+ self.simulate_keystroke(keystroke_text);
+ }
+ }
+
+ // Sets the editor state via a marked string.
+ // `|` characters represent empty selections
+ // `[` to `}` represents a non empty selection with the head at `}`
+ // `{` to `]` represents a non empty selection with the head at `{`
+ pub fn set_state(&mut self, text: &str) {
+ self.editor.update(self.cx, |editor, cx| {
+ let (text_with_ranges, empty_selections) = marked_text(&text);
+ let (unmarked_text, mut selection_ranges) =
+ marked_text_ranges_by(&text_with_ranges, vec![('[', '}'), ('{', ']')]);
+ editor.set_text(unmarked_text, cx);
+
+ let mut selections: Vec<Range<usize>> = empty_selections
+ .into_iter()
+ .map(|offset| offset..offset)
+ .collect();
+ selections.extend(selection_ranges.remove(&('{', ']')).unwrap_or_default());
+ selections.extend(selection_ranges.remove(&('[', '}')).unwrap_or_default());
+
+ editor.change_selections(Some(Autoscroll::Fit), cx, |s| s.select_ranges(selections));
+ })
+ }
+
+ // Asserts the editor state via a marked string.
+ // `|` characters represent empty selections
+ // `[` to `}` represents a non empty selection with the head at `}`
+ // `{` to `]` represents a non empty selection with the head at `{`
+ pub fn assert_editor_state(&mut self, text: &str) {
+ let (text_with_ranges, expected_empty_selections) = marked_text(&text);
+ let (unmarked_text, mut selection_ranges) =
+ marked_text_ranges_by(&text_with_ranges, vec![('[', '}'), ('{', ']')]);
+ let editor_text = self.editor_text();
+ assert_eq!(
+ editor_text, unmarked_text,
+ "Unmarked text doesn't match editor text"
+ );
+
+ let expected_reverse_selections = selection_ranges.remove(&('{', ']')).unwrap_or_default();
+ let expected_forward_selections = selection_ranges.remove(&('[', '}')).unwrap_or_default();
+
+ self.assert_selections(
+ expected_empty_selections,
+ expected_reverse_selections,
+ expected_forward_selections,
+ Some(text.to_string()),
+ )
+ }
+
+ pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
+ let (expected_empty_selections, expected_non_empty_selections): (Vec<_>, Vec<_>) =
+ expected_selections.into_iter().partition_map(|selection| {
+ if selection.is_empty() {
+ Either::Left(selection.head())
+ } else {
+ Either::Right(selection)
+ }
+ });
+
+ let (expected_reverse_selections, expected_forward_selections): (Vec<_>, Vec<_>) =
+ expected_non_empty_selections
+ .into_iter()
+ .partition_map(|selection| {
+ let range = selection.start..selection.end;
+ if selection.reversed {
+ Either::Left(range)
+ } else {
+ Either::Right(range)
+ }
+ });
+
+ self.assert_selections(
+ expected_empty_selections,
+ expected_reverse_selections,
+ expected_forward_selections,
+ None,
+ )
+ }
+
+ fn assert_selections(
+ &mut self,
+ expected_empty_selections: Vec<usize>,
+ expected_reverse_selections: Vec<Range<usize>>,
+ expected_forward_selections: Vec<Range<usize>>,
+ asserted_text: Option<String>,
+ ) {
+ let (empty_selections, reverse_selections, forward_selections) =
+ self.editor.read_with(self.cx, |editor, cx| {
+ let (empty_selections, non_empty_selections): (Vec<_>, Vec<_>) = editor
+ .selections
+ .all::<usize>(cx)
+ .into_iter()
+ .partition_map(|selection| {
+ if selection.is_empty() {
+ Either::Left(selection.head())
+ } else {
+ Either::Right(selection)
+ }
+ });
+
+ let (reverse_selections, forward_selections): (Vec<_>, Vec<_>) =
+ non_empty_selections.into_iter().partition_map(|selection| {
+ let range = selection.start..selection.end;
+ if selection.reversed {
+ Either::Left(range)
+ } else {
+ Either::Right(range)
+ }
+ });
+ (empty_selections, reverse_selections, forward_selections)
+ });
+
+ let asserted_selections = asserted_text.unwrap_or_else(|| {
+ self.insert_markers(
+ &expected_empty_selections,
+ &expected_reverse_selections,
+ &expected_forward_selections,
+ )
+ });
+ let actual_selections =
+ self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
+
+ let unmarked_text = self.editor_text();
+ let all_eq: Result<(), SetEqError<String>> =
+ set_eq!(expected_empty_selections, empty_selections)
+ .map_err(|err| {
+ err.map(|missing| {
+ let mut error_text = unmarked_text.clone();
+ error_text.insert(missing, '|');
+ error_text
+ })
+ })
+ .and_then(|_| {
+ set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
+ err.map(|missing| {
+ let mut error_text = unmarked_text.clone();
+ error_text.insert(missing.start, '{');
+ error_text.insert(missing.end, ']');
+ error_text
+ })
+ })
+ })
+ .and_then(|_| {
+ set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
+ err.map(|missing| {
+ let mut error_text = unmarked_text.clone();
+ error_text.insert(missing.start, '[');
+ error_text.insert(missing.end, '}');
+ error_text
+ })
+ })
+ });
+
+ match all_eq {
+ Err(SetEqError::LeftMissing(location_text)) => {
+ panic!(
+ indoc! {"
+ Editor has extra selection
+ Extra Selection Location:
+ {}
+ Asserted selections:
+ {}
+ Actual selections:
+ {}"},
+ location_text, asserted_selections, actual_selections,
+ );
+ }
+ Err(SetEqError::RightMissing(location_text)) => {
+ panic!(
+ indoc! {"
+ Editor is missing empty selection
+ Missing Selection Location:
+ {}
+ Asserted selections:
+ {}
+ Actual selections:
+ {}"},
+ location_text, asserted_selections, actual_selections,
+ );
+ }
+ _ => {}
+ }
+ }
+
+ fn insert_markers(
+ &mut self,
+ empty_selections: &Vec<usize>,
+ reverse_selections: &Vec<Range<usize>>,
+ forward_selections: &Vec<Range<usize>>,
+ ) -> String {
+ let mut editor_text_with_selections = self.editor_text();
+ let mut selection_marks = BTreeMap::new();
+ for offset in empty_selections {
+ selection_marks.insert(offset, '|');
+ }
+ for range in reverse_selections {
+ selection_marks.insert(&range.start, '{');
+ selection_marks.insert(&range.end, ']');
+ }
+ for range in forward_selections {
+ selection_marks.insert(&range.start, '[');
+ selection_marks.insert(&range.end, '}');
+ }
+ for (offset, mark) in selection_marks.into_iter().rev() {
+ editor_text_with_selections.insert(*offset, mark);
+ }
+
+ editor_text_with_selections
+ }
+
+ pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
+ self.cx.update(|cx| {
+ let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
+ let expected_content = expected_content.map(|content| content.to_owned());
+ assert_eq!(actual_content, expected_content);
+ })
+ }
+}
+
+impl<'a> Deref for EditorTestContext<'a> {
+ type Target = gpui::TestAppContext;
+
+ fn deref(&self) -> &Self::Target {
+ self.cx
+ }
+}
+
+impl<'a> DerefMut for EditorTestContext<'a> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.cx
+ }
+}
@@ -1,25 +1,14 @@
-use std::ops::{Deref, DerefMut, Range};
+use std::ops::{Deref, DerefMut};
-use collections::BTreeMap;
-use itertools::{Either, Itertools};
-
-use editor::{display_map::ToDisplayPoint, Autoscroll};
-use gpui::{json::json, keymap::Keystroke, ViewHandle};
-use indoc::indoc;
-use language::Selection;
+use editor::test::EditorTestContext;
+use gpui::json::json;
use project::Project;
-use util::{
- set_eq,
- test::{marked_text, marked_text_ranges_by, SetEqError},
-};
use workspace::{pane, AppState, WorkspaceHandle};
use crate::{state::Operator, *};
pub struct VimTestContext<'a> {
- cx: &'a mut gpui::TestAppContext,
- window_id: usize,
- editor: ViewHandle<Editor>,
+ cx: EditorTestContext<'a>,
}
impl<'a> VimTestContext<'a> {
@@ -70,9 +59,11 @@ impl<'a> VimTestContext<'a> {
editor.update(cx, |_, cx| cx.focus_self());
Self {
- cx,
- window_id,
- editor,
+ cx: EditorTestContext {
+ cx,
+ window_id,
+ editor,
+ },
}
}
@@ -101,225 +92,13 @@ impl<'a> VimTestContext<'a> {
.read(|cx| cx.global::<Vim>().state.operator_stack.last().copied())
}
- pub fn editor_text(&mut self) -> String {
- self.editor
- .update(self.cx, |editor, cx| editor.snapshot(cx).text())
- }
-
- pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
- let keystroke = Keystroke::parse(keystroke_text).unwrap();
- let input = if keystroke.modified() {
- None
- } else {
- Some(keystroke.key.clone())
- };
- self.cx
- .dispatch_keystroke(self.window_id, keystroke, input, false);
- }
-
- pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
- for keystroke_text in keystroke_texts.into_iter() {
- self.simulate_keystroke(keystroke_text);
- }
- }
-
pub fn set_state(&mut self, text: &str, mode: Mode) {
- self.cx
- .update(|cx| Vim::update(cx, |vim, cx| vim.switch_mode(mode, cx)));
- self.editor.update(self.cx, |editor, cx| {
- let (unmarked_text, markers) = marked_text(&text);
- editor.set_text(unmarked_text, cx);
- let cursor_offset = markers[0];
- editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
- s.replace_cursors_with(|map| vec![cursor_offset.to_display_point(map)])
- });
- })
- }
-
- // Asserts the editor state via a marked string.
- // `|` characters represent empty selections
- // `[` to `}` represents a non empty selection with the head at `}`
- // `{` to `]` represents a non empty selection with the head at `{`
- pub fn assert_editor_state(&mut self, text: &str) {
- let (text_with_ranges, expected_empty_selections) = marked_text(&text);
- let (unmarked_text, mut selection_ranges) =
- marked_text_ranges_by(&text_with_ranges, vec![('[', '}'), ('{', ']')]);
- let editor_text = self.editor_text();
- assert_eq!(
- editor_text, unmarked_text,
- "Unmarked text doesn't match editor text"
- );
-
- let expected_reverse_selections = selection_ranges.remove(&('{', ']')).unwrap_or_default();
- let expected_forward_selections = selection_ranges.remove(&('[', '}')).unwrap_or_default();
-
- self.assert_selections(
- expected_empty_selections,
- expected_reverse_selections,
- expected_forward_selections,
- Some(text.to_string()),
- )
- }
-
- pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
- let (expected_empty_selections, expected_non_empty_selections): (Vec<_>, Vec<_>) =
- expected_selections.into_iter().partition_map(|selection| {
- if selection.is_empty() {
- Either::Left(selection.head())
- } else {
- Either::Right(selection)
- }
- });
-
- let (expected_reverse_selections, expected_forward_selections): (Vec<_>, Vec<_>) =
- expected_non_empty_selections
- .into_iter()
- .partition_map(|selection| {
- let range = selection.start..selection.end;
- if selection.reversed {
- Either::Left(range)
- } else {
- Either::Right(range)
- }
- });
-
- self.assert_selections(
- expected_empty_selections,
- expected_reverse_selections,
- expected_forward_selections,
- None,
- )
- }
-
- fn assert_selections(
- &mut self,
- expected_empty_selections: Vec<usize>,
- expected_reverse_selections: Vec<Range<usize>>,
- expected_forward_selections: Vec<Range<usize>>,
- asserted_text: Option<String>,
- ) {
- let (empty_selections, reverse_selections, forward_selections) =
- self.editor.read_with(self.cx, |editor, cx| {
- let (empty_selections, non_empty_selections): (Vec<_>, Vec<_>) = editor
- .selections
- .all::<usize>(cx)
- .into_iter()
- .partition_map(|selection| {
- if selection.is_empty() {
- Either::Left(selection.head())
- } else {
- Either::Right(selection)
- }
- });
-
- let (reverse_selections, forward_selections): (Vec<_>, Vec<_>) =
- non_empty_selections.into_iter().partition_map(|selection| {
- let range = selection.start..selection.end;
- if selection.reversed {
- Either::Left(range)
- } else {
- Either::Right(range)
- }
- });
- (empty_selections, reverse_selections, forward_selections)
- });
-
- let asserted_selections = asserted_text.unwrap_or_else(|| {
- self.insert_markers(
- &expected_empty_selections,
- &expected_reverse_selections,
- &expected_forward_selections,
- )
+ self.cx.update(|cx| {
+ Vim::update(cx, |vim, cx| {
+ vim.switch_mode(mode, cx);
+ })
});
- let actual_selections =
- self.insert_markers(&empty_selections, &reverse_selections, &forward_selections);
-
- let unmarked_text = self.editor_text();
- let all_eq: Result<(), SetEqError<String>> =
- set_eq!(expected_empty_selections, empty_selections)
- .map_err(|err| {
- err.map(|missing| {
- let mut error_text = unmarked_text.clone();
- error_text.insert(missing, '|');
- error_text
- })
- })
- .and_then(|_| {
- set_eq!(expected_reverse_selections, reverse_selections).map_err(|err| {
- err.map(|missing| {
- let mut error_text = unmarked_text.clone();
- error_text.insert(missing.start, '{');
- error_text.insert(missing.end, ']');
- error_text
- })
- })
- })
- .and_then(|_| {
- set_eq!(expected_forward_selections, forward_selections).map_err(|err| {
- err.map(|missing| {
- let mut error_text = unmarked_text.clone();
- error_text.insert(missing.start, '[');
- error_text.insert(missing.end, '}');
- error_text
- })
- })
- });
-
- match all_eq {
- Err(SetEqError::LeftMissing(location_text)) => {
- panic!(
- indoc! {"
- Editor has extra selection
- Extra Selection Location:
- {}
- Asserted selections:
- {}
- Actual selections:
- {}"},
- location_text, asserted_selections, actual_selections,
- );
- }
- Err(SetEqError::RightMissing(location_text)) => {
- panic!(
- indoc! {"
- Editor is missing empty selection
- Missing Selection Location:
- {}
- Asserted selections:
- {}
- Actual selections:
- {}"},
- location_text, asserted_selections, actual_selections,
- );
- }
- _ => {}
- }
- }
-
- fn insert_markers(
- &mut self,
- empty_selections: &Vec<usize>,
- reverse_selections: &Vec<Range<usize>>,
- forward_selections: &Vec<Range<usize>>,
- ) -> String {
- let mut editor_text_with_selections = self.editor_text();
- let mut selection_marks = BTreeMap::new();
- for offset in empty_selections {
- selection_marks.insert(offset, '|');
- }
- for range in reverse_selections {
- selection_marks.insert(&range.start, '{');
- selection_marks.insert(&range.end, ']');
- }
- for range in forward_selections {
- selection_marks.insert(&range.start, '[');
- selection_marks.insert(&range.end, '}');
- }
- for (offset, mark) in selection_marks.into_iter().rev() {
- editor_text_with_selections.insert(*offset, mark);
- }
-
- editor_text_with_selections
+ self.cx.set_state(text);
}
pub fn assert_binding<const COUNT: usize>(
@@ -331,8 +110,8 @@ impl<'a> VimTestContext<'a> {
mode_after: Mode,
) {
self.set_state(initial_state, initial_mode);
- self.simulate_keystrokes(keystrokes);
- self.assert_editor_state(state_after);
+ self.cx.simulate_keystrokes(keystrokes);
+ self.cx.assert_editor_state(state_after);
assert_eq!(self.mode(), mode_after);
assert_eq!(self.active_operator(), None);
}
@@ -355,10 +134,16 @@ impl<'a> VimTestContext<'a> {
}
impl<'a> Deref for VimTestContext<'a> {
- type Target = gpui::TestAppContext;
+ type Target = EditorTestContext<'a>;
fn deref(&self) -> &Self::Target {
- self.cx
+ &self.cx
+ }
+}
+
+impl<'a> DerefMut for VimTestContext<'a> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.cx
}
}