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