1use std::ops::{Deref, DerefMut};
2
3use editor::test::editor_test_context::EditorTestContext;
4use gpui::{json::json, AppContext, ContextHandle, ViewHandle};
5use project::Project;
6use search::{BufferSearchBar, ProjectSearchBar};
7use workspace::{pane, AppState, WorkspaceHandle};
8
9use crate::{state::Operator, *};
10
11use super::VimBindingTestContext;
12
13pub struct VimTestContext<'a> {
14 cx: EditorTestContext<'a>,
15 workspace: ViewHandle<Workspace>,
16}
17
18impl<'a> VimTestContext<'a> {
19 pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
20 cx.update(|cx| {
21 editor::init(cx);
22 pane::init(cx);
23 search::init(cx);
24 crate::init(cx);
25
26 settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
27 });
28
29 let params = cx.update(AppState::test);
30 let project = Project::test(params.fs.clone(), [], cx).await;
31
32 cx.update(|cx| {
33 cx.update_global(|settings: &mut Settings, _| {
34 settings.vim_mode = enabled;
35 });
36 });
37
38 params
39 .fs
40 .as_fake()
41 .insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
42 .await;
43
44 let (window_id, workspace) = cx.add_window(|cx| {
45 Workspace::new(
46 Default::default(),
47 0,
48 project.clone(),
49 |_, _| unimplemented!(),
50 cx,
51 )
52 });
53
54 // Setup search toolbars and keypress hook
55 workspace.update(cx, |workspace, cx| {
56 observe_keypresses(window_id, cx);
57 workspace.active_pane().update(cx, |pane, cx| {
58 pane.toolbar().update(cx, |toolbar, cx| {
59 let buffer_search_bar = cx.add_view(BufferSearchBar::new);
60 toolbar.add_item(buffer_search_bar, cx);
61 let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
62 toolbar.add_item(project_search_bar, cx);
63 })
64 });
65 });
66
67 project
68 .update(cx, |project, cx| {
69 project.find_or_create_local_worktree("/root", true, cx)
70 })
71 .await
72 .unwrap();
73 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
74 .await;
75
76 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
77 let item = workspace
78 .update(cx, |workspace, cx| {
79 workspace.open_path(file, None, true, cx)
80 })
81 .await
82 .expect("Could not open test file");
83
84 let editor = cx.update(|cx| {
85 item.act_as::<Editor>(cx)
86 .expect("Opened test file wasn't an editor")
87 });
88 editor.update(cx, |_, cx| cx.focus_self());
89
90 Self {
91 cx: EditorTestContext {
92 cx,
93 window_id,
94 editor,
95 },
96 workspace,
97 }
98 }
99
100 pub fn workspace<F, T>(&mut self, read: F) -> T
101 where
102 F: FnOnce(&Workspace, &AppContext) -> T,
103 {
104 self.workspace.read_with(self.cx.cx, read)
105 }
106
107 pub fn enable_vim(&mut self) {
108 self.cx.update(|cx| {
109 cx.update_global(|settings: &mut Settings, _| {
110 settings.vim_mode = true;
111 });
112 })
113 }
114
115 pub fn disable_vim(&mut self) {
116 self.cx.update(|cx| {
117 cx.update_global(|settings: &mut Settings, _| {
118 settings.vim_mode = false;
119 });
120 })
121 }
122
123 pub fn mode(&mut self) -> Mode {
124 self.cx.read(|cx| cx.global::<Vim>().state.mode)
125 }
126
127 pub fn active_operator(&mut self) -> Option<Operator> {
128 self.cx
129 .read(|cx| cx.global::<Vim>().state.operator_stack.last().copied())
130 }
131
132 pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
133 self.cx.update(|cx| {
134 Vim::update(cx, |vim, cx| {
135 vim.switch_mode(mode, false, cx);
136 })
137 });
138 self.cx.set_state(text)
139 }
140
141 pub fn assert_state(&mut self, text: &str, mode: Mode) {
142 self.assert_editor_state(text);
143 assert_eq!(self.mode(), mode, "{}", self.assertion_context());
144 }
145
146 pub fn assert_binding<const COUNT: usize>(
147 &mut self,
148 keystrokes: [&str; COUNT],
149 initial_state: &str,
150 initial_mode: Mode,
151 state_after: &str,
152 mode_after: Mode,
153 ) {
154 self.set_state(initial_state, initial_mode);
155 self.cx.simulate_keystrokes(keystrokes);
156 self.cx.assert_editor_state(state_after);
157 assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
158 assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
159 }
160
161 pub fn binding<const COUNT: usize>(
162 mut self,
163 keystrokes: [&'static str; COUNT],
164 ) -> VimBindingTestContext<'a, COUNT> {
165 let mode = self.mode();
166 VimBindingTestContext::new(keystrokes, mode, mode, self)
167 }
168}
169
170impl<'a> Deref for VimTestContext<'a> {
171 type Target = EditorTestContext<'a>;
172
173 fn deref(&self) -> &Self::Target {
174 &self.cx
175 }
176}
177
178impl<'a> DerefMut for VimTestContext<'a> {
179 fn deref_mut(&mut self) -> &mut Self::Target {
180 &mut self.cx
181 }
182}