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