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