1pub mod dock;
2pub mod item;
3pub mod notifications;
4pub mod pane;
5pub mod pane_group;
6mod persistence;
7pub mod searchable;
8pub mod shared_screen;
9mod status_bar;
10mod toolbar;
11mod workspace_settings;
12
13use anyhow::{anyhow, Context, Result};
14use call::ActiveCall;
15use client::{
16 proto::{self, PeerId},
17 Client, Status, TypedEnvelope, UserStore,
18};
19use collections::{hash_map, HashMap, HashSet};
20use drag_and_drop::DragAndDrop;
21use futures::{
22 channel::{mpsc, oneshot},
23 future::try_join_all,
24 FutureExt, StreamExt,
25};
26use gpui::{
27 actions,
28 elements::*,
29 geometry::{
30 rect::RectF,
31 vector::{vec2f, Vector2F},
32 },
33 impl_actions,
34 platform::{
35 CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
36 WindowBounds, WindowOptions,
37 },
38 AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
39 ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
40 WeakViewHandle, WindowContext, WindowHandle,
41};
42use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
43use itertools::Itertools;
44use language::{LanguageRegistry, Rope};
45use node_runtime::NodeRuntime;
46use std::{
47 any::TypeId,
48 borrow::Cow,
49 cmp, env,
50 future::Future,
51 path::{Path, PathBuf},
52 rc::Rc,
53 str,
54 sync::{atomic::AtomicUsize, Arc},
55 time::Duration,
56};
57
58use crate::{
59 notifications::NotificationTracker,
60 persistence::model::{
61 DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
62 },
63};
64use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
65use lazy_static::lazy_static;
66use notifications::{
67 simple_message_notification::MessageNotification, NotificationHandle, NotifyResultExt,
68};
69pub use pane::*;
70pub use pane_group::*;
71use persistence::{model::SerializedItem, DB};
72pub use persistence::{
73 model::{ItemId, WorkspaceLocation},
74 WorkspaceDb, DB as WORKSPACE_DB,
75};
76use postage::prelude::Stream;
77use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
78use serde::Deserialize;
79use shared_screen::SharedScreen;
80use status_bar::StatusBar;
81pub use status_bar::StatusItemView;
82use theme::{Theme, ThemeSettings};
83pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
84use util::ResultExt;
85pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
86
87lazy_static! {
88 static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
89 .ok()
90 .as_deref()
91 .and_then(parse_pixel_position_env_var);
92 static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
93 .ok()
94 .as_deref()
95 .and_then(parse_pixel_position_env_var);
96}
97
98pub trait Modal: View {
99 fn has_focus(&self) -> bool;
100 fn dismiss_on_event(event: &Self::Event) -> bool;
101}
102
103trait ModalHandle {
104 fn as_any(&self) -> &AnyViewHandle;
105 fn has_focus(&self, cx: &WindowContext) -> bool;
106}
107
108impl<T: Modal> ModalHandle for ViewHandle<T> {
109 fn as_any(&self) -> &AnyViewHandle {
110 self
111 }
112
113 fn has_focus(&self, cx: &WindowContext) -> bool {
114 self.read(cx).has_focus()
115 }
116}
117
118#[derive(Clone, PartialEq)]
119pub struct RemoveWorktreeFromProject(pub WorktreeId);
120
121actions!(
122 workspace,
123 [
124 Open,
125 NewFile,
126 NewWindow,
127 CloseWindow,
128 CloseInactiveTabsAndPanes,
129 AddFolderToProject,
130 Unfollow,
131 SaveAs,
132 ReloadActiveItem,
133 ActivatePreviousPane,
134 ActivateNextPane,
135 FollowNextCollaborator,
136 NewTerminal,
137 NewCenterTerminal,
138 ToggleTerminalFocus,
139 NewSearch,
140 Feedback,
141 Restart,
142 Welcome,
143 ToggleZoom,
144 ToggleLeftDock,
145 ToggleRightDock,
146 ToggleBottomDock,
147 CloseAllDocks,
148 ]
149);
150
151#[derive(Clone, PartialEq)]
152pub struct OpenPaths {
153 pub paths: Vec<PathBuf>,
154}
155
156#[derive(Clone, Deserialize, PartialEq)]
157pub struct ActivatePane(pub usize);
158
159#[derive(Clone, Deserialize, PartialEq)]
160pub struct ActivatePaneInDirection(pub SplitDirection);
161
162#[derive(Clone, Deserialize, PartialEq)]
163pub struct SwapPaneInDirection(pub SplitDirection);
164
165#[derive(Clone, Deserialize, PartialEq)]
166pub struct NewFileInDirection(pub SplitDirection);
167
168#[derive(Clone, PartialEq, Debug, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct SaveAll {
171 pub save_intent: Option<SaveIntent>,
172}
173
174#[derive(Clone, PartialEq, Debug, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct Save {
177 pub save_intent: Option<SaveIntent>,
178}
179
180#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
181#[serde(rename_all = "camelCase")]
182pub struct CloseAllItemsAndPanes {
183 pub save_intent: Option<SaveIntent>,
184}
185
186#[derive(Deserialize)]
187pub struct Toast {
188 id: usize,
189 msg: Cow<'static, str>,
190 #[serde(skip)]
191 on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
192}
193
194impl Toast {
195 pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
196 Toast {
197 id,
198 msg: msg.into(),
199 on_click: None,
200 }
201 }
202
203 pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
204 where
205 M: Into<Cow<'static, str>>,
206 F: Fn(&mut WindowContext) + 'static,
207 {
208 self.on_click = Some((message.into(), Arc::new(on_click)));
209 self
210 }
211}
212
213impl PartialEq for Toast {
214 fn eq(&self, other: &Self) -> bool {
215 self.id == other.id
216 && self.msg == other.msg
217 && self.on_click.is_some() == other.on_click.is_some()
218 }
219}
220
221impl Clone for Toast {
222 fn clone(&self) -> Self {
223 Toast {
224 id: self.id,
225 msg: self.msg.to_owned(),
226 on_click: self.on_click.clone(),
227 }
228 }
229}
230
231#[derive(Clone, Deserialize, PartialEq)]
232pub struct OpenTerminal {
233 pub working_directory: PathBuf,
234}
235
236impl_actions!(
237 workspace,
238 [
239 ActivatePane,
240 ActivatePaneInDirection,
241 SwapPaneInDirection,
242 NewFileInDirection,
243 Toast,
244 OpenTerminal,
245 SaveAll,
246 Save,
247 CloseAllItemsAndPanes,
248 ]
249);
250
251pub type WorkspaceId = i64;
252
253pub fn init_settings(cx: &mut AppContext) {
254 settings::register::<WorkspaceSettings>(cx);
255 settings::register::<item::ItemSettings>(cx);
256}
257
258pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
259 init_settings(cx);
260 pane::init(cx);
261 notifications::init(cx);
262
263 cx.add_global_action({
264 let app_state = Arc::downgrade(&app_state);
265 move |_: &Open, cx: &mut AppContext| {
266 let mut paths = cx.prompt_for_paths(PathPromptOptions {
267 files: true,
268 directories: true,
269 multiple: true,
270 });
271
272 if let Some(app_state) = app_state.upgrade() {
273 cx.spawn(move |mut cx| async move {
274 if let Some(paths) = paths.recv().await.flatten() {
275 cx.update(|cx| {
276 open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
277 });
278 }
279 })
280 .detach();
281 }
282 }
283 });
284 cx.add_async_action(Workspace::open);
285
286 cx.add_async_action(Workspace::follow_next_collaborator);
287 cx.add_async_action(Workspace::close);
288 cx.add_async_action(Workspace::close_inactive_items_and_panes);
289 cx.add_async_action(Workspace::close_all_items_and_panes);
290 cx.add_global_action(Workspace::close_global);
291 cx.add_global_action(restart);
292 cx.add_async_action(Workspace::save_all);
293 cx.add_action(Workspace::add_folder_to_project);
294
295 cx.add_action(
296 |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
297 let pane = workspace.active_pane().clone();
298 workspace.unfollow(&pane, cx);
299 },
300 );
301 cx.add_action(
302 |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
303 workspace
304 .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
305 .detach_and_log_err(cx);
306 },
307 );
308 cx.add_action(
309 |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
310 workspace
311 .save_active_item(SaveIntent::SaveAs, cx)
312 .detach_and_log_err(cx);
313 },
314 );
315 cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
316 workspace.activate_previous_pane(cx)
317 });
318 cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
319 workspace.activate_next_pane(cx)
320 });
321
322 cx.add_action(
323 |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
324 workspace.activate_pane_in_direction(action.0, cx)
325 },
326 );
327
328 cx.add_action(
329 |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
330 workspace.swap_pane_in_direction(action.0, cx)
331 },
332 );
333
334 cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
335 workspace.toggle_dock(DockPosition::Left, cx);
336 });
337 cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
338 workspace.toggle_dock(DockPosition::Right, cx);
339 });
340 cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
341 workspace.toggle_dock(DockPosition::Bottom, cx);
342 });
343 cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
344 workspace.close_all_docks(cx);
345 });
346 cx.add_action(Workspace::activate_pane_at_index);
347 cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
348 workspace.reopen_closed_item(cx).detach();
349 });
350 cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
351 workspace
352 .go_back(workspace.active_pane().downgrade(), cx)
353 .detach();
354 });
355 cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
356 workspace
357 .go_forward(workspace.active_pane().downgrade(), cx)
358 .detach();
359 });
360
361 cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
362 cx.spawn(|workspace, mut cx| async move {
363 let err = install_cli::install_cli(&cx)
364 .await
365 .context("Failed to create CLI symlink");
366
367 workspace.update(&mut cx, |workspace, cx| {
368 if matches!(err, Err(_)) {
369 err.notify_err(workspace, cx);
370 } else {
371 workspace.show_notification(1, cx, |cx| {
372 cx.add_view(|_| {
373 MessageNotification::new("Successfully installed the `zed` binary")
374 })
375 });
376 }
377 })
378 })
379 .detach();
380 });
381}
382
383type ProjectItemBuilders = HashMap<
384 TypeId,
385 fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
386>;
387pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
388 cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
389 builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
390 let item = model.downcast::<I::Item>().unwrap();
391 Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
392 });
393 });
394}
395
396type FollowableItemBuilder = fn(
397 ViewHandle<Pane>,
398 ViewHandle<Workspace>,
399 ViewId,
400 &mut Option<proto::view::Variant>,
401 &mut AppContext,
402) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
403type FollowableItemBuilders = HashMap<
404 TypeId,
405 (
406 FollowableItemBuilder,
407 fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
408 ),
409>;
410pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
411 cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
412 builders.insert(
413 TypeId::of::<I>(),
414 (
415 |pane, workspace, id, state, cx| {
416 I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
417 cx.foreground()
418 .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
419 })
420 },
421 |this| Box::new(this.clone().downcast::<I>().unwrap()),
422 ),
423 );
424 });
425}
426
427type ItemDeserializers = HashMap<
428 Arc<str>,
429 fn(
430 ModelHandle<Project>,
431 WeakViewHandle<Workspace>,
432 WorkspaceId,
433 ItemId,
434 &mut ViewContext<Pane>,
435 ) -> Task<Result<Box<dyn ItemHandle>>>,
436>;
437pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
438 cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
439 if let Some(serialized_item_kind) = I::serialized_item_kind() {
440 deserializers.insert(
441 Arc::from(serialized_item_kind),
442 |project, workspace, workspace_id, item_id, cx| {
443 let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
444 cx.foreground()
445 .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
446 },
447 );
448 }
449 });
450}
451
452pub struct AppState {
453 pub languages: Arc<LanguageRegistry>,
454 pub client: Arc<Client>,
455 pub user_store: ModelHandle<UserStore>,
456 pub workspace_store: ModelHandle<WorkspaceStore>,
457 pub fs: Arc<dyn fs::Fs>,
458 pub build_window_options:
459 fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
460 pub initialize_workspace:
461 fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
462 pub background_actions: BackgroundActions,
463 pub node_runtime: Arc<dyn NodeRuntime>,
464}
465
466pub struct WorkspaceStore {
467 workspaces: HashSet<WeakViewHandle<Workspace>>,
468 followers: Vec<Follower>,
469 client: Arc<Client>,
470 _subscriptions: Vec<client::Subscription>,
471}
472
473#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
474struct Follower {
475 project_id: Option<u64>,
476 peer_id: PeerId,
477}
478
479impl AppState {
480 #[cfg(any(test, feature = "test-support"))]
481 pub fn test(cx: &mut AppContext) -> Arc<Self> {
482 use node_runtime::FakeNodeRuntime;
483 use settings::SettingsStore;
484
485 if !cx.has_global::<SettingsStore>() {
486 cx.set_global(SettingsStore::test(cx));
487 }
488
489 let fs = fs::FakeFs::new(cx.background().clone());
490 let languages = Arc::new(LanguageRegistry::test());
491 let http_client = util::http::FakeHttpClient::with_404_response();
492 let client = Client::new(http_client.clone(), cx);
493 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
494 let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
495
496 theme::init((), cx);
497 client::init(&client, cx);
498 crate::init_settings(cx);
499
500 Arc::new(Self {
501 client,
502 fs,
503 languages,
504 user_store,
505 // channel_store,
506 workspace_store,
507 node_runtime: FakeNodeRuntime::new(),
508 initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
509 build_window_options: |_, _, _| Default::default(),
510 background_actions: || &[],
511 })
512 }
513}
514
515struct DelayedDebouncedEditAction {
516 task: Option<Task<()>>,
517 cancel_channel: Option<oneshot::Sender<()>>,
518}
519
520impl DelayedDebouncedEditAction {
521 fn new() -> DelayedDebouncedEditAction {
522 DelayedDebouncedEditAction {
523 task: None,
524 cancel_channel: None,
525 }
526 }
527
528 fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
529 where
530 F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
531 {
532 if let Some(channel) = self.cancel_channel.take() {
533 _ = channel.send(());
534 }
535
536 let (sender, mut receiver) = oneshot::channel::<()>();
537 self.cancel_channel = Some(sender);
538
539 let previous_task = self.task.take();
540 self.task = Some(cx.spawn(|workspace, mut cx| async move {
541 let mut timer = cx.background().timer(delay).fuse();
542 if let Some(previous_task) = previous_task {
543 previous_task.await;
544 }
545
546 futures::select_biased! {
547 _ = receiver => return,
548 _ = timer => {}
549 }
550
551 if let Some(result) = workspace
552 .update(&mut cx, |workspace, cx| (func)(workspace, cx))
553 .log_err()
554 {
555 result.await.log_err();
556 }
557 }));
558 }
559}
560
561pub enum Event {
562 PaneAdded(ViewHandle<Pane>),
563 ContactRequestedJoin(u64),
564}
565
566pub struct Workspace {
567 weak_self: WeakViewHandle<Self>,
568 modal: Option<ActiveModal>,
569 zoomed: Option<AnyWeakViewHandle>,
570 zoomed_position: Option<DockPosition>,
571 center: PaneGroup,
572 left_dock: ViewHandle<Dock>,
573 bottom_dock: ViewHandle<Dock>,
574 right_dock: ViewHandle<Dock>,
575 panes: Vec<ViewHandle<Pane>>,
576 panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
577 active_pane: ViewHandle<Pane>,
578 last_active_center_pane: Option<WeakViewHandle<Pane>>,
579 last_active_view_id: Option<proto::ViewId>,
580 status_bar: ViewHandle<StatusBar>,
581 titlebar_item: Option<AnyViewHandle>,
582 notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
583 project: ModelHandle<Project>,
584 follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
585 last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
586 window_edited: bool,
587 active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
588 leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
589 database_id: WorkspaceId,
590 app_state: Arc<AppState>,
591 subscriptions: Vec<Subscription>,
592 _apply_leader_updates: Task<Result<()>>,
593 _observe_current_user: Task<Result<()>>,
594 _schedule_serialize: Option<Task<()>>,
595 pane_history_timestamp: Arc<AtomicUsize>,
596}
597
598struct ActiveModal {
599 view: Box<dyn ModalHandle>,
600 previously_focused_view_id: Option<usize>,
601}
602
603#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
604pub struct ViewId {
605 pub creator: PeerId,
606 pub id: u64,
607}
608
609#[derive(Default)]
610struct FollowerState {
611 leader_id: PeerId,
612 active_view_id: Option<ViewId>,
613 items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
614}
615
616enum WorkspaceBounds {}
617
618impl Workspace {
619 pub fn new(
620 workspace_id: WorkspaceId,
621 project: ModelHandle<Project>,
622 app_state: Arc<AppState>,
623 cx: &mut ViewContext<Self>,
624 ) -> Self {
625 cx.observe(&project, |_, _, cx| cx.notify()).detach();
626 cx.subscribe(&project, move |this, _, event, cx| {
627 match event {
628 project::Event::RemoteIdChanged(_) => {
629 this.update_window_title(cx);
630 }
631
632 project::Event::CollaboratorLeft(peer_id) => {
633 this.collaborator_left(*peer_id, cx);
634 }
635
636 project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
637 this.update_window_title(cx);
638 this.serialize_workspace(cx);
639 }
640
641 project::Event::DisconnectedFromHost => {
642 this.update_window_edited(cx);
643 cx.blur();
644 }
645
646 project::Event::Closed => {
647 cx.remove_window();
648 }
649
650 project::Event::DeletedEntry(entry_id) => {
651 for pane in this.panes.iter() {
652 pane.update(cx, |pane, cx| {
653 pane.handle_deleted_project_item(*entry_id, cx)
654 });
655 }
656 }
657
658 project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
659 cx.add_view(|_| MessageNotification::new(message.clone()))
660 }),
661
662 _ => {}
663 }
664 cx.notify()
665 })
666 .detach();
667
668 let weak_handle = cx.weak_handle();
669 let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
670
671 let center_pane = cx.add_view(|cx| {
672 Pane::new(
673 weak_handle.clone(),
674 project.clone(),
675 app_state.background_actions,
676 pane_history_timestamp.clone(),
677 cx,
678 )
679 });
680 cx.subscribe(¢er_pane, Self::handle_pane_event).detach();
681 cx.focus(¢er_pane);
682 cx.emit(Event::PaneAdded(center_pane.clone()));
683
684 app_state.workspace_store.update(cx, |store, _| {
685 store.workspaces.insert(weak_handle.clone());
686 });
687
688 let mut current_user = app_state.user_store.read(cx).watch_current_user();
689 let mut connection_status = app_state.client.status();
690 let _observe_current_user = cx.spawn(|this, mut cx| async move {
691 current_user.recv().await;
692 connection_status.recv().await;
693 let mut stream =
694 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
695
696 while stream.recv().await.is_some() {
697 this.update(&mut cx, |_, cx| cx.notify())?;
698 }
699 anyhow::Ok(())
700 });
701
702 // All leader updates are enqueued and then processed in a single task, so
703 // that each asynchronous operation can be run in order.
704 let (leader_updates_tx, mut leader_updates_rx) =
705 mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
706 let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
707 while let Some((leader_id, update)) = leader_updates_rx.next().await {
708 Self::process_leader_update(&this, leader_id, update, &mut cx)
709 .await
710 .log_err();
711 }
712
713 Ok(())
714 });
715
716 cx.emit_global(WorkspaceCreated(weak_handle.clone()));
717
718 let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
719 let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
720 let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
721 let left_dock_buttons =
722 cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
723 let bottom_dock_buttons =
724 cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
725 let right_dock_buttons =
726 cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
727 let status_bar = cx.add_view(|cx| {
728 let mut status_bar = StatusBar::new(¢er_pane.clone(), cx);
729 status_bar.add_left_item(left_dock_buttons, cx);
730 status_bar.add_right_item(right_dock_buttons, cx);
731 status_bar.add_right_item(bottom_dock_buttons, cx);
732 status_bar
733 });
734
735 cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
736 drag_and_drop.register_container(weak_handle.clone());
737 });
738
739 let mut active_call = None;
740 if cx.has_global::<ModelHandle<ActiveCall>>() {
741 let call = cx.global::<ModelHandle<ActiveCall>>().clone();
742 let mut subscriptions = Vec::new();
743 subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
744 active_call = Some((call, subscriptions));
745 }
746
747 let subscriptions = vec![
748 cx.observe_fullscreen(|_, _, cx| cx.notify()),
749 cx.observe_window_activation(Self::on_window_activation_changed),
750 cx.observe_window_bounds(move |_, mut bounds, display, cx| {
751 // Transform fixed bounds to be stored in terms of the containing display
752 if let WindowBounds::Fixed(mut window_bounds) = bounds {
753 if let Some(screen) = cx.platform().screen_by_id(display) {
754 let screen_bounds = screen.bounds();
755 window_bounds
756 .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
757 window_bounds
758 .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
759 bounds = WindowBounds::Fixed(window_bounds);
760 }
761 }
762
763 cx.background()
764 .spawn(DB.set_window_bounds(workspace_id, bounds, display))
765 .detach_and_log_err(cx);
766 }),
767 cx.observe(&left_dock, |this, _, cx| {
768 this.serialize_workspace(cx);
769 cx.notify();
770 }),
771 cx.observe(&bottom_dock, |this, _, cx| {
772 this.serialize_workspace(cx);
773 cx.notify();
774 }),
775 cx.observe(&right_dock, |this, _, cx| {
776 this.serialize_workspace(cx);
777 cx.notify();
778 }),
779 ];
780
781 cx.defer(|this, cx| {
782 this.update_window_title(cx);
783 });
784 Workspace {
785 weak_self: weak_handle.clone(),
786 modal: None,
787 zoomed: None,
788 zoomed_position: None,
789 center: PaneGroup::new(center_pane.clone()),
790 panes: vec![center_pane.clone()],
791 panes_by_item: Default::default(),
792 active_pane: center_pane.clone(),
793 last_active_center_pane: Some(center_pane.downgrade()),
794 last_active_view_id: None,
795 status_bar,
796 titlebar_item: None,
797 notifications: Default::default(),
798 left_dock,
799 bottom_dock,
800 right_dock,
801 project: project.clone(),
802 follower_states: Default::default(),
803 last_leaders_by_pane: Default::default(),
804 window_edited: false,
805 active_call,
806 database_id: workspace_id,
807 app_state,
808 _observe_current_user,
809 _apply_leader_updates,
810 _schedule_serialize: None,
811 leader_updates_tx,
812 subscriptions,
813 pane_history_timestamp,
814 }
815 }
816
817 fn new_local(
818 abs_paths: Vec<PathBuf>,
819 app_state: Arc<AppState>,
820 requesting_window: Option<WindowHandle<Workspace>>,
821 cx: &mut AppContext,
822 ) -> Task<(
823 WeakViewHandle<Workspace>,
824 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
825 )> {
826 let project_handle = Project::local(
827 app_state.client.clone(),
828 app_state.node_runtime.clone(),
829 app_state.user_store.clone(),
830 app_state.languages.clone(),
831 app_state.fs.clone(),
832 cx,
833 );
834
835 cx.spawn(|mut cx| async move {
836 let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
837
838 let paths_to_open = Arc::new(abs_paths);
839
840 // Get project paths for all of the abs_paths
841 let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
842 let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
843 Vec::with_capacity(paths_to_open.len());
844 for path in paths_to_open.iter().cloned() {
845 if let Some((worktree, project_entry)) = cx
846 .update(|cx| {
847 Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
848 })
849 .await
850 .log_err()
851 {
852 worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
853 project_paths.push((path, Some(project_entry)));
854 } else {
855 project_paths.push((path, None));
856 }
857 }
858
859 let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
860 serialized_workspace.id
861 } else {
862 DB.next_id().await.unwrap_or(0)
863 };
864
865 let window = if let Some(window) = requesting_window {
866 window.replace_root(&mut cx, |cx| {
867 Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
868 });
869 window
870 } else {
871 {
872 let window_bounds_override = window_bounds_env_override(&cx);
873 let (bounds, display) = if let Some(bounds) = window_bounds_override {
874 (Some(bounds), None)
875 } else {
876 serialized_workspace
877 .as_ref()
878 .and_then(|serialized_workspace| {
879 let display = serialized_workspace.display?;
880 let mut bounds = serialized_workspace.bounds?;
881
882 // Stored bounds are relative to the containing display.
883 // So convert back to global coordinates if that screen still exists
884 if let WindowBounds::Fixed(mut window_bounds) = bounds {
885 if let Some(screen) = cx.platform().screen_by_id(display) {
886 let screen_bounds = screen.bounds();
887 window_bounds.set_origin_x(
888 window_bounds.origin_x() + screen_bounds.origin_x(),
889 );
890 window_bounds.set_origin_y(
891 window_bounds.origin_y() + screen_bounds.origin_y(),
892 );
893 bounds = WindowBounds::Fixed(window_bounds);
894 } else {
895 // Screen no longer exists. Return none here.
896 return None;
897 }
898 }
899
900 Some((bounds, display))
901 })
902 .unzip()
903 };
904
905 // Use the serialized workspace to construct the new window
906 cx.add_window(
907 (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
908 |cx| {
909 Workspace::new(
910 workspace_id,
911 project_handle.clone(),
912 app_state.clone(),
913 cx,
914 )
915 },
916 )
917 }
918 };
919
920 // We haven't yielded the main thread since obtaining the window handle,
921 // so the window exists.
922 let workspace = window.root(&cx).unwrap();
923
924 (app_state.initialize_workspace)(
925 workspace.downgrade(),
926 serialized_workspace.is_some(),
927 app_state.clone(),
928 cx.clone(),
929 )
930 .await
931 .log_err();
932
933 window.update(&mut cx, |cx| cx.activate_window());
934
935 let workspace = workspace.downgrade();
936 notify_if_database_failed(&workspace, &mut cx);
937 let opened_items = open_items(
938 serialized_workspace,
939 &workspace,
940 project_paths,
941 app_state,
942 cx,
943 )
944 .await
945 .unwrap_or_default();
946
947 (workspace, opened_items)
948 })
949 }
950
951 pub fn weak_handle(&self) -> WeakViewHandle<Self> {
952 self.weak_self.clone()
953 }
954
955 pub fn left_dock(&self) -> &ViewHandle<Dock> {
956 &self.left_dock
957 }
958
959 pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
960 &self.bottom_dock
961 }
962
963 pub fn right_dock(&self) -> &ViewHandle<Dock> {
964 &self.right_dock
965 }
966
967 pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
968 where
969 T::Event: std::fmt::Debug,
970 {
971 self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
972 }
973
974 pub fn add_panel_with_extra_event_handler<T: Panel, F>(
975 &mut self,
976 panel: ViewHandle<T>,
977 cx: &mut ViewContext<Self>,
978 handler: F,
979 ) where
980 T::Event: std::fmt::Debug,
981 F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
982 {
983 let dock = match panel.position(cx) {
984 DockPosition::Left => &self.left_dock,
985 DockPosition::Bottom => &self.bottom_dock,
986 DockPosition::Right => &self.right_dock,
987 };
988
989 self.subscriptions.push(cx.subscribe(&panel, {
990 let mut dock = dock.clone();
991 let mut prev_position = panel.position(cx);
992 move |this, panel, event, cx| {
993 if T::should_change_position_on_event(event) {
994 let new_position = panel.read(cx).position(cx);
995 let mut was_visible = false;
996 dock.update(cx, |dock, cx| {
997 prev_position = new_position;
998
999 was_visible = dock.is_open()
1000 && dock
1001 .visible_panel()
1002 .map_or(false, |active_panel| active_panel.id() == panel.id());
1003 dock.remove_panel(&panel, cx);
1004 });
1005
1006 if panel.is_zoomed(cx) {
1007 this.zoomed_position = Some(new_position);
1008 }
1009
1010 dock = match panel.read(cx).position(cx) {
1011 DockPosition::Left => &this.left_dock,
1012 DockPosition::Bottom => &this.bottom_dock,
1013 DockPosition::Right => &this.right_dock,
1014 }
1015 .clone();
1016 dock.update(cx, |dock, cx| {
1017 dock.add_panel(panel.clone(), cx);
1018 if was_visible {
1019 dock.set_open(true, cx);
1020 dock.activate_panel(dock.panels_len() - 1, cx);
1021 }
1022 });
1023 } else if T::should_zoom_in_on_event(event) {
1024 dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
1025 if !panel.has_focus(cx) {
1026 cx.focus(&panel);
1027 }
1028 this.zoomed = Some(panel.downgrade().into_any());
1029 this.zoomed_position = Some(panel.read(cx).position(cx));
1030 } else if T::should_zoom_out_on_event(event) {
1031 dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
1032 if this.zoomed_position == Some(prev_position) {
1033 this.zoomed = None;
1034 this.zoomed_position = None;
1035 }
1036 cx.notify();
1037 } else if T::is_focus_event(event) {
1038 let position = panel.read(cx).position(cx);
1039 this.dismiss_zoomed_items_to_reveal(Some(position), cx);
1040 if panel.is_zoomed(cx) {
1041 this.zoomed = Some(panel.downgrade().into_any());
1042 this.zoomed_position = Some(position);
1043 } else {
1044 this.zoomed = None;
1045 this.zoomed_position = None;
1046 }
1047 this.update_active_view_for_followers(cx);
1048 cx.notify();
1049 } else {
1050 handler(this, &panel, event, cx)
1051 }
1052 }
1053 }));
1054
1055 dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
1056 }
1057
1058 pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1059 &self.status_bar
1060 }
1061
1062 pub fn app_state(&self) -> &Arc<AppState> {
1063 &self.app_state
1064 }
1065
1066 pub fn user_store(&self) -> &ModelHandle<UserStore> {
1067 &self.app_state.user_store
1068 }
1069
1070 pub fn project(&self) -> &ModelHandle<Project> {
1071 &self.project
1072 }
1073
1074 pub fn recent_navigation_history(
1075 &self,
1076 limit: Option<usize>,
1077 cx: &AppContext,
1078 ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1079 let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1080 let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1081 for pane in &self.panes {
1082 let pane = pane.read(cx);
1083 pane.nav_history()
1084 .for_each_entry(cx, |entry, (project_path, fs_path)| {
1085 if let Some(fs_path) = &fs_path {
1086 abs_paths_opened
1087 .entry(fs_path.clone())
1088 .or_default()
1089 .insert(project_path.clone());
1090 }
1091 let timestamp = entry.timestamp;
1092 match history.entry(project_path) {
1093 hash_map::Entry::Occupied(mut entry) => {
1094 let (_, old_timestamp) = entry.get();
1095 if ×tamp > old_timestamp {
1096 entry.insert((fs_path, timestamp));
1097 }
1098 }
1099 hash_map::Entry::Vacant(entry) => {
1100 entry.insert((fs_path, timestamp));
1101 }
1102 }
1103 });
1104 }
1105
1106 history
1107 .into_iter()
1108 .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1109 .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1110 .rev()
1111 .filter(|(history_path, abs_path)| {
1112 let latest_project_path_opened = abs_path
1113 .as_ref()
1114 .and_then(|abs_path| abs_paths_opened.get(abs_path))
1115 .and_then(|project_paths| {
1116 project_paths
1117 .iter()
1118 .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1119 });
1120
1121 match latest_project_path_opened {
1122 Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1123 None => true,
1124 }
1125 })
1126 .take(limit.unwrap_or(usize::MAX))
1127 .collect()
1128 }
1129
1130 fn navigate_history(
1131 &mut self,
1132 pane: WeakViewHandle<Pane>,
1133 mode: NavigationMode,
1134 cx: &mut ViewContext<Workspace>,
1135 ) -> Task<Result<()>> {
1136 let to_load = if let Some(pane) = pane.upgrade(cx) {
1137 cx.focus(&pane);
1138
1139 pane.update(cx, |pane, cx| {
1140 loop {
1141 // Retrieve the weak item handle from the history.
1142 let entry = pane.nav_history_mut().pop(mode, cx)?;
1143
1144 // If the item is still present in this pane, then activate it.
1145 if let Some(index) = entry
1146 .item
1147 .upgrade(cx)
1148 .and_then(|v| pane.index_for_item(v.as_ref()))
1149 {
1150 let prev_active_item_index = pane.active_item_index();
1151 pane.nav_history_mut().set_mode(mode);
1152 pane.activate_item(index, true, true, cx);
1153 pane.nav_history_mut().set_mode(NavigationMode::Normal);
1154
1155 let mut navigated = prev_active_item_index != pane.active_item_index();
1156 if let Some(data) = entry.data {
1157 navigated |= pane.active_item()?.navigate(data, cx);
1158 }
1159
1160 if navigated {
1161 break None;
1162 }
1163 }
1164 // If the item is no longer present in this pane, then retrieve its
1165 // project path in order to reopen it.
1166 else {
1167 break pane
1168 .nav_history()
1169 .path_for_item(entry.item.id())
1170 .map(|(project_path, _)| (project_path, entry));
1171 }
1172 }
1173 })
1174 } else {
1175 None
1176 };
1177
1178 if let Some((project_path, entry)) = to_load {
1179 // If the item was no longer present, then load it again from its previous path.
1180 let task = self.load_path(project_path, cx);
1181 cx.spawn(|workspace, mut cx| async move {
1182 let task = task.await;
1183 let mut navigated = false;
1184 if let Some((project_entry_id, build_item)) = task.log_err() {
1185 let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1186 pane.nav_history_mut().set_mode(mode);
1187 pane.active_item().map(|p| p.id())
1188 })?;
1189
1190 pane.update(&mut cx, |pane, cx| {
1191 let item = pane.open_item(project_entry_id, true, cx, build_item);
1192 navigated |= Some(item.id()) != prev_active_item_id;
1193 pane.nav_history_mut().set_mode(NavigationMode::Normal);
1194 if let Some(data) = entry.data {
1195 navigated |= item.navigate(data, cx);
1196 }
1197 })?;
1198 }
1199
1200 if !navigated {
1201 workspace
1202 .update(&mut cx, |workspace, cx| {
1203 Self::navigate_history(workspace, pane, mode, cx)
1204 })?
1205 .await?;
1206 }
1207
1208 Ok(())
1209 })
1210 } else {
1211 Task::ready(Ok(()))
1212 }
1213 }
1214
1215 pub fn go_back(
1216 &mut self,
1217 pane: WeakViewHandle<Pane>,
1218 cx: &mut ViewContext<Workspace>,
1219 ) -> Task<Result<()>> {
1220 self.navigate_history(pane, NavigationMode::GoingBack, cx)
1221 }
1222
1223 pub fn go_forward(
1224 &mut self,
1225 pane: WeakViewHandle<Pane>,
1226 cx: &mut ViewContext<Workspace>,
1227 ) -> Task<Result<()>> {
1228 self.navigate_history(pane, NavigationMode::GoingForward, cx)
1229 }
1230
1231 pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1232 self.navigate_history(
1233 self.active_pane().downgrade(),
1234 NavigationMode::ReopeningClosedItem,
1235 cx,
1236 )
1237 }
1238
1239 pub fn client(&self) -> &Client {
1240 &self.app_state.client
1241 }
1242
1243 pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1244 self.titlebar_item = Some(item);
1245 cx.notify();
1246 }
1247
1248 pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1249 self.titlebar_item.clone()
1250 }
1251
1252 /// Call the given callback with a workspace whose project is local.
1253 ///
1254 /// If the given workspace has a local project, then it will be passed
1255 /// to the callback. Otherwise, a new empty window will be created.
1256 pub fn with_local_workspace<T, F>(
1257 &mut self,
1258 cx: &mut ViewContext<Self>,
1259 callback: F,
1260 ) -> Task<Result<T>>
1261 where
1262 T: 'static,
1263 F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1264 {
1265 if self.project.read(cx).is_local() {
1266 Task::Ready(Some(Ok(callback(self, cx))))
1267 } else {
1268 let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1269 cx.spawn(|_vh, mut cx| async move {
1270 let (workspace, _) = task.await;
1271 workspace.update(&mut cx, callback)
1272 })
1273 }
1274 }
1275
1276 pub fn worktrees<'a>(
1277 &self,
1278 cx: &'a AppContext,
1279 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1280 self.project.read(cx).worktrees(cx)
1281 }
1282
1283 pub fn visible_worktrees<'a>(
1284 &self,
1285 cx: &'a AppContext,
1286 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1287 self.project.read(cx).visible_worktrees(cx)
1288 }
1289
1290 pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1291 let futures = self
1292 .worktrees(cx)
1293 .filter_map(|worktree| worktree.read(cx).as_local())
1294 .map(|worktree| worktree.scan_complete())
1295 .collect::<Vec<_>>();
1296 async move {
1297 for future in futures {
1298 future.await;
1299 }
1300 }
1301 }
1302
1303 pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1304 cx.spawn(|mut cx| async move {
1305 let window = cx
1306 .windows()
1307 .into_iter()
1308 .find(|window| window.is_active(&cx).unwrap_or(false));
1309 if let Some(window) = window {
1310 //This can only get called when the window's project connection has been lost
1311 //so we don't need to prompt the user for anything and instead just close the window
1312 window.remove(&mut cx);
1313 }
1314 })
1315 .detach();
1316 }
1317
1318 pub fn close(
1319 &mut self,
1320 _: &CloseWindow,
1321 cx: &mut ViewContext<Self>,
1322 ) -> Option<Task<Result<()>>> {
1323 let window = cx.window();
1324 let prepare = self.prepare_to_close(false, cx);
1325 Some(cx.spawn(|_, mut cx| async move {
1326 if prepare.await? {
1327 window.remove(&mut cx);
1328 }
1329 Ok(())
1330 }))
1331 }
1332
1333 pub fn prepare_to_close(
1334 &mut self,
1335 quitting: bool,
1336 cx: &mut ViewContext<Self>,
1337 ) -> Task<Result<bool>> {
1338 let active_call = self.active_call().cloned();
1339 let window = cx.window();
1340
1341 cx.spawn(|this, mut cx| async move {
1342 let workspace_count = cx
1343 .windows()
1344 .into_iter()
1345 .filter(|window| window.root_is::<Workspace>())
1346 .count();
1347
1348 if let Some(active_call) = active_call {
1349 if !quitting
1350 && workspace_count == 1
1351 && active_call.read_with(&cx, |call, _| call.room().is_some())
1352 {
1353 let answer = window.prompt(
1354 PromptLevel::Warning,
1355 "Do you want to leave the current call?",
1356 &["Close window and hang up", "Cancel"],
1357 &mut cx,
1358 );
1359
1360 if let Some(mut answer) = answer {
1361 if answer.next().await == Some(1) {
1362 return anyhow::Ok(false);
1363 } else {
1364 active_call
1365 .update(&mut cx, |call, cx| call.hang_up(cx))
1366 .await
1367 .log_err();
1368 }
1369 }
1370 }
1371 }
1372
1373 Ok(this
1374 .update(&mut cx, |this, cx| {
1375 this.save_all_internal(SaveIntent::Close, cx)
1376 })?
1377 .await?)
1378 })
1379 }
1380
1381 fn save_all(
1382 &mut self,
1383 action: &SaveAll,
1384 cx: &mut ViewContext<Self>,
1385 ) -> Option<Task<Result<()>>> {
1386 let save_all =
1387 self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
1388 Some(cx.foreground().spawn(async move {
1389 save_all.await?;
1390 Ok(())
1391 }))
1392 }
1393
1394 fn save_all_internal(
1395 &mut self,
1396 mut save_intent: SaveIntent,
1397 cx: &mut ViewContext<Self>,
1398 ) -> Task<Result<bool>> {
1399 if self.project.read(cx).is_read_only() {
1400 return Task::ready(Ok(true));
1401 }
1402 let dirty_items = self
1403 .panes
1404 .iter()
1405 .flat_map(|pane| {
1406 pane.read(cx).items().filter_map(|item| {
1407 if item.is_dirty(cx) {
1408 Some((pane.downgrade(), item.boxed_clone()))
1409 } else {
1410 None
1411 }
1412 })
1413 })
1414 .collect::<Vec<_>>();
1415
1416 let project = self.project.clone();
1417 cx.spawn(|workspace, mut cx| async move {
1418 // Override save mode and display "Save all files" prompt
1419 if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1420 let mut answer = workspace.update(&mut cx, |_, cx| {
1421 let prompt = Pane::file_names_for_prompt(
1422 &mut dirty_items.iter().map(|(_, handle)| handle),
1423 dirty_items.len(),
1424 cx,
1425 );
1426 cx.prompt(
1427 PromptLevel::Warning,
1428 &prompt,
1429 &["Save all", "Discard all", "Cancel"],
1430 )
1431 })?;
1432 match answer.next().await {
1433 Some(0) => save_intent = SaveIntent::SaveAll,
1434 Some(1) => save_intent = SaveIntent::Skip,
1435 _ => {}
1436 }
1437 }
1438 for (pane, item) in dirty_items {
1439 let (singleton, project_entry_ids) =
1440 cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1441 if singleton || !project_entry_ids.is_empty() {
1442 if let Some(ix) =
1443 pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1444 {
1445 if !Pane::save_item(
1446 project.clone(),
1447 &pane,
1448 ix,
1449 &*item,
1450 save_intent,
1451 &mut cx,
1452 )
1453 .await?
1454 {
1455 return Ok(false);
1456 }
1457 }
1458 }
1459 }
1460 Ok(true)
1461 })
1462 }
1463
1464 pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1465 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1466 files: true,
1467 directories: true,
1468 multiple: true,
1469 });
1470
1471 Some(cx.spawn(|this, mut cx| async move {
1472 if let Some(paths) = paths.recv().await.flatten() {
1473 if let Some(task) = this
1474 .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1475 .log_err()
1476 {
1477 task.await?
1478 }
1479 }
1480 Ok(())
1481 }))
1482 }
1483
1484 pub fn open_workspace_for_paths(
1485 &mut self,
1486 paths: Vec<PathBuf>,
1487 cx: &mut ViewContext<Self>,
1488 ) -> Task<Result<()>> {
1489 let window = cx.window().downcast::<Self>();
1490 let is_remote = self.project.read(cx).is_remote();
1491 let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1492 let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1493 let close_task = if is_remote || has_worktree || has_dirty_items {
1494 None
1495 } else {
1496 Some(self.prepare_to_close(false, cx))
1497 };
1498 let app_state = self.app_state.clone();
1499
1500 cx.spawn(|_, mut cx| async move {
1501 let window_to_replace = if let Some(close_task) = close_task {
1502 if !close_task.await? {
1503 return Ok(());
1504 }
1505 window
1506 } else {
1507 None
1508 };
1509 cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
1510 .await?;
1511 Ok(())
1512 })
1513 }
1514
1515 #[allow(clippy::type_complexity)]
1516 pub fn open_paths(
1517 &mut self,
1518 mut abs_paths: Vec<PathBuf>,
1519 visible: bool,
1520 cx: &mut ViewContext<Self>,
1521 ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1522 log::info!("open paths {:?}", abs_paths);
1523
1524 let fs = self.app_state.fs.clone();
1525
1526 // Sort the paths to ensure we add worktrees for parents before their children.
1527 abs_paths.sort_unstable();
1528 cx.spawn(|this, mut cx| async move {
1529 let mut tasks = Vec::with_capacity(abs_paths.len());
1530 for abs_path in &abs_paths {
1531 let project_path = match this
1532 .update(&mut cx, |this, cx| {
1533 Workspace::project_path_for_path(
1534 this.project.clone(),
1535 abs_path,
1536 visible,
1537 cx,
1538 )
1539 })
1540 .log_err()
1541 {
1542 Some(project_path) => project_path.await.log_err(),
1543 None => None,
1544 };
1545
1546 let this = this.clone();
1547 let task = cx.spawn(|mut cx| {
1548 let fs = fs.clone();
1549 let abs_path = abs_path.clone();
1550 async move {
1551 let (worktree, project_path) = project_path?;
1552 if fs.is_file(&abs_path).await {
1553 Some(
1554 this.update(&mut cx, |this, cx| {
1555 this.open_path(project_path, None, true, cx)
1556 })
1557 .log_err()?
1558 .await,
1559 )
1560 } else {
1561 this.update(&mut cx, |workspace, cx| {
1562 let worktree = worktree.read(cx);
1563 let worktree_abs_path = worktree.abs_path();
1564 let entry_id = if abs_path == worktree_abs_path.as_ref() {
1565 worktree.root_entry()
1566 } else {
1567 abs_path
1568 .strip_prefix(worktree_abs_path.as_ref())
1569 .ok()
1570 .and_then(|relative_path| {
1571 worktree.entry_for_path(relative_path)
1572 })
1573 }
1574 .map(|entry| entry.id);
1575 if let Some(entry_id) = entry_id {
1576 workspace.project().update(cx, |_, cx| {
1577 cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1578 })
1579 }
1580 })
1581 .log_err()?;
1582 None
1583 }
1584 }
1585 });
1586 tasks.push(task);
1587 }
1588
1589 futures::future::join_all(tasks).await
1590 })
1591 }
1592
1593 fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1594 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1595 files: false,
1596 directories: true,
1597 multiple: true,
1598 });
1599 cx.spawn(|this, mut cx| async move {
1600 if let Some(paths) = paths.recv().await.flatten() {
1601 let results = this
1602 .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1603 .await;
1604 for result in results.into_iter().flatten() {
1605 result.log_err();
1606 }
1607 }
1608 anyhow::Ok(())
1609 })
1610 .detach_and_log_err(cx);
1611 }
1612
1613 fn project_path_for_path(
1614 project: ModelHandle<Project>,
1615 abs_path: &Path,
1616 visible: bool,
1617 cx: &mut AppContext,
1618 ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1619 let entry = project.update(cx, |project, cx| {
1620 project.find_or_create_local_worktree(abs_path, visible, cx)
1621 });
1622 cx.spawn(|cx| async move {
1623 let (worktree, path) = entry.await?;
1624 let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1625 Ok((
1626 worktree,
1627 ProjectPath {
1628 worktree_id,
1629 path: path.into(),
1630 },
1631 ))
1632 })
1633 }
1634
1635 /// Returns the modal that was toggled closed if it was open.
1636 pub fn toggle_modal<V, F>(
1637 &mut self,
1638 cx: &mut ViewContext<Self>,
1639 add_view: F,
1640 ) -> Option<ViewHandle<V>>
1641 where
1642 V: 'static + Modal,
1643 F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1644 {
1645 cx.notify();
1646 // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1647 // it. Otherwise, create a new modal and set it as active.
1648 if let Some(already_open_modal) = self
1649 .dismiss_modal(cx)
1650 .and_then(|modal| modal.downcast::<V>())
1651 {
1652 cx.focus_self();
1653 Some(already_open_modal)
1654 } else {
1655 let modal = add_view(self, cx);
1656 cx.subscribe(&modal, |this, _, event, cx| {
1657 if V::dismiss_on_event(event) {
1658 this.dismiss_modal(cx);
1659 }
1660 })
1661 .detach();
1662 let previously_focused_view_id = cx.focused_view_id();
1663 cx.focus(&modal);
1664 self.modal = Some(ActiveModal {
1665 view: Box::new(modal),
1666 previously_focused_view_id,
1667 });
1668 None
1669 }
1670 }
1671
1672 pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1673 self.modal
1674 .as_ref()
1675 .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
1676 }
1677
1678 pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
1679 if let Some(modal) = self.modal.take() {
1680 if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
1681 if modal.view.has_focus(cx) {
1682 cx.window_context().focus(Some(previously_focused_view_id));
1683 }
1684 }
1685 cx.notify();
1686 Some(modal.view.as_any().clone())
1687 } else {
1688 None
1689 }
1690 }
1691
1692 pub fn items<'a>(
1693 &'a self,
1694 cx: &'a AppContext,
1695 ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1696 self.panes.iter().flat_map(|pane| pane.read(cx).items())
1697 }
1698
1699 pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1700 self.items_of_type(cx).max_by_key(|item| item.id())
1701 }
1702
1703 pub fn items_of_type<'a, T: Item>(
1704 &'a self,
1705 cx: &'a AppContext,
1706 ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1707 self.panes
1708 .iter()
1709 .flat_map(|pane| pane.read(cx).items_of_type())
1710 }
1711
1712 pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1713 self.active_pane().read(cx).active_item()
1714 }
1715
1716 fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1717 self.active_item(cx).and_then(|item| item.project_path(cx))
1718 }
1719
1720 pub fn save_active_item(
1721 &mut self,
1722 save_intent: SaveIntent,
1723 cx: &mut ViewContext<Self>,
1724 ) -> Task<Result<()>> {
1725 let project = self.project.clone();
1726 let pane = self.active_pane();
1727 let item_ix = pane.read(cx).active_item_index();
1728 let item = pane.read(cx).active_item();
1729 let pane = pane.downgrade();
1730
1731 cx.spawn(|_, mut cx| async move {
1732 if let Some(item) = item {
1733 Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1734 .await
1735 .map(|_| ())
1736 } else {
1737 Ok(())
1738 }
1739 })
1740 }
1741
1742 pub fn close_inactive_items_and_panes(
1743 &mut self,
1744 _: &CloseInactiveTabsAndPanes,
1745 cx: &mut ViewContext<Self>,
1746 ) -> Option<Task<Result<()>>> {
1747 self.close_all_internal(true, SaveIntent::Close, cx)
1748 }
1749
1750 pub fn close_all_items_and_panes(
1751 &mut self,
1752 action: &CloseAllItemsAndPanes,
1753 cx: &mut ViewContext<Self>,
1754 ) -> Option<Task<Result<()>>> {
1755 self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1756 }
1757
1758 fn close_all_internal(
1759 &mut self,
1760 retain_active_pane: bool,
1761 save_intent: SaveIntent,
1762 cx: &mut ViewContext<Self>,
1763 ) -> Option<Task<Result<()>>> {
1764 let current_pane = self.active_pane();
1765
1766 let mut tasks = Vec::new();
1767
1768 if retain_active_pane {
1769 if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1770 pane.close_inactive_items(&CloseInactiveItems, cx)
1771 }) {
1772 tasks.push(current_pane_close);
1773 };
1774 }
1775
1776 for pane in self.panes() {
1777 if retain_active_pane && pane.id() == current_pane.id() {
1778 continue;
1779 }
1780
1781 if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1782 pane.close_all_items(
1783 &CloseAllItems {
1784 save_intent: Some(save_intent),
1785 },
1786 cx,
1787 )
1788 }) {
1789 tasks.push(close_pane_items)
1790 }
1791 }
1792
1793 if tasks.is_empty() {
1794 None
1795 } else {
1796 Some(cx.spawn(|_, _| async move {
1797 for task in tasks {
1798 task.await?
1799 }
1800 Ok(())
1801 }))
1802 }
1803 }
1804
1805 pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1806 let dock = match dock_side {
1807 DockPosition::Left => &self.left_dock,
1808 DockPosition::Bottom => &self.bottom_dock,
1809 DockPosition::Right => &self.right_dock,
1810 };
1811 let mut focus_center = false;
1812 let mut reveal_dock = false;
1813 dock.update(cx, |dock, cx| {
1814 let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1815 let was_visible = dock.is_open() && !other_is_zoomed;
1816 dock.set_open(!was_visible, cx);
1817
1818 if let Some(active_panel) = dock.active_panel() {
1819 if was_visible {
1820 if active_panel.has_focus(cx) {
1821 focus_center = true;
1822 }
1823 } else {
1824 cx.focus(active_panel.as_any());
1825 reveal_dock = true;
1826 }
1827 }
1828 });
1829
1830 if reveal_dock {
1831 self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1832 }
1833
1834 if focus_center {
1835 cx.focus_self();
1836 }
1837
1838 cx.notify();
1839 self.serialize_workspace(cx);
1840 }
1841
1842 pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1843 let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1844
1845 for dock in docks {
1846 dock.update(cx, |dock, cx| {
1847 dock.set_open(false, cx);
1848 });
1849 }
1850
1851 cx.focus_self();
1852 cx.notify();
1853 self.serialize_workspace(cx);
1854 }
1855
1856 /// Transfer focus to the panel of the given type.
1857 pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1858 self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1859 .as_any()
1860 .clone()
1861 .downcast()
1862 }
1863
1864 /// Focus the panel of the given type if it isn't already focused. If it is
1865 /// already focused, then transfer focus back to the workspace center.
1866 pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1867 self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1868 }
1869
1870 /// Focus or unfocus the given panel type, depending on the given callback.
1871 fn focus_or_unfocus_panel<T: Panel>(
1872 &mut self,
1873 cx: &mut ViewContext<Self>,
1874 should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1875 ) -> Option<Rc<dyn PanelHandle>> {
1876 for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1877 if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1878 let mut focus_center = false;
1879 let mut reveal_dock = false;
1880 let panel = dock.update(cx, |dock, cx| {
1881 dock.activate_panel(panel_index, cx);
1882
1883 let panel = dock.active_panel().cloned();
1884 if let Some(panel) = panel.as_ref() {
1885 if should_focus(&**panel, cx) {
1886 dock.set_open(true, cx);
1887 cx.focus(panel.as_any());
1888 reveal_dock = true;
1889 } else {
1890 // if panel.is_zoomed(cx) {
1891 // dock.set_open(false, cx);
1892 // }
1893 focus_center = true;
1894 }
1895 }
1896 panel
1897 });
1898
1899 if focus_center {
1900 cx.focus_self();
1901 }
1902
1903 self.serialize_workspace(cx);
1904 cx.notify();
1905 return panel;
1906 }
1907 }
1908 None
1909 }
1910
1911 pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1912 for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1913 let dock = dock.read(cx);
1914 if let Some(panel) = dock.panel::<T>() {
1915 return Some(panel);
1916 }
1917 }
1918 None
1919 }
1920
1921 fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1922 for pane in &self.panes {
1923 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1924 }
1925
1926 self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1927 self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1928 self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1929 self.zoomed = None;
1930 self.zoomed_position = None;
1931
1932 cx.notify();
1933 }
1934
1935 #[cfg(any(test, feature = "test-support"))]
1936 pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1937 self.zoomed.and_then(|view| view.upgrade(cx))
1938 }
1939
1940 fn dismiss_zoomed_items_to_reveal(
1941 &mut self,
1942 dock_to_reveal: Option<DockPosition>,
1943 cx: &mut ViewContext<Self>,
1944 ) {
1945 // If a center pane is zoomed, unzoom it.
1946 for pane in &self.panes {
1947 if pane != &self.active_pane || dock_to_reveal.is_some() {
1948 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1949 }
1950 }
1951
1952 // If another dock is zoomed, hide it.
1953 let mut focus_center = false;
1954 for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1955 dock.update(cx, |dock, cx| {
1956 if Some(dock.position()) != dock_to_reveal {
1957 if let Some(panel) = dock.active_panel() {
1958 if panel.is_zoomed(cx) {
1959 focus_center |= panel.has_focus(cx);
1960 dock.set_open(false, cx);
1961 }
1962 }
1963 }
1964 });
1965 }
1966
1967 if focus_center {
1968 cx.focus_self();
1969 }
1970
1971 if self.zoomed_position != dock_to_reveal {
1972 self.zoomed = None;
1973 self.zoomed_position = None;
1974 }
1975
1976 cx.notify();
1977 }
1978
1979 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1980 let pane = cx.add_view(|cx| {
1981 Pane::new(
1982 self.weak_handle(),
1983 self.project.clone(),
1984 self.app_state.background_actions,
1985 self.pane_history_timestamp.clone(),
1986 cx,
1987 )
1988 });
1989 cx.subscribe(&pane, Self::handle_pane_event).detach();
1990 self.panes.push(pane.clone());
1991 cx.focus(&pane);
1992 cx.emit(Event::PaneAdded(pane.clone()));
1993 pane
1994 }
1995
1996 pub fn add_item_to_center(
1997 &mut self,
1998 item: Box<dyn ItemHandle>,
1999 cx: &mut ViewContext<Self>,
2000 ) -> bool {
2001 if let Some(center_pane) = self.last_active_center_pane.clone() {
2002 if let Some(center_pane) = center_pane.upgrade(cx) {
2003 center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2004 true
2005 } else {
2006 false
2007 }
2008 } else {
2009 false
2010 }
2011 }
2012
2013 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
2014 self.active_pane
2015 .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2016 }
2017
2018 pub fn split_item(
2019 &mut self,
2020 split_direction: SplitDirection,
2021 item: Box<dyn ItemHandle>,
2022 cx: &mut ViewContext<Self>,
2023 ) {
2024 let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2025 new_pane.update(cx, move |new_pane, cx| {
2026 new_pane.add_item(item, true, true, None, cx)
2027 })
2028 }
2029
2030 pub fn open_abs_path(
2031 &mut self,
2032 abs_path: PathBuf,
2033 visible: bool,
2034 cx: &mut ViewContext<Self>,
2035 ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2036 cx.spawn(|workspace, mut cx| async move {
2037 let open_paths_task_result = workspace
2038 .update(&mut cx, |workspace, cx| {
2039 workspace.open_paths(vec![abs_path.clone()], visible, cx)
2040 })
2041 .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2042 .await;
2043 anyhow::ensure!(
2044 open_paths_task_result.len() == 1,
2045 "open abs path {abs_path:?} task returned incorrect number of results"
2046 );
2047 match open_paths_task_result
2048 .into_iter()
2049 .next()
2050 .expect("ensured single task result")
2051 {
2052 Some(open_result) => {
2053 open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2054 }
2055 None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2056 }
2057 })
2058 }
2059
2060 pub fn split_abs_path(
2061 &mut self,
2062 abs_path: PathBuf,
2063 visible: bool,
2064 cx: &mut ViewContext<Self>,
2065 ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2066 let project_path_task =
2067 Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2068 cx.spawn(|this, mut cx| async move {
2069 let (_, path) = project_path_task.await?;
2070 this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2071 .await
2072 })
2073 }
2074
2075 pub fn open_path(
2076 &mut self,
2077 path: impl Into<ProjectPath>,
2078 pane: Option<WeakViewHandle<Pane>>,
2079 focus_item: bool,
2080 cx: &mut ViewContext<Self>,
2081 ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2082 let pane = pane.unwrap_or_else(|| {
2083 self.last_active_center_pane.clone().unwrap_or_else(|| {
2084 self.panes
2085 .first()
2086 .expect("There must be an active pane")
2087 .downgrade()
2088 })
2089 });
2090
2091 let task = self.load_path(path.into(), cx);
2092 cx.spawn(|_, mut cx| async move {
2093 let (project_entry_id, build_item) = task.await?;
2094 pane.update(&mut cx, |pane, cx| {
2095 pane.open_item(project_entry_id, focus_item, cx, build_item)
2096 })
2097 })
2098 }
2099
2100 pub fn split_path(
2101 &mut self,
2102 path: impl Into<ProjectPath>,
2103 cx: &mut ViewContext<Self>,
2104 ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2105 let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2106 self.panes
2107 .first()
2108 .expect("There must be an active pane")
2109 .downgrade()
2110 });
2111
2112 if let Member::Pane(center_pane) = &self.center.root {
2113 if center_pane.read(cx).items_len() == 0 {
2114 return self.open_path(path, Some(pane), true, cx);
2115 }
2116 }
2117
2118 let task = self.load_path(path.into(), cx);
2119 cx.spawn(|this, mut cx| async move {
2120 let (project_entry_id, build_item) = task.await?;
2121 this.update(&mut cx, move |this, cx| -> Option<_> {
2122 let pane = pane.upgrade(cx)?;
2123 let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2124 new_pane.update(cx, |new_pane, cx| {
2125 Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2126 })
2127 })
2128 .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2129 })
2130 }
2131
2132 fn load_path(
2133 &mut self,
2134 path: ProjectPath,
2135 cx: &mut ViewContext<Self>,
2136 ) -> Task<
2137 Result<(
2138 Option<ProjectEntryId>,
2139 impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2140 )>,
2141 > {
2142 let project = self.project().clone();
2143 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2144 cx.spawn(|_, mut cx| async move {
2145 let (project_entry_id, project_item) = project_item.await?;
2146 let build_item = cx.update(|cx| {
2147 cx.default_global::<ProjectItemBuilders>()
2148 .get(&project_item.model_type())
2149 .ok_or_else(|| anyhow!("no item builder for project item"))
2150 .cloned()
2151 })?;
2152 let build_item =
2153 move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2154 Ok((project_entry_id, build_item))
2155 })
2156 }
2157
2158 pub fn open_project_item<T>(
2159 &mut self,
2160 project_item: ModelHandle<T::Item>,
2161 cx: &mut ViewContext<Self>,
2162 ) -> ViewHandle<T>
2163 where
2164 T: ProjectItem,
2165 {
2166 use project::Item as _;
2167
2168 let entry_id = project_item.read(cx).entry_id(cx);
2169 if let Some(item) = entry_id
2170 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2171 .and_then(|item| item.downcast())
2172 {
2173 self.activate_item(&item, cx);
2174 return item;
2175 }
2176
2177 let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2178 self.add_item(Box::new(item.clone()), cx);
2179 item
2180 }
2181
2182 pub fn split_project_item<T>(
2183 &mut self,
2184 project_item: ModelHandle<T::Item>,
2185 cx: &mut ViewContext<Self>,
2186 ) -> ViewHandle<T>
2187 where
2188 T: ProjectItem,
2189 {
2190 use project::Item as _;
2191
2192 let entry_id = project_item.read(cx).entry_id(cx);
2193 if let Some(item) = entry_id
2194 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2195 .and_then(|item| item.downcast())
2196 {
2197 self.activate_item(&item, cx);
2198 return item;
2199 }
2200
2201 let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2202 self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2203 item
2204 }
2205
2206 pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2207 if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2208 self.active_pane.update(cx, |pane, cx| {
2209 pane.add_item(Box::new(shared_screen), false, true, None, cx)
2210 });
2211 }
2212 }
2213
2214 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2215 let result = self.panes.iter().find_map(|pane| {
2216 pane.read(cx)
2217 .index_for_item(item)
2218 .map(|ix| (pane.clone(), ix))
2219 });
2220 if let Some((pane, ix)) = result {
2221 pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2222 true
2223 } else {
2224 false
2225 }
2226 }
2227
2228 fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2229 let panes = self.center.panes();
2230 if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2231 cx.focus(&pane);
2232 } else {
2233 self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2234 }
2235 }
2236
2237 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2238 let panes = self.center.panes();
2239 if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2240 let next_ix = (ix + 1) % panes.len();
2241 let next_pane = panes[next_ix].clone();
2242 cx.focus(&next_pane);
2243 }
2244 }
2245
2246 pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2247 let panes = self.center.panes();
2248 if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2249 let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2250 let prev_pane = panes[prev_ix].clone();
2251 cx.focus(&prev_pane);
2252 }
2253 }
2254
2255 pub fn activate_pane_in_direction(
2256 &mut self,
2257 direction: SplitDirection,
2258 cx: &mut ViewContext<Self>,
2259 ) {
2260 if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2261 cx.focus(pane);
2262 }
2263 }
2264
2265 pub fn swap_pane_in_direction(
2266 &mut self,
2267 direction: SplitDirection,
2268 cx: &mut ViewContext<Self>,
2269 ) {
2270 if let Some(to) = self
2271 .find_pane_in_direction(direction, cx)
2272 .map(|pane| pane.clone())
2273 {
2274 self.center.swap(&self.active_pane.clone(), &to);
2275 cx.notify();
2276 }
2277 }
2278
2279 fn find_pane_in_direction(
2280 &mut self,
2281 direction: SplitDirection,
2282 cx: &mut ViewContext<Self>,
2283 ) -> Option<&ViewHandle<Pane>> {
2284 let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2285 return None;
2286 };
2287 let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2288 let center = match cursor {
2289 Some(cursor) if bounding_box.contains_point(cursor) => cursor,
2290 _ => bounding_box.center(),
2291 };
2292
2293 let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
2294
2295 let target = match direction {
2296 SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
2297 SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
2298 SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
2299 SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
2300 };
2301 self.center.pane_at_pixel_position(target)
2302 }
2303
2304 fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2305 if self.active_pane != pane {
2306 self.active_pane = pane.clone();
2307 self.status_bar.update(cx, |status_bar, cx| {
2308 status_bar.set_active_pane(&self.active_pane, cx);
2309 });
2310 self.active_item_path_changed(cx);
2311 self.last_active_center_pane = Some(pane.downgrade());
2312 }
2313
2314 self.dismiss_zoomed_items_to_reveal(None, cx);
2315 if pane.read(cx).is_zoomed() {
2316 self.zoomed = Some(pane.downgrade().into_any());
2317 } else {
2318 self.zoomed = None;
2319 }
2320 self.zoomed_position = None;
2321 self.update_active_view_for_followers(cx);
2322
2323 cx.notify();
2324 }
2325
2326 fn handle_pane_event(
2327 &mut self,
2328 pane: ViewHandle<Pane>,
2329 event: &pane::Event,
2330 cx: &mut ViewContext<Self>,
2331 ) {
2332 match event {
2333 pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2334 pane::Event::Split(direction) => {
2335 self.split_and_clone(pane, *direction, cx);
2336 }
2337 pane::Event::Remove => self.remove_pane(pane, cx),
2338 pane::Event::ActivateItem { local } => {
2339 if *local {
2340 self.unfollow(&pane, cx);
2341 }
2342 if &pane == self.active_pane() {
2343 self.active_item_path_changed(cx);
2344 }
2345 }
2346 pane::Event::ChangeItemTitle => {
2347 if pane == self.active_pane {
2348 self.active_item_path_changed(cx);
2349 }
2350 self.update_window_edited(cx);
2351 }
2352 pane::Event::RemoveItem { item_id } => {
2353 self.update_window_edited(cx);
2354 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2355 if entry.get().id() == pane.id() {
2356 entry.remove();
2357 }
2358 }
2359 }
2360 pane::Event::Focus => {
2361 self.handle_pane_focused(pane.clone(), cx);
2362 }
2363 pane::Event::ZoomIn => {
2364 if pane == self.active_pane {
2365 pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2366 if pane.read(cx).has_focus() {
2367 self.zoomed = Some(pane.downgrade().into_any());
2368 self.zoomed_position = None;
2369 }
2370 cx.notify();
2371 }
2372 }
2373 pane::Event::ZoomOut => {
2374 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2375 if self.zoomed_position.is_none() {
2376 self.zoomed = None;
2377 }
2378 cx.notify();
2379 }
2380 }
2381
2382 self.serialize_workspace(cx);
2383 }
2384
2385 pub fn split_pane(
2386 &mut self,
2387 pane_to_split: ViewHandle<Pane>,
2388 split_direction: SplitDirection,
2389 cx: &mut ViewContext<Self>,
2390 ) -> ViewHandle<Pane> {
2391 let new_pane = self.add_pane(cx);
2392 self.center
2393 .split(&pane_to_split, &new_pane, split_direction)
2394 .unwrap();
2395 cx.notify();
2396 new_pane
2397 }
2398
2399 pub fn split_and_clone(
2400 &mut self,
2401 pane: ViewHandle<Pane>,
2402 direction: SplitDirection,
2403 cx: &mut ViewContext<Self>,
2404 ) -> Option<ViewHandle<Pane>> {
2405 let item = pane.read(cx).active_item()?;
2406 let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2407 let new_pane = self.add_pane(cx);
2408 new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2409 self.center.split(&pane, &new_pane, direction).unwrap();
2410 Some(new_pane)
2411 } else {
2412 None
2413 };
2414 cx.notify();
2415 maybe_pane_handle
2416 }
2417
2418 pub fn split_pane_with_item(
2419 &mut self,
2420 pane_to_split: WeakViewHandle<Pane>,
2421 split_direction: SplitDirection,
2422 from: WeakViewHandle<Pane>,
2423 item_id_to_move: usize,
2424 cx: &mut ViewContext<Self>,
2425 ) {
2426 let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
2427 return;
2428 };
2429 let Some(from) = from.upgrade(cx) else {
2430 return;
2431 };
2432
2433 let new_pane = self.add_pane(cx);
2434 self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2435 self.center
2436 .split(&pane_to_split, &new_pane, split_direction)
2437 .unwrap();
2438 cx.notify();
2439 }
2440
2441 pub fn split_pane_with_project_entry(
2442 &mut self,
2443 pane_to_split: WeakViewHandle<Pane>,
2444 split_direction: SplitDirection,
2445 project_entry: ProjectEntryId,
2446 cx: &mut ViewContext<Self>,
2447 ) -> Option<Task<Result<()>>> {
2448 let pane_to_split = pane_to_split.upgrade(cx)?;
2449 let new_pane = self.add_pane(cx);
2450 self.center
2451 .split(&pane_to_split, &new_pane, split_direction)
2452 .unwrap();
2453
2454 let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2455 let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2456 Some(cx.foreground().spawn(async move {
2457 task.await?;
2458 Ok(())
2459 }))
2460 }
2461
2462 pub fn move_item(
2463 &mut self,
2464 source: ViewHandle<Pane>,
2465 destination: ViewHandle<Pane>,
2466 item_id_to_move: usize,
2467 destination_index: usize,
2468 cx: &mut ViewContext<Self>,
2469 ) {
2470 let item_to_move = source
2471 .read(cx)
2472 .items()
2473 .enumerate()
2474 .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2475
2476 if item_to_move.is_none() {
2477 log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2478 return;
2479 }
2480 let (item_ix, item_handle) = item_to_move.unwrap();
2481 let item_handle = item_handle.clone();
2482
2483 if source != destination {
2484 // Close item from previous pane
2485 source.update(cx, |source, cx| {
2486 source.remove_item(item_ix, false, cx);
2487 });
2488 }
2489
2490 // This automatically removes duplicate items in the pane
2491 destination.update(cx, |destination, cx| {
2492 destination.add_item(item_handle, true, true, Some(destination_index), cx);
2493 cx.focus_self();
2494 });
2495 }
2496
2497 fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2498 if self.center.remove(&pane).unwrap() {
2499 self.force_remove_pane(&pane, cx);
2500 self.unfollow(&pane, cx);
2501 self.last_leaders_by_pane.remove(&pane.downgrade());
2502 for removed_item in pane.read(cx).items() {
2503 self.panes_by_item.remove(&removed_item.id());
2504 }
2505
2506 cx.notify();
2507 } else {
2508 self.active_item_path_changed(cx);
2509 }
2510 }
2511
2512 pub fn panes(&self) -> &[ViewHandle<Pane>] {
2513 &self.panes
2514 }
2515
2516 pub fn active_pane(&self) -> &ViewHandle<Pane> {
2517 &self.active_pane
2518 }
2519
2520 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2521 self.follower_states.retain(|_, state| {
2522 if state.leader_id == peer_id {
2523 for item in state.items_by_leader_view_id.values() {
2524 item.set_leader_peer_id(None, cx);
2525 }
2526 false
2527 } else {
2528 true
2529 }
2530 });
2531 cx.notify();
2532 }
2533
2534 fn start_following(
2535 &mut self,
2536 leader_id: PeerId,
2537 cx: &mut ViewContext<Self>,
2538 ) -> Option<Task<Result<()>>> {
2539 let pane = self.active_pane().clone();
2540
2541 self.last_leaders_by_pane
2542 .insert(pane.downgrade(), leader_id);
2543 self.unfollow(&pane, cx);
2544 self.follower_states.insert(
2545 pane.clone(),
2546 FollowerState {
2547 leader_id,
2548 active_view_id: None,
2549 items_by_leader_view_id: Default::default(),
2550 },
2551 );
2552 cx.notify();
2553
2554 let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2555 let project_id = self.project.read(cx).remote_id();
2556 let request = self.app_state.client.request(proto::Follow {
2557 room_id,
2558 project_id,
2559 leader_id: Some(leader_id),
2560 });
2561
2562 Some(cx.spawn(|this, mut cx| async move {
2563 let response = request.await?;
2564 this.update(&mut cx, |this, _| {
2565 let state = this
2566 .follower_states
2567 .get_mut(&pane)
2568 .ok_or_else(|| anyhow!("following interrupted"))?;
2569 state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2570 Some(ViewId::from_proto(active_view_id)?)
2571 } else {
2572 None
2573 };
2574 Ok::<_, anyhow::Error>(())
2575 })??;
2576 Self::add_views_from_leader(
2577 this.clone(),
2578 leader_id,
2579 vec![pane],
2580 response.views,
2581 &mut cx,
2582 )
2583 .await?;
2584 this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2585 Ok(())
2586 }))
2587 }
2588
2589 pub fn follow_next_collaborator(
2590 &mut self,
2591 _: &FollowNextCollaborator,
2592 cx: &mut ViewContext<Self>,
2593 ) -> Option<Task<Result<()>>> {
2594 let collaborators = self.project.read(cx).collaborators();
2595 let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2596 let mut collaborators = collaborators.keys().copied();
2597 for peer_id in collaborators.by_ref() {
2598 if peer_id == leader_id {
2599 break;
2600 }
2601 }
2602 collaborators.next()
2603 } else if let Some(last_leader_id) =
2604 self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2605 {
2606 if collaborators.contains_key(last_leader_id) {
2607 Some(*last_leader_id)
2608 } else {
2609 None
2610 }
2611 } else {
2612 None
2613 };
2614
2615 let pane = self.active_pane.clone();
2616 let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2617 else {
2618 return None;
2619 };
2620 if Some(leader_id) == self.unfollow(&pane, cx) {
2621 return None;
2622 }
2623 self.follow(leader_id, cx)
2624 }
2625
2626 pub fn follow(
2627 &mut self,
2628 leader_id: PeerId,
2629 cx: &mut ViewContext<Self>,
2630 ) -> Option<Task<Result<()>>> {
2631 let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2632 let project = self.project.read(cx);
2633
2634 let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2635 return None;
2636 };
2637
2638 let other_project_id = match remote_participant.location {
2639 call::ParticipantLocation::External => None,
2640 call::ParticipantLocation::UnsharedProject => None,
2641 call::ParticipantLocation::SharedProject { project_id } => {
2642 if Some(project_id) == project.remote_id() {
2643 None
2644 } else {
2645 Some(project_id)
2646 }
2647 }
2648 };
2649
2650 // if they are active in another project, follow there.
2651 if let Some(project_id) = other_project_id {
2652 let app_state = self.app_state.clone();
2653 return Some(crate::join_remote_project(
2654 project_id,
2655 remote_participant.user.id,
2656 app_state,
2657 cx,
2658 ));
2659 }
2660
2661 // if you're already following, find the right pane and focus it.
2662 for (pane, state) in &self.follower_states {
2663 if leader_id == state.leader_id {
2664 cx.focus(pane);
2665 return None;
2666 }
2667 }
2668
2669 // Otherwise, follow.
2670 self.start_following(leader_id, cx)
2671 }
2672
2673 pub fn unfollow(
2674 &mut self,
2675 pane: &ViewHandle<Pane>,
2676 cx: &mut ViewContext<Self>,
2677 ) -> Option<PeerId> {
2678 let state = self.follower_states.remove(pane)?;
2679 let leader_id = state.leader_id;
2680 for (_, item) in state.items_by_leader_view_id {
2681 item.set_leader_peer_id(None, cx);
2682 }
2683
2684 if self
2685 .follower_states
2686 .values()
2687 .all(|state| state.leader_id != state.leader_id)
2688 {
2689 let project_id = self.project.read(cx).remote_id();
2690 let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2691 self.app_state
2692 .client
2693 .send(proto::Unfollow {
2694 room_id,
2695 project_id,
2696 leader_id: Some(leader_id),
2697 })
2698 .log_err();
2699 }
2700
2701 cx.notify();
2702 Some(leader_id)
2703 }
2704
2705 pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2706 self.follower_states
2707 .values()
2708 .any(|state| state.leader_id == peer_id)
2709 }
2710
2711 fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2712 // TODO: There should be a better system in place for this
2713 // (https://github.com/zed-industries/zed/issues/1290)
2714 let is_fullscreen = cx.window_is_fullscreen();
2715 let container_theme = if is_fullscreen {
2716 let mut container_theme = theme.titlebar.container;
2717 container_theme.padding.left = container_theme.padding.right;
2718 container_theme
2719 } else {
2720 theme.titlebar.container
2721 };
2722
2723 enum TitleBar {}
2724 MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2725 Stack::new()
2726 .with_children(
2727 self.titlebar_item
2728 .as_ref()
2729 .map(|item| ChildView::new(item, cx)),
2730 )
2731 .contained()
2732 .with_style(container_theme)
2733 })
2734 .on_click(MouseButton::Left, |event, _, cx| {
2735 if event.click_count == 2 {
2736 cx.zoom_window();
2737 }
2738 })
2739 .constrained()
2740 .with_height(theme.titlebar.height)
2741 .into_any_named("titlebar")
2742 }
2743
2744 fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2745 let active_entry = self.active_project_path(cx);
2746 self.project
2747 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2748 self.update_window_title(cx);
2749 }
2750
2751 fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2752 let project = self.project().read(cx);
2753 let mut title = String::new();
2754
2755 if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2756 let filename = path
2757 .path
2758 .file_name()
2759 .map(|s| s.to_string_lossy())
2760 .or_else(|| {
2761 Some(Cow::Borrowed(
2762 project
2763 .worktree_for_id(path.worktree_id, cx)?
2764 .read(cx)
2765 .root_name(),
2766 ))
2767 });
2768
2769 if let Some(filename) = filename {
2770 title.push_str(filename.as_ref());
2771 title.push_str(" β ");
2772 }
2773 }
2774
2775 for (i, name) in project.worktree_root_names(cx).enumerate() {
2776 if i > 0 {
2777 title.push_str(", ");
2778 }
2779 title.push_str(name);
2780 }
2781
2782 if title.is_empty() {
2783 title = "empty project".to_string();
2784 }
2785
2786 if project.is_remote() {
2787 title.push_str(" β");
2788 } else if project.is_shared() {
2789 title.push_str(" β");
2790 }
2791
2792 cx.set_window_title(&title);
2793 }
2794
2795 fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2796 let is_edited = !self.project.read(cx).is_read_only()
2797 && self
2798 .items(cx)
2799 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2800 if is_edited != self.window_edited {
2801 self.window_edited = is_edited;
2802 cx.set_window_edited(self.window_edited)
2803 }
2804 }
2805
2806 fn render_disconnected_overlay(
2807 &self,
2808 cx: &mut ViewContext<Workspace>,
2809 ) -> Option<AnyElement<Workspace>> {
2810 if self.project.read(cx).is_read_only() {
2811 enum DisconnectedOverlay {}
2812 Some(
2813 MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2814 let theme = &theme::current(cx);
2815 Label::new(
2816 "Your connection to the remote project has been lost.",
2817 theme.workspace.disconnected_overlay.text.clone(),
2818 )
2819 .aligned()
2820 .contained()
2821 .with_style(theme.workspace.disconnected_overlay.container)
2822 })
2823 .with_cursor_style(CursorStyle::Arrow)
2824 .capture_all()
2825 .into_any_named("disconnected overlay"),
2826 )
2827 } else {
2828 None
2829 }
2830 }
2831
2832 fn render_notifications(
2833 &self,
2834 theme: &theme::Workspace,
2835 cx: &AppContext,
2836 ) -> Option<AnyElement<Workspace>> {
2837 if self.notifications.is_empty() {
2838 None
2839 } else {
2840 Some(
2841 Flex::column()
2842 .with_children(self.notifications.iter().map(|(_, _, notification)| {
2843 ChildView::new(notification.as_any(), cx)
2844 .contained()
2845 .with_style(theme.notification)
2846 }))
2847 .constrained()
2848 .with_width(theme.notifications.width)
2849 .contained()
2850 .with_style(theme.notifications.container)
2851 .aligned()
2852 .bottom()
2853 .right()
2854 .into_any(),
2855 )
2856 }
2857 }
2858
2859 // RPC handlers
2860
2861 fn handle_follow(
2862 &mut self,
2863 follower_project_id: Option<u64>,
2864 cx: &mut ViewContext<Self>,
2865 ) -> proto::FollowResponse {
2866 let client = &self.app_state.client;
2867 let project_id = self.project.read(cx).remote_id();
2868
2869 let active_view_id = self.active_item(cx).and_then(|i| {
2870 Some(
2871 i.to_followable_item_handle(cx)?
2872 .remote_id(client, cx)?
2873 .to_proto(),
2874 )
2875 });
2876
2877 cx.notify();
2878
2879 self.last_active_view_id = active_view_id.clone();
2880 proto::FollowResponse {
2881 active_view_id,
2882 views: self
2883 .panes()
2884 .iter()
2885 .flat_map(|pane| {
2886 let leader_id = self.leader_for_pane(pane);
2887 pane.read(cx).items().filter_map({
2888 let cx = &cx;
2889 move |item| {
2890 let item = item.to_followable_item_handle(cx)?;
2891 if (project_id.is_none() || project_id != follower_project_id)
2892 && item.is_project_item(cx)
2893 {
2894 return None;
2895 }
2896 let id = item.remote_id(client, cx)?.to_proto();
2897 let variant = item.to_state_proto(cx)?;
2898 Some(proto::View {
2899 id: Some(id),
2900 leader_id,
2901 variant: Some(variant),
2902 })
2903 }
2904 })
2905 })
2906 .collect(),
2907 }
2908 }
2909
2910 fn handle_update_followers(
2911 &mut self,
2912 leader_id: PeerId,
2913 message: proto::UpdateFollowers,
2914 _cx: &mut ViewContext<Self>,
2915 ) {
2916 self.leader_updates_tx
2917 .unbounded_send((leader_id, message))
2918 .ok();
2919 }
2920
2921 async fn process_leader_update(
2922 this: &WeakViewHandle<Self>,
2923 leader_id: PeerId,
2924 update: proto::UpdateFollowers,
2925 cx: &mut AsyncAppContext,
2926 ) -> Result<()> {
2927 match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2928 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2929 this.update(cx, |this, _| {
2930 for (_, state) in &mut this.follower_states {
2931 if state.leader_id == leader_id {
2932 state.active_view_id =
2933 if let Some(active_view_id) = update_active_view.id.clone() {
2934 Some(ViewId::from_proto(active_view_id)?)
2935 } else {
2936 None
2937 };
2938 }
2939 }
2940 anyhow::Ok(())
2941 })??;
2942 }
2943 proto::update_followers::Variant::UpdateView(update_view) => {
2944 let variant = update_view
2945 .variant
2946 .ok_or_else(|| anyhow!("missing update view variant"))?;
2947 let id = update_view
2948 .id
2949 .ok_or_else(|| anyhow!("missing update view id"))?;
2950 let mut tasks = Vec::new();
2951 this.update(cx, |this, cx| {
2952 let project = this.project.clone();
2953 for (_, state) in &mut this.follower_states {
2954 if state.leader_id == leader_id {
2955 let view_id = ViewId::from_proto(id.clone())?;
2956 if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2957 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2958 }
2959 }
2960 }
2961 anyhow::Ok(())
2962 })??;
2963 try_join_all(tasks).await.log_err();
2964 }
2965 proto::update_followers::Variant::CreateView(view) => {
2966 let panes = this.read_with(cx, |this, _| {
2967 this.follower_states
2968 .iter()
2969 .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2970 .cloned()
2971 .collect()
2972 })?;
2973 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2974 }
2975 }
2976 this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2977 Ok(())
2978 }
2979
2980 async fn add_views_from_leader(
2981 this: WeakViewHandle<Self>,
2982 leader_id: PeerId,
2983 panes: Vec<ViewHandle<Pane>>,
2984 views: Vec<proto::View>,
2985 cx: &mut AsyncAppContext,
2986 ) -> Result<()> {
2987 let this = this
2988 .upgrade(cx)
2989 .ok_or_else(|| anyhow!("workspace dropped"))?;
2990
2991 let item_builders = cx.update(|cx| {
2992 cx.default_global::<FollowableItemBuilders>()
2993 .values()
2994 .map(|b| b.0)
2995 .collect::<Vec<_>>()
2996 });
2997
2998 let mut item_tasks_by_pane = HashMap::default();
2999 for pane in panes {
3000 let mut item_tasks = Vec::new();
3001 let mut leader_view_ids = Vec::new();
3002 for view in &views {
3003 let Some(id) = &view.id else { continue };
3004 let id = ViewId::from_proto(id.clone())?;
3005 let mut variant = view.variant.clone();
3006 if variant.is_none() {
3007 Err(anyhow!("missing view variant"))?;
3008 }
3009 for build_item in &item_builders {
3010 let task = cx
3011 .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
3012 if let Some(task) = task {
3013 item_tasks.push(task);
3014 leader_view_ids.push(id);
3015 break;
3016 } else {
3017 assert!(variant.is_some());
3018 }
3019 }
3020 }
3021
3022 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3023 }
3024
3025 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3026 let items = futures::future::try_join_all(item_tasks).await?;
3027 this.update(cx, |this, cx| {
3028 let state = this.follower_states.get_mut(&pane)?;
3029 for (id, item) in leader_view_ids.into_iter().zip(items) {
3030 item.set_leader_peer_id(Some(leader_id), cx);
3031 state.items_by_leader_view_id.insert(id, item);
3032 }
3033
3034 Some(())
3035 });
3036 }
3037 Ok(())
3038 }
3039
3040 fn update_active_view_for_followers(&mut self, cx: &AppContext) {
3041 let mut is_project_item = true;
3042 let mut update = proto::UpdateActiveView::default();
3043 if self.active_pane.read(cx).has_focus() {
3044 let item = self
3045 .active_item(cx)
3046 .and_then(|item| item.to_followable_item_handle(cx));
3047 if let Some(item) = item {
3048 is_project_item = item.is_project_item(cx);
3049 update = proto::UpdateActiveView {
3050 id: item
3051 .remote_id(&self.app_state.client, cx)
3052 .map(|id| id.to_proto()),
3053 leader_id: self.leader_for_pane(&self.active_pane),
3054 };
3055 }
3056 }
3057
3058 if update.id != self.last_active_view_id {
3059 self.last_active_view_id = update.id.clone();
3060 self.update_followers(
3061 is_project_item,
3062 proto::update_followers::Variant::UpdateActiveView(update),
3063 cx,
3064 );
3065 }
3066 }
3067
3068 fn update_followers(
3069 &self,
3070 project_only: bool,
3071 update: proto::update_followers::Variant,
3072 cx: &AppContext,
3073 ) -> Option<()> {
3074 let project_id = if project_only {
3075 self.project.read(cx).remote_id()
3076 } else {
3077 None
3078 };
3079 self.app_state().workspace_store.read_with(cx, |store, cx| {
3080 store.update_followers(project_id, update, cx)
3081 })
3082 }
3083
3084 pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3085 self.follower_states.get(pane).map(|state| state.leader_id)
3086 }
3087
3088 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3089 cx.notify();
3090
3091 let call = self.active_call()?;
3092 let room = call.read(cx).room()?.read(cx);
3093 let participant = room.remote_participant_for_peer_id(leader_id)?;
3094 let mut items_to_activate = Vec::new();
3095
3096 let leader_in_this_app;
3097 let leader_in_this_project;
3098 match participant.location {
3099 call::ParticipantLocation::SharedProject { project_id } => {
3100 leader_in_this_app = true;
3101 leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3102 }
3103 call::ParticipantLocation::UnsharedProject => {
3104 leader_in_this_app = true;
3105 leader_in_this_project = false;
3106 }
3107 call::ParticipantLocation::External => {
3108 leader_in_this_app = false;
3109 leader_in_this_project = false;
3110 }
3111 };
3112
3113 for (pane, state) in &self.follower_states {
3114 if state.leader_id != leader_id {
3115 continue;
3116 }
3117 if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3118 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3119 if leader_in_this_project || !item.is_project_item(cx) {
3120 items_to_activate.push((pane.clone(), item.boxed_clone()));
3121 }
3122 } else {
3123 log::warn!(
3124 "unknown view id {:?} for leader {:?}",
3125 active_view_id,
3126 leader_id
3127 );
3128 }
3129 continue;
3130 }
3131 if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3132 items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3133 }
3134 }
3135
3136 for (pane, item) in items_to_activate {
3137 let pane_was_focused = pane.read(cx).has_focus();
3138 if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3139 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3140 } else {
3141 pane.update(cx, |pane, cx| {
3142 pane.add_item(item.boxed_clone(), false, false, None, cx)
3143 });
3144 }
3145
3146 if pane_was_focused {
3147 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3148 }
3149 }
3150
3151 None
3152 }
3153
3154 fn shared_screen_for_peer(
3155 &self,
3156 peer_id: PeerId,
3157 pane: &ViewHandle<Pane>,
3158 cx: &mut ViewContext<Self>,
3159 ) -> Option<ViewHandle<SharedScreen>> {
3160 let call = self.active_call()?;
3161 let room = call.read(cx).room()?.read(cx);
3162 let participant = room.remote_participant_for_peer_id(peer_id)?;
3163 let track = participant.video_tracks.values().next()?.clone();
3164 let user = participant.user.clone();
3165
3166 for item in pane.read(cx).items_of_type::<SharedScreen>() {
3167 if item.read(cx).peer_id == peer_id {
3168 return Some(item);
3169 }
3170 }
3171
3172 Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3173 }
3174
3175 pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3176 if active {
3177 self.update_active_view_for_followers(cx);
3178 cx.background()
3179 .spawn(persistence::DB.update_timestamp(self.database_id()))
3180 .detach();
3181 } else {
3182 for pane in &self.panes {
3183 pane.update(cx, |pane, cx| {
3184 if let Some(item) = pane.active_item() {
3185 item.workspace_deactivated(cx);
3186 }
3187 if matches!(
3188 settings::get::<WorkspaceSettings>(cx).autosave,
3189 AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3190 ) {
3191 for item in pane.items() {
3192 Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3193 .detach_and_log_err(cx);
3194 }
3195 }
3196 });
3197 }
3198 }
3199 }
3200
3201 fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3202 self.active_call.as_ref().map(|(call, _)| call)
3203 }
3204
3205 fn on_active_call_event(
3206 &mut self,
3207 _: ModelHandle<ActiveCall>,
3208 event: &call::room::Event,
3209 cx: &mut ViewContext<Self>,
3210 ) {
3211 match event {
3212 call::room::Event::ParticipantLocationChanged { participant_id }
3213 | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3214 self.leader_updated(*participant_id, cx);
3215 }
3216 _ => {}
3217 }
3218 }
3219
3220 pub fn database_id(&self) -> WorkspaceId {
3221 self.database_id
3222 }
3223
3224 fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3225 let project = self.project().read(cx);
3226
3227 if project.is_local() {
3228 Some(
3229 project
3230 .visible_worktrees(cx)
3231 .map(|worktree| worktree.read(cx).abs_path())
3232 .collect::<Vec<_>>()
3233 .into(),
3234 )
3235 } else {
3236 None
3237 }
3238 }
3239
3240 fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3241 match member {
3242 Member::Axis(PaneAxis { members, .. }) => {
3243 for child in members.iter() {
3244 self.remove_panes(child.clone(), cx)
3245 }
3246 }
3247 Member::Pane(pane) => {
3248 self.force_remove_pane(&pane, cx);
3249 }
3250 }
3251 }
3252
3253 fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3254 self.panes.retain(|p| p != pane);
3255 cx.focus(self.panes.last().unwrap());
3256 if self.last_active_center_pane == Some(pane.downgrade()) {
3257 self.last_active_center_pane = None;
3258 }
3259 cx.notify();
3260 }
3261
3262 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3263 self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3264 cx.background().timer(Duration::from_millis(100)).await;
3265 this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3266 .ok();
3267 }));
3268 }
3269
3270 fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3271 fn serialize_pane_handle(
3272 pane_handle: &ViewHandle<Pane>,
3273 cx: &AppContext,
3274 ) -> SerializedPane {
3275 let (items, active) = {
3276 let pane = pane_handle.read(cx);
3277 let active_item_id = pane.active_item().map(|item| item.id());
3278 (
3279 pane.items()
3280 .filter_map(|item_handle| {
3281 Some(SerializedItem {
3282 kind: Arc::from(item_handle.serialized_item_kind()?),
3283 item_id: item_handle.id(),
3284 active: Some(item_handle.id()) == active_item_id,
3285 })
3286 })
3287 .collect::<Vec<_>>(),
3288 pane.has_focus(),
3289 )
3290 };
3291
3292 SerializedPane::new(items, active)
3293 }
3294
3295 fn build_serialized_pane_group(
3296 pane_group: &Member,
3297 cx: &AppContext,
3298 ) -> SerializedPaneGroup {
3299 match pane_group {
3300 Member::Axis(PaneAxis {
3301 axis,
3302 members,
3303 flexes,
3304 bounding_boxes: _,
3305 }) => SerializedPaneGroup::Group {
3306 axis: *axis,
3307 children: members
3308 .iter()
3309 .map(|member| build_serialized_pane_group(member, cx))
3310 .collect::<Vec<_>>(),
3311 flexes: Some(flexes.borrow().clone()),
3312 },
3313 Member::Pane(pane_handle) => {
3314 SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3315 }
3316 }
3317 }
3318
3319 fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3320 let left_dock = this.left_dock.read(cx);
3321 let left_visible = left_dock.is_open();
3322 let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3323 Some(
3324 cx.view_ui_name(panel.as_any().window(), panel.id())?
3325 .to_string(),
3326 )
3327 });
3328 let left_dock_zoom = left_dock
3329 .visible_panel()
3330 .map(|panel| panel.is_zoomed(cx))
3331 .unwrap_or(false);
3332
3333 let right_dock = this.right_dock.read(cx);
3334 let right_visible = right_dock.is_open();
3335 let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3336 Some(
3337 cx.view_ui_name(panel.as_any().window(), panel.id())?
3338 .to_string(),
3339 )
3340 });
3341 let right_dock_zoom = right_dock
3342 .visible_panel()
3343 .map(|panel| panel.is_zoomed(cx))
3344 .unwrap_or(false);
3345
3346 let bottom_dock = this.bottom_dock.read(cx);
3347 let bottom_visible = bottom_dock.is_open();
3348 let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3349 Some(
3350 cx.view_ui_name(panel.as_any().window(), panel.id())?
3351 .to_string(),
3352 )
3353 });
3354 let bottom_dock_zoom = bottom_dock
3355 .visible_panel()
3356 .map(|panel| panel.is_zoomed(cx))
3357 .unwrap_or(false);
3358
3359 DockStructure {
3360 left: DockData {
3361 visible: left_visible,
3362 active_panel: left_active_panel,
3363 zoom: left_dock_zoom,
3364 },
3365 right: DockData {
3366 visible: right_visible,
3367 active_panel: right_active_panel,
3368 zoom: right_dock_zoom,
3369 },
3370 bottom: DockData {
3371 visible: bottom_visible,
3372 active_panel: bottom_active_panel,
3373 zoom: bottom_dock_zoom,
3374 },
3375 }
3376 }
3377
3378 if let Some(location) = self.location(cx) {
3379 // Load bearing special case:
3380 // - with_local_workspace() relies on this to not have other stuff open
3381 // when you open your log
3382 if !location.paths().is_empty() {
3383 let center_group = build_serialized_pane_group(&self.center.root, cx);
3384 let docks = build_serialized_docks(self, cx);
3385
3386 let serialized_workspace = SerializedWorkspace {
3387 id: self.database_id,
3388 location,
3389 center_group,
3390 bounds: Default::default(),
3391 display: Default::default(),
3392 docks,
3393 };
3394
3395 cx.background()
3396 .spawn(persistence::DB.save_workspace(serialized_workspace))
3397 .detach();
3398 }
3399 }
3400 }
3401
3402 pub(crate) fn load_workspace(
3403 workspace: WeakViewHandle<Workspace>,
3404 serialized_workspace: SerializedWorkspace,
3405 paths_to_open: Vec<Option<ProjectPath>>,
3406 cx: &mut AppContext,
3407 ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3408 cx.spawn(|mut cx| async move {
3409 let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
3410 (
3411 workspace.project().clone(),
3412 workspace.last_active_center_pane.clone(),
3413 )
3414 })?;
3415
3416 let mut center_group = None;
3417 let mut center_items = None;
3418 // Traverse the splits tree and add to things
3419 if let Some((group, active_pane, items)) = serialized_workspace
3420 .center_group
3421 .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3422 .await
3423 {
3424 center_items = Some(items);
3425 center_group = Some((group, active_pane))
3426 }
3427
3428 let mut items_by_project_path = cx.read(|cx| {
3429 center_items
3430 .unwrap_or_default()
3431 .into_iter()
3432 .filter_map(|item| {
3433 let item = item?;
3434 let project_path = item.project_path(cx)?;
3435 Some((project_path, item))
3436 })
3437 .collect::<HashMap<_, _>>()
3438 });
3439
3440 let opened_items = paths_to_open
3441 .into_iter()
3442 .map(|path_to_open| {
3443 path_to_open
3444 .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3445 })
3446 .collect::<Vec<_>>();
3447
3448 // Remove old panes from workspace panes list
3449 workspace.update(&mut cx, |workspace, cx| {
3450 if let Some((center_group, active_pane)) = center_group {
3451 workspace.remove_panes(workspace.center.root.clone(), cx);
3452
3453 // Swap workspace center group
3454 workspace.center = PaneGroup::with_root(center_group);
3455
3456 // Change the focus to the workspace first so that we retrigger focus in on the pane.
3457 cx.focus_self();
3458
3459 if let Some(active_pane) = active_pane {
3460 cx.focus(&active_pane);
3461 } else {
3462 cx.focus(workspace.panes.last().unwrap());
3463 }
3464 } else {
3465 let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3466 if let Some(old_center_handle) = old_center_handle {
3467 cx.focus(&old_center_handle)
3468 } else {
3469 cx.focus_self()
3470 }
3471 }
3472
3473 let docks = serialized_workspace.docks;
3474 workspace.left_dock.update(cx, |dock, cx| {
3475 dock.set_open(docks.left.visible, cx);
3476 if let Some(active_panel) = docks.left.active_panel {
3477 if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3478 dock.activate_panel(ix, cx);
3479 }
3480 }
3481 dock.active_panel()
3482 .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3483 if docks.left.visible && docks.left.zoom {
3484 cx.focus_self()
3485 }
3486 });
3487 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3488 workspace.right_dock.update(cx, |dock, cx| {
3489 dock.set_open(docks.right.visible, cx);
3490 if let Some(active_panel) = docks.right.active_panel {
3491 if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3492 dock.activate_panel(ix, cx);
3493 }
3494 }
3495 dock.active_panel()
3496 .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3497
3498 if docks.right.visible && docks.right.zoom {
3499 cx.focus_self()
3500 }
3501 });
3502 workspace.bottom_dock.update(cx, |dock, cx| {
3503 dock.set_open(docks.bottom.visible, cx);
3504 if let Some(active_panel) = docks.bottom.active_panel {
3505 if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3506 dock.activate_panel(ix, cx);
3507 }
3508 }
3509
3510 dock.active_panel()
3511 .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3512
3513 if docks.bottom.visible && docks.bottom.zoom {
3514 cx.focus_self()
3515 }
3516 });
3517
3518 cx.notify();
3519 })?;
3520
3521 // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3522 workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3523
3524 Ok(opened_items)
3525 })
3526 }
3527
3528 #[cfg(any(test, feature = "test-support"))]
3529 pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3530 use node_runtime::FakeNodeRuntime;
3531
3532 let client = project.read(cx).client();
3533 let user_store = project.read(cx).user_store();
3534
3535 let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
3536 let app_state = Arc::new(AppState {
3537 languages: project.read(cx).languages().clone(),
3538 workspace_store,
3539 client,
3540 user_store,
3541 fs: project.read(cx).fs().clone(),
3542 build_window_options: |_, _, _| Default::default(),
3543 initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3544 background_actions: || &[],
3545 node_runtime: FakeNodeRuntime::new(),
3546 });
3547 Self::new(0, project, app_state, cx)
3548 }
3549
3550 fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3551 let dock = match position {
3552 DockPosition::Left => &self.left_dock,
3553 DockPosition::Right => &self.right_dock,
3554 DockPosition::Bottom => &self.bottom_dock,
3555 };
3556 let active_panel = dock.read(cx).visible_panel()?;
3557 let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3558 dock.read(cx).render_placeholder(cx)
3559 } else {
3560 ChildView::new(dock, cx).into_any()
3561 };
3562
3563 Some(
3564 element
3565 .constrained()
3566 .dynamically(move |constraint, _, cx| match position {
3567 DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3568 Vector2F::new(20., constraint.min.y()),
3569 Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3570 ),
3571 DockPosition::Bottom => SizeConstraint::new(
3572 Vector2F::new(constraint.min.x(), 20.),
3573 Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3574 ),
3575 })
3576 .into_any(),
3577 )
3578 }
3579}
3580
3581fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3582 ZED_WINDOW_POSITION
3583 .zip(*ZED_WINDOW_SIZE)
3584 .map(|(position, size)| {
3585 WindowBounds::Fixed(RectF::new(
3586 cx.platform().screens()[0].bounds().origin() + position,
3587 size,
3588 ))
3589 })
3590}
3591
3592async fn open_items(
3593 serialized_workspace: Option<SerializedWorkspace>,
3594 workspace: &WeakViewHandle<Workspace>,
3595 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3596 app_state: Arc<AppState>,
3597 mut cx: AsyncAppContext,
3598) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
3599 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3600
3601 if let Some(serialized_workspace) = serialized_workspace {
3602 let workspace = workspace.clone();
3603 let restored_items = cx
3604 .update(|cx| {
3605 Workspace::load_workspace(
3606 workspace,
3607 serialized_workspace,
3608 project_paths_to_open
3609 .iter()
3610 .map(|(_, project_path)| project_path)
3611 .cloned()
3612 .collect(),
3613 cx,
3614 )
3615 })
3616 .await?;
3617
3618 let restored_project_paths = cx.read(|cx| {
3619 restored_items
3620 .iter()
3621 .filter_map(|item| item.as_ref()?.project_path(cx))
3622 .collect::<HashSet<_>>()
3623 });
3624
3625 for restored_item in restored_items {
3626 opened_items.push(restored_item.map(Ok));
3627 }
3628
3629 project_paths_to_open
3630 .iter_mut()
3631 .for_each(|(_, project_path)| {
3632 if let Some(project_path_to_open) = project_path {
3633 if restored_project_paths.contains(project_path_to_open) {
3634 *project_path = None;
3635 }
3636 }
3637 });
3638 } else {
3639 for _ in 0..project_paths_to_open.len() {
3640 opened_items.push(None);
3641 }
3642 }
3643 assert!(opened_items.len() == project_paths_to_open.len());
3644
3645 let tasks =
3646 project_paths_to_open
3647 .into_iter()
3648 .enumerate()
3649 .map(|(i, (abs_path, project_path))| {
3650 let workspace = workspace.clone();
3651 cx.spawn(|mut cx| {
3652 let fs = app_state.fs.clone();
3653 async move {
3654 let file_project_path = project_path?;
3655 if fs.is_file(&abs_path).await {
3656 Some((
3657 i,
3658 workspace
3659 .update(&mut cx, |workspace, cx| {
3660 workspace.open_path(file_project_path, None, true, cx)
3661 })
3662 .log_err()?
3663 .await,
3664 ))
3665 } else {
3666 None
3667 }
3668 }
3669 })
3670 });
3671
3672 for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3673 .await
3674 .into_iter()
3675 {
3676 if let Some((i, path_open_result)) = maybe_opened_path {
3677 opened_items[i] = Some(path_open_result);
3678 }
3679 }
3680
3681 Ok(opened_items)
3682}
3683
3684fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3685 const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3686 const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3687 const MESSAGE_ID: usize = 2;
3688
3689 if workspace
3690 .read_with(cx, |workspace, cx| {
3691 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3692 })
3693 .unwrap_or(false)
3694 {
3695 return;
3696 }
3697
3698 if db::kvp::KEY_VALUE_STORE
3699 .read_kvp(NEW_DOCK_HINT_KEY)
3700 .ok()
3701 .flatten()
3702 .is_some()
3703 {
3704 if !workspace
3705 .read_with(cx, |workspace, cx| {
3706 workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3707 })
3708 .unwrap_or(false)
3709 {
3710 cx.update(|cx| {
3711 cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3712 let entry = tracker
3713 .entry(TypeId::of::<MessageNotification>())
3714 .or_default();
3715 if !entry.contains(&MESSAGE_ID) {
3716 entry.push(MESSAGE_ID);
3717 }
3718 });
3719 });
3720 }
3721
3722 return;
3723 }
3724
3725 cx.spawn(|_| async move {
3726 db::kvp::KEY_VALUE_STORE
3727 .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3728 .await
3729 .ok();
3730 })
3731 .detach();
3732
3733 workspace
3734 .update(cx, |workspace, cx| {
3735 workspace.show_notification_once(2, cx, |cx| {
3736 cx.add_view(|_| {
3737 MessageNotification::new_element(|text, _| {
3738 Text::new(
3739 "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3740 text,
3741 )
3742 .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3743 let code_span_background_color = settings::get::<ThemeSettings>(cx)
3744 .theme
3745 .editor
3746 .document_highlight_read_background;
3747
3748 cx.scene().push_quad(gpui::Quad {
3749 bounds,
3750 background: Some(code_span_background_color),
3751 border: Default::default(),
3752 corner_radii: (2.0).into(),
3753 })
3754 })
3755 .into_any()
3756 })
3757 .with_click_message("Read more about the new panel system")
3758 .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3759 })
3760 })
3761 })
3762 .ok();
3763}
3764
3765fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3766 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3767
3768 workspace
3769 .update(cx, |workspace, cx| {
3770 if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3771 workspace.show_notification_once(0, cx, |cx| {
3772 cx.add_view(|_| {
3773 MessageNotification::new("Failed to load the database file.")
3774 .with_click_message("Click to let us know about this error")
3775 .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3776 })
3777 });
3778 }
3779 })
3780 .log_err();
3781}
3782
3783impl Entity for Workspace {
3784 type Event = Event;
3785
3786 fn release(&mut self, cx: &mut AppContext) {
3787 self.app_state.workspace_store.update(cx, |store, _| {
3788 store.workspaces.remove(&self.weak_self);
3789 })
3790 }
3791}
3792
3793impl View for Workspace {
3794 fn ui_name() -> &'static str {
3795 "Workspace"
3796 }
3797
3798 fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3799 let theme = theme::current(cx).clone();
3800 Stack::new()
3801 .with_child(
3802 Flex::column()
3803 .with_child(self.render_titlebar(&theme, cx))
3804 .with_child(
3805 Stack::new()
3806 .with_child({
3807 let project = self.project.clone();
3808 Flex::row()
3809 .with_children(self.render_dock(DockPosition::Left, cx))
3810 .with_child(
3811 Flex::column()
3812 .with_child(
3813 FlexItem::new(
3814 self.center.render(
3815 &project,
3816 &theme,
3817 &self.follower_states,
3818 self.active_call(),
3819 self.active_pane(),
3820 self.zoomed
3821 .as_ref()
3822 .and_then(|zoomed| zoomed.upgrade(cx))
3823 .as_ref(),
3824 &self.app_state,
3825 cx,
3826 ),
3827 )
3828 .flex(1., true),
3829 )
3830 .with_children(
3831 self.render_dock(DockPosition::Bottom, cx),
3832 )
3833 .flex(1., true),
3834 )
3835 .with_children(self.render_dock(DockPosition::Right, cx))
3836 })
3837 .with_child(Overlay::new(
3838 Stack::new()
3839 .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3840 enum ZoomBackground {}
3841 let zoomed = zoomed.upgrade(cx)?;
3842
3843 let mut foreground_style =
3844 theme.workspace.zoomed_pane_foreground;
3845 if let Some(zoomed_dock_position) = self.zoomed_position {
3846 foreground_style =
3847 theme.workspace.zoomed_panel_foreground;
3848 let margin = foreground_style.margin.top;
3849 let border = foreground_style.border.top;
3850
3851 // Only include a margin and border on the opposite side.
3852 foreground_style.margin.top = 0.;
3853 foreground_style.margin.left = 0.;
3854 foreground_style.margin.bottom = 0.;
3855 foreground_style.margin.right = 0.;
3856 foreground_style.border.top = false;
3857 foreground_style.border.left = false;
3858 foreground_style.border.bottom = false;
3859 foreground_style.border.right = false;
3860 match zoomed_dock_position {
3861 DockPosition::Left => {
3862 foreground_style.margin.right = margin;
3863 foreground_style.border.right = border;
3864 }
3865 DockPosition::Right => {
3866 foreground_style.margin.left = margin;
3867 foreground_style.border.left = border;
3868 }
3869 DockPosition::Bottom => {
3870 foreground_style.margin.top = margin;
3871 foreground_style.border.top = border;
3872 }
3873 }
3874 }
3875
3876 Some(
3877 ChildView::new(&zoomed, cx)
3878 .contained()
3879 .with_style(foreground_style)
3880 .aligned()
3881 .contained()
3882 .with_style(theme.workspace.zoomed_background)
3883 .mouse::<ZoomBackground>(0)
3884 .capture_all()
3885 .on_down(
3886 MouseButton::Left,
3887 |_, this: &mut Self, cx| {
3888 this.zoom_out(cx);
3889 },
3890 ),
3891 )
3892 }))
3893 .with_children(self.modal.as_ref().map(|modal| {
3894 // Prevent clicks within the modal from falling
3895 // through to the rest of the workspace.
3896 enum ModalBackground {}
3897 MouseEventHandler::new::<ModalBackground, _>(
3898 0,
3899 cx,
3900 |_, cx| ChildView::new(modal.view.as_any(), cx),
3901 )
3902 .on_click(MouseButton::Left, |_, _, _| {})
3903 .contained()
3904 .with_style(theme.workspace.modal)
3905 .aligned()
3906 .top()
3907 }))
3908 .with_children(self.render_notifications(&theme.workspace, cx)),
3909 ))
3910 .provide_resize_bounds::<WorkspaceBounds>()
3911 .flex(1.0, true),
3912 )
3913 .with_child(ChildView::new(&self.status_bar, cx))
3914 .contained()
3915 .with_background_color(theme.workspace.background),
3916 )
3917 .with_children(DragAndDrop::render(cx))
3918 .with_children(self.render_disconnected_overlay(cx))
3919 .into_any_named("workspace")
3920 }
3921
3922 fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3923 if cx.is_self_focused() {
3924 cx.focus(&self.active_pane);
3925 }
3926 }
3927
3928 fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3929 DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3930 }
3931}
3932
3933impl WorkspaceStore {
3934 pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3935 Self {
3936 workspaces: Default::default(),
3937 followers: Default::default(),
3938 _subscriptions: vec![
3939 client.add_request_handler(cx.handle(), Self::handle_follow),
3940 client.add_message_handler(cx.handle(), Self::handle_unfollow),
3941 client.add_message_handler(cx.handle(), Self::handle_update_followers),
3942 ],
3943 client,
3944 }
3945 }
3946
3947 pub fn update_followers(
3948 &self,
3949 project_id: Option<u64>,
3950 update: proto::update_followers::Variant,
3951 cx: &AppContext,
3952 ) -> Option<()> {
3953 if !cx.has_global::<ModelHandle<ActiveCall>>() {
3954 return None;
3955 }
3956
3957 let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3958 let follower_ids: Vec<_> = self
3959 .followers
3960 .iter()
3961 .filter_map(|follower| {
3962 if follower.project_id == project_id || project_id.is_none() {
3963 Some(follower.peer_id.into())
3964 } else {
3965 None
3966 }
3967 })
3968 .collect();
3969 if follower_ids.is_empty() {
3970 return None;
3971 }
3972 self.client
3973 .send(proto::UpdateFollowers {
3974 room_id,
3975 project_id,
3976 follower_ids,
3977 variant: Some(update),
3978 })
3979 .log_err()
3980 }
3981
3982 async fn handle_follow(
3983 this: ModelHandle<Self>,
3984 envelope: TypedEnvelope<proto::Follow>,
3985 _: Arc<Client>,
3986 mut cx: AsyncAppContext,
3987 ) -> Result<proto::FollowResponse> {
3988 this.update(&mut cx, |this, cx| {
3989 let follower = Follower {
3990 project_id: envelope.payload.project_id,
3991 peer_id: envelope.original_sender_id()?,
3992 };
3993 let active_project = ActiveCall::global(cx)
3994 .read(cx)
3995 .location()
3996 .map(|project| project.id());
3997
3998 let mut response = proto::FollowResponse::default();
3999 for workspace in &this.workspaces {
4000 let Some(workspace) = workspace.upgrade(cx) else {
4001 continue;
4002 };
4003
4004 workspace.update(cx.as_mut(), |workspace, cx| {
4005 let handler_response = workspace.handle_follow(follower.project_id, cx);
4006 if response.views.is_empty() {
4007 response.views = handler_response.views;
4008 } else {
4009 response.views.extend_from_slice(&handler_response.views);
4010 }
4011
4012 if let Some(active_view_id) = handler_response.active_view_id.clone() {
4013 if response.active_view_id.is_none()
4014 || Some(workspace.project.id()) == active_project
4015 {
4016 response.active_view_id = Some(active_view_id);
4017 }
4018 }
4019 });
4020 }
4021
4022 if let Err(ix) = this.followers.binary_search(&follower) {
4023 this.followers.insert(ix, follower);
4024 }
4025
4026 Ok(response)
4027 })
4028 }
4029
4030 async fn handle_unfollow(
4031 this: ModelHandle<Self>,
4032 envelope: TypedEnvelope<proto::Unfollow>,
4033 _: Arc<Client>,
4034 mut cx: AsyncAppContext,
4035 ) -> Result<()> {
4036 this.update(&mut cx, |this, _| {
4037 let follower = Follower {
4038 project_id: envelope.payload.project_id,
4039 peer_id: envelope.original_sender_id()?,
4040 };
4041 if let Ok(ix) = this.followers.binary_search(&follower) {
4042 this.followers.remove(ix);
4043 }
4044 Ok(())
4045 })
4046 }
4047
4048 async fn handle_update_followers(
4049 this: ModelHandle<Self>,
4050 envelope: TypedEnvelope<proto::UpdateFollowers>,
4051 _: Arc<Client>,
4052 mut cx: AsyncAppContext,
4053 ) -> Result<()> {
4054 let leader_id = envelope.original_sender_id()?;
4055 let update = envelope.payload;
4056 this.update(&mut cx, |this, cx| {
4057 for workspace in &this.workspaces {
4058 let Some(workspace) = workspace.upgrade(cx) else {
4059 continue;
4060 };
4061 workspace.update(cx.as_mut(), |workspace, cx| {
4062 let project_id = workspace.project.read(cx).remote_id();
4063 if update.project_id != project_id && update.project_id.is_some() {
4064 return;
4065 }
4066 workspace.handle_update_followers(leader_id, update.clone(), cx);
4067 });
4068 }
4069 Ok(())
4070 })
4071 }
4072}
4073
4074impl Entity for WorkspaceStore {
4075 type Event = ();
4076}
4077
4078impl ViewId {
4079 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4080 Ok(Self {
4081 creator: message
4082 .creator
4083 .ok_or_else(|| anyhow!("creator is missing"))?,
4084 id: message.id,
4085 })
4086 }
4087
4088 pub(crate) fn to_proto(&self) -> proto::ViewId {
4089 proto::ViewId {
4090 creator: Some(self.creator),
4091 id: self.id,
4092 }
4093 }
4094}
4095
4096pub trait WorkspaceHandle {
4097 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4098}
4099
4100impl WorkspaceHandle for ViewHandle<Workspace> {
4101 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4102 self.read(cx)
4103 .worktrees(cx)
4104 .flat_map(|worktree| {
4105 let worktree_id = worktree.read(cx).id();
4106 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4107 worktree_id,
4108 path: f.path.clone(),
4109 })
4110 })
4111 .collect::<Vec<_>>()
4112 }
4113}
4114
4115impl std::fmt::Debug for OpenPaths {
4116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4117 f.debug_struct("OpenPaths")
4118 .field("paths", &self.paths)
4119 .finish()
4120 }
4121}
4122
4123pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
4124
4125pub fn activate_workspace_for_project(
4126 cx: &mut AsyncAppContext,
4127 predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
4128) -> Option<WeakViewHandle<Workspace>> {
4129 for window in cx.windows() {
4130 let handle = window
4131 .update(cx, |cx| {
4132 if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
4133 let project = workspace_handle.read(cx).project.clone();
4134 if project.update(cx, &predicate) {
4135 cx.activate_window();
4136 return Some(workspace_handle.clone());
4137 }
4138 }
4139 None
4140 })
4141 .flatten();
4142
4143 if let Some(handle) = handle {
4144 return Some(handle.downgrade());
4145 }
4146 }
4147 None
4148}
4149
4150pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4151 DB.last_workspace().await.log_err().flatten()
4152}
4153
4154async fn join_channel_internal(
4155 channel_id: u64,
4156 app_state: &Arc<AppState>,
4157 requesting_window: Option<WindowHandle<Workspace>>,
4158 active_call: &ModelHandle<ActiveCall>,
4159 cx: &mut AsyncAppContext,
4160) -> Result<bool> {
4161 let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4162 let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4163 return (false, None);
4164 };
4165
4166 let already_in_channel = room.channel_id() == Some(channel_id);
4167 let should_prompt = room.is_sharing_project()
4168 && room.remote_participants().len() > 0
4169 && !already_in_channel;
4170 let open_room = if already_in_channel {
4171 active_call.room().cloned()
4172 } else {
4173 None
4174 };
4175 (should_prompt, open_room)
4176 });
4177
4178 if let Some(room) = open_room {
4179 let task = room.update(cx, |room, cx| {
4180 if let Some((project, host)) = room.most_active_project(cx) {
4181 return Some(join_remote_project(project, host, app_state.clone(), cx));
4182 }
4183
4184 None
4185 });
4186 if let Some(task) = task {
4187 task.await?;
4188 }
4189 return anyhow::Ok(true);
4190 }
4191
4192 if should_prompt {
4193 if let Some(workspace) = requesting_window {
4194 if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4195 let answer = window.prompt(
4196 PromptLevel::Warning,
4197 "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4198 &["Yes, Join Channel", "Cancel"],
4199 cx,
4200 );
4201
4202 if let Some(mut answer) = answer {
4203 if answer.next().await == Some(1) {
4204 return Ok(false);
4205 }
4206 }
4207 } else {
4208 return Ok(false); // unreachable!() hopefully
4209 }
4210 } else {
4211 return Ok(false); // unreachable!() hopefully
4212 }
4213 }
4214
4215 let client = cx.read(|cx| active_call.read(cx).client());
4216
4217 let mut client_status = client.status();
4218
4219 // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4220 'outer: loop {
4221 let Some(status) = client_status.recv().await else {
4222 return Err(anyhow!("error connecting"));
4223 };
4224
4225 match status {
4226 Status::Connecting
4227 | Status::Authenticating
4228 | Status::Reconnecting
4229 | Status::Reauthenticating => continue,
4230 Status::Connected { .. } => break 'outer,
4231 Status::SignedOut => return Err(anyhow!("not signed in")),
4232 Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4233 Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4234 return Err(anyhow!("zed is offline"))
4235 }
4236 }
4237 }
4238
4239 let room = active_call
4240 .update(cx, |active_call, cx| {
4241 active_call.join_channel(channel_id, cx)
4242 })
4243 .await?;
4244
4245 let Some(room) = room else {
4246 return anyhow::Ok(true);
4247 };
4248
4249 room.update(cx, |room, _| room.room_update_completed())
4250 .await;
4251
4252 let task = room.update(cx, |room, cx| {
4253 if let Some((project, host)) = room.most_active_project(cx) {
4254 return Some(join_remote_project(project, host, app_state.clone(), cx));
4255 }
4256
4257 None
4258 });
4259 if let Some(task) = task {
4260 task.await?;
4261 return anyhow::Ok(true);
4262 }
4263 anyhow::Ok(false)
4264}
4265
4266pub fn join_channel(
4267 channel_id: u64,
4268 app_state: Arc<AppState>,
4269 requesting_window: Option<WindowHandle<Workspace>>,
4270 cx: &mut AppContext,
4271) -> Task<Result<()>> {
4272 let active_call = ActiveCall::global(cx);
4273 cx.spawn(|mut cx| async move {
4274 let result = join_channel_internal(
4275 channel_id,
4276 &app_state,
4277 requesting_window,
4278 &active_call,
4279 &mut cx,
4280 )
4281 .await;
4282
4283 // join channel succeeded, and opened a window
4284 if matches!(result, Ok(true)) {
4285 return anyhow::Ok(());
4286 }
4287
4288 if requesting_window.is_some() {
4289 return anyhow::Ok(());
4290 }
4291
4292 // find an existing workspace to focus and show call controls
4293 let mut active_window = activate_any_workspace_window(&mut cx);
4294 if active_window.is_none() {
4295 // no open workspaces, make one to show the error in (blergh)
4296 cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4297 .await;
4298 }
4299
4300 active_window = activate_any_workspace_window(&mut cx);
4301 if active_window.is_none() {
4302 return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4303 }
4304
4305 if let Err(err) = result {
4306 let prompt = active_window.unwrap().update(&mut cx, |_, cx| {
4307 cx.prompt(
4308 PromptLevel::Critical,
4309 &format!("Failed to join channel: {}", err),
4310 &["Ok"],
4311 )
4312 });
4313
4314 if let Some(mut prompt) = prompt {
4315 prompt.next().await;
4316 } else {
4317 return Err(err);
4318 }
4319 }
4320
4321 // return ok, we showed the error to the user.
4322 return anyhow::Ok(());
4323 })
4324}
4325
4326pub async fn get_any_active_workspace(
4327 app_state: Arc<AppState>,
4328 mut cx: AsyncAppContext,
4329) -> Result<ViewHandle<Workspace>> {
4330 // find an existing workspace to focus and show call controls
4331 let active_window = activate_any_workspace_window(&mut cx);
4332 if active_window.is_none() {
4333 cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))
4334 .await;
4335 }
4336
4337 let Some(active_window) = activate_any_workspace_window(&mut cx) else {
4338 return Err(anyhow!("could not open zed"))?;
4339 };
4340
4341 Ok(active_window)
4342}
4343
4344pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<ViewHandle<Workspace>> {
4345 for window in cx.windows() {
4346 if let Some(workspace) = window
4347 .update(cx, |cx| {
4348 cx.root_view()
4349 .clone()
4350 .downcast::<Workspace>()
4351 .map(|workspace| {
4352 cx.activate_window();
4353 workspace
4354 })
4355 })
4356 .flatten()
4357 {
4358 return Some(workspace);
4359 }
4360 }
4361 None
4362}
4363
4364#[allow(clippy::type_complexity)]
4365pub fn open_paths(
4366 abs_paths: &[PathBuf],
4367 app_state: &Arc<AppState>,
4368 requesting_window: Option<WindowHandle<Workspace>>,
4369 cx: &mut AppContext,
4370) -> Task<
4371 Result<(
4372 WeakViewHandle<Workspace>,
4373 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4374 )>,
4375> {
4376 let app_state = app_state.clone();
4377 let abs_paths = abs_paths.to_vec();
4378 cx.spawn(|mut cx| async move {
4379 // Open paths in existing workspace if possible
4380 let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4381 project.contains_paths(&abs_paths, cx)
4382 });
4383
4384 if let Some(existing) = existing {
4385 Ok((
4386 existing.clone(),
4387 existing
4388 .update(&mut cx, |workspace, cx| {
4389 workspace.open_paths(abs_paths, true, cx)
4390 })?
4391 .await,
4392 ))
4393 } else {
4394 Ok(cx
4395 .update(|cx| {
4396 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4397 })
4398 .await)
4399 }
4400 })
4401}
4402
4403pub fn open_new(
4404 app_state: &Arc<AppState>,
4405 cx: &mut AppContext,
4406 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4407) -> Task<()> {
4408 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4409 cx.spawn(|mut cx| async move {
4410 let (workspace, opened_paths) = task.await;
4411
4412 workspace
4413 .update(&mut cx, |workspace, cx| {
4414 if opened_paths.is_empty() {
4415 init(workspace, cx)
4416 }
4417 })
4418 .log_err();
4419 })
4420}
4421
4422pub fn create_and_open_local_file(
4423 path: &'static Path,
4424 cx: &mut ViewContext<Workspace>,
4425 default_content: impl 'static + Send + FnOnce() -> Rope,
4426) -> Task<Result<Box<dyn ItemHandle>>> {
4427 cx.spawn(|workspace, mut cx| async move {
4428 let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4429 if !fs.is_file(path).await {
4430 fs.create_file(path, Default::default()).await?;
4431 fs.save(path, &default_content(), Default::default())
4432 .await?;
4433 }
4434
4435 let mut items = workspace
4436 .update(&mut cx, |workspace, cx| {
4437 workspace.with_local_workspace(cx, |workspace, cx| {
4438 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4439 })
4440 })?
4441 .await?
4442 .await;
4443
4444 let item = items.pop().flatten();
4445 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4446 })
4447}
4448
4449pub fn join_remote_project(
4450 project_id: u64,
4451 follow_user_id: u64,
4452 app_state: Arc<AppState>,
4453 cx: &mut AppContext,
4454) -> Task<Result<()>> {
4455 cx.spawn(|mut cx| async move {
4456 let windows = cx.windows();
4457 let existing_workspace = windows.into_iter().find_map(|window| {
4458 window.downcast::<Workspace>().and_then(|window| {
4459 window
4460 .read_root_with(&cx, |workspace, cx| {
4461 if workspace.project().read(cx).remote_id() == Some(project_id) {
4462 Some(cx.handle().downgrade())
4463 } else {
4464 None
4465 }
4466 })
4467 .unwrap_or(None)
4468 })
4469 });
4470
4471 let workspace = if let Some(existing_workspace) = existing_workspace {
4472 existing_workspace
4473 } else {
4474 let active_call = cx.read(ActiveCall::global);
4475 let room = active_call
4476 .read_with(&cx, |call, _| call.room().cloned())
4477 .ok_or_else(|| anyhow!("not in a call"))?;
4478 let project = room
4479 .update(&mut cx, |room, cx| {
4480 room.join_project(
4481 project_id,
4482 app_state.languages.clone(),
4483 app_state.fs.clone(),
4484 cx,
4485 )
4486 })
4487 .await?;
4488
4489 let window_bounds_override = window_bounds_env_override(&cx);
4490 let window = cx.add_window(
4491 (app_state.build_window_options)(
4492 window_bounds_override,
4493 None,
4494 cx.platform().as_ref(),
4495 ),
4496 |cx| Workspace::new(0, project, app_state.clone(), cx),
4497 );
4498 let workspace = window.root(&cx).unwrap();
4499 (app_state.initialize_workspace)(
4500 workspace.downgrade(),
4501 false,
4502 app_state.clone(),
4503 cx.clone(),
4504 )
4505 .await
4506 .log_err();
4507
4508 workspace.downgrade()
4509 };
4510
4511 workspace.window().activate(&mut cx);
4512 cx.platform().activate(true);
4513
4514 workspace.update(&mut cx, |workspace, cx| {
4515 if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4516 let follow_peer_id = room
4517 .read(cx)
4518 .remote_participants()
4519 .iter()
4520 .find(|(_, participant)| participant.user.id == follow_user_id)
4521 .map(|(_, p)| p.peer_id)
4522 .or_else(|| {
4523 // If we couldn't follow the given user, follow the host instead.
4524 let collaborator = workspace
4525 .project()
4526 .read(cx)
4527 .collaborators()
4528 .values()
4529 .find(|collaborator| collaborator.replica_id == 0)?;
4530 Some(collaborator.peer_id)
4531 });
4532
4533 if let Some(follow_peer_id) = follow_peer_id {
4534 workspace
4535 .follow(follow_peer_id, cx)
4536 .map(|follow| follow.detach_and_log_err(cx));
4537 }
4538 }
4539 })?;
4540
4541 anyhow::Ok(())
4542 })
4543}
4544
4545pub fn restart(_: &Restart, cx: &mut AppContext) {
4546 let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4547 cx.spawn(|mut cx| async move {
4548 let mut workspace_windows = cx
4549 .windows()
4550 .into_iter()
4551 .filter_map(|window| window.downcast::<Workspace>())
4552 .collect::<Vec<_>>();
4553
4554 // If multiple windows have unsaved changes, and need a save prompt,
4555 // prompt in the active window before switching to a different window.
4556 workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4557
4558 if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4559 let answer = window.prompt(
4560 PromptLevel::Info,
4561 "Are you sure you want to restart?",
4562 &["Restart", "Cancel"],
4563 &mut cx,
4564 );
4565
4566 if let Some(mut answer) = answer {
4567 let answer = answer.next().await;
4568 if answer != Some(0) {
4569 return Ok(());
4570 }
4571 }
4572 }
4573
4574 // If the user cancels any save prompt, then keep the app open.
4575 for window in workspace_windows {
4576 if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4577 workspace.prepare_to_close(true, cx)
4578 }) {
4579 if !should_close.await? {
4580 return Ok(());
4581 }
4582 }
4583 }
4584 cx.platform().restart();
4585 anyhow::Ok(())
4586 })
4587 .detach_and_log_err(cx);
4588}
4589
4590fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4591 let mut parts = value.split(',');
4592 let width: usize = parts.next()?.parse().ok()?;
4593 let height: usize = parts.next()?.parse().ok()?;
4594 Some(vec2f(width as f32, height as f32))
4595}
4596
4597#[cfg(test)]
4598mod tests {
4599 use super::*;
4600 use crate::{
4601 dock::test::{TestPanel, TestPanelEvent},
4602 item::test::{TestItem, TestItemEvent, TestProjectItem},
4603 };
4604 use fs::FakeFs;
4605 use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4606 use project::{Project, ProjectEntryId};
4607 use serde_json::json;
4608 use settings::SettingsStore;
4609 use std::{cell::RefCell, rc::Rc};
4610
4611 #[gpui::test]
4612 async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4613 init_test(cx);
4614
4615 let fs = FakeFs::new(cx.background());
4616 let project = Project::test(fs, [], cx).await;
4617 let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4618 let workspace = window.root(cx);
4619
4620 // Adding an item with no ambiguity renders the tab without detail.
4621 let item1 = window.add_view(cx, |_| {
4622 let mut item = TestItem::new();
4623 item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4624 item
4625 });
4626 workspace.update(cx, |workspace, cx| {
4627 workspace.add_item(Box::new(item1.clone()), cx);
4628 });
4629 item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4630
4631 // Adding an item that creates ambiguity increases the level of detail on
4632 // both tabs.
4633 let item2 = window.add_view(cx, |_| {
4634 let mut item = TestItem::new();
4635 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4636 item
4637 });
4638 workspace.update(cx, |workspace, cx| {
4639 workspace.add_item(Box::new(item2.clone()), cx);
4640 });
4641 item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4642 item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4643
4644 // Adding an item that creates ambiguity increases the level of detail only
4645 // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4646 // we stop at the highest detail available.
4647 let item3 = window.add_view(cx, |_| {
4648 let mut item = TestItem::new();
4649 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4650 item
4651 });
4652 workspace.update(cx, |workspace, cx| {
4653 workspace.add_item(Box::new(item3.clone()), cx);
4654 });
4655 item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4656 item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4657 item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4658 }
4659
4660 #[gpui::test]
4661 async fn test_tracking_active_path(cx: &mut TestAppContext) {
4662 init_test(cx);
4663
4664 let fs = FakeFs::new(cx.background());
4665 fs.insert_tree(
4666 "/root1",
4667 json!({
4668 "one.txt": "",
4669 "two.txt": "",
4670 }),
4671 )
4672 .await;
4673 fs.insert_tree(
4674 "/root2",
4675 json!({
4676 "three.txt": "",
4677 }),
4678 )
4679 .await;
4680
4681 let project = Project::test(fs, ["root1".as_ref()], cx).await;
4682 let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4683 let workspace = window.root(cx);
4684 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4685 let worktree_id = project.read_with(cx, |project, cx| {
4686 project.worktrees(cx).next().unwrap().read(cx).id()
4687 });
4688
4689 let item1 = window.add_view(cx, |cx| {
4690 TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4691 });
4692 let item2 = window.add_view(cx, |cx| {
4693 TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4694 });
4695
4696 // Add an item to an empty pane
4697 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4698 project.read_with(cx, |project, cx| {
4699 assert_eq!(
4700 project.active_entry(),
4701 project
4702 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4703 .map(|e| e.id)
4704 );
4705 });
4706 assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4707
4708 // Add a second item to a non-empty pane
4709 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4710 assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4711 project.read_with(cx, |project, cx| {
4712 assert_eq!(
4713 project.active_entry(),
4714 project
4715 .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4716 .map(|e| e.id)
4717 );
4718 });
4719
4720 // Close the active item
4721 pane.update(cx, |pane, cx| {
4722 pane.close_active_item(&Default::default(), cx).unwrap()
4723 })
4724 .await
4725 .unwrap();
4726 assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4727 project.read_with(cx, |project, cx| {
4728 assert_eq!(
4729 project.active_entry(),
4730 project
4731 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4732 .map(|e| e.id)
4733 );
4734 });
4735
4736 // Add a project folder
4737 project
4738 .update(cx, |project, cx| {
4739 project.find_or_create_local_worktree("/root2", true, cx)
4740 })
4741 .await
4742 .unwrap();
4743 assert_eq!(
4744 window.current_title(cx).as_deref(),
4745 Some("one.txt β root1, root2")
4746 );
4747
4748 // Remove a project folder
4749 project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4750 assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4751 }
4752
4753 #[gpui::test]
4754 async fn test_close_window(cx: &mut TestAppContext) {
4755 init_test(cx);
4756
4757 let fs = FakeFs::new(cx.background());
4758 fs.insert_tree("/root", json!({ "one": "" })).await;
4759
4760 let project = Project::test(fs, ["root".as_ref()], cx).await;
4761 let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4762 let workspace = window.root(cx);
4763
4764 // When there are no dirty items, there's nothing to do.
4765 let item1 = window.add_view(cx, |_| TestItem::new());
4766 workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4767 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4768 assert!(task.await.unwrap());
4769
4770 // When there are dirty untitled items, prompt to save each one. If the user
4771 // cancels any prompt, then abort.
4772 let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4773 let item3 = window.add_view(cx, |cx| {
4774 TestItem::new()
4775 .with_dirty(true)
4776 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4777 });
4778 workspace.update(cx, |w, cx| {
4779 w.add_item(Box::new(item2.clone()), cx);
4780 w.add_item(Box::new(item3.clone()), cx);
4781 });
4782 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4783 cx.foreground().run_until_parked();
4784 window.simulate_prompt_answer(2, cx); // cancel save all
4785 cx.foreground().run_until_parked();
4786 window.simulate_prompt_answer(2, cx); // cancel save all
4787 cx.foreground().run_until_parked();
4788 assert!(!window.has_pending_prompt(cx));
4789 assert!(!task.await.unwrap());
4790 }
4791
4792 #[gpui::test]
4793 async fn test_close_pane_items(cx: &mut TestAppContext) {
4794 init_test(cx);
4795
4796 let fs = FakeFs::new(cx.background());
4797
4798 let project = Project::test(fs, None, cx).await;
4799 let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4800 let workspace = window.root(cx);
4801
4802 let item1 = window.add_view(cx, |cx| {
4803 TestItem::new()
4804 .with_dirty(true)
4805 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4806 });
4807 let item2 = window.add_view(cx, |cx| {
4808 TestItem::new()
4809 .with_dirty(true)
4810 .with_conflict(true)
4811 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4812 });
4813 let item3 = window.add_view(cx, |cx| {
4814 TestItem::new()
4815 .with_dirty(true)
4816 .with_conflict(true)
4817 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4818 });
4819 let item4 = window.add_view(cx, |cx| {
4820 TestItem::new()
4821 .with_dirty(true)
4822 .with_project_items(&[TestProjectItem::new_untitled(cx)])
4823 });
4824 let pane = workspace.update(cx, |workspace, cx| {
4825 workspace.add_item(Box::new(item1.clone()), cx);
4826 workspace.add_item(Box::new(item2.clone()), cx);
4827 workspace.add_item(Box::new(item3.clone()), cx);
4828 workspace.add_item(Box::new(item4.clone()), cx);
4829 workspace.active_pane().clone()
4830 });
4831
4832 let close_items = pane.update(cx, |pane, cx| {
4833 pane.activate_item(1, true, true, cx);
4834 assert_eq!(pane.active_item().unwrap().id(), item2.id());
4835 let item1_id = item1.id();
4836 let item3_id = item3.id();
4837 let item4_id = item4.id();
4838 pane.close_items(cx, SaveIntent::Close, move |id| {
4839 [item1_id, item3_id, item4_id].contains(&id)
4840 })
4841 });
4842 cx.foreground().run_until_parked();
4843
4844 assert!(window.has_pending_prompt(cx));
4845 // Ignore "Save all" prompt
4846 window.simulate_prompt_answer(2, cx);
4847 cx.foreground().run_until_parked();
4848 // There's a prompt to save item 1.
4849 pane.read_with(cx, |pane, _| {
4850 assert_eq!(pane.items_len(), 4);
4851 assert_eq!(pane.active_item().unwrap().id(), item1.id());
4852 });
4853 // Confirm saving item 1.
4854 window.simulate_prompt_answer(0, cx);
4855 cx.foreground().run_until_parked();
4856
4857 // Item 1 is saved. There's a prompt to save item 3.
4858 pane.read_with(cx, |pane, cx| {
4859 assert_eq!(item1.read(cx).save_count, 1);
4860 assert_eq!(item1.read(cx).save_as_count, 0);
4861 assert_eq!(item1.read(cx).reload_count, 0);
4862 assert_eq!(pane.items_len(), 3);
4863 assert_eq!(pane.active_item().unwrap().id(), item3.id());
4864 });
4865 assert!(window.has_pending_prompt(cx));
4866
4867 // Cancel saving item 3.
4868 window.simulate_prompt_answer(1, cx);
4869 cx.foreground().run_until_parked();
4870
4871 // Item 3 is reloaded. There's a prompt to save item 4.
4872 pane.read_with(cx, |pane, cx| {
4873 assert_eq!(item3.read(cx).save_count, 0);
4874 assert_eq!(item3.read(cx).save_as_count, 0);
4875 assert_eq!(item3.read(cx).reload_count, 1);
4876 assert_eq!(pane.items_len(), 2);
4877 assert_eq!(pane.active_item().unwrap().id(), item4.id());
4878 });
4879 assert!(window.has_pending_prompt(cx));
4880
4881 // Confirm saving item 4.
4882 window.simulate_prompt_answer(0, cx);
4883 cx.foreground().run_until_parked();
4884
4885 // There's a prompt for a path for item 4.
4886 cx.simulate_new_path_selection(|_| Some(Default::default()));
4887 close_items.await.unwrap();
4888
4889 // The requested items are closed.
4890 pane.read_with(cx, |pane, cx| {
4891 assert_eq!(item4.read(cx).save_count, 0);
4892 assert_eq!(item4.read(cx).save_as_count, 1);
4893 assert_eq!(item4.read(cx).reload_count, 0);
4894 assert_eq!(pane.items_len(), 1);
4895 assert_eq!(pane.active_item().unwrap().id(), item2.id());
4896 });
4897 }
4898
4899 #[gpui::test]
4900 async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4901 init_test(cx);
4902
4903 let fs = FakeFs::new(cx.background());
4904
4905 let project = Project::test(fs, [], cx).await;
4906 let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4907 let workspace = window.root(cx);
4908
4909 // Create several workspace items with single project entries, and two
4910 // workspace items with multiple project entries.
4911 let single_entry_items = (0..=4)
4912 .map(|project_entry_id| {
4913 window.add_view(cx, |cx| {
4914 TestItem::new()
4915 .with_dirty(true)
4916 .with_project_items(&[TestProjectItem::new(
4917 project_entry_id,
4918 &format!("{project_entry_id}.txt"),
4919 cx,
4920 )])
4921 })
4922 })
4923 .collect::<Vec<_>>();
4924 let item_2_3 = window.add_view(cx, |cx| {
4925 TestItem::new()
4926 .with_dirty(true)
4927 .with_singleton(false)
4928 .with_project_items(&[
4929 single_entry_items[2].read(cx).project_items[0].clone(),
4930 single_entry_items[3].read(cx).project_items[0].clone(),
4931 ])
4932 });
4933 let item_3_4 = window.add_view(cx, |cx| {
4934 TestItem::new()
4935 .with_dirty(true)
4936 .with_singleton(false)
4937 .with_project_items(&[
4938 single_entry_items[3].read(cx).project_items[0].clone(),
4939 single_entry_items[4].read(cx).project_items[0].clone(),
4940 ])
4941 });
4942
4943 // Create two panes that contain the following project entries:
4944 // left pane:
4945 // multi-entry items: (2, 3)
4946 // single-entry items: 0, 1, 2, 3, 4
4947 // right pane:
4948 // single-entry items: 1
4949 // multi-entry items: (3, 4)
4950 let left_pane = workspace.update(cx, |workspace, cx| {
4951 let left_pane = workspace.active_pane().clone();
4952 workspace.add_item(Box::new(item_2_3.clone()), cx);
4953 for item in single_entry_items {
4954 workspace.add_item(Box::new(item), cx);
4955 }
4956 left_pane.update(cx, |pane, cx| {
4957 pane.activate_item(2, true, true, cx);
4958 });
4959
4960 workspace
4961 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4962 .unwrap();
4963
4964 left_pane
4965 });
4966
4967 //Need to cause an effect flush in order to respect new focus
4968 workspace.update(cx, |workspace, cx| {
4969 workspace.add_item(Box::new(item_3_4.clone()), cx);
4970 cx.focus(&left_pane);
4971 });
4972
4973 // When closing all of the items in the left pane, we should be prompted twice:
4974 // once for project entry 0, and once for project entry 2. After those two
4975 // prompts, the task should complete.
4976
4977 let close = left_pane.update(cx, |pane, cx| {
4978 pane.close_items(cx, SaveIntent::Close, move |_| true)
4979 });
4980 cx.foreground().run_until_parked();
4981 // Discard "Save all" prompt
4982 window.simulate_prompt_answer(2, cx);
4983
4984 cx.foreground().run_until_parked();
4985 left_pane.read_with(cx, |pane, cx| {
4986 assert_eq!(
4987 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4988 &[ProjectEntryId::from_proto(0)]
4989 );
4990 });
4991 window.simulate_prompt_answer(0, cx);
4992
4993 cx.foreground().run_until_parked();
4994 left_pane.read_with(cx, |pane, cx| {
4995 assert_eq!(
4996 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4997 &[ProjectEntryId::from_proto(2)]
4998 );
4999 });
5000 window.simulate_prompt_answer(0, cx);
5001
5002 cx.foreground().run_until_parked();
5003 close.await.unwrap();
5004 left_pane.read_with(cx, |pane, _| {
5005 assert_eq!(pane.items_len(), 0);
5006 });
5007 }
5008
5009 #[gpui::test]
5010 async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5011 init_test(cx);
5012
5013 let fs = FakeFs::new(cx.background());
5014
5015 let project = Project::test(fs, [], cx).await;
5016 let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5017 let workspace = window.root(cx);
5018 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5019
5020 let item = window.add_view(cx, |cx| {
5021 TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5022 });
5023 let item_id = item.id();
5024 workspace.update(cx, |workspace, cx| {
5025 workspace.add_item(Box::new(item.clone()), cx);
5026 });
5027
5028 // Autosave on window change.
5029 item.update(cx, |item, cx| {
5030 cx.update_global(|settings: &mut SettingsStore, cx| {
5031 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5032 settings.autosave = Some(AutosaveSetting::OnWindowChange);
5033 })
5034 });
5035 item.is_dirty = true;
5036 });
5037
5038 // Deactivating the window saves the file.
5039 window.simulate_deactivation(cx);
5040 deterministic.run_until_parked();
5041 item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
5042
5043 // Autosave on focus change.
5044 item.update(cx, |item, cx| {
5045 cx.focus_self();
5046 cx.update_global(|settings: &mut SettingsStore, cx| {
5047 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5048 settings.autosave = Some(AutosaveSetting::OnFocusChange);
5049 })
5050 });
5051 item.is_dirty = true;
5052 });
5053
5054 // Blurring the item saves the file.
5055 item.update(cx, |_, cx| cx.blur());
5056 deterministic.run_until_parked();
5057 item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5058
5059 // Deactivating the window still saves the file.
5060 window.simulate_activation(cx);
5061 item.update(cx, |item, cx| {
5062 cx.focus_self();
5063 item.is_dirty = true;
5064 });
5065 window.simulate_deactivation(cx);
5066
5067 deterministic.run_until_parked();
5068 item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5069
5070 // Autosave after delay.
5071 item.update(cx, |item, cx| {
5072 cx.update_global(|settings: &mut SettingsStore, cx| {
5073 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5074 settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5075 })
5076 });
5077 item.is_dirty = true;
5078 cx.emit(TestItemEvent::Edit);
5079 });
5080
5081 // Delay hasn't fully expired, so the file is still dirty and unsaved.
5082 deterministic.advance_clock(Duration::from_millis(250));
5083 item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5084
5085 // After delay expires, the file is saved.
5086 deterministic.advance_clock(Duration::from_millis(250));
5087 item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5088
5089 // Autosave on focus change, ensuring closing the tab counts as such.
5090 item.update(cx, |item, cx| {
5091 cx.update_global(|settings: &mut SettingsStore, cx| {
5092 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5093 settings.autosave = Some(AutosaveSetting::OnFocusChange);
5094 })
5095 });
5096 item.is_dirty = true;
5097 });
5098
5099 pane.update(cx, |pane, cx| {
5100 pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5101 })
5102 .await
5103 .unwrap();
5104 assert!(!window.has_pending_prompt(cx));
5105 item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5106
5107 // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5108 workspace.update(cx, |workspace, cx| {
5109 workspace.add_item(Box::new(item.clone()), cx);
5110 });
5111 item.update(cx, |item, cx| {
5112 item.project_items[0].update(cx, |item, _| {
5113 item.entry_id = None;
5114 });
5115 item.is_dirty = true;
5116 cx.blur();
5117 });
5118 deterministic.run_until_parked();
5119 item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5120
5121 // Ensure autosave is prevented for deleted files also when closing the buffer.
5122 let _close_items = pane.update(cx, |pane, cx| {
5123 pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5124 });
5125 deterministic.run_until_parked();
5126 assert!(window.has_pending_prompt(cx));
5127 item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5128 }
5129
5130 #[gpui::test]
5131 async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5132 init_test(cx);
5133
5134 let fs = FakeFs::new(cx.background());
5135
5136 let project = Project::test(fs, [], cx).await;
5137 let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5138 let workspace = window.root(cx);
5139
5140 let item = window.add_view(cx, |cx| {
5141 TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5142 });
5143 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5144 let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5145 let toolbar_notify_count = Rc::new(RefCell::new(0));
5146
5147 workspace.update(cx, |workspace, cx| {
5148 workspace.add_item(Box::new(item.clone()), cx);
5149 let toolbar_notification_count = toolbar_notify_count.clone();
5150 cx.observe(&toolbar, move |_, _, _| {
5151 *toolbar_notification_count.borrow_mut() += 1
5152 })
5153 .detach();
5154 });
5155
5156 pane.read_with(cx, |pane, _| {
5157 assert!(!pane.can_navigate_backward());
5158 assert!(!pane.can_navigate_forward());
5159 });
5160
5161 item.update(cx, |item, cx| {
5162 item.set_state("one".to_string(), cx);
5163 });
5164
5165 // Toolbar must be notified to re-render the navigation buttons
5166 assert_eq!(*toolbar_notify_count.borrow(), 1);
5167
5168 pane.read_with(cx, |pane, _| {
5169 assert!(pane.can_navigate_backward());
5170 assert!(!pane.can_navigate_forward());
5171 });
5172
5173 workspace
5174 .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5175 .await
5176 .unwrap();
5177
5178 assert_eq!(*toolbar_notify_count.borrow(), 3);
5179 pane.read_with(cx, |pane, _| {
5180 assert!(!pane.can_navigate_backward());
5181 assert!(pane.can_navigate_forward());
5182 });
5183 }
5184
5185 #[gpui::test]
5186 async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5187 init_test(cx);
5188 let fs = FakeFs::new(cx.background());
5189
5190 let project = Project::test(fs, [], cx).await;
5191 let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5192 let workspace = window.root(cx);
5193
5194 let panel = workspace.update(cx, |workspace, cx| {
5195 let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5196 workspace.add_panel(panel.clone(), cx);
5197
5198 workspace
5199 .right_dock()
5200 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5201
5202 panel
5203 });
5204
5205 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5206 pane.update(cx, |pane, cx| {
5207 let item = cx.add_view(|_| TestItem::new());
5208 pane.add_item(Box::new(item), true, true, None, cx);
5209 });
5210
5211 // Transfer focus from center to panel
5212 workspace.update(cx, |workspace, cx| {
5213 workspace.toggle_panel_focus::<TestPanel>(cx);
5214 });
5215
5216 workspace.read_with(cx, |workspace, cx| {
5217 assert!(workspace.right_dock().read(cx).is_open());
5218 assert!(!panel.is_zoomed(cx));
5219 assert!(panel.has_focus(cx));
5220 });
5221
5222 // Transfer focus from panel to center
5223 workspace.update(cx, |workspace, cx| {
5224 workspace.toggle_panel_focus::<TestPanel>(cx);
5225 });
5226
5227 workspace.read_with(cx, |workspace, cx| {
5228 assert!(workspace.right_dock().read(cx).is_open());
5229 assert!(!panel.is_zoomed(cx));
5230 assert!(!panel.has_focus(cx));
5231 });
5232
5233 // Close the dock
5234 workspace.update(cx, |workspace, cx| {
5235 workspace.toggle_dock(DockPosition::Right, cx);
5236 });
5237
5238 workspace.read_with(cx, |workspace, cx| {
5239 assert!(!workspace.right_dock().read(cx).is_open());
5240 assert!(!panel.is_zoomed(cx));
5241 assert!(!panel.has_focus(cx));
5242 });
5243
5244 // Open the dock
5245 workspace.update(cx, |workspace, cx| {
5246 workspace.toggle_dock(DockPosition::Right, cx);
5247 });
5248
5249 workspace.read_with(cx, |workspace, cx| {
5250 assert!(workspace.right_dock().read(cx).is_open());
5251 assert!(!panel.is_zoomed(cx));
5252 assert!(panel.has_focus(cx));
5253 });
5254
5255 // Focus and zoom panel
5256 panel.update(cx, |panel, cx| {
5257 cx.focus_self();
5258 panel.set_zoomed(true, cx)
5259 });
5260
5261 workspace.read_with(cx, |workspace, cx| {
5262 assert!(workspace.right_dock().read(cx).is_open());
5263 assert!(panel.is_zoomed(cx));
5264 assert!(panel.has_focus(cx));
5265 });
5266
5267 // Transfer focus to the center closes the dock
5268 workspace.update(cx, |workspace, cx| {
5269 workspace.toggle_panel_focus::<TestPanel>(cx);
5270 });
5271
5272 workspace.read_with(cx, |workspace, cx| {
5273 assert!(!workspace.right_dock().read(cx).is_open());
5274 assert!(panel.is_zoomed(cx));
5275 assert!(!panel.has_focus(cx));
5276 });
5277
5278 // Transferring focus back to the panel keeps it zoomed
5279 workspace.update(cx, |workspace, cx| {
5280 workspace.toggle_panel_focus::<TestPanel>(cx);
5281 });
5282
5283 workspace.read_with(cx, |workspace, cx| {
5284 assert!(workspace.right_dock().read(cx).is_open());
5285 assert!(panel.is_zoomed(cx));
5286 assert!(panel.has_focus(cx));
5287 });
5288
5289 // Close the dock while it is zoomed
5290 workspace.update(cx, |workspace, cx| {
5291 workspace.toggle_dock(DockPosition::Right, cx)
5292 });
5293
5294 workspace.read_with(cx, |workspace, cx| {
5295 assert!(!workspace.right_dock().read(cx).is_open());
5296 assert!(panel.is_zoomed(cx));
5297 assert!(workspace.zoomed.is_none());
5298 assert!(!panel.has_focus(cx));
5299 });
5300
5301 // Opening the dock, when it's zoomed, retains focus
5302 workspace.update(cx, |workspace, cx| {
5303 workspace.toggle_dock(DockPosition::Right, cx)
5304 });
5305
5306 workspace.read_with(cx, |workspace, cx| {
5307 assert!(workspace.right_dock().read(cx).is_open());
5308 assert!(panel.is_zoomed(cx));
5309 assert!(workspace.zoomed.is_some());
5310 assert!(panel.has_focus(cx));
5311 });
5312
5313 // Unzoom and close the panel, zoom the active pane.
5314 panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5315 workspace.update(cx, |workspace, cx| {
5316 workspace.toggle_dock(DockPosition::Right, cx)
5317 });
5318 pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5319
5320 // Opening a dock unzooms the pane.
5321 workspace.update(cx, |workspace, cx| {
5322 workspace.toggle_dock(DockPosition::Right, cx)
5323 });
5324 workspace.read_with(cx, |workspace, cx| {
5325 let pane = pane.read(cx);
5326 assert!(!pane.is_zoomed());
5327 assert!(!pane.has_focus());
5328 assert!(workspace.right_dock().read(cx).is_open());
5329 assert!(workspace.zoomed.is_none());
5330 });
5331 }
5332
5333 #[gpui::test]
5334 async fn test_panels(cx: &mut gpui::TestAppContext) {
5335 init_test(cx);
5336 let fs = FakeFs::new(cx.background());
5337
5338 let project = Project::test(fs, [], cx).await;
5339 let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5340 let workspace = window.root(cx);
5341
5342 let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5343 // Add panel_1 on the left, panel_2 on the right.
5344 let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5345 workspace.add_panel(panel_1.clone(), cx);
5346 workspace
5347 .left_dock()
5348 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5349 let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5350 workspace.add_panel(panel_2.clone(), cx);
5351 workspace
5352 .right_dock()
5353 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5354
5355 let left_dock = workspace.left_dock();
5356 assert_eq!(
5357 left_dock.read(cx).visible_panel().unwrap().id(),
5358 panel_1.id()
5359 );
5360 assert_eq!(
5361 left_dock.read(cx).active_panel_size(cx).unwrap(),
5362 panel_1.size(cx)
5363 );
5364
5365 left_dock.update(cx, |left_dock, cx| {
5366 left_dock.resize_active_panel(Some(1337.), cx)
5367 });
5368 assert_eq!(
5369 workspace
5370 .right_dock()
5371 .read(cx)
5372 .visible_panel()
5373 .unwrap()
5374 .id(),
5375 panel_2.id()
5376 );
5377
5378 (panel_1, panel_2)
5379 });
5380
5381 // Move panel_1 to the right
5382 panel_1.update(cx, |panel_1, cx| {
5383 panel_1.set_position(DockPosition::Right, cx)
5384 });
5385
5386 workspace.update(cx, |workspace, cx| {
5387 // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5388 // Since it was the only panel on the left, the left dock should now be closed.
5389 assert!(!workspace.left_dock().read(cx).is_open());
5390 assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5391 let right_dock = workspace.right_dock();
5392 assert_eq!(
5393 right_dock.read(cx).visible_panel().unwrap().id(),
5394 panel_1.id()
5395 );
5396 assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5397
5398 // Now we move panel_2Β to the left
5399 panel_2.set_position(DockPosition::Left, cx);
5400 });
5401
5402 workspace.update(cx, |workspace, cx| {
5403 // Since panel_2 was not visible on the right, we don't open the left dock.
5404 assert!(!workspace.left_dock().read(cx).is_open());
5405 // And the right dock is unaffected in it's displaying of panel_1
5406 assert!(workspace.right_dock().read(cx).is_open());
5407 assert_eq!(
5408 workspace
5409 .right_dock()
5410 .read(cx)
5411 .visible_panel()
5412 .unwrap()
5413 .id(),
5414 panel_1.id()
5415 );
5416 });
5417
5418 // Move panel_1 back to the left
5419 panel_1.update(cx, |panel_1, cx| {
5420 panel_1.set_position(DockPosition::Left, cx)
5421 });
5422
5423 workspace.update(cx, |workspace, cx| {
5424 // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5425 let left_dock = workspace.left_dock();
5426 assert!(left_dock.read(cx).is_open());
5427 assert_eq!(
5428 left_dock.read(cx).visible_panel().unwrap().id(),
5429 panel_1.id()
5430 );
5431 assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5432 // And right the dock should be closed as it no longer has any panels.
5433 assert!(!workspace.right_dock().read(cx).is_open());
5434
5435 // Now we move panel_1 to the bottom
5436 panel_1.set_position(DockPosition::Bottom, cx);
5437 });
5438
5439 workspace.update(cx, |workspace, cx| {
5440 // Since panel_1 was visible on the left, we close the left dock.
5441 assert!(!workspace.left_dock().read(cx).is_open());
5442 // The bottom dock is sized based on the panel's default size,
5443 // since the panel orientation changed from vertical to horizontal.
5444 let bottom_dock = workspace.bottom_dock();
5445 assert_eq!(
5446 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5447 panel_1.size(cx),
5448 );
5449 // Close bottom dock and move panel_1 back to the left.
5450 bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5451 panel_1.set_position(DockPosition::Left, cx);
5452 });
5453
5454 // Emit activated event on panel 1
5455 panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5456
5457 // Now the left dock is open and panel_1 is active and focused.
5458 workspace.read_with(cx, |workspace, cx| {
5459 let left_dock = workspace.left_dock();
5460 assert!(left_dock.read(cx).is_open());
5461 assert_eq!(
5462 left_dock.read(cx).visible_panel().unwrap().id(),
5463 panel_1.id()
5464 );
5465 assert!(panel_1.is_focused(cx));
5466 });
5467
5468 // Emit closed event on panel 2, which is not active
5469 panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5470
5471 // Wo don't close the left dock, because panel_2 wasn't the active panel
5472 workspace.read_with(cx, |workspace, cx| {
5473 let left_dock = workspace.left_dock();
5474 assert!(left_dock.read(cx).is_open());
5475 assert_eq!(
5476 left_dock.read(cx).visible_panel().unwrap().id(),
5477 panel_1.id()
5478 );
5479 });
5480
5481 // Emitting a ZoomIn event shows the panel as zoomed.
5482 panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5483 workspace.read_with(cx, |workspace, _| {
5484 assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5485 assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5486 });
5487
5488 // Move panel to another dock while it is zoomed
5489 panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5490 workspace.read_with(cx, |workspace, _| {
5491 assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5492 assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5493 });
5494
5495 // If focus is transferred to another view that's not a panel or another pane, we still show
5496 // the panel as zoomed.
5497 let focus_receiver = window.add_view(cx, |_| EmptyView);
5498 focus_receiver.update(cx, |_, cx| cx.focus_self());
5499 workspace.read_with(cx, |workspace, _| {
5500 assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5501 assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5502 });
5503
5504 // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5505 workspace.update(cx, |_, cx| cx.focus_self());
5506 workspace.read_with(cx, |workspace, _| {
5507 assert_eq!(workspace.zoomed, None);
5508 assert_eq!(workspace.zoomed_position, None);
5509 });
5510
5511 // If focus is transferred again to another view that's not a panel or a pane, we won't
5512 // show the panel as zoomed because it wasn't zoomed before.
5513 focus_receiver.update(cx, |_, cx| cx.focus_self());
5514 workspace.read_with(cx, |workspace, _| {
5515 assert_eq!(workspace.zoomed, None);
5516 assert_eq!(workspace.zoomed_position, None);
5517 });
5518
5519 // When focus is transferred back to the panel, it is zoomed again.
5520 panel_1.update(cx, |_, cx| cx.focus_self());
5521 workspace.read_with(cx, |workspace, _| {
5522 assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5523 assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5524 });
5525
5526 // Emitting a ZoomOut event unzooms the panel.
5527 panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5528 workspace.read_with(cx, |workspace, _| {
5529 assert_eq!(workspace.zoomed, None);
5530 assert_eq!(workspace.zoomed_position, None);
5531 });
5532
5533 // Emit closed event on panel 1, which is active
5534 panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5535
5536 // Now the left dock is closed, because panel_1 was the active panel
5537 workspace.read_with(cx, |workspace, cx| {
5538 let right_dock = workspace.right_dock();
5539 assert!(!right_dock.read(cx).is_open());
5540 });
5541 }
5542
5543 pub fn init_test(cx: &mut TestAppContext) {
5544 cx.foreground().forbid_parking();
5545 cx.update(|cx| {
5546 cx.set_global(SettingsStore::test(cx));
5547 theme::init((), cx);
5548 language::init(cx);
5549 crate::init_settings(cx);
5550 Project::init_settings(cx);
5551 });
5552 }
5553}