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