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