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