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