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