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