1use crate::{
2 AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
3 ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
4 PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
5 Size, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
6};
7use anyhow::Result;
8use collections::VecDeque;
9use futures::channel::oneshot;
10use parking_lot::Mutex;
11use std::{
12 cell::RefCell,
13 path::{Path, PathBuf},
14 rc::{Rc, Weak},
15 sync::Arc,
16};
17#[cfg(target_os = "windows")]
18use windows::Win32::{
19 Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
20 System::Com::{CLSCTX_INPROC_SERVER, CoCreateInstance},
21};
22
23/// TestPlatform implements the Platform trait for use in tests.
24pub(crate) struct TestPlatform {
25 background_executor: BackgroundExecutor,
26 foreground_executor: ForegroundExecutor,
27
28 pub(crate) active_window: RefCell<Option<TestWindow>>,
29 active_display: Rc<dyn PlatformDisplay>,
30 active_cursor: Mutex<CursorStyle>,
31 current_clipboard_item: Mutex<Option<ClipboardItem>>,
32 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
33 current_primary_item: Mutex<Option<ClipboardItem>>,
34 pub(crate) prompts: RefCell<TestPrompts>,
35 screen_capture_sources: RefCell<Vec<TestScreenCaptureSource>>,
36 pub opened_url: RefCell<Option<String>>,
37 pub text_system: Arc<dyn PlatformTextSystem>,
38 #[cfg(target_os = "windows")]
39 bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
40 weak: Weak<Self>,
41}
42
43#[derive(Clone)]
44/// A fake screen capture source, used for testing.
45pub struct TestScreenCaptureSource {}
46
47pub struct TestScreenCaptureStream {}
48
49impl ScreenCaptureSource for TestScreenCaptureSource {
50 fn resolution(&self) -> Result<Size<DevicePixels>> {
51 Ok(size(DevicePixels(1), DevicePixels(1)))
52 }
53
54 fn stream(
55 &self,
56 _foreground_executor: &ForegroundExecutor,
57 _frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
58 ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
59 let (mut tx, rx) = oneshot::channel();
60 let stream = TestScreenCaptureStream {};
61 tx.send(Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>))
62 .ok();
63 rx
64 }
65}
66
67impl ScreenCaptureStream for TestScreenCaptureStream {}
68
69struct TestPrompt {
70 msg: String,
71 detail: Option<String>,
72 answers: Vec<String>,
73 tx: oneshot::Sender<usize>,
74}
75
76#[derive(Default)]
77pub(crate) struct TestPrompts {
78 multiple_choice: VecDeque<TestPrompt>,
79 new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
80}
81
82impl TestPlatform {
83 pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
84 #[cfg(target_os = "windows")]
85 let bitmap_factory = unsafe {
86 windows::Win32::System::Ole::OleInitialize(None)
87 .expect("unable to initialize Windows OLE");
88 std::mem::ManuallyDrop::new(
89 CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
90 .expect("Error creating bitmap factory."),
91 )
92 };
93
94 let text_system = Arc::new(NoopTextSystem);
95
96 Rc::new_cyclic(|weak| TestPlatform {
97 background_executor: executor,
98 foreground_executor,
99 prompts: Default::default(),
100 screen_capture_sources: Default::default(),
101 active_cursor: Default::default(),
102 active_display: Rc::new(TestDisplay::new()),
103 active_window: Default::default(),
104 current_clipboard_item: Mutex::new(None),
105 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
106 current_primary_item: Mutex::new(None),
107 weak: weak.clone(),
108 opened_url: Default::default(),
109 #[cfg(target_os = "windows")]
110 bitmap_factory,
111 text_system,
112 })
113 }
114
115 pub(crate) fn simulate_new_path_selection(
116 &self,
117 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
118 ) {
119 let (path, tx) = self
120 .prompts
121 .borrow_mut()
122 .new_path
123 .pop_front()
124 .expect("no pending new path prompt");
125 self.background_executor().set_waiting_hint(None);
126 tx.send(Ok(select_path(&path))).ok();
127 }
128
129 #[track_caller]
130 pub(crate) fn simulate_prompt_answer(&self, response: &str) {
131 let prompt = self
132 .prompts
133 .borrow_mut()
134 .multiple_choice
135 .pop_front()
136 .expect("no pending multiple choice prompt");
137 self.background_executor().set_waiting_hint(None);
138 let Some(ix) = prompt.answers.iter().position(|a| a == response) else {
139 panic!(
140 "PROMPT: {}\n{:?}\n{:?}\nCannot respond with {}",
141 prompt.msg, prompt.detail, prompt.answers, response
142 )
143 };
144 prompt.tx.send(ix).ok();
145 }
146
147 pub(crate) fn has_pending_prompt(&self) -> bool {
148 !self.prompts.borrow().multiple_choice.is_empty()
149 }
150
151 pub(crate) fn pending_prompt(&self) -> Option<(String, String)> {
152 let prompts = self.prompts.borrow();
153 let prompt = prompts.multiple_choice.front()?;
154 Some((
155 prompt.msg.clone(),
156 prompt.detail.clone().unwrap_or_default(),
157 ))
158 }
159
160 pub(crate) fn set_screen_capture_sources(&self, sources: Vec<TestScreenCaptureSource>) {
161 *self.screen_capture_sources.borrow_mut() = sources;
162 }
163
164 pub(crate) fn prompt(
165 &self,
166 msg: &str,
167 detail: Option<&str>,
168 answers: &[PromptButton],
169 ) -> oneshot::Receiver<usize> {
170 let (tx, rx) = oneshot::channel();
171 let answers: Vec<String> = answers.iter().map(|s| s.label().to_string()).collect();
172 self.background_executor()
173 .set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
174 self.prompts
175 .borrow_mut()
176 .multiple_choice
177 .push_back(TestPrompt {
178 msg: msg.to_string(),
179 detail: detail.map(|s| s.to_string()),
180 answers: answers.clone(),
181 tx,
182 });
183 rx
184 }
185
186 pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
187 let executor = self.foreground_executor().clone();
188 let previous_window = self.active_window.borrow_mut().take();
189 self.active_window.borrow_mut().clone_from(&window);
190
191 executor
192 .spawn(async move {
193 if let Some(previous_window) = previous_window {
194 if let Some(window) = window.as_ref() {
195 if Rc::ptr_eq(&previous_window.0, &window.0) {
196 return;
197 }
198 }
199 previous_window.simulate_active_status_change(false);
200 }
201 if let Some(window) = window {
202 window.simulate_active_status_change(true);
203 }
204 })
205 .detach();
206 }
207
208 pub(crate) fn did_prompt_for_new_path(&self) -> bool {
209 !self.prompts.borrow().new_path.is_empty()
210 }
211}
212
213impl Platform for TestPlatform {
214 fn background_executor(&self) -> BackgroundExecutor {
215 self.background_executor.clone()
216 }
217
218 fn foreground_executor(&self) -> ForegroundExecutor {
219 self.foreground_executor.clone()
220 }
221
222 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
223 self.text_system.clone()
224 }
225
226 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
227 Box::new(TestKeyboardLayout)
228 }
229
230 fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
231
232 fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
233 unimplemented!()
234 }
235
236 fn quit(&self) {}
237
238 fn restart(&self, _: Option<PathBuf>) {
239 //
240 }
241
242 fn activate(&self, _ignoring_other_apps: bool) {
243 //
244 }
245
246 fn hide(&self) {
247 unimplemented!()
248 }
249
250 fn hide_other_apps(&self) {
251 unimplemented!()
252 }
253
254 fn unhide_other_apps(&self) {
255 unimplemented!()
256 }
257
258 fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
259 vec![self.active_display.clone()]
260 }
261
262 fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
263 Some(self.active_display.clone())
264 }
265
266 fn is_screen_capture_supported(&self) -> bool {
267 true
268 }
269
270 fn screen_capture_sources(
271 &self,
272 ) -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
273 let (mut tx, rx) = oneshot::channel();
274 tx.send(Ok(self
275 .screen_capture_sources
276 .borrow()
277 .iter()
278 .map(|source| Box::new(source.clone()) as Box<dyn ScreenCaptureSource>)
279 .collect()))
280 .ok();
281 rx
282 }
283
284 fn active_window(&self) -> Option<crate::AnyWindowHandle> {
285 self.active_window
286 .borrow()
287 .as_ref()
288 .map(|window| window.0.lock().handle)
289 }
290
291 fn open_window(
292 &self,
293 handle: AnyWindowHandle,
294 params: WindowParams,
295 ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
296 let window = TestWindow::new(
297 handle,
298 params,
299 self.weak.clone(),
300 self.active_display.clone(),
301 );
302 Ok(Box::new(window))
303 }
304
305 fn window_appearance(&self) -> WindowAppearance {
306 WindowAppearance::Light
307 }
308
309 fn open_url(&self, url: &str) {
310 *self.opened_url.borrow_mut() = Some(url.to_string())
311 }
312
313 fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
314 unimplemented!()
315 }
316
317 fn prompt_for_paths(
318 &self,
319 _options: crate::PathPromptOptions,
320 ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
321 unimplemented!()
322 }
323
324 fn prompt_for_new_path(
325 &self,
326 directory: &std::path::Path,
327 ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
328 let (tx, rx) = oneshot::channel();
329 self.background_executor()
330 .set_waiting_hint(Some(format!("PROMPT FOR PATH: {:?}", directory)));
331 self.prompts
332 .borrow_mut()
333 .new_path
334 .push_back((directory.to_path_buf(), tx));
335 rx
336 }
337
338 fn can_select_mixed_files_and_dirs(&self) -> bool {
339 true
340 }
341
342 fn reveal_path(&self, _path: &std::path::Path) {
343 unimplemented!()
344 }
345
346 fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
347
348 fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
349 unimplemented!()
350 }
351
352 fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
353 fn set_dock_menu(&self, _menu: Vec<crate::MenuItem>, _keymap: &Keymap) {}
354
355 fn add_recent_document(&self, _paths: &Path) {}
356
357 fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
358
359 fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
360
361 fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
362
363 fn app_path(&self) -> Result<std::path::PathBuf> {
364 unimplemented!()
365 }
366
367 fn path_for_auxiliary_executable(&self, _name: &str) -> Result<std::path::PathBuf> {
368 unimplemented!()
369 }
370
371 fn set_cursor_style(&self, style: crate::CursorStyle) {
372 *self.active_cursor.lock() = style;
373 }
374
375 fn should_auto_hide_scrollbars(&self) -> bool {
376 false
377 }
378
379 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
380 fn write_to_primary(&self, item: ClipboardItem) {
381 *self.current_primary_item.lock() = Some(item);
382 }
383
384 fn write_to_clipboard(&self, item: ClipboardItem) {
385 *self.current_clipboard_item.lock() = Some(item);
386 }
387
388 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
389 fn read_from_primary(&self) -> Option<ClipboardItem> {
390 self.current_primary_item.lock().clone()
391 }
392
393 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
394 self.current_clipboard_item.lock().clone()
395 }
396
397 fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
398 Task::ready(Ok(()))
399 }
400
401 fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
402 Task::ready(Ok(None))
403 }
404
405 fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
406 Task::ready(Ok(()))
407 }
408
409 fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
410 unimplemented!()
411 }
412
413 fn open_with_system(&self, _path: &Path) {
414 unimplemented!()
415 }
416}
417
418impl TestScreenCaptureSource {
419 /// Create a fake screen capture source, for testing.
420 pub fn new() -> Self {
421 Self {}
422 }
423}
424
425#[cfg(target_os = "windows")]
426impl Drop for TestPlatform {
427 fn drop(&mut self) {
428 unsafe {
429 std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
430 windows::Win32::System::Ole::OleUninitialize();
431 }
432 }
433}
434
435struct TestKeyboardLayout;
436
437impl PlatformKeyboardLayout for TestKeyboardLayout {
438 fn id(&self) -> &str {
439 "zed.keyboard.example"
440 }
441
442 fn name(&self) -> &str {
443 "zed.keyboard.example"
444 }
445}