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