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