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