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