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