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