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