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