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