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