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