1use std::ops::{Deref, DerefMut};
2
3use editor::test::{
4 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
5};
6use futures::Future;
7use gpui::{Context, View, VisualContext};
8use lsp::request;
9use search::{project_search::ProjectSearchBar, BufferSearchBar};
10
11use crate::{state::Operator, *};
12
13pub struct VimTestContext {
14 cx: EditorLspTestContext,
15}
16
17impl VimTestContext {
18 pub fn init(cx: &mut gpui::TestAppContext) {
19 if cx.has_global::<Vim>() {
20 return;
21 }
22 cx.update(|cx| {
23 search::init(cx);
24 let settings = SettingsStore::test(cx);
25 cx.set_global(settings);
26 release_channel::init("0.0.0", cx);
27 command_palette::init(cx);
28 crate::init(cx);
29 });
30 }
31
32 pub async fn new(cx: &mut gpui::TestAppContext, enabled: bool) -> VimTestContext {
33 Self::init(cx);
34 let lsp = EditorLspTestContext::new_rust(Default::default(), cx).await;
35 Self::new_with_lsp(lsp, enabled)
36 }
37
38 pub async fn new_typescript(cx: &mut gpui::TestAppContext) -> VimTestContext {
39 Self::init(cx);
40 Self::new_with_lsp(
41 EditorLspTestContext::new_typescript(Default::default(), cx).await,
42 true,
43 )
44 }
45
46 pub fn new_with_lsp(mut cx: EditorLspTestContext, enabled: bool) -> VimTestContext {
47 cx.update(|cx| {
48 cx.update_global(|store: &mut SettingsStore, cx| {
49 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
50 });
51 settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap();
52 if enabled {
53 settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
54 }
55 });
56
57 // Setup search toolbars and keypress hook
58 cx.update_workspace(|workspace, cx| {
59 observe_keystrokes(cx);
60 workspace.active_pane().update(cx, |pane, cx| {
61 pane.toolbar().update(cx, |toolbar, cx| {
62 let buffer_search_bar = cx.new_view(BufferSearchBar::new);
63 toolbar.add_item(buffer_search_bar, cx);
64
65 let project_search_bar = cx.new_view(|_| ProjectSearchBar::new());
66 toolbar.add_item(project_search_bar, cx);
67 })
68 });
69 workspace.status_bar().update(cx, |status_bar, cx| {
70 let vim_mode_indicator = cx.new_view(ModeIndicator::new);
71 status_bar.add_right_item(vim_mode_indicator, cx);
72 });
73 });
74
75 Self { cx }
76 }
77
78 pub fn update_view<F, T, R>(&mut self, view: View<T>, update: F) -> R
79 where
80 T: 'static,
81 F: FnOnce(&mut T, &mut ViewContext<T>) -> R + 'static,
82 {
83 let window = self.window.clone();
84 self.update_window(window, move |_, cx| view.update(cx, update))
85 .unwrap()
86 }
87
88 pub fn workspace<F, T>(&mut self, update: F) -> T
89 where
90 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
91 {
92 self.cx.update_workspace(update)
93 }
94
95 pub fn enable_vim(&mut self) {
96 self.cx.update(|cx| {
97 cx.update_global(|store: &mut SettingsStore, cx| {
98 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true));
99 });
100 })
101 }
102
103 pub fn disable_vim(&mut self) {
104 self.cx.update(|cx| {
105 cx.update_global(|store: &mut SettingsStore, cx| {
106 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false));
107 });
108 })
109 }
110
111 pub fn mode(&mut self) -> Mode {
112 self.cx.read(|cx| cx.global::<Vim>().state().mode)
113 }
114
115 pub fn active_operator(&mut self) -> Option<Operator> {
116 self.cx
117 .read(|cx| cx.global::<Vim>().state().operator_stack.last().copied())
118 }
119
120 pub fn set_state(&mut self, text: &str, mode: Mode) {
121 let window = self.window;
122 self.cx.set_state(text);
123 self.update_window(window, |_, cx| {
124 Vim::update(cx, |vim, cx| {
125 vim.switch_mode(mode, true, cx);
126 })
127 })
128 .unwrap();
129 self.cx.cx.cx.run_until_parked();
130 }
131
132 #[track_caller]
133 pub fn assert_state(&mut self, text: &str, mode: Mode) {
134 self.assert_editor_state(text);
135 assert_eq!(self.mode(), mode, "{}", self.assertion_context());
136 }
137
138 pub fn assert_binding<const COUNT: usize>(
139 &mut self,
140 keystrokes: [&str; COUNT],
141 initial_state: &str,
142 initial_mode: Mode,
143 state_after: &str,
144 mode_after: Mode,
145 ) {
146 self.set_state(initial_state, initial_mode);
147 self.cx.simulate_keystrokes(keystrokes);
148 self.cx.assert_editor_state(state_after);
149 assert_eq!(self.mode(), mode_after, "{}", self.assertion_context());
150 assert_eq!(self.active_operator(), None, "{}", self.assertion_context());
151 }
152
153 pub fn handle_request<T, F, Fut>(
154 &self,
155 handler: F,
156 ) -> futures::channel::mpsc::UnboundedReceiver<()>
157 where
158 T: 'static + request::Request,
159 T::Params: 'static + Send,
160 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
161 Fut: 'static + Send + Future<Output = Result<T::Result>>,
162 {
163 self.cx.handle_request::<T, F, Fut>(handler)
164 }
165}
166
167impl Deref for VimTestContext {
168 type Target = EditorTestContext;
169
170 fn deref(&self) -> &Self::Target {
171 &self.cx
172 }
173}
174
175impl DerefMut for VimTestContext {
176 fn deref_mut(&mut self) -> &mut Self::Target {
177 &mut self.cx
178 }
179}