1use crate::{
2 AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap,
3 Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance,
4 WindowParams,
5};
6use anyhow::Result;
7use collections::VecDeque;
8use futures::channel::oneshot;
9use parking_lot::Mutex;
10use std::{
11 cell::RefCell,
12 path::{Path, PathBuf},
13 rc::{Rc, Weak},
14 sync::Arc,
15};
16#[cfg(target_os = "windows")]
17use windows::Win32::{
18 Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
19 System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
20};
21
22/// TestPlatform implements the Platform trait for use in tests.
23pub(crate) struct TestPlatform {
24 background_executor: BackgroundExecutor,
25 foreground_executor: ForegroundExecutor,
26
27 pub(crate) active_window: RefCell<Option<TestWindow>>,
28 active_display: Rc<dyn PlatformDisplay>,
29 active_cursor: Mutex<CursorStyle>,
30 current_clipboard_item: Mutex<Option<ClipboardItem>>,
31 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
32 current_primary_item: Mutex<Option<ClipboardItem>>,
33 pub(crate) prompts: RefCell<TestPrompts>,
34 pub opened_url: RefCell<Option<String>>,
35 pub text_system: Arc<dyn PlatformTextSystem>,
36 #[cfg(target_os = "windows")]
37 bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
38 weak: Weak<Self>,
39}
40
41#[derive(Default)]
42pub(crate) struct TestPrompts {
43 multiple_choice: VecDeque<oneshot::Sender<usize>>,
44 new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
45}
46
47impl TestPlatform {
48 pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
49 #[cfg(target_os = "windows")]
50 let bitmap_factory = unsafe {
51 windows::Win32::System::Ole::OleInitialize(None)
52 .expect("unable to initialize Windows OLE");
53 std::mem::ManuallyDrop::new(
54 CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
55 .expect("Error creating bitmap factory."),
56 )
57 };
58
59 #[cfg(target_os = "macos")]
60 let text_system = Arc::new(crate::platform::mac::MacTextSystem::new());
61
62 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
63 let text_system = Arc::new(crate::platform::linux::CosmicTextSystem::new());
64
65 #[cfg(target_os = "windows")]
66 let text_system = Arc::new(
67 crate::platform::windows::DirectWriteTextSystem::new(&bitmap_factory)
68 .expect("Unable to initialize direct write."),
69 );
70
71 Rc::new_cyclic(|weak| TestPlatform {
72 background_executor: executor,
73 foreground_executor,
74 prompts: Default::default(),
75 active_cursor: Default::default(),
76 active_display: Rc::new(TestDisplay::new()),
77 active_window: Default::default(),
78 current_clipboard_item: Mutex::new(None),
79 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
80 current_primary_item: Mutex::new(None),
81 weak: weak.clone(),
82 opened_url: Default::default(),
83 #[cfg(target_os = "windows")]
84 bitmap_factory,
85 text_system,
86 })
87 }
88
89 pub(crate) fn simulate_new_path_selection(
90 &self,
91 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
92 ) {
93 let (path, tx) = self
94 .prompts
95 .borrow_mut()
96 .new_path
97 .pop_front()
98 .expect("no pending new path prompt");
99 tx.send(Ok(select_path(&path))).ok();
100 }
101
102 pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) {
103 let tx = self
104 .prompts
105 .borrow_mut()
106 .multiple_choice
107 .pop_front()
108 .expect("no pending multiple choice prompt");
109 self.background_executor().set_waiting_hint(None);
110 tx.send(response_ix).ok();
111 }
112
113 pub(crate) fn has_pending_prompt(&self) -> bool {
114 !self.prompts.borrow().multiple_choice.is_empty()
115 }
116
117 pub(crate) fn prompt(&self, msg: &str, detail: Option<&str>) -> oneshot::Receiver<usize> {
118 let (tx, rx) = oneshot::channel();
119 self.background_executor()
120 .set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
121 self.prompts.borrow_mut().multiple_choice.push_back(tx);
122 rx
123 }
124
125 pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
126 let executor = self.foreground_executor().clone();
127 let previous_window = self.active_window.borrow_mut().take();
128 self.active_window.borrow_mut().clone_from(&window);
129
130 executor
131 .spawn(async move {
132 if let Some(previous_window) = previous_window {
133 if let Some(window) = window.as_ref() {
134 if Rc::ptr_eq(&previous_window.0, &window.0) {
135 return;
136 }
137 }
138 previous_window.simulate_active_status_change(false);
139 }
140 if let Some(window) = window {
141 window.simulate_active_status_change(true);
142 }
143 })
144 .detach();
145 }
146
147 pub(crate) fn did_prompt_for_new_path(&self) -> bool {
148 !self.prompts.borrow().new_path.is_empty()
149 }
150}
151
152impl Platform for TestPlatform {
153 fn background_executor(&self) -> BackgroundExecutor {
154 self.background_executor.clone()
155 }
156
157 fn foreground_executor(&self) -> ForegroundExecutor {
158 self.foreground_executor.clone()
159 }
160
161 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
162 self.text_system.clone()
163 }
164
165 fn keyboard_layout(&self) -> String {
166 "zed.keyboard.example".to_string()
167 }
168
169 fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
170
171 fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
172 unimplemented!()
173 }
174
175 fn quit(&self) {}
176
177 fn restart(&self, _: Option<PathBuf>) {
178 unimplemented!()
179 }
180
181 fn activate(&self, _ignoring_other_apps: bool) {
182 //
183 }
184
185 fn hide(&self) {
186 unimplemented!()
187 }
188
189 fn hide_other_apps(&self) {
190 unimplemented!()
191 }
192
193 fn unhide_other_apps(&self) {
194 unimplemented!()
195 }
196
197 fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
198 vec![self.active_display.clone()]
199 }
200
201 fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
202 Some(self.active_display.clone())
203 }
204
205 fn active_window(&self) -> Option<crate::AnyWindowHandle> {
206 self.active_window
207 .borrow()
208 .as_ref()
209 .map(|window| window.0.lock().handle)
210 }
211
212 fn open_window(
213 &self,
214 handle: AnyWindowHandle,
215 params: WindowParams,
216 ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
217 let window = TestWindow::new(
218 handle,
219 params,
220 self.weak.clone(),
221 self.active_display.clone(),
222 );
223 Ok(Box::new(window))
224 }
225
226 fn window_appearance(&self) -> WindowAppearance {
227 WindowAppearance::Light
228 }
229
230 fn open_url(&self, url: &str) {
231 *self.opened_url.borrow_mut() = Some(url.to_string())
232 }
233
234 fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
235 unimplemented!()
236 }
237
238 fn prompt_for_paths(
239 &self,
240 _options: crate::PathPromptOptions,
241 ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
242 unimplemented!()
243 }
244
245 fn prompt_for_new_path(
246 &self,
247 directory: &std::path::Path,
248 ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
249 let (tx, rx) = oneshot::channel();
250 self.prompts
251 .borrow_mut()
252 .new_path
253 .push_back((directory.to_path_buf(), tx));
254 rx
255 }
256
257 fn reveal_path(&self, _path: &std::path::Path) {
258 unimplemented!()
259 }
260
261 fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
262
263 fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
264 unimplemented!()
265 }
266
267 fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
268 fn set_dock_menu(&self, _menu: Vec<crate::MenuItem>, _keymap: &Keymap) {}
269
270 fn add_recent_document(&self, _paths: &Path) {}
271
272 fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
273
274 fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
275
276 fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
277
278 fn app_path(&self) -> Result<std::path::PathBuf> {
279 unimplemented!()
280 }
281
282 fn path_for_auxiliary_executable(&self, _name: &str) -> Result<std::path::PathBuf> {
283 unimplemented!()
284 }
285
286 fn set_cursor_style(&self, style: crate::CursorStyle) {
287 *self.active_cursor.lock() = style;
288 }
289
290 fn should_auto_hide_scrollbars(&self) -> bool {
291 false
292 }
293
294 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
295 fn write_to_primary(&self, item: ClipboardItem) {
296 *self.current_primary_item.lock() = Some(item);
297 }
298
299 fn write_to_clipboard(&self, item: ClipboardItem) {
300 *self.current_clipboard_item.lock() = Some(item);
301 }
302
303 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
304 fn read_from_primary(&self) -> Option<ClipboardItem> {
305 self.current_primary_item.lock().clone()
306 }
307
308 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
309 self.current_clipboard_item.lock().clone()
310 }
311
312 fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
313 Task::ready(Ok(()))
314 }
315
316 fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
317 Task::ready(Ok(None))
318 }
319
320 fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
321 Task::ready(Ok(()))
322 }
323
324 fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
325 unimplemented!()
326 }
327
328 fn open_with_system(&self, _path: &Path) {
329 unimplemented!()
330 }
331}
332
333#[cfg(target_os = "windows")]
334impl Drop for TestPlatform {
335 fn drop(&mut self) {
336 unsafe {
337 std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
338 windows::Win32::System::Ole::OleUninitialize();
339 }
340 }
341}