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<WeakHandle<Workspace>> {
4107 cx.run_on_main(move |cx| {
4108 for window in cx.windows() {
4109 let handle = cx
4110 .update_window(window, |cx| {
4111 if let Some(workspace_handle) = cx.root_view()?.downcast::<Workspace>() {
4112 let project = workspace_handle.read(cx).project.clone();
4113 if project.update(cx, |project, cx| predicate(project, cx)) {
4114 cx.activate_window();
4115 return Some(workspace_handle.clone());
4116 }
4117 }
4118 None
4119 })
4120 .log_err()
4121 .flatten();
4122
4123 if let Some(handle) = handle {
4124 return Some(handle.downgrade());
4125 }
4126 }
4127
4128 None
4129 })
4130 .ok()?
4131 .await
4132}
4133
4134// pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4135// DB.last_workspace().await.log_err().flatten()
4136// }
4137
4138// async fn join_channel_internal(
4139// channel_id: u64,
4140// app_state: &Arc<AppState>,
4141// requesting_window: Option<WindowHandle<Workspace>>,
4142// active_call: &ModelHandle<ActiveCall>,
4143// cx: &mut AsyncAppContext,
4144// ) -> Result<bool> {
4145// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4146// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4147// return (false, None);
4148// };
4149
4150// let already_in_channel = room.channel_id() == Some(channel_id);
4151// let should_prompt = room.is_sharing_project()
4152// && room.remote_participants().len() > 0
4153// && !already_in_channel;
4154// let open_room = if already_in_channel {
4155// active_call.room().cloned()
4156// } else {
4157// None
4158// };
4159// (should_prompt, open_room)
4160// });
4161
4162// if let Some(room) = open_room {
4163// let task = room.update(cx, |room, cx| {
4164// if let Some((project, host)) = room.most_active_project(cx) {
4165// return Some(join_remote_project(project, host, app_state.clone(), cx));
4166// }
4167
4168// None
4169// });
4170// if let Some(task) = task {
4171// task.await?;
4172// }
4173// return anyhow::Ok(true);
4174// }
4175
4176// if should_prompt {
4177// if let Some(workspace) = requesting_window {
4178// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4179// let answer = window.prompt(
4180// PromptLevel::Warning,
4181// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4182// &["Yes, Join Channel", "Cancel"],
4183// cx,
4184// );
4185
4186// if let Some(mut answer) = answer {
4187// if answer.next().await == Some(1) {
4188// return Ok(false);
4189// }
4190// }
4191// } else {
4192// return Ok(false); // unreachable!() hopefully
4193// }
4194// } else {
4195// return Ok(false); // unreachable!() hopefully
4196// }
4197// }
4198
4199// let client = cx.read(|cx| active_call.read(cx).client());
4200
4201// let mut client_status = client.status();
4202
4203// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4204// 'outer: loop {
4205// let Some(status) = client_status.recv().await else {
4206// return Err(anyhow!("error connecting"));
4207// };
4208
4209// match status {
4210// Status::Connecting
4211// | Status::Authenticating
4212// | Status::Reconnecting
4213// | Status::Reauthenticating => continue,
4214// Status::Connected { .. } => break 'outer,
4215// Status::SignedOut => return Err(anyhow!("not signed in")),
4216// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4217// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4218// return Err(anyhow!("zed is offline"))
4219// }
4220// }
4221// }
4222
4223// let room = active_call
4224// .update(cx, |active_call, cx| {
4225// active_call.join_channel(channel_id, cx)
4226// })
4227// .await?;
4228
4229// room.update(cx, |room, _| room.room_update_completed())
4230// .await;
4231
4232// let task = room.update(cx, |room, cx| {
4233// if let Some((project, host)) = room.most_active_project(cx) {
4234// return Some(join_remote_project(project, host, app_state.clone(), cx));
4235// }
4236
4237// None
4238// });
4239// if let Some(task) = task {
4240// task.await?;
4241// return anyhow::Ok(true);
4242// }
4243// anyhow::Ok(false)
4244// }
4245
4246// pub fn join_channel(
4247// channel_id: u64,
4248// app_state: Arc<AppState>,
4249// requesting_window: Option<WindowHandle<Workspace>>,
4250// cx: &mut AppContext,
4251// ) -> Task<Result<()>> {
4252// let active_call = ActiveCall::global(cx);
4253// cx.spawn(|mut cx| async move {
4254// let result = join_channel_internal(
4255// channel_id,
4256// &app_state,
4257// requesting_window,
4258// &active_call,
4259// &mut cx,
4260// )
4261// .await;
4262
4263// // join channel succeeded, and opened a window
4264// if matches!(result, Ok(true)) {
4265// return anyhow::Ok(());
4266// }
4267
4268// if requesting_window.is_some() {
4269// return anyhow::Ok(());
4270// }
4271
4272// // find an existing workspace to focus and show call controls
4273// let mut active_window = activate_any_workspace_window(&mut cx);
4274// if active_window.is_none() {
4275// // no open workspaces, make one to show the error in (blergh)
4276// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4277// .await;
4278// }
4279
4280// active_window = activate_any_workspace_window(&mut cx);
4281// if active_window.is_none() {
4282// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4283// }
4284
4285// if let Err(err) = result {
4286// let prompt = active_window.unwrap().prompt(
4287// PromptLevel::Critical,
4288// &format!("Failed to join channel: {}", err),
4289// &["Ok"],
4290// &mut cx,
4291// );
4292// if let Some(mut prompt) = prompt {
4293// prompt.next().await;
4294// } else {
4295// return Err(err);
4296// }
4297// }
4298
4299// // return ok, we showed the error to the user.
4300// return anyhow::Ok(());
4301// })
4302// }
4303
4304// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4305// for window in cx.windows() {
4306// let found = window.update(cx, |cx| {
4307// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4308// if is_workspace {
4309// cx.activate_window();
4310// }
4311// is_workspace
4312// });
4313// if found == Some(true) {
4314// return Some(window);
4315// }
4316// }
4317// None
4318// }
4319
4320use client2::{proto::PeerId, Client, UserStore};
4321use collections::HashSet;
4322use gpui2::{
4323 AppContext, AsyncAppContext, DisplayId, Handle, MainThread, Task, WeakHandle, WindowBounds,
4324 WindowHandle, WindowOptions,
4325};
4326use item::ItemHandle;
4327use language2::LanguageRegistry;
4328use node_runtime::NodeRuntime;
4329use project2::Project;
4330use std::{path::PathBuf, sync::Arc};
4331use util::ResultExt;
4332
4333#[allow(clippy::type_complexity)]
4334pub fn open_paths(
4335 abs_paths: &[PathBuf],
4336 app_state: &Arc<AppState>,
4337 requesting_window: Option<WindowHandle<Workspace>>,
4338 cx: &mut AppContext,
4339) -> Task<
4340 anyhow::Result<(
4341 WeakHandle<Workspace>,
4342 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4343 )>,
4344> {
4345 let app_state = app_state.clone();
4346 let abs_paths = abs_paths.to_vec();
4347 cx.spawn(|mut cx| async move {
4348 // Open paths in existing workspace if possible
4349 let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4350 project.contains_paths(&abs_paths, cx)
4351 })
4352 .await;
4353
4354 if let Some(existing) = existing {
4355 Ok((
4356 existing.clone(),
4357 existing
4358 .update(&mut cx, |workspace, cx| {
4359 workspace.open_paths(abs_paths, true, cx)
4360 })?
4361 .await,
4362 ))
4363 } else {
4364 Ok(cx
4365 .update(|cx| {
4366 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4367 })
4368 .await)
4369 }
4370 })
4371}
4372
4373// pub fn open_new(
4374// app_state: &Arc<AppState>,
4375// cx: &mut AppContext,
4376// init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4377// ) -> Task<()> {
4378// let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4379// cx.spawn(|mut cx| async move {
4380// let (workspace, opened_paths) = task.await;
4381
4382// workspace
4383// .update(&mut cx, |workspace, cx| {
4384// if opened_paths.is_empty() {
4385// init(workspace, cx)
4386// }
4387// })
4388// .log_err();
4389// })
4390// }
4391
4392// pub fn create_and_open_local_file(
4393// path: &'static Path,
4394// cx: &mut ViewContext<Workspace>,
4395// default_content: impl 'static + Send + FnOnce() -> Rope,
4396// ) -> Task<Result<Box<dyn ItemHandle>>> {
4397// cx.spawn(|workspace, mut cx| async move {
4398// let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4399// if !fs.is_file(path).await {
4400// fs.create_file(path, Default::default()).await?;
4401// fs.save(path, &default_content(), Default::default())
4402// .await?;
4403// }
4404
4405// let mut items = workspace
4406// .update(&mut cx, |workspace, cx| {
4407// workspace.with_local_workspace(cx, |workspace, cx| {
4408// workspace.open_paths(vec![path.to_path_buf()], false, cx)
4409// })
4410// })?
4411// .await?
4412// .await;
4413
4414// let item = items.pop().flatten();
4415// item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4416// })
4417// }
4418
4419// pub fn join_remote_project(
4420// project_id: u64,
4421// follow_user_id: u64,
4422// app_state: Arc<AppState>,
4423// cx: &mut AppContext,
4424// ) -> Task<Result<()>> {
4425// cx.spawn(|mut cx| async move {
4426// let windows = cx.windows();
4427// let existing_workspace = windows.into_iter().find_map(|window| {
4428// window.downcast::<Workspace>().and_then(|window| {
4429// window
4430// .read_root_with(&cx, |workspace, cx| {
4431// if workspace.project().read(cx).remote_id() == Some(project_id) {
4432// Some(cx.handle().downgrade())
4433// } else {
4434// None
4435// }
4436// })
4437// .unwrap_or(None)
4438// })
4439// });
4440
4441// let workspace = if let Some(existing_workspace) = existing_workspace {
4442// existing_workspace
4443// } else {
4444// let active_call = cx.read(ActiveCall::global);
4445// let room = active_call
4446// .read_with(&cx, |call, _| call.room().cloned())
4447// .ok_or_else(|| anyhow!("not in a call"))?;
4448// let project = room
4449// .update(&mut cx, |room, cx| {
4450// room.join_project(
4451// project_id,
4452// app_state.languages.clone(),
4453// app_state.fs.clone(),
4454// cx,
4455// )
4456// })
4457// .await?;
4458
4459// let window_bounds_override = window_bounds_env_override(&cx);
4460// let window = cx.add_window(
4461// (app_state.build_window_options)(
4462// window_bounds_override,
4463// None,
4464// cx.platform().as_ref(),
4465// ),
4466// |cx| Workspace::new(0, project, app_state.clone(), cx),
4467// );
4468// let workspace = window.root(&cx).unwrap();
4469// (app_state.initialize_workspace)(
4470// workspace.downgrade(),
4471// false,
4472// app_state.clone(),
4473// cx.clone(),
4474// )
4475// .await
4476// .log_err();
4477
4478// workspace.downgrade()
4479// };
4480
4481// workspace.window().activate(&mut cx);
4482// cx.platform().activate(true);
4483
4484// workspace.update(&mut cx, |workspace, cx| {
4485// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4486// let follow_peer_id = room
4487// .read(cx)
4488// .remote_participants()
4489// .iter()
4490// .find(|(_, participant)| participant.user.id == follow_user_id)
4491// .map(|(_, p)| p.peer_id)
4492// .or_else(|| {
4493// // If we couldn't follow the given user, follow the host instead.
4494// let collaborator = workspace
4495// .project()
4496// .read(cx)
4497// .collaborators()
4498// .values()
4499// .find(|collaborator| collaborator.replica_id == 0)?;
4500// Some(collaborator.peer_id)
4501// });
4502
4503// if let Some(follow_peer_id) = follow_peer_id {
4504// workspace
4505// .follow(follow_peer_id, cx)
4506// .map(|follow| follow.detach_and_log_err(cx));
4507// }
4508// }
4509// })?;
4510
4511// anyhow::Ok(())
4512// })
4513// }
4514
4515// pub fn restart(_: &Restart, cx: &mut AppContext) {
4516// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4517// cx.spawn(|mut cx| async move {
4518// let mut workspace_windows = cx
4519// .windows()
4520// .into_iter()
4521// .filter_map(|window| window.downcast::<Workspace>())
4522// .collect::<Vec<_>>();
4523
4524// // If multiple windows have unsaved changes, and need a save prompt,
4525// // prompt in the active window before switching to a different window.
4526// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4527
4528// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4529// let answer = window.prompt(
4530// PromptLevel::Info,
4531// "Are you sure you want to restart?",
4532// &["Restart", "Cancel"],
4533// &mut cx,
4534// );
4535
4536// if let Some(mut answer) = answer {
4537// let answer = answer.next().await;
4538// if answer != Some(0) {
4539// return Ok(());
4540// }
4541// }
4542// }
4543
4544// // If the user cancels any save prompt, then keep the app open.
4545// for window in workspace_windows {
4546// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4547// workspace.prepare_to_close(true, cx)
4548// }) {
4549// if !should_close.await? {
4550// return Ok(());
4551// }
4552// }
4553// }
4554// cx.platform().restart();
4555// anyhow::Ok(())
4556// })
4557// .detach_and_log_err(cx);
4558// }
4559
4560// fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4561// let mut parts = value.split(',');
4562// let width: usize = parts.next()?.parse().ok()?;
4563// let height: usize = parts.next()?.parse().ok()?;
4564// Some(vec2f(width as f32, height as f32))
4565// }
4566
4567// #[cfg(test)]
4568// mod tests {
4569// use super::*;
4570// use crate::{
4571// dock::test::{TestPanel, TestPanelEvent},
4572// item::test::{TestItem, TestItemEvent, TestProjectItem},
4573// };
4574// use fs::FakeFs;
4575// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4576// use project::{Project, ProjectEntryId};
4577// use serde_json::json;
4578// use settings::SettingsStore;
4579// use std::{cell::RefCell, rc::Rc};
4580
4581// #[gpui::test]
4582// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4583// init_test(cx);
4584
4585// let fs = FakeFs::new(cx.background());
4586// let project = Project::test(fs, [], cx).await;
4587// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4588// let workspace = window.root(cx);
4589
4590// // Adding an item with no ambiguity renders the tab without detail.
4591// let item1 = window.add_view(cx, |_| {
4592// let mut item = TestItem::new();
4593// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4594// item
4595// });
4596// workspace.update(cx, |workspace, cx| {
4597// workspace.add_item(Box::new(item1.clone()), cx);
4598// });
4599// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4600
4601// // Adding an item that creates ambiguity increases the level of detail on
4602// // both tabs.
4603// let item2 = window.add_view(cx, |_| {
4604// let mut item = TestItem::new();
4605// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4606// item
4607// });
4608// workspace.update(cx, |workspace, cx| {
4609// workspace.add_item(Box::new(item2.clone()), cx);
4610// });
4611// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4612// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4613
4614// // Adding an item that creates ambiguity increases the level of detail only
4615// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4616// // we stop at the highest detail available.
4617// let item3 = window.add_view(cx, |_| {
4618// let mut item = TestItem::new();
4619// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4620// item
4621// });
4622// workspace.update(cx, |workspace, cx| {
4623// workspace.add_item(Box::new(item3.clone()), cx);
4624// });
4625// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4626// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4627// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4628// }
4629
4630// #[gpui::test]
4631// async fn test_tracking_active_path(cx: &mut TestAppContext) {
4632// init_test(cx);
4633
4634// let fs = FakeFs::new(cx.background());
4635// fs.insert_tree(
4636// "/root1",
4637// json!({
4638// "one.txt": "",
4639// "two.txt": "",
4640// }),
4641// )
4642// .await;
4643// fs.insert_tree(
4644// "/root2",
4645// json!({
4646// "three.txt": "",
4647// }),
4648// )
4649// .await;
4650
4651// let project = Project::test(fs, ["root1".as_ref()], cx).await;
4652// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4653// let workspace = window.root(cx);
4654// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4655// let worktree_id = project.read_with(cx, |project, cx| {
4656// project.worktrees(cx).next().unwrap().read(cx).id()
4657// });
4658
4659// let item1 = window.add_view(cx, |cx| {
4660// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4661// });
4662// let item2 = window.add_view(cx, |cx| {
4663// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4664// });
4665
4666// // Add an item to an empty pane
4667// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4668// project.read_with(cx, |project, cx| {
4669// assert_eq!(
4670// project.active_entry(),
4671// project
4672// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4673// .map(|e| e.id)
4674// );
4675// });
4676// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4677
4678// // Add a second item to a non-empty pane
4679// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4680// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4681// project.read_with(cx, |project, cx| {
4682// assert_eq!(
4683// project.active_entry(),
4684// project
4685// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4686// .map(|e| e.id)
4687// );
4688// });
4689
4690// // Close the active item
4691// pane.update(cx, |pane, cx| {
4692// pane.close_active_item(&Default::default(), cx).unwrap()
4693// })
4694// .await
4695// .unwrap();
4696// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4697// project.read_with(cx, |project, cx| {
4698// assert_eq!(
4699// project.active_entry(),
4700// project
4701// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4702// .map(|e| e.id)
4703// );
4704// });
4705
4706// // Add a project folder
4707// project
4708// .update(cx, |project, cx| {
4709// project.find_or_create_local_worktree("/root2", true, cx)
4710// })
4711// .await
4712// .unwrap();
4713// assert_eq!(
4714// window.current_title(cx).as_deref(),
4715// Some("one.txt β root1, root2")
4716// );
4717
4718// // Remove a project folder
4719// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4720// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4721// }
4722
4723// #[gpui::test]
4724// async fn test_close_window(cx: &mut TestAppContext) {
4725// init_test(cx);
4726
4727// let fs = FakeFs::new(cx.background());
4728// fs.insert_tree("/root", json!({ "one": "" })).await;
4729
4730// let project = Project::test(fs, ["root".as_ref()], cx).await;
4731// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4732// let workspace = window.root(cx);
4733
4734// // When there are no dirty items, there's nothing to do.
4735// let item1 = window.add_view(cx, |_| TestItem::new());
4736// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4737// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4738// assert!(task.await.unwrap());
4739
4740// // When there are dirty untitled items, prompt to save each one. If the user
4741// // cancels any prompt, then abort.
4742// let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4743// let item3 = window.add_view(cx, |cx| {
4744// TestItem::new()
4745// .with_dirty(true)
4746// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4747// });
4748// workspace.update(cx, |w, cx| {
4749// w.add_item(Box::new(item2.clone()), cx);
4750// w.add_item(Box::new(item3.clone()), cx);
4751// });
4752// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4753// cx.foreground().run_until_parked();
4754// window.simulate_prompt_answer(2, cx); // cancel save all
4755// cx.foreground().run_until_parked();
4756// window.simulate_prompt_answer(2, cx); // cancel save all
4757// cx.foreground().run_until_parked();
4758// assert!(!window.has_pending_prompt(cx));
4759// assert!(!task.await.unwrap());
4760// }
4761
4762// #[gpui::test]
4763// async fn test_close_pane_items(cx: &mut TestAppContext) {
4764// init_test(cx);
4765
4766// let fs = FakeFs::new(cx.background());
4767
4768// let project = Project::test(fs, None, cx).await;
4769// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4770// let workspace = window.root(cx);
4771
4772// let item1 = window.add_view(cx, |cx| {
4773// TestItem::new()
4774// .with_dirty(true)
4775// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4776// });
4777// let item2 = window.add_view(cx, |cx| {
4778// TestItem::new()
4779// .with_dirty(true)
4780// .with_conflict(true)
4781// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4782// });
4783// let item3 = window.add_view(cx, |cx| {
4784// TestItem::new()
4785// .with_dirty(true)
4786// .with_conflict(true)
4787// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4788// });
4789// let item4 = window.add_view(cx, |cx| {
4790// TestItem::new()
4791// .with_dirty(true)
4792// .with_project_items(&[TestProjectItem::new_untitled(cx)])
4793// });
4794// let pane = workspace.update(cx, |workspace, cx| {
4795// workspace.add_item(Box::new(item1.clone()), cx);
4796// workspace.add_item(Box::new(item2.clone()), cx);
4797// workspace.add_item(Box::new(item3.clone()), cx);
4798// workspace.add_item(Box::new(item4.clone()), cx);
4799// workspace.active_pane().clone()
4800// });
4801
4802// let close_items = pane.update(cx, |pane, cx| {
4803// pane.activate_item(1, true, true, cx);
4804// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4805// let item1_id = item1.id();
4806// let item3_id = item3.id();
4807// let item4_id = item4.id();
4808// pane.close_items(cx, SaveIntent::Close, move |id| {
4809// [item1_id, item3_id, item4_id].contains(&id)
4810// })
4811// });
4812// cx.foreground().run_until_parked();
4813
4814// assert!(window.has_pending_prompt(cx));
4815// // Ignore "Save all" prompt
4816// window.simulate_prompt_answer(2, cx);
4817// cx.foreground().run_until_parked();
4818// // There's a prompt to save item 1.
4819// pane.read_with(cx, |pane, _| {
4820// assert_eq!(pane.items_len(), 4);
4821// assert_eq!(pane.active_item().unwrap().id(), item1.id());
4822// });
4823// // Confirm saving item 1.
4824// window.simulate_prompt_answer(0, cx);
4825// cx.foreground().run_until_parked();
4826
4827// // Item 1 is saved. There's a prompt to save item 3.
4828// pane.read_with(cx, |pane, cx| {
4829// assert_eq!(item1.read(cx).save_count, 1);
4830// assert_eq!(item1.read(cx).save_as_count, 0);
4831// assert_eq!(item1.read(cx).reload_count, 0);
4832// assert_eq!(pane.items_len(), 3);
4833// assert_eq!(pane.active_item().unwrap().id(), item3.id());
4834// });
4835// assert!(window.has_pending_prompt(cx));
4836
4837// // Cancel saving item 3.
4838// window.simulate_prompt_answer(1, cx);
4839// cx.foreground().run_until_parked();
4840
4841// // Item 3 is reloaded. There's a prompt to save item 4.
4842// pane.read_with(cx, |pane, cx| {
4843// assert_eq!(item3.read(cx).save_count, 0);
4844// assert_eq!(item3.read(cx).save_as_count, 0);
4845// assert_eq!(item3.read(cx).reload_count, 1);
4846// assert_eq!(pane.items_len(), 2);
4847// assert_eq!(pane.active_item().unwrap().id(), item4.id());
4848// });
4849// assert!(window.has_pending_prompt(cx));
4850
4851// // Confirm saving item 4.
4852// window.simulate_prompt_answer(0, cx);
4853// cx.foreground().run_until_parked();
4854
4855// // There's a prompt for a path for item 4.
4856// cx.simulate_new_path_selection(|_| Some(Default::default()));
4857// close_items.await.unwrap();
4858
4859// // The requested items are closed.
4860// pane.read_with(cx, |pane, cx| {
4861// assert_eq!(item4.read(cx).save_count, 0);
4862// assert_eq!(item4.read(cx).save_as_count, 1);
4863// assert_eq!(item4.read(cx).reload_count, 0);
4864// assert_eq!(pane.items_len(), 1);
4865// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4866// });
4867// }
4868
4869// #[gpui::test]
4870// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4871// init_test(cx);
4872
4873// let fs = FakeFs::new(cx.background());
4874
4875// let project = Project::test(fs, [], cx).await;
4876// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4877// let workspace = window.root(cx);
4878
4879// // Create several workspace items with single project entries, and two
4880// // workspace items with multiple project entries.
4881// let single_entry_items = (0..=4)
4882// .map(|project_entry_id| {
4883// window.add_view(cx, |cx| {
4884// TestItem::new()
4885// .with_dirty(true)
4886// .with_project_items(&[TestProjectItem::new(
4887// project_entry_id,
4888// &format!("{project_entry_id}.txt"),
4889// cx,
4890// )])
4891// })
4892// })
4893// .collect::<Vec<_>>();
4894// let item_2_3 = window.add_view(cx, |cx| {
4895// TestItem::new()
4896// .with_dirty(true)
4897// .with_singleton(false)
4898// .with_project_items(&[
4899// single_entry_items[2].read(cx).project_items[0].clone(),
4900// single_entry_items[3].read(cx).project_items[0].clone(),
4901// ])
4902// });
4903// let item_3_4 = window.add_view(cx, |cx| {
4904// TestItem::new()
4905// .with_dirty(true)
4906// .with_singleton(false)
4907// .with_project_items(&[
4908// single_entry_items[3].read(cx).project_items[0].clone(),
4909// single_entry_items[4].read(cx).project_items[0].clone(),
4910// ])
4911// });
4912
4913// // Create two panes that contain the following project entries:
4914// // left pane:
4915// // multi-entry items: (2, 3)
4916// // single-entry items: 0, 1, 2, 3, 4
4917// // right pane:
4918// // single-entry items: 1
4919// // multi-entry items: (3, 4)
4920// let left_pane = workspace.update(cx, |workspace, cx| {
4921// let left_pane = workspace.active_pane().clone();
4922// workspace.add_item(Box::new(item_2_3.clone()), cx);
4923// for item in single_entry_items {
4924// workspace.add_item(Box::new(item), cx);
4925// }
4926// left_pane.update(cx, |pane, cx| {
4927// pane.activate_item(2, true, true, cx);
4928// });
4929
4930// workspace
4931// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4932// .unwrap();
4933
4934// left_pane
4935// });
4936
4937// //Need to cause an effect flush in order to respect new focus
4938// workspace.update(cx, |workspace, cx| {
4939// workspace.add_item(Box::new(item_3_4.clone()), cx);
4940// cx.focus(&left_pane);
4941// });
4942
4943// // When closing all of the items in the left pane, we should be prompted twice:
4944// // once for project entry 0, and once for project entry 2. After those two
4945// // prompts, the task should complete.
4946
4947// let close = left_pane.update(cx, |pane, cx| {
4948// pane.close_items(cx, SaveIntent::Close, move |_| true)
4949// });
4950// cx.foreground().run_until_parked();
4951// // Discard "Save all" prompt
4952// window.simulate_prompt_answer(2, cx);
4953
4954// cx.foreground().run_until_parked();
4955// left_pane.read_with(cx, |pane, cx| {
4956// assert_eq!(
4957// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4958// &[ProjectEntryId::from_proto(0)]
4959// );
4960// });
4961// window.simulate_prompt_answer(0, cx);
4962
4963// cx.foreground().run_until_parked();
4964// left_pane.read_with(cx, |pane, cx| {
4965// assert_eq!(
4966// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4967// &[ProjectEntryId::from_proto(2)]
4968// );
4969// });
4970// window.simulate_prompt_answer(0, cx);
4971
4972// cx.foreground().run_until_parked();
4973// close.await.unwrap();
4974// left_pane.read_with(cx, |pane, _| {
4975// assert_eq!(pane.items_len(), 0);
4976// });
4977// }
4978
4979// #[gpui::test]
4980// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4981// init_test(cx);
4982
4983// let fs = FakeFs::new(cx.background());
4984
4985// let project = Project::test(fs, [], cx).await;
4986// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4987// let workspace = window.root(cx);
4988// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4989
4990// let item = window.add_view(cx, |cx| {
4991// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4992// });
4993// let item_id = item.id();
4994// workspace.update(cx, |workspace, cx| {
4995// workspace.add_item(Box::new(item.clone()), cx);
4996// });
4997
4998// // Autosave on window change.
4999// item.update(cx, |item, cx| {
5000// cx.update_global(|settings: &mut SettingsStore, cx| {
5001// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5002// settings.autosave = Some(AutosaveSetting::OnWindowChange);
5003// })
5004// });
5005// item.is_dirty = true;
5006// });
5007
5008// // Deactivating the window saves the file.
5009// window.simulate_deactivation(cx);
5010// deterministic.run_until_parked();
5011// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
5012
5013// // Autosave on focus change.
5014// item.update(cx, |item, cx| {
5015// cx.focus_self();
5016// cx.update_global(|settings: &mut SettingsStore, cx| {
5017// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5018// settings.autosave = Some(AutosaveSetting::OnFocusChange);
5019// })
5020// });
5021// item.is_dirty = true;
5022// });
5023
5024// // Blurring the item saves the file.
5025// item.update(cx, |_, cx| cx.blur());
5026// deterministic.run_until_parked();
5027// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5028
5029// // Deactivating the window still saves the file.
5030// window.simulate_activation(cx);
5031// item.update(cx, |item, cx| {
5032// cx.focus_self();
5033// item.is_dirty = true;
5034// });
5035// window.simulate_deactivation(cx);
5036
5037// deterministic.run_until_parked();
5038// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5039
5040// // Autosave after delay.
5041// item.update(cx, |item, cx| {
5042// cx.update_global(|settings: &mut SettingsStore, cx| {
5043// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5044// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5045// })
5046// });
5047// item.is_dirty = true;
5048// cx.emit(TestItemEvent::Edit);
5049// });
5050
5051// // Delay hasn't fully expired, so the file is still dirty and unsaved.
5052// deterministic.advance_clock(Duration::from_millis(250));
5053// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5054
5055// // After delay expires, the file is saved.
5056// deterministic.advance_clock(Duration::from_millis(250));
5057// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5058
5059// // Autosave on focus change, ensuring closing the tab counts as such.
5060// item.update(cx, |item, cx| {
5061// cx.update_global(|settings: &mut SettingsStore, cx| {
5062// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5063// settings.autosave = Some(AutosaveSetting::OnFocusChange);
5064// })
5065// });
5066// item.is_dirty = true;
5067// });
5068
5069// pane.update(cx, |pane, cx| {
5070// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5071// })
5072// .await
5073// .unwrap();
5074// assert!(!window.has_pending_prompt(cx));
5075// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5076
5077// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5078// workspace.update(cx, |workspace, cx| {
5079// workspace.add_item(Box::new(item.clone()), cx);
5080// });
5081// item.update(cx, |item, cx| {
5082// item.project_items[0].update(cx, |item, _| {
5083// item.entry_id = None;
5084// });
5085// item.is_dirty = true;
5086// cx.blur();
5087// });
5088// deterministic.run_until_parked();
5089// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5090
5091// // Ensure autosave is prevented for deleted files also when closing the buffer.
5092// let _close_items = pane.update(cx, |pane, cx| {
5093// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5094// });
5095// deterministic.run_until_parked();
5096// assert!(window.has_pending_prompt(cx));
5097// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5098// }
5099
5100// #[gpui::test]
5101// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5102// init_test(cx);
5103
5104// let fs = FakeFs::new(cx.background());
5105
5106// let project = Project::test(fs, [], cx).await;
5107// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5108// let workspace = window.root(cx);
5109
5110// let item = window.add_view(cx, |cx| {
5111// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5112// });
5113// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5114// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5115// let toolbar_notify_count = Rc::new(RefCell::new(0));
5116
5117// workspace.update(cx, |workspace, cx| {
5118// workspace.add_item(Box::new(item.clone()), cx);
5119// let toolbar_notification_count = toolbar_notify_count.clone();
5120// cx.observe(&toolbar, move |_, _, _| {
5121// *toolbar_notification_count.borrow_mut() += 1
5122// })
5123// .detach();
5124// });
5125
5126// pane.read_with(cx, |pane, _| {
5127// assert!(!pane.can_navigate_backward());
5128// assert!(!pane.can_navigate_forward());
5129// });
5130
5131// item.update(cx, |item, cx| {
5132// item.set_state("one".to_string(), cx);
5133// });
5134
5135// // Toolbar must be notified to re-render the navigation buttons
5136// assert_eq!(*toolbar_notify_count.borrow(), 1);
5137
5138// pane.read_with(cx, |pane, _| {
5139// assert!(pane.can_navigate_backward());
5140// assert!(!pane.can_navigate_forward());
5141// });
5142
5143// workspace
5144// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5145// .await
5146// .unwrap();
5147
5148// assert_eq!(*toolbar_notify_count.borrow(), 3);
5149// pane.read_with(cx, |pane, _| {
5150// assert!(!pane.can_navigate_backward());
5151// assert!(pane.can_navigate_forward());
5152// });
5153// }
5154
5155// #[gpui::test]
5156// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5157// init_test(cx);
5158// let fs = FakeFs::new(cx.background());
5159
5160// let project = Project::test(fs, [], cx).await;
5161// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5162// let workspace = window.root(cx);
5163
5164// let panel = workspace.update(cx, |workspace, cx| {
5165// let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5166// workspace.add_panel(panel.clone(), cx);
5167
5168// workspace
5169// .right_dock()
5170// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5171
5172// panel
5173// });
5174
5175// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5176// pane.update(cx, |pane, cx| {
5177// let item = cx.add_view(|_| TestItem::new());
5178// pane.add_item(Box::new(item), true, true, None, cx);
5179// });
5180
5181// // Transfer focus from center to panel
5182// workspace.update(cx, |workspace, cx| {
5183// workspace.toggle_panel_focus::<TestPanel>(cx);
5184// });
5185
5186// workspace.read_with(cx, |workspace, cx| {
5187// assert!(workspace.right_dock().read(cx).is_open());
5188// assert!(!panel.is_zoomed(cx));
5189// assert!(panel.has_focus(cx));
5190// });
5191
5192// // Transfer focus from panel to center
5193// workspace.update(cx, |workspace, cx| {
5194// workspace.toggle_panel_focus::<TestPanel>(cx);
5195// });
5196
5197// workspace.read_with(cx, |workspace, cx| {
5198// assert!(workspace.right_dock().read(cx).is_open());
5199// assert!(!panel.is_zoomed(cx));
5200// assert!(!panel.has_focus(cx));
5201// });
5202
5203// // Close the dock
5204// workspace.update(cx, |workspace, cx| {
5205// workspace.toggle_dock(DockPosition::Right, cx);
5206// });
5207
5208// workspace.read_with(cx, |workspace, cx| {
5209// assert!(!workspace.right_dock().read(cx).is_open());
5210// assert!(!panel.is_zoomed(cx));
5211// assert!(!panel.has_focus(cx));
5212// });
5213
5214// // Open the dock
5215// workspace.update(cx, |workspace, cx| {
5216// workspace.toggle_dock(DockPosition::Right, cx);
5217// });
5218
5219// workspace.read_with(cx, |workspace, cx| {
5220// assert!(workspace.right_dock().read(cx).is_open());
5221// assert!(!panel.is_zoomed(cx));
5222// assert!(panel.has_focus(cx));
5223// });
5224
5225// // Focus and zoom panel
5226// panel.update(cx, |panel, cx| {
5227// cx.focus_self();
5228// panel.set_zoomed(true, cx)
5229// });
5230
5231// workspace.read_with(cx, |workspace, cx| {
5232// assert!(workspace.right_dock().read(cx).is_open());
5233// assert!(panel.is_zoomed(cx));
5234// assert!(panel.has_focus(cx));
5235// });
5236
5237// // Transfer focus to the center closes the dock
5238// workspace.update(cx, |workspace, cx| {
5239// workspace.toggle_panel_focus::<TestPanel>(cx);
5240// });
5241
5242// workspace.read_with(cx, |workspace, cx| {
5243// assert!(!workspace.right_dock().read(cx).is_open());
5244// assert!(panel.is_zoomed(cx));
5245// assert!(!panel.has_focus(cx));
5246// });
5247
5248// // Transferring focus back to the panel keeps it zoomed
5249// workspace.update(cx, |workspace, cx| {
5250// workspace.toggle_panel_focus::<TestPanel>(cx);
5251// });
5252
5253// workspace.read_with(cx, |workspace, cx| {
5254// assert!(workspace.right_dock().read(cx).is_open());
5255// assert!(panel.is_zoomed(cx));
5256// assert!(panel.has_focus(cx));
5257// });
5258
5259// // Close the dock while it is zoomed
5260// workspace.update(cx, |workspace, cx| {
5261// workspace.toggle_dock(DockPosition::Right, cx)
5262// });
5263
5264// workspace.read_with(cx, |workspace, cx| {
5265// assert!(!workspace.right_dock().read(cx).is_open());
5266// assert!(panel.is_zoomed(cx));
5267// assert!(workspace.zoomed.is_none());
5268// assert!(!panel.has_focus(cx));
5269// });
5270
5271// // Opening the dock, when it's zoomed, retains focus
5272// workspace.update(cx, |workspace, cx| {
5273// workspace.toggle_dock(DockPosition::Right, cx)
5274// });
5275
5276// workspace.read_with(cx, |workspace, cx| {
5277// assert!(workspace.right_dock().read(cx).is_open());
5278// assert!(panel.is_zoomed(cx));
5279// assert!(workspace.zoomed.is_some());
5280// assert!(panel.has_focus(cx));
5281// });
5282
5283// // Unzoom and close the panel, zoom the active pane.
5284// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5285// workspace.update(cx, |workspace, cx| {
5286// workspace.toggle_dock(DockPosition::Right, cx)
5287// });
5288// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5289
5290// // Opening a dock unzooms the pane.
5291// workspace.update(cx, |workspace, cx| {
5292// workspace.toggle_dock(DockPosition::Right, cx)
5293// });
5294// workspace.read_with(cx, |workspace, cx| {
5295// let pane = pane.read(cx);
5296// assert!(!pane.is_zoomed());
5297// assert!(!pane.has_focus());
5298// assert!(workspace.right_dock().read(cx).is_open());
5299// assert!(workspace.zoomed.is_none());
5300// });
5301// }
5302
5303// #[gpui::test]
5304// async fn test_panels(cx: &mut gpui::TestAppContext) {
5305// init_test(cx);
5306// let fs = FakeFs::new(cx.background());
5307
5308// let project = Project::test(fs, [], cx).await;
5309// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5310// let workspace = window.root(cx);
5311
5312// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5313// // Add panel_1 on the left, panel_2 on the right.
5314// let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5315// workspace.add_panel(panel_1.clone(), cx);
5316// workspace
5317// .left_dock()
5318// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5319// let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5320// workspace.add_panel(panel_2.clone(), cx);
5321// workspace
5322// .right_dock()
5323// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5324
5325// let left_dock = workspace.left_dock();
5326// assert_eq!(
5327// left_dock.read(cx).visible_panel().unwrap().id(),
5328// panel_1.id()
5329// );
5330// assert_eq!(
5331// left_dock.read(cx).active_panel_size(cx).unwrap(),
5332// panel_1.size(cx)
5333// );
5334
5335// left_dock.update(cx, |left_dock, cx| {
5336// left_dock.resize_active_panel(Some(1337.), cx)
5337// });
5338// assert_eq!(
5339// workspace
5340// .right_dock()
5341// .read(cx)
5342// .visible_panel()
5343// .unwrap()
5344// .id(),
5345// panel_2.id()
5346// );
5347
5348// (panel_1, panel_2)
5349// });
5350
5351// // Move panel_1 to the right
5352// panel_1.update(cx, |panel_1, cx| {
5353// panel_1.set_position(DockPosition::Right, cx)
5354// });
5355
5356// workspace.update(cx, |workspace, cx| {
5357// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5358// // Since it was the only panel on the left, the left dock should now be closed.
5359// assert!(!workspace.left_dock().read(cx).is_open());
5360// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5361// let right_dock = workspace.right_dock();
5362// assert_eq!(
5363// right_dock.read(cx).visible_panel().unwrap().id(),
5364// panel_1.id()
5365// );
5366// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5367
5368// // Now we move panel_2Β to the left
5369// panel_2.set_position(DockPosition::Left, cx);
5370// });
5371
5372// workspace.update(cx, |workspace, cx| {
5373// // Since panel_2 was not visible on the right, we don't open the left dock.
5374// assert!(!workspace.left_dock().read(cx).is_open());
5375// // And the right dock is unaffected in it's displaying of panel_1
5376// assert!(workspace.right_dock().read(cx).is_open());
5377// assert_eq!(
5378// workspace
5379// .right_dock()
5380// .read(cx)
5381// .visible_panel()
5382// .unwrap()
5383// .id(),
5384// panel_1.id()
5385// );
5386// });
5387
5388// // Move panel_1 back to the left
5389// panel_1.update(cx, |panel_1, cx| {
5390// panel_1.set_position(DockPosition::Left, cx)
5391// });
5392
5393// workspace.update(cx, |workspace, cx| {
5394// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5395// let left_dock = workspace.left_dock();
5396// assert!(left_dock.read(cx).is_open());
5397// assert_eq!(
5398// left_dock.read(cx).visible_panel().unwrap().id(),
5399// panel_1.id()
5400// );
5401// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5402// // And right the dock should be closed as it no longer has any panels.
5403// assert!(!workspace.right_dock().read(cx).is_open());
5404
5405// // Now we move panel_1 to the bottom
5406// panel_1.set_position(DockPosition::Bottom, cx);
5407// });
5408
5409// workspace.update(cx, |workspace, cx| {
5410// // Since panel_1 was visible on the left, we close the left dock.
5411// assert!(!workspace.left_dock().read(cx).is_open());
5412// // The bottom dock is sized based on the panel's default size,
5413// // since the panel orientation changed from vertical to horizontal.
5414// let bottom_dock = workspace.bottom_dock();
5415// assert_eq!(
5416// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5417// panel_1.size(cx),
5418// );
5419// // Close bottom dock and move panel_1 back to the left.
5420// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5421// panel_1.set_position(DockPosition::Left, cx);
5422// });
5423
5424// // Emit activated event on panel 1
5425// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5426
5427// // Now the left dock is open and panel_1 is active and focused.
5428// workspace.read_with(cx, |workspace, cx| {
5429// let left_dock = workspace.left_dock();
5430// assert!(left_dock.read(cx).is_open());
5431// assert_eq!(
5432// left_dock.read(cx).visible_panel().unwrap().id(),
5433// panel_1.id()
5434// );
5435// assert!(panel_1.is_focused(cx));
5436// });
5437
5438// // Emit closed event on panel 2, which is not active
5439// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5440
5441// // Wo don't close the left dock, because panel_2 wasn't the active panel
5442// workspace.read_with(cx, |workspace, cx| {
5443// let left_dock = workspace.left_dock();
5444// assert!(left_dock.read(cx).is_open());
5445// assert_eq!(
5446// left_dock.read(cx).visible_panel().unwrap().id(),
5447// panel_1.id()
5448// );
5449// });
5450
5451// // Emitting a ZoomIn event shows the panel as zoomed.
5452// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5453// workspace.read_with(cx, |workspace, _| {
5454// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5455// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5456// });
5457
5458// // Move panel to another dock while it is zoomed
5459// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5460// workspace.read_with(cx, |workspace, _| {
5461// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5462// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5463// });
5464
5465// // If focus is transferred to another view that's not a panel or another pane, we still show
5466// // the panel as zoomed.
5467// let focus_receiver = window.add_view(cx, |_| EmptyView);
5468// focus_receiver.update(cx, |_, cx| cx.focus_self());
5469// workspace.read_with(cx, |workspace, _| {
5470// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5471// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5472// });
5473
5474// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5475// workspace.update(cx, |_, cx| cx.focus_self());
5476// workspace.read_with(cx, |workspace, _| {
5477// assert_eq!(workspace.zoomed, None);
5478// assert_eq!(workspace.zoomed_position, None);
5479// });
5480
5481// // If focus is transferred again to another view that's not a panel or a pane, we won't
5482// // show the panel as zoomed because it wasn't zoomed before.
5483// focus_receiver.update(cx, |_, cx| cx.focus_self());
5484// workspace.read_with(cx, |workspace, _| {
5485// assert_eq!(workspace.zoomed, None);
5486// assert_eq!(workspace.zoomed_position, None);
5487// });
5488
5489// // When focus is transferred back to the panel, it is zoomed again.
5490// panel_1.update(cx, |_, cx| cx.focus_self());
5491// workspace.read_with(cx, |workspace, _| {
5492// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5493// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5494// });
5495
5496// // Emitting a ZoomOut event unzooms the panel.
5497// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5498// workspace.read_with(cx, |workspace, _| {
5499// assert_eq!(workspace.zoomed, None);
5500// assert_eq!(workspace.zoomed_position, None);
5501// });
5502
5503// // Emit closed event on panel 1, which is active
5504// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5505
5506// // Now the left dock is closed, because panel_1 was the active panel
5507// workspace.read_with(cx, |workspace, cx| {
5508// let right_dock = workspace.right_dock();
5509// assert!(!right_dock.read(cx).is_open());
5510// });
5511// }
5512
5513// pub fn init_test(cx: &mut TestAppContext) {
5514// cx.foreground().forbid_parking();
5515// cx.update(|cx| {
5516// cx.set_global(SettingsStore::test(cx));
5517// theme::init((), cx);
5518// language::init(cx);
5519// crate::init_settings(cx);
5520// Project::init_settings(cx);
5521// });
5522// }
5523// }