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