1#![allow(unused_variables, unused_mut)]
2//todo!()
3
4mod assets;
5pub mod languages;
6mod only_instance;
7mod open_listener;
8
9pub use assets::*;
10use breadcrumbs::Breadcrumbs;
11use collections::VecDeque;
12use editor::{Editor, MultiBuffer};
13use gpui::{
14 actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions,
15 ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
16};
17pub use only_instance::*;
18pub use open_listener::*;
19
20use anyhow::{anyhow, Context as _};
21use project_panel::ProjectPanel;
22use settings::{initial_local_settings_content, Settings};
23use std::{borrow::Cow, ops::Deref, sync::Arc};
24use terminal_view::terminal_panel::TerminalPanel;
25use util::{
26 asset_str,
27 channel::{AppCommitSha, ReleaseChannel},
28 paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
29 ResultExt,
30};
31use uuid::Uuid;
32use workspace::{
33 create_and_open_local_file, dock::PanelHandle,
34 notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
35 NewWindow, Workspace, WorkspaceSettings,
36};
37use zed_actions::{OpenBrowser, OpenZedURL};
38
39actions!(
40 About,
41 DebugElements,
42 DecreaseBufferFontSize,
43 Hide,
44 HideOthers,
45 IncreaseBufferFontSize,
46 Minimize,
47 OpenDefaultKeymap,
48 OpenDefaultSettings,
49 OpenKeymap,
50 OpenLicenses,
51 OpenLocalSettings,
52 OpenLog,
53 OpenSettings,
54 OpenTelemetryLog,
55 Quit,
56 ResetBufferFontSize,
57 ResetDatabase,
58 ShowAll,
59 ToggleFullScreen,
60 Zoom,
61);
62
63pub fn build_window_options(
64 bounds: Option<WindowBounds>,
65 display_uuid: Option<Uuid>,
66 cx: &mut AppContext,
67) -> WindowOptions {
68 let bounds = bounds.unwrap_or(WindowBounds::Maximized);
69 let display = display_uuid.and_then(|uuid| {
70 cx.displays()
71 .into_iter()
72 .find(|display| display.uuid().ok() == Some(uuid))
73 });
74
75 WindowOptions {
76 bounds,
77 titlebar: Some(TitlebarOptions {
78 title: None,
79 appears_transparent: true,
80 traffic_light_position: Some(point(px(8.), px(8.))),
81 }),
82 center: false,
83 focus: false,
84 show: false,
85 kind: WindowKind::Normal,
86 is_movable: true,
87 display_id: display.map(|display| display.id()),
88 }
89}
90
91pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
92 cx.observe_new_views(move |workspace: &mut Workspace, cx| {
93 let workspace_handle = cx.view().clone();
94 cx.subscribe(&workspace_handle, {
95 move |workspace, _, event, cx| {
96 if let workspace::Event::PaneAdded(pane) = event {
97 pane.update(cx, |pane, cx| {
98 pane.toolbar().update(cx, |toolbar, cx| {
99 let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
100 toolbar.add_item(breadcrumbs, cx);
101 let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
102 toolbar.add_item(buffer_search_bar.clone(), cx);
103 // todo!()
104 // let quick_action_bar = cx.add_view(|_| {
105 // QuickActionBar::new(buffer_search_bar, workspace)
106 // });
107 // toolbar.add_item(quick_action_bar, cx);
108 let diagnostic_editor_controls =
109 cx.build_view(|_| diagnostics::ToolbarControls::new());
110 // toolbar.add_item(diagnostic_editor_controls, cx);
111 // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
112 // toolbar.add_item(project_search_bar, cx);
113 // let submit_feedback_button =
114 // cx.build_view(|_| SubmitFeedbackButton::new());
115 // toolbar.add_item(submit_feedback_button, cx);
116 // let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
117 // toolbar.add_item(feedback_info_text, cx);
118 // let lsp_log_item =
119 // cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
120 // toolbar.add_item(lsp_log_item, cx);
121 // let syntax_tree_item = cx
122 // .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
123 // toolbar.add_item(syntax_tree_item, cx);
124 })
125 });
126 }
127 }
128 })
129 .detach();
130
131 // cx.emit(workspace2::Event::PaneAdded(
132 // workspace.active_pane().clone(),
133 // ));
134
135 // let collab_titlebar_item =
136 // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
137 // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
138
139 let copilot =
140 cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
141 let diagnostic_summary =
142 cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
143 let activity_indicator =
144 activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
145 let active_buffer_language =
146 cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
147 // let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
148 let feedback_button = cx
149 .build_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace));
150 // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
151 // let feedback_button = cx.add_view(|_| {
152 // feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
153 // });
154 let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
155 workspace.status_bar().update(cx, |status_bar, cx| {
156 status_bar.add_left_item(diagnostic_summary, cx);
157 status_bar.add_left_item(activity_indicator, cx);
158 status_bar.add_right_item(feedback_button, cx);
159 // status_bar.add_right_item(copilot, cx);
160 status_bar.add_right_item(copilot, cx);
161 status_bar.add_right_item(active_buffer_language, cx);
162 // status_bar.add_right_item(vim_mode_indicator, cx);
163 status_bar.add_right_item(cursor_position, cx);
164 });
165
166 auto_update::notify_of_any_new_update(cx);
167
168 // vim::observe_keystrokes(cx);
169
170 let handle = cx.view().downgrade();
171 cx.on_window_should_close(move |cx| {
172 handle
173 .update(cx, |workspace, cx| {
174 if let Some(task) = workspace.close(&Default::default(), cx) {
175 task.detach_and_log_err(cx);
176 }
177 false
178 })
179 .unwrap_or(true)
180 });
181
182 cx.spawn(|workspace_handle, mut cx| async move {
183 let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
184 let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
185 // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
186 let channels_panel =
187 collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
188 // let chat_panel =
189 // collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
190 // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
191 // workspace_handle.clone(),
192 // cx.clone(),
193 // );
194 let (
195 project_panel,
196 terminal_panel,
197 // assistant_panel,
198 channels_panel,
199 // chat_panel,
200 // notification_panel,
201 ) = futures::try_join!(
202 project_panel,
203 terminal_panel,
204 // assistant_panel,
205 channels_panel,
206 // chat_panel,
207 // notification_panel,
208 )?;
209
210 workspace_handle.update(&mut cx, |workspace, cx| {
211 let project_panel_position = project_panel.position(cx);
212 workspace.add_panel(project_panel, cx);
213 workspace.add_panel(terminal_panel, cx);
214 // workspace.add_panel(assistant_panel, cx);
215 workspace.add_panel(channels_panel, cx);
216 // workspace.add_panel(chat_panel, cx);
217 // workspace.add_panel(notification_panel, cx);
218
219 // if !was_deserialized
220 // && workspace
221 // .project()
222 // .read(cx)
223 // .visible_worktrees(cx)
224 // .any(|tree| {
225 // tree.read(cx)
226 // .root_entry()
227 // .map_or(false, |entry| entry.is_dir())
228 // })
229 // {
230 // workspace.toggle_dock(project_panel_position, cx);
231 // }
232 // cx.focus_self();
233 })
234 })
235 .detach();
236
237 workspace
238 .register_action(about)
239 .register_action(|_, _: &Hide, cx| {
240 cx.hide();
241 })
242 .register_action(|_, _: &HideOthers, cx| {
243 cx.hide_other_apps();
244 })
245 .register_action(|_, _: &ShowAll, cx| {
246 cx.unhide_other_apps();
247 })
248 .register_action(|_, _: &Minimize, cx| {
249 cx.minimize_window();
250 })
251 .register_action(|_, _: &Zoom, cx| {
252 cx.zoom_window();
253 })
254 .register_action(|_, _: &ToggleFullScreen, cx| {
255 cx.toggle_full_screen();
256 })
257 .register_action(quit)
258 .register_action(|_, action: &OpenZedURL, cx| {
259 cx.global::<Arc<OpenListener>>()
260 .open_urls(&[action.url.clone()])
261 })
262 .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url))
263 //todo!(buffer font size)
264 // cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
265 // theme::adjust_font_size(cx, |size| *size += 1.0)
266 // });
267 // cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
268 // theme::adjust_font_size(cx, |size| *size -= 1.0)
269 // });
270 // cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx));
271 .register_action(|_, _: &install_cli::Install, cx| {
272 cx.spawn(|_, cx| async move {
273 install_cli::install_cli(cx.deref())
274 .await
275 .context("error creating CLI symlink")
276 })
277 .detach_and_log_err(cx);
278 })
279 .register_action(|workspace, _: &OpenLog, cx| {
280 open_log_file(workspace, cx);
281 })
282 .register_action(|workspace, _: &OpenLicenses, cx| {
283 open_bundled_file(
284 workspace,
285 asset_str::<Assets>("licenses.md"),
286 "Open Source License Attribution",
287 "Markdown",
288 cx,
289 );
290 })
291 .register_action(
292 move |workspace: &mut Workspace,
293 _: &OpenTelemetryLog,
294 cx: &mut ViewContext<Workspace>| {
295 open_telemetry_log_file(workspace, cx);
296 },
297 )
298 .register_action(
299 move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
300 create_and_open_local_file(&paths::KEYMAP, cx, Default::default)
301 .detach_and_log_err(cx);
302 },
303 )
304 .register_action(
305 move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
306 create_and_open_local_file(&paths::SETTINGS, cx, || {
307 settings::initial_user_settings_content().as_ref().into()
308 })
309 .detach_and_log_err(cx);
310 },
311 )
312 .register_action(open_local_settings_file)
313 .register_action(
314 move |workspace: &mut Workspace,
315 _: &OpenDefaultKeymap,
316 cx: &mut ViewContext<Workspace>| {
317 open_bundled_file(
318 workspace,
319 settings::default_keymap(),
320 "Default Key Bindings",
321 "JSON",
322 cx,
323 );
324 },
325 )
326 .register_action(
327 move |workspace: &mut Workspace,
328 _: &OpenDefaultSettings,
329 cx: &mut ViewContext<Workspace>| {
330 open_bundled_file(
331 workspace,
332 settings::default_settings(),
333 "Default Settings",
334 "JSON",
335 cx,
336 );
337 },
338 )
339 //todo!()
340 // cx.add_action({
341 // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
342 // let app_state = workspace.app_state().clone();
343 // let markdown = app_state.languages.language_for_name("JSON");
344 // let window = cx.window();
345 // cx.spawn(|workspace, mut cx| async move {
346 // let markdown = markdown.await.log_err();
347 // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
348 // anyhow!("could not debug elements for window {}", window.id())
349 // })?)
350 // .unwrap();
351 // workspace
352 // .update(&mut cx, |workspace, cx| {
353 // workspace.with_local_workspace(cx, move |workspace, cx| {
354 // let project = workspace.project().clone();
355 // let buffer = project
356 // .update(cx, |project, cx| {
357 // project.create_buffer(&content, markdown, cx)
358 // })
359 // .expect("creating buffers on a local workspace always succeeds");
360 // let buffer = cx.add_model(|cx| {
361 // MultiBuffer::singleton(buffer, cx)
362 // .with_title("Debug Elements".into())
363 // });
364 // workspace.add_item(
365 // Box::new(cx.add_view(|cx| {
366 // Editor::for_multibuffer(buffer, Some(project.clone()), cx)
367 // })),
368 // cx,
369 // );
370 // })
371 // })?
372 // .await
373 // })
374 // .detach_and_log_err(cx);
375 // }
376 // });
377 // .register_action(
378 // |workspace: &mut Workspace,
379 // _: &project_panel::ToggleFocus,
380 // cx: &mut ViewContext<Workspace>| {
381 // workspace.toggle_panel_focus::<ProjectPanel>(cx);
382 // },
383 // );
384 // cx.add_action(
385 // |workspace: &mut Workspace,
386 // _: &collab_ui::collab_panel::ToggleFocus,
387 // cx: &mut ViewContext<Workspace>| {
388 // workspace.toggle_panel_focus::<collab_ui::collab_panel::CollabPanel>(cx);
389 // },
390 // );
391 // cx.add_action(
392 // |workspace: &mut Workspace,
393 // _: &collab_ui::chat_panel::ToggleFocus,
394 // cx: &mut ViewContext<Workspace>| {
395 // workspace.toggle_panel_focus::<collab_ui::chat_panel::ChatPanel>(cx);
396 // },
397 // );
398 // cx.add_action(
399 // |workspace: &mut Workspace,
400 // _: &collab_ui::notification_panel::ToggleFocus,
401 // cx: &mut ViewContext<Workspace>| {
402 // workspace.toggle_panel_focus::<collab_ui::notification_panel::NotificationPanel>(cx);
403 // },
404 // );
405 // cx.add_action(
406 // |workspace: &mut Workspace,
407 // _: &terminal_panel::ToggleFocus,
408 // cx: &mut ViewContext<Workspace>| {
409 // workspace.toggle_panel_focus::<TerminalPanel>(cx);
410 // },
411 // );
412 .register_action({
413 let app_state = Arc::downgrade(&app_state);
414 move |_, _: &NewWindow, cx| {
415 if let Some(app_state) = app_state.upgrade() {
416 open_new(&app_state, cx, |workspace, cx| {
417 Editor::new_file(workspace, &Default::default(), cx)
418 })
419 .detach();
420 }
421 }
422 })
423 .register_action({
424 let app_state = Arc::downgrade(&app_state);
425 move |_, _: &NewFile, cx| {
426 if let Some(app_state) = app_state.upgrade() {
427 open_new(&app_state, cx, |workspace, cx| {
428 Editor::new_file(workspace, &Default::default(), cx)
429 })
430 .detach();
431 }
432 }
433 });
434
435 workspace.focus_handle(cx).focus(cx);
436 //todo!()
437 // load_default_keymap(cx);
438 })
439 .detach();
440}
441
442fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
443 use std::fmt::Write as _;
444
445 let app_name = cx.global::<ReleaseChannel>().display_name();
446 let version = env!("CARGO_PKG_VERSION");
447 let mut message = format!("{app_name} {version}");
448 if let Some(sha) = cx.try_global::<AppCommitSha>() {
449 write!(&mut message, "\n\n{}", sha.0).unwrap();
450 }
451
452 let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]);
453 cx.foreground_executor()
454 .spawn(async {
455 prompt.await.ok();
456 })
457 .detach();
458}
459
460fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
461 let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
462 cx.spawn(|_, mut cx| async move {
463 let mut workspace_windows = cx.update(|_, cx| {
464 cx.windows()
465 .into_iter()
466 .filter_map(|window| window.downcast::<Workspace>())
467 .collect::<Vec<_>>()
468 })?;
469
470 // If multiple windows have unsaved changes, and need a save prompt,
471 // prompt in the active window before switching to a different window.
472 cx.update(|_, cx| {
473 workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
474 })
475 .log_err();
476
477 if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
478 let answer = cx
479 .update(|_, cx| {
480 cx.prompt(
481 PromptLevel::Info,
482 "Are you sure you want to quit?",
483 &["Quit", "Cancel"],
484 )
485 })
486 .log_err();
487
488 if let Some(mut answer) = answer {
489 let answer = answer.await.ok();
490 if answer != Some(0) {
491 return Ok(());
492 }
493 }
494 }
495
496 // If the user cancels any save prompt, then keep the app open.
497 for window in workspace_windows {
498 if let Some(should_close) = window
499 .update(&mut cx, |workspace, cx| {
500 workspace.prepare_to_close(true, cx)
501 })
502 .log_err()
503 {
504 if !should_close.await? {
505 return Ok(());
506 }
507 }
508 }
509 cx.update(|_, cx| {
510 cx.quit();
511 })?;
512 anyhow::Ok(())
513 })
514 .detach_and_log_err(cx);
515}
516
517fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
518 const MAX_LINES: usize = 1000;
519 workspace
520 .with_local_workspace(cx, move |workspace, cx| {
521 let fs = workspace.app_state().fs.clone();
522 cx.spawn(|workspace, mut cx| async move {
523 let (old_log, new_log) =
524 futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG));
525
526 let mut lines = VecDeque::with_capacity(MAX_LINES);
527 for line in old_log
528 .iter()
529 .flat_map(|log| log.lines())
530 .chain(new_log.iter().flat_map(|log| log.lines()))
531 {
532 if lines.len() == MAX_LINES {
533 lines.pop_front();
534 }
535 lines.push_back(line);
536 }
537 let log = lines
538 .into_iter()
539 .flat_map(|line| [line, "\n"])
540 .collect::<String>();
541
542 workspace
543 .update(&mut cx, |workspace, cx| {
544 let project = workspace.project().clone();
545 let buffer = project
546 .update(cx, |project, cx| project.create_buffer("", None, cx))
547 .expect("creating buffers on a local workspace always succeeds");
548 buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
549
550 let buffer = cx.build_model(|cx| {
551 MultiBuffer::singleton(buffer, cx).with_title("Log".into())
552 });
553 workspace.add_item(
554 Box::new(cx.build_view(|cx| {
555 Editor::for_multibuffer(buffer, Some(project), cx)
556 })),
557 cx,
558 );
559 })
560 .log_err();
561 })
562 .detach();
563 })
564 .detach();
565}
566
567fn open_local_settings_file(
568 workspace: &mut Workspace,
569 _: &OpenLocalSettings,
570 cx: &mut ViewContext<Workspace>,
571) {
572 let project = workspace.project().clone();
573 let worktree = project
574 .read(cx)
575 .visible_worktrees(cx)
576 .find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
577 if let Some(worktree) = worktree {
578 let tree_id = worktree.read(cx).id();
579 cx.spawn(|workspace, mut cx| async move {
580 let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH;
581
582 if let Some(dir_path) = file_path.parent() {
583 if worktree.update(&mut cx, |tree, _| tree.entry_for_path(dir_path).is_none())? {
584 project
585 .update(&mut cx, |project, cx| {
586 project.create_entry((tree_id, dir_path), true, cx)
587 })?
588 .await
589 .context("worktree was removed")?;
590 }
591 }
592
593 if worktree.update(&mut cx, |tree, _| tree.entry_for_path(file_path).is_none())? {
594 project
595 .update(&mut cx, |project, cx| {
596 project.create_entry((tree_id, file_path), false, cx)
597 })?
598 .await
599 .context("worktree was removed")?;
600 }
601
602 let editor = workspace
603 .update(&mut cx, |workspace, cx| {
604 workspace.open_path((tree_id, file_path), None, true, cx)
605 })?
606 .await?
607 .downcast::<Editor>()
608 .ok_or_else(|| anyhow!("unexpected item type"))?;
609
610 editor
611 .downgrade()
612 .update(&mut cx, |editor, cx| {
613 if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
614 if buffer.read(cx).is_empty() {
615 buffer.update(cx, |buffer, cx| {
616 buffer.edit([(0..0, initial_local_settings_content())], None, cx)
617 });
618 }
619 }
620 })
621 .ok();
622
623 anyhow::Ok(())
624 })
625 .detach();
626 } else {
627 workspace.show_notification(0, cx, |cx| {
628 cx.build_view(|_| MessageNotification::new("This project has no folders open."))
629 })
630 }
631}
632
633fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
634 workspace.with_local_workspace(cx, move |workspace, cx| {
635 let app_state = workspace.app_state().clone();
636 cx.spawn(|workspace, mut cx| async move {
637 async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
638 let path = app_state.client.telemetry().log_file_path()?;
639 app_state.fs.load(&path).await.log_err()
640 }
641
642 let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string());
643
644 const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024;
645 let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN);
646 if let Some(newline_offset) = log[start_offset..].find('\n') {
647 start_offset += newline_offset + 1;
648 }
649 let log_suffix = &log[start_offset..];
650 let json = app_state.languages.language_for_name("JSON").await.log_err();
651
652 workspace.update(&mut cx, |workspace, cx| {
653 let project = workspace.project().clone();
654 let buffer = project
655 .update(cx, |project, cx| project.create_buffer("", None, cx))
656 .expect("creating buffers on a local workspace always succeeds");
657 buffer.update(cx, |buffer, cx| {
658 buffer.set_language(json, cx);
659 buffer.edit(
660 [(
661 0..0,
662 concat!(
663 "// Zed collects anonymous usage data to help us understand how people are using the app.\n",
664 "// Telemetry can be disabled via the `settings.json` file.\n",
665 "// Here is the data that has been reported for the current session:\n",
666 "\n"
667 ),
668 )],
669 None,
670 cx,
671 );
672 buffer.edit([(buffer.len()..buffer.len(), log_suffix)], None, cx);
673 });
674
675 let buffer = cx.build_model(|cx| {
676 MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into())
677 });
678 workspace.add_item(
679 Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
680 cx,
681 );
682 }).log_err()?;
683
684 Some(())
685 })
686 .detach();
687 }).detach();
688}
689
690fn open_bundled_file(
691 workspace: &mut Workspace,
692 text: Cow<'static, str>,
693 title: &'static str,
694 language: &'static str,
695 cx: &mut ViewContext<Workspace>,
696) {
697 let language = workspace.app_state().languages.language_for_name(language);
698 cx.spawn(|workspace, mut cx| async move {
699 let language = language.await.log_err();
700 workspace
701 .update(&mut cx, |workspace, cx| {
702 workspace.with_local_workspace(cx, |workspace, cx| {
703 let project = workspace.project();
704 let buffer = project.update(cx, move |project, cx| {
705 project
706 .create_buffer(text.as_ref(), language, cx)
707 .expect("creating buffers on a local workspace always succeeds")
708 });
709 let buffer = cx.build_model(|cx| {
710 MultiBuffer::singleton(buffer, cx).with_title(title.into())
711 });
712 workspace.add_item(
713 Box::new(cx.build_view(|cx| {
714 Editor::for_multibuffer(buffer, Some(project.clone()), cx)
715 })),
716 cx,
717 );
718 })
719 })?
720 .await
721 })
722 .detach_and_log_err(cx);
723}
724
725// todo!()
726// #[cfg(test)]
727// mod tests {
728// use super::*;
729// use assets::Assets;
730// use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
731// use fs::{FakeFs, Fs};
732// use gpui::{
733// actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
734// AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
735// };
736// use language::LanguageRegistry;
737// use project::{project_settings::ProjectSettings, Project, ProjectPath};
738// use serde_json::json;
739// use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
740// use std::{
741// collections::HashSet,
742// path::{Path, PathBuf},
743// };
744// use theme::{ThemeRegistry, ThemeSettings};
745// use workspace::{
746// item::{Item, ItemHandle},
747// open_new, open_paths, pane, NewFile, SaveIntent, SplitDirection, WorkspaceHandle,
748// };
749
750// #[gpui::test]
751// async fn test_open_paths_action(cx: &mut TestAppContext) {
752// let app_state = init_test(cx);
753// app_state
754// .fs
755// .as_fake()
756// .insert_tree(
757// "/root",
758// json!({
759// "a": {
760// "aa": null,
761// "ab": null,
762// },
763// "b": {
764// "ba": null,
765// "bb": null,
766// },
767// "c": {
768// "ca": null,
769// "cb": null,
770// },
771// "d": {
772// "da": null,
773// "db": null,
774// },
775// }),
776// )
777// .await;
778
779// cx.update(|cx| {
780// open_paths(
781// &[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
782// &app_state,
783// None,
784// cx,
785// )
786// })
787// .await
788// .unwrap();
789// assert_eq!(cx.windows().len(), 1);
790
791// cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
792// .await
793// .unwrap();
794// assert_eq!(cx.windows().len(), 1);
795// let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
796// workspace_1.update(cx, |workspace, cx| {
797// assert_eq!(workspace.worktrees(cx).count(), 2);
798// assert!(workspace.left_dock().read(cx).is_open());
799// assert!(workspace.active_pane().is_focused(cx));
800// });
801
802// cx.update(|cx| {
803// open_paths(
804// &[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
805// &app_state,
806// None,
807// cx,
808// )
809// })
810// .await
811// .unwrap();
812// assert_eq!(cx.windows().len(), 2);
813
814// // Replace existing windows
815// let window = cx.windows()[0].downcast::<Workspace>().unwrap();
816// cx.update(|cx| {
817// open_paths(
818// &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
819// &app_state,
820// Some(window),
821// cx,
822// )
823// })
824// .await
825// .unwrap();
826// assert_eq!(cx.windows().len(), 2);
827// let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
828// workspace_1.update(cx, |workspace, cx| {
829// assert_eq!(
830// workspace
831// .worktrees(cx)
832// .map(|w| w.read(cx).abs_path())
833// .collect::<Vec<_>>(),
834// &[Path::new("/root/c").into(), Path::new("/root/d").into()]
835// );
836// assert!(workspace.left_dock().read(cx).is_open());
837// assert!(workspace.active_pane().is_focused(cx));
838// });
839// }
840
841// #[gpui::test]
842// async fn test_window_edit_state(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
843// let app_state = init_test(cx);
844// app_state
845// .fs
846// .as_fake()
847// .insert_tree("/root", json!({"a": "hey"}))
848// .await;
849
850// cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
851// .await
852// .unwrap();
853// assert_eq!(cx.windows().len(), 1);
854
855// // When opening the workspace, the window is not in a edited state.
856// let window = cx.windows()[0].downcast::<Workspace>().unwrap();
857// let workspace = window.root(cx);
858// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
859// let editor = workspace.read_with(cx, |workspace, cx| {
860// workspace
861// .active_item(cx)
862// .unwrap()
863// .downcast::<Editor>()
864// .unwrap()
865// });
866// assert!(!window.is_edited(cx));
867
868// // Editing a buffer marks the window as edited.
869// editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
870// assert!(window.is_edited(cx));
871
872// // Undoing the edit restores the window's edited state.
873// editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx));
874// assert!(!window.is_edited(cx));
875
876// // Redoing the edit marks the window as edited again.
877// editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx));
878// assert!(window.is_edited(cx));
879
880// // Closing the item restores the window's edited state.
881// let close = pane.update(cx, |pane, cx| {
882// drop(editor);
883// pane.close_active_item(&Default::default(), cx).unwrap()
884// });
885// executor.run_until_parked();
886
887// window.simulate_prompt_answer(1, cx);
888// close.await.unwrap();
889// assert!(!window.is_edited(cx));
890
891// // Opening the buffer again doesn't impact the window's edited state.
892// cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
893// .await
894// .unwrap();
895// let editor = workspace.read_with(cx, |workspace, cx| {
896// workspace
897// .active_item(cx)
898// .unwrap()
899// .downcast::<Editor>()
900// .unwrap()
901// });
902// assert!(!window.is_edited(cx));
903
904// // Editing the buffer marks the window as edited.
905// editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
906// assert!(window.is_edited(cx));
907
908// // Ensure closing the window via the mouse gets preempted due to the
909// // buffer having unsaved changes.
910// assert!(!window.simulate_close(cx));
911// executor.run_until_parked();
912// assert_eq!(cx.windows().len(), 1);
913
914// // The window is successfully closed after the user dismisses the prompt.
915// window.simulate_prompt_answer(1, cx);
916// executor.run_until_parked();
917// assert_eq!(cx.windows().len(), 0);
918// }
919
920// #[gpui::test]
921// async fn test_new_empty_workspace(cx: &mut TestAppContext) {
922// let app_state = init_test(cx);
923// cx.update(|cx| {
924// open_new(&app_state, cx, |workspace, cx| {
925// Editor::new_file(workspace, &Default::default(), cx)
926// })
927// })
928// .await;
929
930// let window = cx
931// .windows()
932// .first()
933// .unwrap()
934// .downcast::<Workspace>()
935// .unwrap();
936// let workspace = window.root(cx);
937
938// let editor = workspace.update(cx, |workspace, cx| {
939// workspace
940// .active_item(cx)
941// .unwrap()
942// .downcast::<editor::Editor>()
943// .unwrap()
944// });
945
946// editor.update(cx, |editor, cx| {
947// assert!(editor.text(cx).is_empty());
948// assert!(!editor.is_dirty(cx));
949// });
950
951// let save_task = workspace.update(cx, |workspace, cx| {
952// workspace.save_active_item(SaveIntent::Save, cx)
953// });
954// app_state.fs.create_dir(Path::new("/root")).await.unwrap();
955// cx.foreground().run_until_parked();
956// cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name")));
957// save_task.await.unwrap();
958// editor.read_with(cx, |editor, cx| {
959// assert!(!editor.is_dirty(cx));
960// assert_eq!(editor.title(cx), "the-new-name");
961// });
962// }
963
964// #[gpui::test]
965// async fn test_open_entry(cx: &mut TestAppContext) {
966// let app_state = init_test(cx);
967// app_state
968// .fs
969// .as_fake()
970// .insert_tree(
971// "/root",
972// json!({
973// "a": {
974// "file1": "contents 1",
975// "file2": "contents 2",
976// "file3": "contents 3",
977// },
978// }),
979// )
980// .await;
981
982// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
983// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
984// let workspace = window.root(cx);
985
986// let entries = cx.read(|cx| workspace.file_project_paths(cx));
987// let file1 = entries[0].clone();
988// let file2 = entries[1].clone();
989// let file3 = entries[2].clone();
990
991// // Open the first entry
992// let entry_1 = workspace
993// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
994// .await
995// .unwrap();
996// cx.read(|cx| {
997// let pane = workspace.read(cx).active_pane().read(cx);
998// assert_eq!(
999// pane.active_item().unwrap().project_path(cx),
1000// Some(file1.clone())
1001// );
1002// assert_eq!(pane.items_len(), 1);
1003// });
1004
1005// // Open the second entry
1006// workspace
1007// .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1008// .await
1009// .unwrap();
1010// cx.read(|cx| {
1011// let pane = workspace.read(cx).active_pane().read(cx);
1012// assert_eq!(
1013// pane.active_item().unwrap().project_path(cx),
1014// Some(file2.clone())
1015// );
1016// assert_eq!(pane.items_len(), 2);
1017// });
1018
1019// // Open the first entry again. The existing pane item is activated.
1020// let entry_1b = workspace
1021// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1022// .await
1023// .unwrap();
1024// assert_eq!(entry_1.id(), entry_1b.id());
1025
1026// cx.read(|cx| {
1027// let pane = workspace.read(cx).active_pane().read(cx);
1028// assert_eq!(
1029// pane.active_item().unwrap().project_path(cx),
1030// Some(file1.clone())
1031// );
1032// assert_eq!(pane.items_len(), 2);
1033// });
1034
1035// // Split the pane with the first entry, then open the second entry again.
1036// workspace
1037// .update(cx, |w, cx| {
1038// w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, cx);
1039// w.open_path(file2.clone(), None, true, cx)
1040// })
1041// .await
1042// .unwrap();
1043
1044// workspace.read_with(cx, |w, cx| {
1045// assert_eq!(
1046// w.active_pane()
1047// .read(cx)
1048// .active_item()
1049// .unwrap()
1050// .project_path(cx),
1051// Some(file2.clone())
1052// );
1053// });
1054
1055// // Open the third entry twice concurrently. Only one pane item is added.
1056// let (t1, t2) = workspace.update(cx, |w, cx| {
1057// (
1058// w.open_path(file3.clone(), None, true, cx),
1059// w.open_path(file3.clone(), None, true, cx),
1060// )
1061// });
1062// t1.await.unwrap();
1063// t2.await.unwrap();
1064// cx.read(|cx| {
1065// let pane = workspace.read(cx).active_pane().read(cx);
1066// assert_eq!(
1067// pane.active_item().unwrap().project_path(cx),
1068// Some(file3.clone())
1069// );
1070// let pane_entries = pane
1071// .items()
1072// .map(|i| i.project_path(cx).unwrap())
1073// .collect::<Vec<_>>();
1074// assert_eq!(pane_entries, &[file1, file2, file3]);
1075// });
1076// }
1077
1078// #[gpui::test]
1079// async fn test_open_paths(cx: &mut TestAppContext) {
1080// let app_state = init_test(cx);
1081
1082// app_state
1083// .fs
1084// .as_fake()
1085// .insert_tree(
1086// "/",
1087// json!({
1088// "dir1": {
1089// "a.txt": ""
1090// },
1091// "dir2": {
1092// "b.txt": ""
1093// },
1094// "dir3": {
1095// "c.txt": ""
1096// },
1097// "d.txt": ""
1098// }),
1099// )
1100// .await;
1101
1102// cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
1103// .await
1104// .unwrap();
1105// assert_eq!(cx.windows().len(), 1);
1106// let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
1107
1108// #[track_caller]
1109// fn assert_project_panel_selection(
1110// workspace: &Workspace,
1111// expected_worktree_path: &Path,
1112// expected_entry_path: &Path,
1113// cx: &AppContext,
1114// ) {
1115// let project_panel = [
1116// workspace.left_dock().read(cx).panel::<ProjectPanel>(),
1117// workspace.right_dock().read(cx).panel::<ProjectPanel>(),
1118// workspace.bottom_dock().read(cx).panel::<ProjectPanel>(),
1119// ]
1120// .into_iter()
1121// .find_map(std::convert::identity)
1122// .expect("found no project panels")
1123// .read(cx);
1124// let (selected_worktree, selected_entry) = project_panel
1125// .selected_entry(cx)
1126// .expect("project panel should have a selected entry");
1127// assert_eq!(
1128// selected_worktree.abs_path().as_ref(),
1129// expected_worktree_path,
1130// "Unexpected project panel selected worktree path"
1131// );
1132// assert_eq!(
1133// selected_entry.path.as_ref(),
1134// expected_entry_path,
1135// "Unexpected project panel selected entry path"
1136// );
1137// }
1138
1139// // Open a file within an existing worktree.
1140// workspace
1141// .update(cx, |view, cx| {
1142// view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
1143// })
1144// .await;
1145// cx.read(|cx| {
1146// let workspace = workspace.read(cx);
1147// assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx);
1148// assert_eq!(
1149// workspace
1150// .active_pane()
1151// .read(cx)
1152// .active_item()
1153// .unwrap()
1154// .as_any()
1155// .downcast_ref::<Editor>()
1156// .unwrap()
1157// .read(cx)
1158// .title(cx),
1159// "a.txt"
1160// );
1161// });
1162
1163// // Open a file outside of any existing worktree.
1164// workspace
1165// .update(cx, |view, cx| {
1166// view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
1167// })
1168// .await;
1169// cx.read(|cx| {
1170// let workspace = workspace.read(cx);
1171// assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx);
1172// let worktree_roots = workspace
1173// .worktrees(cx)
1174// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1175// .collect::<HashSet<_>>();
1176// assert_eq!(
1177// worktree_roots,
1178// vec!["/dir1", "/dir2/b.txt"]
1179// .into_iter()
1180// .map(Path::new)
1181// .collect(),
1182// );
1183// assert_eq!(
1184// workspace
1185// .active_pane()
1186// .read(cx)
1187// .active_item()
1188// .unwrap()
1189// .as_any()
1190// .downcast_ref::<Editor>()
1191// .unwrap()
1192// .read(cx)
1193// .title(cx),
1194// "b.txt"
1195// );
1196// });
1197
1198// // Ensure opening a directory and one of its children only adds one worktree.
1199// workspace
1200// .update(cx, |view, cx| {
1201// view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
1202// })
1203// .await;
1204// cx.read(|cx| {
1205// let workspace = workspace.read(cx);
1206// assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx);
1207// let worktree_roots = workspace
1208// .worktrees(cx)
1209// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1210// .collect::<HashSet<_>>();
1211// assert_eq!(
1212// worktree_roots,
1213// vec!["/dir1", "/dir2/b.txt", "/dir3"]
1214// .into_iter()
1215// .map(Path::new)
1216// .collect(),
1217// );
1218// assert_eq!(
1219// workspace
1220// .active_pane()
1221// .read(cx)
1222// .active_item()
1223// .unwrap()
1224// .as_any()
1225// .downcast_ref::<Editor>()
1226// .unwrap()
1227// .read(cx)
1228// .title(cx),
1229// "c.txt"
1230// );
1231// });
1232
1233// // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
1234// workspace
1235// .update(cx, |view, cx| {
1236// view.open_paths(vec!["/d.txt".into()], false, cx)
1237// })
1238// .await;
1239// cx.read(|cx| {
1240// let workspace = workspace.read(cx);
1241// assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx);
1242// let worktree_roots = workspace
1243// .worktrees(cx)
1244// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1245// .collect::<HashSet<_>>();
1246// assert_eq!(
1247// worktree_roots,
1248// vec!["/dir1", "/dir2/b.txt", "/dir3", "/d.txt"]
1249// .into_iter()
1250// .map(Path::new)
1251// .collect(),
1252// );
1253
1254// let visible_worktree_roots = workspace
1255// .visible_worktrees(cx)
1256// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1257// .collect::<HashSet<_>>();
1258// assert_eq!(
1259// visible_worktree_roots,
1260// vec!["/dir1", "/dir2/b.txt", "/dir3"]
1261// .into_iter()
1262// .map(Path::new)
1263// .collect(),
1264// );
1265
1266// assert_eq!(
1267// workspace
1268// .active_pane()
1269// .read(cx)
1270// .active_item()
1271// .unwrap()
1272// .as_any()
1273// .downcast_ref::<Editor>()
1274// .unwrap()
1275// .read(cx)
1276// .title(cx),
1277// "d.txt"
1278// );
1279// });
1280// }
1281
1282// #[gpui::test]
1283// async fn test_opening_excluded_paths(cx: &mut TestAppContext) {
1284// let app_state = init_test(cx);
1285// cx.update(|cx| {
1286// cx.update_global::<SettingsStore, _, _>(|store, cx| {
1287// store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1288// project_settings.file_scan_exclusions =
1289// Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
1290// });
1291// });
1292// });
1293// app_state
1294// .fs
1295// .as_fake()
1296// .insert_tree(
1297// "/root",
1298// json!({
1299// ".gitignore": "ignored_dir\n",
1300// ".git": {
1301// "HEAD": "ref: refs/heads/main",
1302// },
1303// "regular_dir": {
1304// "file": "regular file contents",
1305// },
1306// "ignored_dir": {
1307// "ignored_subdir": {
1308// "file": "ignored subfile contents",
1309// },
1310// "file": "ignored file contents",
1311// },
1312// "excluded_dir": {
1313// "file": "excluded file contents",
1314// },
1315// }),
1316// )
1317// .await;
1318
1319// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1320// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1321// let workspace = window.root(cx);
1322
1323// let initial_entries = cx.read(|cx| workspace.file_project_paths(cx));
1324// let paths_to_open = [
1325// Path::new("/root/excluded_dir/file").to_path_buf(),
1326// Path::new("/root/.git/HEAD").to_path_buf(),
1327// Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
1328// ];
1329// let (opened_workspace, new_items) = cx
1330// .update(|cx| workspace::open_paths(&paths_to_open, &app_state, None, cx))
1331// .await
1332// .unwrap();
1333
1334// assert_eq!(
1335// opened_workspace.id(),
1336// workspace.id(),
1337// "Excluded files in subfolders of a workspace root should be opened in the workspace"
1338// );
1339// let mut opened_paths = cx.read(|cx| {
1340// assert_eq!(
1341// new_items.len(),
1342// paths_to_open.len(),
1343// "Expect to get the same number of opened items as submitted paths to open"
1344// );
1345// new_items
1346// .iter()
1347// .zip(paths_to_open.iter())
1348// .map(|(i, path)| {
1349// match i {
1350// Some(Ok(i)) => {
1351// Some(i.project_path(cx).map(|p| p.path.display().to_string()))
1352// }
1353// Some(Err(e)) => panic!("Excluded file {path:?} failed to open: {e:?}"),
1354// None => None,
1355// }
1356// .flatten()
1357// })
1358// .collect::<Vec<_>>()
1359// });
1360// opened_paths.sort();
1361// assert_eq!(
1362// opened_paths,
1363// vec![
1364// None,
1365// Some(".git/HEAD".to_string()),
1366// Some("excluded_dir/file".to_string()),
1367// ],
1368// "Excluded files should get opened, excluded dir should not get opened"
1369// );
1370
1371// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1372// assert_eq!(
1373// initial_entries, entries,
1374// "Workspace entries should not change after opening excluded files and directories paths"
1375// );
1376
1377// cx.read(|cx| {
1378// let pane = workspace.read(cx).active_pane().read(cx);
1379// let mut opened_buffer_paths = pane
1380// .items()
1381// .map(|i| {
1382// i.project_path(cx)
1383// .expect("all excluded files that got open should have a path")
1384// .path
1385// .display()
1386// .to_string()
1387// })
1388// .collect::<Vec<_>>();
1389// opened_buffer_paths.sort();
1390// assert_eq!(
1391// opened_buffer_paths,
1392// vec![".git/HEAD".to_string(), "excluded_dir/file".to_string()],
1393// "Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane"
1394// );
1395// });
1396// }
1397
1398// #[gpui::test]
1399// async fn test_save_conflicting_item(cx: &mut TestAppContext) {
1400// let app_state = init_test(cx);
1401// app_state
1402// .fs
1403// .as_fake()
1404// .insert_tree("/root", json!({ "a.txt": "" }))
1405// .await;
1406
1407// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1408// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1409// let workspace = window.root(cx);
1410
1411// // Open a file within an existing worktree.
1412// workspace
1413// .update(cx, |view, cx| {
1414// view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
1415// })
1416// .await;
1417// let editor = cx.read(|cx| {
1418// let pane = workspace.read(cx).active_pane().read(cx);
1419// let item = pane.active_item().unwrap();
1420// item.downcast::<Editor>().unwrap()
1421// });
1422
1423// editor.update(cx, |editor, cx| editor.handle_input("x", cx));
1424// app_state
1425// .fs
1426// .as_fake()
1427// .insert_file("/root/a.txt", "changed".to_string())
1428// .await;
1429// editor
1430// .condition(cx, |editor, cx| editor.has_conflict(cx))
1431// .await;
1432// cx.read(|cx| assert!(editor.is_dirty(cx)));
1433
1434// let save_task = workspace.update(cx, |workspace, cx| {
1435// workspace.save_active_item(SaveIntent::Save, cx)
1436// });
1437// cx.foreground().run_until_parked();
1438// window.simulate_prompt_answer(0, cx);
1439// save_task.await.unwrap();
1440// editor.read_with(cx, |editor, cx| {
1441// assert!(!editor.is_dirty(cx));
1442// assert!(!editor.has_conflict(cx));
1443// });
1444// }
1445
1446// #[gpui::test]
1447// async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
1448// let app_state = init_test(cx);
1449// app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1450
1451// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1452// project.update(cx, |project, _| project.languages().add(rust_lang()));
1453// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1454// let workspace = window.root(cx);
1455// let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
1456
1457// // Create a new untitled buffer
1458// cx.dispatch_action(window.into(), NewFile);
1459// let editor = workspace.read_with(cx, |workspace, cx| {
1460// workspace
1461// .active_item(cx)
1462// .unwrap()
1463// .downcast::<Editor>()
1464// .unwrap()
1465// });
1466
1467// editor.update(cx, |editor, cx| {
1468// assert!(!editor.is_dirty(cx));
1469// assert_eq!(editor.title(cx), "untitled");
1470// assert!(Arc::ptr_eq(
1471// &editor.language_at(0, cx).unwrap(),
1472// &languages::PLAIN_TEXT
1473// ));
1474// editor.handle_input("hi", cx);
1475// assert!(editor.is_dirty(cx));
1476// });
1477
1478// // Save the buffer. This prompts for a filename.
1479// let save_task = workspace.update(cx, |workspace, cx| {
1480// workspace.save_active_item(SaveIntent::Save, cx)
1481// });
1482// cx.foreground().run_until_parked();
1483// cx.simulate_new_path_selection(|parent_dir| {
1484// assert_eq!(parent_dir, Path::new("/root"));
1485// Some(parent_dir.join("the-new-name.rs"))
1486// });
1487// cx.read(|cx| {
1488// assert!(editor.is_dirty(cx));
1489// assert_eq!(editor.read(cx).title(cx), "untitled");
1490// });
1491
1492// // When the save completes, the buffer's title is updated and the language is assigned based
1493// // on the path.
1494// save_task.await.unwrap();
1495// editor.read_with(cx, |editor, cx| {
1496// assert!(!editor.is_dirty(cx));
1497// assert_eq!(editor.title(cx), "the-new-name.rs");
1498// assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust");
1499// });
1500
1501// // Edit the file and save it again. This time, there is no filename prompt.
1502// editor.update(cx, |editor, cx| {
1503// editor.handle_input(" there", cx);
1504// assert!(editor.is_dirty(cx));
1505// });
1506// let save_task = workspace.update(cx, |workspace, cx| {
1507// workspace.save_active_item(SaveIntent::Save, cx)
1508// });
1509// save_task.await.unwrap();
1510// assert!(!cx.did_prompt_for_new_path());
1511// editor.read_with(cx, |editor, cx| {
1512// assert!(!editor.is_dirty(cx));
1513// assert_eq!(editor.title(cx), "the-new-name.rs")
1514// });
1515
1516// // Open the same newly-created file in another pane item. The new editor should reuse
1517// // the same buffer.
1518// cx.dispatch_action(window.into(), NewFile);
1519// workspace
1520// .update(cx, |workspace, cx| {
1521// workspace.split_and_clone(
1522// workspace.active_pane().clone(),
1523// SplitDirection::Right,
1524// cx,
1525// );
1526// workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx)
1527// })
1528// .await
1529// .unwrap();
1530// let editor2 = workspace.update(cx, |workspace, cx| {
1531// workspace
1532// .active_item(cx)
1533// .unwrap()
1534// .downcast::<Editor>()
1535// .unwrap()
1536// });
1537// cx.read(|cx| {
1538// assert_eq!(
1539// editor2.read(cx).buffer().read(cx).as_singleton().unwrap(),
1540// editor.read(cx).buffer().read(cx).as_singleton().unwrap()
1541// );
1542// })
1543// }
1544
1545// #[gpui::test]
1546// async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
1547// let app_state = init_test(cx);
1548// app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1549
1550// let project = Project::test(app_state.fs.clone(), [], cx).await;
1551// project.update(cx, |project, _| project.languages().add(rust_lang()));
1552// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1553// let workspace = window.root(cx);
1554
1555// // Create a new untitled buffer
1556// cx.dispatch_action(window.into(), NewFile);
1557// let editor = workspace.read_with(cx, |workspace, cx| {
1558// workspace
1559// .active_item(cx)
1560// .unwrap()
1561// .downcast::<Editor>()
1562// .unwrap()
1563// });
1564
1565// editor.update(cx, |editor, cx| {
1566// assert!(Arc::ptr_eq(
1567// &editor.language_at(0, cx).unwrap(),
1568// &languages::PLAIN_TEXT
1569// ));
1570// editor.handle_input("hi", cx);
1571// assert!(editor.is_dirty(cx));
1572// });
1573
1574// // Save the buffer. This prompts for a filename.
1575// let save_task = workspace.update(cx, |workspace, cx| {
1576// workspace.save_active_item(SaveIntent::Save, cx)
1577// });
1578// cx.foreground().run_until_parked();
1579// cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
1580// save_task.await.unwrap();
1581// // The buffer is not dirty anymore and the language is assigned based on the path.
1582// editor.read_with(cx, |editor, cx| {
1583// assert!(!editor.is_dirty(cx));
1584// assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust")
1585// });
1586// }
1587
1588// #[gpui::test]
1589// async fn test_pane_actions(cx: &mut TestAppContext) {
1590// let app_state = init_test(cx);
1591// app_state
1592// .fs
1593// .as_fake()
1594// .insert_tree(
1595// "/root",
1596// json!({
1597// "a": {
1598// "file1": "contents 1",
1599// "file2": "contents 2",
1600// "file3": "contents 3",
1601// },
1602// }),
1603// )
1604// .await;
1605
1606// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1607// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1608// let workspace = window.root(cx);
1609
1610// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1611// let file1 = entries[0].clone();
1612
1613// let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
1614
1615// workspace
1616// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1617// .await
1618// .unwrap();
1619
1620// let (editor_1, buffer) = pane_1.update(cx, |pane_1, cx| {
1621// let editor = pane_1.active_item().unwrap().downcast::<Editor>().unwrap();
1622// assert_eq!(editor.project_path(cx), Some(file1.clone()));
1623// let buffer = editor.update(cx, |editor, cx| {
1624// editor.insert("dirt", cx);
1625// editor.buffer().downgrade()
1626// });
1627// (editor.downgrade(), buffer)
1628// });
1629
1630// cx.dispatch_action(window.into(), pane::SplitRight);
1631// let editor_2 = cx.update(|cx| {
1632// let pane_2 = workspace.read(cx).active_pane().clone();
1633// assert_ne!(pane_1, pane_2);
1634
1635// let pane2_item = pane_2.read(cx).active_item().unwrap();
1636// assert_eq!(pane2_item.project_path(cx), Some(file1.clone()));
1637
1638// pane2_item.downcast::<Editor>().unwrap().downgrade()
1639// });
1640// cx.dispatch_action(
1641// window.into(),
1642// workspace::CloseActiveItem { save_intent: None },
1643// );
1644
1645// cx.foreground().run_until_parked();
1646// workspace.read_with(cx, |workspace, _| {
1647// assert_eq!(workspace.panes().len(), 1);
1648// assert_eq!(workspace.active_pane(), &pane_1);
1649// });
1650
1651// cx.dispatch_action(
1652// window.into(),
1653// workspace::CloseActiveItem { save_intent: None },
1654// );
1655// cx.foreground().run_until_parked();
1656// window.simulate_prompt_answer(1, cx);
1657// cx.foreground().run_until_parked();
1658
1659// workspace.read_with(cx, |workspace, cx| {
1660// assert_eq!(workspace.panes().len(), 1);
1661// assert!(workspace.active_item(cx).is_none());
1662// });
1663
1664// cx.assert_dropped(editor_1);
1665// cx.assert_dropped(editor_2);
1666// cx.assert_dropped(buffer);
1667// }
1668
1669// #[gpui::test]
1670// async fn test_navigation(cx: &mut TestAppContext) {
1671// let app_state = init_test(cx);
1672// app_state
1673// .fs
1674// .as_fake()
1675// .insert_tree(
1676// "/root",
1677// json!({
1678// "a": {
1679// "file1": "contents 1\n".repeat(20),
1680// "file2": "contents 2\n".repeat(20),
1681// "file3": "contents 3\n".repeat(20),
1682// },
1683// }),
1684// )
1685// .await;
1686
1687// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1688// let workspace = cx
1689// .add_window(|cx| Workspace::test_new(project.clone(), cx))
1690// .root(cx);
1691// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1692
1693// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1694// let file1 = entries[0].clone();
1695// let file2 = entries[1].clone();
1696// let file3 = entries[2].clone();
1697
1698// let editor1 = workspace
1699// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1700// .await
1701// .unwrap()
1702// .downcast::<Editor>()
1703// .unwrap();
1704// editor1.update(cx, |editor, cx| {
1705// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1706// s.select_display_ranges([DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)])
1707// });
1708// });
1709// let editor2 = workspace
1710// .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1711// .await
1712// .unwrap()
1713// .downcast::<Editor>()
1714// .unwrap();
1715// let editor3 = workspace
1716// .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
1717// .await
1718// .unwrap()
1719// .downcast::<Editor>()
1720// .unwrap();
1721
1722// editor3
1723// .update(cx, |editor, cx| {
1724// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1725// s.select_display_ranges([DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)])
1726// });
1727// editor.newline(&Default::default(), cx);
1728// editor.newline(&Default::default(), cx);
1729// editor.move_down(&Default::default(), cx);
1730// editor.move_down(&Default::default(), cx);
1731// editor.save(project.clone(), cx)
1732// })
1733// .await
1734// .unwrap();
1735// editor3.update(cx, |editor, cx| {
1736// editor.set_scroll_position(vec2f(0., 12.5), cx)
1737// });
1738// assert_eq!(
1739// active_location(&workspace, cx),
1740// (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1741// );
1742
1743// workspace
1744// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1745// .await
1746// .unwrap();
1747// assert_eq!(
1748// active_location(&workspace, cx),
1749// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1750// );
1751
1752// workspace
1753// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1754// .await
1755// .unwrap();
1756// assert_eq!(
1757// active_location(&workspace, cx),
1758// (file2.clone(), DisplayPoint::new(0, 0), 0.)
1759// );
1760
1761// workspace
1762// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1763// .await
1764// .unwrap();
1765// assert_eq!(
1766// active_location(&workspace, cx),
1767// (file1.clone(), DisplayPoint::new(10, 0), 0.)
1768// );
1769
1770// workspace
1771// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1772// .await
1773// .unwrap();
1774// assert_eq!(
1775// active_location(&workspace, cx),
1776// (file1.clone(), DisplayPoint::new(0, 0), 0.)
1777// );
1778
1779// // Go back one more time and ensure we don't navigate past the first item in the history.
1780// workspace
1781// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1782// .await
1783// .unwrap();
1784// assert_eq!(
1785// active_location(&workspace, cx),
1786// (file1.clone(), DisplayPoint::new(0, 0), 0.)
1787// );
1788
1789// workspace
1790// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1791// .await
1792// .unwrap();
1793// assert_eq!(
1794// active_location(&workspace, cx),
1795// (file1.clone(), DisplayPoint::new(10, 0), 0.)
1796// );
1797
1798// workspace
1799// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1800// .await
1801// .unwrap();
1802// assert_eq!(
1803// active_location(&workspace, cx),
1804// (file2.clone(), DisplayPoint::new(0, 0), 0.)
1805// );
1806
1807// // Go forward to an item that has been closed, ensuring it gets re-opened at the same
1808// // location.
1809// pane.update(cx, |pane, cx| {
1810// let editor3_id = editor3.id();
1811// drop(editor3);
1812// pane.close_item_by_id(editor3_id, SaveIntent::Close, cx)
1813// })
1814// .await
1815// .unwrap();
1816// workspace
1817// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1818// .await
1819// .unwrap();
1820// assert_eq!(
1821// active_location(&workspace, cx),
1822// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1823// );
1824
1825// workspace
1826// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1827// .await
1828// .unwrap();
1829// assert_eq!(
1830// active_location(&workspace, cx),
1831// (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1832// );
1833
1834// workspace
1835// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1836// .await
1837// .unwrap();
1838// assert_eq!(
1839// active_location(&workspace, cx),
1840// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1841// );
1842
1843// // Go back to an item that has been closed and removed from disk, ensuring it gets skipped.
1844// pane.update(cx, |pane, cx| {
1845// let editor2_id = editor2.id();
1846// drop(editor2);
1847// pane.close_item_by_id(editor2_id, SaveIntent::Close, cx)
1848// })
1849// .await
1850// .unwrap();
1851// app_state
1852// .fs
1853// .remove_file(Path::new("/root/a/file2"), Default::default())
1854// .await
1855// .unwrap();
1856// cx.foreground().run_until_parked();
1857
1858// workspace
1859// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1860// .await
1861// .unwrap();
1862// assert_eq!(
1863// active_location(&workspace, cx),
1864// (file1.clone(), DisplayPoint::new(10, 0), 0.)
1865// );
1866// workspace
1867// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1868// .await
1869// .unwrap();
1870// assert_eq!(
1871// active_location(&workspace, cx),
1872// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1873// );
1874
1875// // Modify file to collapse multiple nav history entries into the same location.
1876// // Ensure we don't visit the same location twice when navigating.
1877// editor1.update(cx, |editor, cx| {
1878// editor.change_selections(None, cx, |s| {
1879// s.select_display_ranges([DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)])
1880// })
1881// });
1882
1883// for _ in 0..5 {
1884// editor1.update(cx, |editor, cx| {
1885// editor.change_selections(None, cx, |s| {
1886// s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
1887// });
1888// });
1889// editor1.update(cx, |editor, cx| {
1890// editor.change_selections(None, cx, |s| {
1891// s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)])
1892// })
1893// });
1894// }
1895
1896// editor1.update(cx, |editor, cx| {
1897// editor.transact(cx, |editor, cx| {
1898// editor.change_selections(None, cx, |s| {
1899// s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)])
1900// });
1901// editor.insert("", cx);
1902// })
1903// });
1904
1905// editor1.update(cx, |editor, cx| {
1906// editor.change_selections(None, cx, |s| {
1907// s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
1908// })
1909// });
1910// workspace
1911// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1912// .await
1913// .unwrap();
1914// assert_eq!(
1915// active_location(&workspace, cx),
1916// (file1.clone(), DisplayPoint::new(2, 0), 0.)
1917// );
1918// workspace
1919// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1920// .await
1921// .unwrap();
1922// assert_eq!(
1923// active_location(&workspace, cx),
1924// (file1.clone(), DisplayPoint::new(3, 0), 0.)
1925// );
1926
1927// fn active_location(
1928// workspace: &ViewHandle<Workspace>,
1929// cx: &mut TestAppContext,
1930// ) -> (ProjectPath, DisplayPoint, f32) {
1931// workspace.update(cx, |workspace, cx| {
1932// let item = workspace.active_item(cx).unwrap();
1933// let editor = item.downcast::<Editor>().unwrap();
1934// let (selections, scroll_position) = editor.update(cx, |editor, cx| {
1935// (
1936// editor.selections.display_ranges(cx),
1937// editor.scroll_position(cx),
1938// )
1939// });
1940// (
1941// item.project_path(cx).unwrap(),
1942// selections[0].start,
1943// scroll_position.y(),
1944// )
1945// })
1946// }
1947// }
1948
1949// #[gpui::test]
1950// async fn test_reopening_closed_items(cx: &mut TestAppContext) {
1951// let app_state = init_test(cx);
1952// app_state
1953// .fs
1954// .as_fake()
1955// .insert_tree(
1956// "/root",
1957// json!({
1958// "a": {
1959// "file1": "",
1960// "file2": "",
1961// "file3": "",
1962// "file4": "",
1963// },
1964// }),
1965// )
1966// .await;
1967
1968// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1969// let workspace = cx
1970// .add_window(|cx| Workspace::test_new(project, cx))
1971// .root(cx);
1972// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1973
1974// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1975// let file1 = entries[0].clone();
1976// let file2 = entries[1].clone();
1977// let file3 = entries[2].clone();
1978// let file4 = entries[3].clone();
1979
1980// let file1_item_id = workspace
1981// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1982// .await
1983// .unwrap()
1984// .id();
1985// let file2_item_id = workspace
1986// .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1987// .await
1988// .unwrap()
1989// .id();
1990// let file3_item_id = workspace
1991// .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
1992// .await
1993// .unwrap()
1994// .id();
1995// let file4_item_id = workspace
1996// .update(cx, |w, cx| w.open_path(file4.clone(), None, true, cx))
1997// .await
1998// .unwrap()
1999// .id();
2000// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2001
2002// // Close all the pane items in some arbitrary order.
2003// pane.update(cx, |pane, cx| {
2004// pane.close_item_by_id(file1_item_id, SaveIntent::Close, cx)
2005// })
2006// .await
2007// .unwrap();
2008// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2009
2010// pane.update(cx, |pane, cx| {
2011// pane.close_item_by_id(file4_item_id, SaveIntent::Close, cx)
2012// })
2013// .await
2014// .unwrap();
2015// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2016
2017// pane.update(cx, |pane, cx| {
2018// pane.close_item_by_id(file2_item_id, SaveIntent::Close, cx)
2019// })
2020// .await
2021// .unwrap();
2022// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2023
2024// pane.update(cx, |pane, cx| {
2025// pane.close_item_by_id(file3_item_id, SaveIntent::Close, cx)
2026// })
2027// .await
2028// .unwrap();
2029// assert_eq!(active_path(&workspace, cx), None);
2030
2031// // Reopen all the closed items, ensuring they are reopened in the same order
2032// // in which they were closed.
2033// workspace
2034// .update(cx, Workspace::reopen_closed_item)
2035// .await
2036// .unwrap();
2037// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2038
2039// workspace
2040// .update(cx, Workspace::reopen_closed_item)
2041// .await
2042// .unwrap();
2043// assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2044
2045// workspace
2046// .update(cx, Workspace::reopen_closed_item)
2047// .await
2048// .unwrap();
2049// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2050
2051// workspace
2052// .update(cx, Workspace::reopen_closed_item)
2053// .await
2054// .unwrap();
2055// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2056
2057// // Reopening past the last closed item is a no-op.
2058// workspace
2059// .update(cx, Workspace::reopen_closed_item)
2060// .await
2061// .unwrap();
2062// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2063
2064// // Reopening closed items doesn't interfere with navigation history.
2065// workspace
2066// .update(cx, |workspace, cx| {
2067// workspace.go_back(workspace.active_pane().downgrade(), cx)
2068// })
2069// .await
2070// .unwrap();
2071// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2072
2073// workspace
2074// .update(cx, |workspace, cx| {
2075// workspace.go_back(workspace.active_pane().downgrade(), cx)
2076// })
2077// .await
2078// .unwrap();
2079// assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2080
2081// workspace
2082// .update(cx, |workspace, cx| {
2083// workspace.go_back(workspace.active_pane().downgrade(), cx)
2084// })
2085// .await
2086// .unwrap();
2087// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2088
2089// workspace
2090// .update(cx, |workspace, cx| {
2091// workspace.go_back(workspace.active_pane().downgrade(), cx)
2092// })
2093// .await
2094// .unwrap();
2095// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2096
2097// workspace
2098// .update(cx, |workspace, cx| {
2099// workspace.go_back(workspace.active_pane().downgrade(), cx)
2100// })
2101// .await
2102// .unwrap();
2103// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2104
2105// workspace
2106// .update(cx, |workspace, cx| {
2107// workspace.go_back(workspace.active_pane().downgrade(), cx)
2108// })
2109// .await
2110// .unwrap();
2111// assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2112
2113// workspace
2114// .update(cx, |workspace, cx| {
2115// workspace.go_back(workspace.active_pane().downgrade(), cx)
2116// })
2117// .await
2118// .unwrap();
2119// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2120
2121// workspace
2122// .update(cx, |workspace, cx| {
2123// workspace.go_back(workspace.active_pane().downgrade(), cx)
2124// })
2125// .await
2126// .unwrap();
2127// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2128
2129// fn active_path(
2130// workspace: &ViewHandle<Workspace>,
2131// cx: &TestAppContext,
2132// ) -> Option<ProjectPath> {
2133// workspace.read_with(cx, |workspace, cx| {
2134// let item = workspace.active_item(cx)?;
2135// item.project_path(cx)
2136// })
2137// }
2138// }
2139
2140// #[gpui::test]
2141// async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
2142// struct TestView;
2143
2144// impl Entity for TestView {
2145// type Event = ();
2146// }
2147
2148// impl View for TestView {
2149// fn ui_name() -> &'static str {
2150// "TestView"
2151// }
2152
2153// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2154// Empty::new().into_any()
2155// }
2156// }
2157
2158// let executor = cx.background();
2159// let fs = FakeFs::new(executor.clone());
2160
2161// actions!(test, [A, B]);
2162// // From the Atom keymap
2163// actions!(workspace, [ActivatePreviousPane]);
2164// // From the JetBrains keymap
2165// actions!(pane, [ActivatePrevItem]);
2166
2167// fs.save(
2168// "/settings.json".as_ref(),
2169// &r#"
2170// {
2171// "base_keymap": "Atom"
2172// }
2173// "#
2174// .into(),
2175// Default::default(),
2176// )
2177// .await
2178// .unwrap();
2179
2180// fs.save(
2181// "/keymap.json".as_ref(),
2182// &r#"
2183// [
2184// {
2185// "bindings": {
2186// "backspace": "test::A"
2187// }
2188// }
2189// ]
2190// "#
2191// .into(),
2192// Default::default(),
2193// )
2194// .await
2195// .unwrap();
2196
2197// cx.update(|cx| {
2198// cx.set_global(SettingsStore::test(cx));
2199// theme::init(Assets, cx);
2200// welcome::init(cx);
2201
2202// cx.add_global_action(|_: &A, _cx| {});
2203// cx.add_global_action(|_: &B, _cx| {});
2204// cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2205// cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2206
2207// let settings_rx = watch_config_file(
2208// executor.clone(),
2209// fs.clone(),
2210// PathBuf::from("/settings.json"),
2211// );
2212// let keymap_rx =
2213// watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2214
2215// handle_keymap_file_changes(keymap_rx, cx);
2216// handle_settings_file_changes(settings_rx, cx);
2217// });
2218
2219// cx.foreground().run_until_parked();
2220
2221// let window = cx.add_window(|_| TestView);
2222
2223// // Test loading the keymap base at all
2224// assert_key_bindings_for(
2225// window.into(),
2226// cx,
2227// vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2228// line!(),
2229// );
2230
2231// // Test modifying the users keymap, while retaining the base keymap
2232// fs.save(
2233// "/keymap.json".as_ref(),
2234// &r#"
2235// [
2236// {
2237// "bindings": {
2238// "backspace": "test::B"
2239// }
2240// }
2241// ]
2242// "#
2243// .into(),
2244// Default::default(),
2245// )
2246// .await
2247// .unwrap();
2248
2249// cx.foreground().run_until_parked();
2250
2251// assert_key_bindings_for(
2252// window.into(),
2253// cx,
2254// vec![("backspace", &B), ("k", &ActivatePreviousPane)],
2255// line!(),
2256// );
2257
2258// // Test modifying the base, while retaining the users keymap
2259// fs.save(
2260// "/settings.json".as_ref(),
2261// &r#"
2262// {
2263// "base_keymap": "JetBrains"
2264// }
2265// "#
2266// .into(),
2267// Default::default(),
2268// )
2269// .await
2270// .unwrap();
2271
2272// cx.foreground().run_until_parked();
2273
2274// assert_key_bindings_for(
2275// window.into(),
2276// cx,
2277// vec![("backspace", &B), ("[", &ActivatePrevItem)],
2278// line!(),
2279// );
2280
2281// #[track_caller]
2282// fn assert_key_bindings_for<'a>(
2283// window: AnyWindowHandle,
2284// cx: &TestAppContext,
2285// actions: Vec<(&'static str, &'a dyn Action)>,
2286// line: u32,
2287// ) {
2288// for (key, action) in actions {
2289// // assert that...
2290// assert!(
2291// cx.available_actions(window, 0)
2292// .into_iter()
2293// .any(|(_, bound_action, b)| {
2294// // action names match...
2295// bound_action.name() == action.name()
2296// && bound_action.namespace() == action.namespace()
2297// // and key strokes contain the given key
2298// && b.iter()
2299// .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2300// }),
2301// "On {} Failed to find {} with key binding {}",
2302// line,
2303// action.name(),
2304// key
2305// );
2306// }
2307// }
2308// }
2309
2310// #[gpui::test]
2311// async fn test_disabled_keymap_binding(cx: &mut gpui::TestAppContext) {
2312// struct TestView;
2313
2314// impl Entity for TestView {
2315// type Event = ();
2316// }
2317
2318// impl View for TestView {
2319// fn ui_name() -> &'static str {
2320// "TestView"
2321// }
2322
2323// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2324// Empty::new().into_any()
2325// }
2326// }
2327
2328// let executor = cx.background();
2329// let fs = FakeFs::new(executor.clone());
2330
2331// actions!(test, [A, B]);
2332// // From the Atom keymap
2333// actions!(workspace, [ActivatePreviousPane]);
2334// // From the JetBrains keymap
2335// actions!(pane, [ActivatePrevItem]);
2336
2337// fs.save(
2338// "/settings.json".as_ref(),
2339// &r#"
2340// {
2341// "base_keymap": "Atom"
2342// }
2343// "#
2344// .into(),
2345// Default::default(),
2346// )
2347// .await
2348// .unwrap();
2349
2350// fs.save(
2351// "/keymap.json".as_ref(),
2352// &r#"
2353// [
2354// {
2355// "bindings": {
2356// "backspace": "test::A"
2357// }
2358// }
2359// ]
2360// "#
2361// .into(),
2362// Default::default(),
2363// )
2364// .await
2365// .unwrap();
2366
2367// cx.update(|cx| {
2368// cx.set_global(SettingsStore::test(cx));
2369// theme::init(Assets, cx);
2370// welcome::init(cx);
2371
2372// cx.add_global_action(|_: &A, _cx| {});
2373// cx.add_global_action(|_: &B, _cx| {});
2374// cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2375// cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2376
2377// let settings_rx = watch_config_file(
2378// executor.clone(),
2379// fs.clone(),
2380// PathBuf::from("/settings.json"),
2381// );
2382// let keymap_rx =
2383// watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2384
2385// handle_keymap_file_changes(keymap_rx, cx);
2386// handle_settings_file_changes(settings_rx, cx);
2387// });
2388
2389// cx.foreground().run_until_parked();
2390
2391// let window = cx.add_window(|_| TestView);
2392
2393// // Test loading the keymap base at all
2394// assert_key_bindings_for(
2395// window.into(),
2396// cx,
2397// vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2398// line!(),
2399// );
2400
2401// // Test disabling the key binding for the base keymap
2402// fs.save(
2403// "/keymap.json".as_ref(),
2404// &r#"
2405// [
2406// {
2407// "bindings": {
2408// "backspace": null
2409// }
2410// }
2411// ]
2412// "#
2413// .into(),
2414// Default::default(),
2415// )
2416// .await
2417// .unwrap();
2418
2419// cx.foreground().run_until_parked();
2420
2421// assert_key_bindings_for(
2422// window.into(),
2423// cx,
2424// vec![("k", &ActivatePreviousPane)],
2425// line!(),
2426// );
2427
2428// // Test modifying the base, while retaining the users keymap
2429// fs.save(
2430// "/settings.json".as_ref(),
2431// &r#"
2432// {
2433// "base_keymap": "JetBrains"
2434// }
2435// "#
2436// .into(),
2437// Default::default(),
2438// )
2439// .await
2440// .unwrap();
2441
2442// cx.foreground().run_until_parked();
2443
2444// assert_key_bindings_for(window.into(), cx, vec![("[", &ActivatePrevItem)], line!());
2445
2446// #[track_caller]
2447// fn assert_key_bindings_for<'a>(
2448// window: AnyWindowHandle,
2449// cx: &TestAppContext,
2450// actions: Vec<(&'static str, &'a dyn Action)>,
2451// line: u32,
2452// ) {
2453// for (key, action) in actions {
2454// // assert that...
2455// assert!(
2456// cx.available_actions(window, 0)
2457// .into_iter()
2458// .any(|(_, bound_action, b)| {
2459// // action names match...
2460// bound_action.name() == action.name()
2461// && bound_action.namespace() == action.namespace()
2462// // and key strokes contain the given key
2463// && b.iter()
2464// .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2465// }),
2466// "On {} Failed to find {} with key binding {}",
2467// line,
2468// action.name(),
2469// key
2470// );
2471// }
2472// }
2473// }
2474
2475// #[gpui::test]
2476// fn test_bundled_settings_and_themes(cx: &mut AppContext) {
2477// cx.platform()
2478// .fonts()
2479// .add_fonts(&[
2480// Assets
2481// .load("fonts/zed-sans/zed-sans-extended.ttf")
2482// .unwrap()
2483// .to_vec()
2484// .into(),
2485// Assets
2486// .load("fonts/zed-mono/zed-mono-extended.ttf")
2487// .unwrap()
2488// .to_vec()
2489// .into(),
2490// Assets
2491// .load("fonts/plex/IBMPlexSans-Regular.ttf")
2492// .unwrap()
2493// .to_vec()
2494// .into(),
2495// ])
2496// .unwrap();
2497// let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
2498// let mut settings = SettingsStore::default();
2499// settings
2500// .set_default_settings(&settings::default_settings(), cx)
2501// .unwrap();
2502// cx.set_global(settings);
2503// theme::init(Assets, cx);
2504
2505// let mut has_default_theme = false;
2506// for theme_name in themes.list(false).map(|meta| meta.name) {
2507// let theme = themes.get(&theme_name).unwrap();
2508// assert_eq!(theme.meta.name, theme_name);
2509// if theme.meta.name == settings::get::<ThemeSettings>(cx).theme.meta.name {
2510// has_default_theme = true;
2511// }
2512// }
2513// assert!(has_default_theme);
2514// }
2515
2516// #[gpui::test]
2517// fn test_bundled_languages(cx: &mut AppContext) {
2518// cx.set_global(SettingsStore::test(cx));
2519// let mut languages = LanguageRegistry::test();
2520// languages.set_executor(cx.background().clone());
2521// let languages = Arc::new(languages);
2522// let node_runtime = node_runtime::FakeNodeRuntime::new();
2523// languages::init(languages.clone(), node_runtime, cx);
2524// for name in languages.language_names() {
2525// languages.language_for_name(&name);
2526// }
2527// cx.foreground().run_until_parked();
2528// }
2529
2530// fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
2531// cx.foreground().forbid_parking();
2532// cx.update(|cx| {
2533// let mut app_state = AppState::test(cx);
2534// let state = Arc::get_mut(&mut app_state).unwrap();
2535// state.initialize_workspace = initialize_workspace;
2536// state.build_window_options = build_window_options;
2537// theme::init((), cx);
2538// audio::init((), cx);
2539// channel::init(&app_state.client, app_state.user_store.clone(), cx);
2540// call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2541// notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2542// workspace::init(app_state.clone(), cx);
2543// Project::init_settings(cx);
2544// language::init(cx);
2545// editor::init(cx);
2546// project_panel::init_settings(cx);
2547// collab_ui::init(&app_state, cx);
2548// pane::init(cx);
2549// project_panel::init((), cx);
2550// terminal_view::init(cx);
2551// assistant::init(cx);
2552// app_state
2553// })
2554// }
2555
2556// fn rust_lang() -> Arc<language::Language> {
2557// Arc::new(language::Language::new(
2558// language::LanguageConfig {
2559// name: "Rust".into(),
2560// path_suffixes: vec!["rs".to_string()],
2561// ..Default::default()
2562// },
2563// Some(tree_sitter_rust::language()),
2564// ))
2565// }
2566// }