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