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