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