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