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