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