From 98f9575653c4fad6ece47e85813d966e9bda199f Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Wed, 25 May 2022 10:22:49 -0700 Subject: [PATCH] WIP --- crates/editor/src/editor.rs | 288 +++++++++++--------------- crates/editor/src/test.rs | 312 ++++++++++++++++++++++++++++- crates/vim/src/vim_test_context.rs | 263 +++--------------------- 3 files changed, 449 insertions(+), 414 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 37080789d4..bb181a6ea1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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)); + async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { + let mut cx = EditorTestContext::new(cx).await; - view.update(cx, |view, cx| { - // two selections on the same line - select_ranges( - 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"}); - // indent from mid-tabstop to full tabstop - view.tab(&Tab, 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"}); - // 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 + 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"}); - // select across line ending - select_ranges( - view, - indoc! {" - one two - t[hree - ] four"}, - cx, - ); + cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx)); + cx.assert_editor_state(indoc! {" + one two + t[hree + } four"}); - // 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. + 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"}); - // 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, - ); - - 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.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) - }); + 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"}); - 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"); + // 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"}); - 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"); - }); + // 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)); + async fn test_delete(cx: &mut gpui::TestAppContext) { + let mut cx = EditorTestContext::new(cx).await; - 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); - }); + 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"}); - assert_eq!( - buffer.read(cx).read(cx).text(), - "on two three\nfou five six\nseven ten\n" - ); + // 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, cx: &mut ViewContext) -> Editor { - Editor::new(EditorMode::Full, buffer, None, None, None, cx) - } - fn assert_selection_ranges( marked_text: &str, selection_marker_pairs: Vec<(char, char)>, diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index cb064be545..a8bcc94ee2 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -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, + cx: &mut ViewContext, +) -> 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, +} + +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(&mut self, update: F) -> T + where + F: FnOnce(&mut Editor, &mut ViewContext) -> 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(&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> = 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>) { + 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, + expected_reverse_selections: Vec>, + expected_forward_selections: Vec>, + asserted_text: Option, + ) { + 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::(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> = + 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, + reverse_selections: &Vec>, + forward_selections: &Vec>, + ) -> 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 + } +} diff --git a/crates/vim/src/vim_test_context.rs b/crates/vim/src/vim_test_context.rs index b4a93e158c..52d4778b38 100644 --- a/crates/vim/src/vim_test_context.rs +++ b/crates/vim/src/vim_test_context.rs @@ -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, + 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::().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(&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>) { - 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, - expected_reverse_selections: Vec>, - expected_forward_selections: Vec>, - asserted_text: Option, - ) { - 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::(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> = - 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, - reverse_selections: &Vec>, - forward_selections: &Vec>, - ) -> 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( @@ -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 } }