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