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