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