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
3413fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3414 let display_origin = cx
3415 .update(|cx| Some(cx.displays().first()?.bounds().origin))
3416 .ok()??;
3417 ZED_WINDOW_POSITION
3418 .zip(*ZED_WINDOW_SIZE)
3419 .map(|(position, size)| {
3420 WindowBounds::Fixed(Bounds {
3421 origin: display_origin + position,
3422 size,
3423 })
3424 })
3425}
3426
3427fn open_items(
3428 serialized_workspace: Option<SerializedWorkspace>,
3429 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3430 app_state: Arc<AppState>,
3431 cx: &mut ViewContext<Workspace>,
3432) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3433 let restored_items = serialized_workspace.map(|serialized_workspace| {
3434 Workspace::load_workspace(
3435 serialized_workspace,
3436 project_paths_to_open
3437 .iter()
3438 .map(|(_, project_path)| project_path)
3439 .cloned()
3440 .collect(),
3441 cx,
3442 )
3443 });
3444
3445 cx.spawn(|workspace, mut cx| async move {
3446 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3447
3448 if let Some(restored_items) = restored_items {
3449 let restored_items = restored_items.await?;
3450
3451 let restored_project_paths = restored_items
3452 .iter()
3453 .filter_map(|item| {
3454 cx.update(|_, cx| item.as_ref()?.project_path(cx))
3455 .ok()
3456 .flatten()
3457 })
3458 .collect::<HashSet<_>>();
3459
3460 for restored_item in restored_items {
3461 opened_items.push(restored_item.map(Ok));
3462 }
3463
3464 project_paths_to_open
3465 .iter_mut()
3466 .for_each(|(_, project_path)| {
3467 if let Some(project_path_to_open) = project_path {
3468 if restored_project_paths.contains(project_path_to_open) {
3469 *project_path = None;
3470 }
3471 }
3472 });
3473 } else {
3474 for _ in 0..project_paths_to_open.len() {
3475 opened_items.push(None);
3476 }
3477 }
3478 assert!(opened_items.len() == project_paths_to_open.len());
3479
3480 let tasks =
3481 project_paths_to_open
3482 .into_iter()
3483 .enumerate()
3484 .map(|(i, (abs_path, project_path))| {
3485 let workspace = workspace.clone();
3486 cx.spawn(|mut cx| {
3487 let fs = app_state.fs.clone();
3488 async move {
3489 let file_project_path = project_path?;
3490 if fs.is_file(&abs_path).await {
3491 Some((
3492 i,
3493 workspace
3494 .update(&mut cx, |workspace, cx| {
3495 workspace.open_path(file_project_path, None, true, cx)
3496 })
3497 .log_err()?
3498 .await,
3499 ))
3500 } else {
3501 None
3502 }
3503 }
3504 })
3505 });
3506
3507 let tasks = tasks.collect::<Vec<_>>();
3508
3509 let tasks = futures::future::join_all(tasks.into_iter());
3510 for maybe_opened_path in tasks.await.into_iter() {
3511 if let Some((i, path_open_result)) = maybe_opened_path {
3512 opened_items[i] = Some(path_open_result);
3513 }
3514 }
3515
3516 Ok(opened_items)
3517 })
3518}
3519
3520// todo!()
3521// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
3522// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3523// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3524// const MESSAGE_ID: usize = 2;
3525
3526// if workspace
3527// .read_with(cx, |workspace, cx| {
3528// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3529// })
3530// .unwrap_or(false)
3531// {
3532// return;
3533// }
3534
3535// if db::kvp::KEY_VALUE_STORE
3536// .read_kvp(NEW_DOCK_HINT_KEY)
3537// .ok()
3538// .flatten()
3539// .is_some()
3540// {
3541// if !workspace
3542// .read_with(cx, |workspace, cx| {
3543// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3544// })
3545// .unwrap_or(false)
3546// {
3547// cx.update(|cx| {
3548// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3549// let entry = tracker
3550// .entry(TypeId::of::<MessageNotification>())
3551// .or_default();
3552// if !entry.contains(&MESSAGE_ID) {
3553// entry.push(MESSAGE_ID);
3554// }
3555// });
3556// });
3557// }
3558
3559// return;
3560// }
3561
3562// cx.spawn(|_| async move {
3563// db::kvp::KEY_VALUE_STORE
3564// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3565// .await
3566// .ok();
3567// })
3568// .detach();
3569
3570// workspace
3571// .update(cx, |workspace, cx| {
3572// workspace.show_notification_once(2, cx, |cx| {
3573// cx.build_view(|_| {
3574// MessageNotification::new_element(|text, _| {
3575// Text::new(
3576// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3577// text,
3578// )
3579// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3580// let code_span_background_color = settings::get::<ThemeSettings>(cx)
3581// .theme
3582// .editor
3583// .document_highlight_read_background;
3584
3585// cx.scene().push_quad(gpui::Quad {
3586// bounds,
3587// background: Some(code_span_background_color),
3588// border: Default::default(),
3589// corner_radii: (2.0).into(),
3590// })
3591// })
3592// .into_any()
3593// })
3594// .with_click_message("Read more about the new panel system")
3595// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3596// })
3597// })
3598// })
3599// .ok();
3600
3601fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3602 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3603
3604 workspace
3605 .update(cx, |workspace, cx| {
3606 if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3607 workspace.show_notification_once(0, cx, |cx| {
3608 cx.build_view(|_| {
3609 MessageNotification::new("Failed to load the database file.")
3610 .with_click_message("Click to let us know about this error")
3611 .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3612 })
3613 });
3614 }
3615 })
3616 .log_err();
3617}
3618
3619impl FocusableView for Workspace {
3620 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3621 self.active_pane.focus_handle(cx)
3622 }
3623}
3624
3625impl Render for Workspace {
3626 type Element = Div;
3627
3628 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3629 let mut context = KeyContext::default();
3630 context.add("Workspace");
3631
3632 let (ui_font, ui_font_size) = {
3633 let theme_settings = ThemeSettings::get_global(cx);
3634 (
3635 theme_settings.ui_font.family.clone(),
3636 theme_settings.ui_font_size.clone(),
3637 )
3638 };
3639
3640 cx.set_rem_size(ui_font_size);
3641
3642 self.actions(div(), cx)
3643 .key_context(context)
3644 .relative()
3645 .size_full()
3646 .flex()
3647 .flex_col()
3648 .font(ui_font)
3649 .gap_0()
3650 .justify_start()
3651 .items_start()
3652 .text_color(cx.theme().colors().text)
3653 .bg(cx.theme().colors().background)
3654 .children(self.titlebar_item.clone())
3655 .child(
3656 // todo! should this be a component a view?
3657 div()
3658 .id("workspace")
3659 .relative()
3660 .flex_1()
3661 .w_full()
3662 .flex()
3663 .overflow_hidden()
3664 .border_t()
3665 .border_b()
3666 .border_color(cx.theme().colors().border)
3667 .child(self.modal_layer.clone())
3668 .child(
3669 div()
3670 .flex()
3671 .flex_row()
3672 .flex_1()
3673 .h_full()
3674 // Left Dock
3675 .child(
3676 div()
3677 .flex()
3678 .flex_none()
3679 .overflow_hidden()
3680 .child(self.left_dock.clone()),
3681 )
3682 // Panes
3683 .child(
3684 div()
3685 .flex()
3686 .flex_col()
3687 .flex_1()
3688 .child(self.center.render(
3689 &self.project,
3690 &self.follower_states,
3691 &self.active_pane,
3692 self.zoomed.as_ref(),
3693 &self.app_state,
3694 cx,
3695 ))
3696 .child(self.bottom_dock.clone()),
3697 )
3698 // Right Dock
3699 .child(
3700 div()
3701 .flex()
3702 .flex_none()
3703 .overflow_hidden()
3704 .child(self.right_dock.clone()),
3705 ),
3706 ),
3707 )
3708 .child(self.status_bar.clone())
3709 }
3710}
3711
3712// impl View for Workspace {
3713
3714// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3715// let theme = theme::current(cx).clone();
3716// Stack::new()
3717// .with_child(
3718// Flex::column()
3719// .with_child(self.render_titlebar(&theme, cx))
3720// .with_child(
3721// Stack::new()
3722// .with_child({
3723// let project = self.project.clone();
3724// Flex::row()
3725// .with_children(self.render_dock(DockPosition::Left, cx))
3726// .with_child(
3727// Flex::column()
3728// .with_child(
3729// FlexItem::new(
3730// self.center.render(
3731// &project,
3732// &theme,
3733// &self.follower_states,
3734// self.active_call(),
3735// self.active_pane(),
3736// self.zoomed
3737// .as_ref()
3738// .and_then(|zoomed| zoomed.upgrade(cx))
3739// .as_ref(),
3740// &self.app_state,
3741// cx,
3742// ),
3743// )
3744// .flex(1., true),
3745// )
3746// .with_children(
3747// self.render_dock(DockPosition::Bottom, cx),
3748// )
3749// .flex(1., true),
3750// )
3751// .with_children(self.render_dock(DockPosition::Right, cx))
3752// })
3753// .with_child(Overlay::new(
3754// Stack::new()
3755// .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3756// enum ZoomBackground {}
3757// let zoomed = zoomed.upgrade(cx)?;
3758
3759// let mut foreground_style =
3760// theme.workspace.zoomed_pane_foreground;
3761// if let Some(zoomed_dock_position) = self.zoomed_position {
3762// foreground_style =
3763// theme.workspace.zoomed_panel_foreground;
3764// let margin = foreground_style.margin.top;
3765// let border = foreground_style.border.top;
3766
3767// // Only include a margin and border on the opposite side.
3768// foreground_style.margin.top = 0.;
3769// foreground_style.margin.left = 0.;
3770// foreground_style.margin.bottom = 0.;
3771// foreground_style.margin.right = 0.;
3772// foreground_style.border.top = false;
3773// foreground_style.border.left = false;
3774// foreground_style.border.bottom = false;
3775// foreground_style.border.right = false;
3776// match zoomed_dock_position {
3777// DockPosition::Left => {
3778// foreground_style.margin.right = margin;
3779// foreground_style.border.right = border;
3780// }
3781// DockPosition::Right => {
3782// foreground_style.margin.left = margin;
3783// foreground_style.border.left = border;
3784// }
3785// DockPosition::Bottom => {
3786// foreground_style.margin.top = margin;
3787// foreground_style.border.top = border;
3788// }
3789// }
3790// }
3791
3792// Some(
3793// ChildView::new(&zoomed, cx)
3794// .contained()
3795// .with_style(foreground_style)
3796// .aligned()
3797// .contained()
3798// .with_style(theme.workspace.zoomed_background)
3799// .mouse::<ZoomBackground>(0)
3800// .capture_all()
3801// .on_down(
3802// MouseButton::Left,
3803// |_, this: &mut Self, cx| {
3804// this.zoom_out(cx);
3805// },
3806// ),
3807// )
3808// }))
3809// .with_children(self.modal.as_ref().map(|modal| {
3810// // Prevent clicks within the modal from falling
3811// // through to the rest of the workspace.
3812// enum ModalBackground {}
3813// MouseEventHandler::new::<ModalBackground, _>(
3814// 0,
3815// cx,
3816// |_, cx| ChildView::new(modal.view.as_any(), cx),
3817// )
3818// .on_click(MouseButton::Left, |_, _, _| {})
3819// .contained()
3820// .with_style(theme.workspace.modal)
3821// .aligned()
3822// .top()
3823// }))
3824// .with_children(self.render_notifications(&theme.workspace, cx)),
3825// ))
3826// .provide_resize_bounds::<WorkspaceBounds>()
3827// .flex(1.0, true),
3828// )
3829// .with_child(ChildView::new(&self.status_bar, cx))
3830// .contained()
3831// .with_background_color(theme.workspace.background),
3832// )
3833// .with_children(DragAndDrop::render(cx))
3834// .with_children(self.render_disconnected_overlay(cx))
3835// .into_any_named("workspace")
3836// }
3837
3838// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3839// DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3840// }
3841// }
3842
3843impl WorkspaceStore {
3844 pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
3845 Self {
3846 workspaces: Default::default(),
3847 followers: Default::default(),
3848 _subscriptions: vec![],
3849 // client.add_request_handler(cx.weak_model(), Self::handle_follow),
3850 // client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3851 // client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3852 // ],
3853 client,
3854 }
3855 }
3856
3857 pub fn update_followers(
3858 &self,
3859 project_id: Option<u64>,
3860 room_id: u64,
3861 update: proto::update_followers::Variant,
3862 cx: &AppContext,
3863 ) -> Option<()> {
3864 let follower_ids: Vec<_> = self
3865 .followers
3866 .iter()
3867 .filter_map(|follower| {
3868 if follower.project_id == project_id || project_id.is_none() {
3869 Some(follower.peer_id.into())
3870 } else {
3871 None
3872 }
3873 })
3874 .collect();
3875 if follower_ids.is_empty() {
3876 return None;
3877 }
3878 self.client
3879 .send(proto::UpdateFollowers {
3880 room_id,
3881 project_id,
3882 follower_ids,
3883 variant: Some(update),
3884 })
3885 .log_err()
3886 }
3887
3888 pub async fn handle_follow(
3889 this: Model<Self>,
3890 envelope: TypedEnvelope<proto::Follow>,
3891 _: Arc<Client>,
3892 mut cx: AsyncAppContext,
3893 ) -> Result<proto::FollowResponse> {
3894 this.update(&mut cx, |this, cx| {
3895 let follower = Follower {
3896 project_id: envelope.payload.project_id,
3897 peer_id: envelope.original_sender_id()?,
3898 };
3899 let mut response = proto::FollowResponse::default();
3900 let active_project = this
3901 .workspaces
3902 .iter()
3903 .next()
3904 .and_then(|workspace| {
3905 workspace
3906 .read_with(cx, |this, cx| this.call_handler.active_project(cx))
3907 .log_err()
3908 })
3909 .flatten();
3910 for workspace in &this.workspaces {
3911 workspace
3912 .update(cx, |workspace, cx| {
3913 let handler_response = workspace.handle_follow(follower.project_id, cx);
3914 if response.views.is_empty() {
3915 response.views = handler_response.views;
3916 } else {
3917 response.views.extend_from_slice(&handler_response.views);
3918 }
3919
3920 if let Some(active_view_id) = handler_response.active_view_id.clone() {
3921 if response.active_view_id.is_none()
3922 || Some(workspace.project.downgrade()) == active_project
3923 {
3924 response.active_view_id = Some(active_view_id);
3925 }
3926 }
3927 })
3928 .ok();
3929 }
3930
3931 if let Err(ix) = this.followers.binary_search(&follower) {
3932 this.followers.insert(ix, follower);
3933 }
3934
3935 Ok(response)
3936 })?
3937 }
3938
3939 async fn handle_unfollow(
3940 model: Model<Self>,
3941 envelope: TypedEnvelope<proto::Unfollow>,
3942 _: Arc<Client>,
3943 mut cx: AsyncAppContext,
3944 ) -> Result<()> {
3945 model.update(&mut cx, |this, _| {
3946 let follower = Follower {
3947 project_id: envelope.payload.project_id,
3948 peer_id: envelope.original_sender_id()?,
3949 };
3950 if let Ok(ix) = this.followers.binary_search(&follower) {
3951 this.followers.remove(ix);
3952 }
3953 Ok(())
3954 })?
3955 }
3956
3957 async fn handle_update_followers(
3958 this: Model<Self>,
3959 envelope: TypedEnvelope<proto::UpdateFollowers>,
3960 _: Arc<Client>,
3961 mut cx: AsyncWindowContext,
3962 ) -> Result<()> {
3963 let leader_id = envelope.original_sender_id()?;
3964 let update = envelope.payload;
3965
3966 this.update(&mut cx, |this, cx| {
3967 for workspace in &this.workspaces {
3968 workspace.update(cx, |workspace, cx| {
3969 let project_id = workspace.project.read(cx).remote_id();
3970 if update.project_id != project_id && update.project_id.is_some() {
3971 return;
3972 }
3973 workspace.handle_update_followers(leader_id, update.clone(), cx);
3974 })?;
3975 }
3976 Ok(())
3977 })?
3978 }
3979}
3980
3981impl ViewId {
3982 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3983 Ok(Self {
3984 creator: message
3985 .creator
3986 .ok_or_else(|| anyhow!("creator is missing"))?,
3987 id: message.id,
3988 })
3989 }
3990
3991 pub(crate) fn to_proto(&self) -> proto::ViewId {
3992 proto::ViewId {
3993 creator: Some(self.creator),
3994 id: self.id,
3995 }
3996 }
3997}
3998
3999pub trait WorkspaceHandle {
4000 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4001}
4002
4003impl WorkspaceHandle for View<Workspace> {
4004 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4005 self.read(cx)
4006 .worktrees(cx)
4007 .flat_map(|worktree| {
4008 let worktree_id = worktree.read(cx).id();
4009 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4010 worktree_id,
4011 path: f.path.clone(),
4012 })
4013 })
4014 .collect::<Vec<_>>()
4015 }
4016}
4017
4018impl std::fmt::Debug for OpenPaths {
4019 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4020 f.debug_struct("OpenPaths")
4021 .field("paths", &self.paths)
4022 .finish()
4023 }
4024}
4025
4026pub struct WorkspaceCreated(pub WeakView<Workspace>);
4027
4028pub fn activate_workspace_for_project(
4029 cx: &mut AppContext,
4030 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4031) -> Option<WindowHandle<Workspace>> {
4032 for window in cx.windows() {
4033 let Some(workspace) = window.downcast::<Workspace>() else {
4034 continue;
4035 };
4036
4037 let predicate = workspace
4038 .update(cx, |workspace, cx| {
4039 let project = workspace.project.read(cx);
4040 if predicate(project, cx) {
4041 cx.activate_window();
4042 true
4043 } else {
4044 false
4045 }
4046 })
4047 .log_err()
4048 .unwrap_or(false);
4049
4050 if predicate {
4051 return Some(workspace);
4052 }
4053 }
4054
4055 None
4056}
4057
4058pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4059 DB.last_workspace().await.log_err().flatten()
4060}
4061
4062// async fn join_channel_internal(
4063// channel_id: u64,
4064// app_state: &Arc<AppState>,
4065// requesting_window: Option<WindowHandle<Workspace>>,
4066// active_call: &ModelHandle<ActiveCall>,
4067// cx: &mut AsyncAppContext,
4068// ) -> Result<bool> {
4069// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4070// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4071// return (false, None);
4072// };
4073
4074// let already_in_channel = room.channel_id() == Some(channel_id);
4075// let should_prompt = room.is_sharing_project()
4076// && room.remote_participants().len() > 0
4077// && !already_in_channel;
4078// let open_room = if already_in_channel {
4079// active_call.room().cloned()
4080// } else {
4081// None
4082// };
4083// (should_prompt, open_room)
4084// });
4085
4086// if let Some(room) = open_room {
4087// let task = room.update(cx, |room, cx| {
4088// if let Some((project, host)) = room.most_active_project(cx) {
4089// return Some(join_remote_project(project, host, app_state.clone(), cx));
4090// }
4091
4092// None
4093// });
4094// if let Some(task) = task {
4095// task.await?;
4096// }
4097// return anyhow::Ok(true);
4098// }
4099
4100// if should_prompt {
4101// if let Some(workspace) = requesting_window {
4102// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4103// let answer = window.prompt(
4104// PromptLevel::Warning,
4105// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4106// &["Yes, Join Channel", "Cancel"],
4107// cx,
4108// );
4109
4110// if let Some(mut answer) = answer {
4111// if answer.next().await == Some(1) {
4112// return Ok(false);
4113// }
4114// }
4115// } else {
4116// return Ok(false); // unreachable!() hopefully
4117// }
4118// } else {
4119// return Ok(false); // unreachable!() hopefully
4120// }
4121// }
4122
4123// let client = cx.read(|cx| active_call.read(cx).client());
4124
4125// let mut client_status = client.status();
4126
4127// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4128// 'outer: loop {
4129// let Some(status) = client_status.recv().await else {
4130// return Err(anyhow!("error connecting"));
4131// };
4132
4133// match status {
4134// Status::Connecting
4135// | Status::Authenticating
4136// | Status::Reconnecting
4137// | Status::Reauthenticating => continue,
4138// Status::Connected { .. } => break 'outer,
4139// Status::SignedOut => return Err(anyhow!("not signed in")),
4140// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4141// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4142// return Err(anyhow!("zed is offline"))
4143// }
4144// }
4145// }
4146
4147// let room = active_call
4148// .update(cx, |active_call, cx| {
4149// active_call.join_channel(channel_id, cx)
4150// })
4151// .await?;
4152
4153// room.update(cx, |room, _| room.room_update_completed())
4154// .await;
4155
4156// let task = room.update(cx, |room, cx| {
4157// if let Some((project, host)) = room.most_active_project(cx) {
4158// return Some(join_remote_project(project, host, app_state.clone(), cx));
4159// }
4160
4161// None
4162// });
4163// if let Some(task) = task {
4164// task.await?;
4165// return anyhow::Ok(true);
4166// }
4167// anyhow::Ok(false)
4168// }
4169
4170// pub fn join_channel(
4171// channel_id: u64,
4172// app_state: Arc<AppState>,
4173// requesting_window: Option<WindowHandle<Workspace>>,
4174// cx: &mut AppContext,
4175// ) -> Task<Result<()>> {
4176// let active_call = ActiveCall::global(cx);
4177// cx.spawn(|mut cx| async move {
4178// let result = join_channel_internal(
4179// channel_id,
4180// &app_state,
4181// requesting_window,
4182// &active_call,
4183// &mut cx,
4184// )
4185// .await;
4186
4187// // join channel succeeded, and opened a window
4188// if matches!(result, Ok(true)) {
4189// return anyhow::Ok(());
4190// }
4191
4192// if requesting_window.is_some() {
4193// return anyhow::Ok(());
4194// }
4195
4196// // find an existing workspace to focus and show call controls
4197// let mut active_window = activate_any_workspace_window(&mut cx);
4198// if active_window.is_none() {
4199// // no open workspaces, make one to show the error in (blergh)
4200// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4201// .await;
4202// }
4203
4204// active_window = activate_any_workspace_window(&mut cx);
4205// if active_window.is_none() {
4206// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4207// }
4208
4209// if let Err(err) = result {
4210// let prompt = active_window.unwrap().prompt(
4211// PromptLevel::Critical,
4212// &format!("Failed to join channel: {}", err),
4213// &["Ok"],
4214// &mut cx,
4215// );
4216// if let Some(mut prompt) = prompt {
4217// prompt.next().await;
4218// } else {
4219// return Err(err);
4220// }
4221// }
4222
4223// // return ok, we showed the error to the user.
4224// return anyhow::Ok(());
4225// })
4226// }
4227
4228// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4229// for window in cx.windows() {
4230// let found = window.update(cx, |cx| {
4231// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4232// if is_workspace {
4233// cx.activate_window();
4234// }
4235// is_workspace
4236// });
4237// if found == Some(true) {
4238// return Some(window);
4239// }
4240// }
4241// None
4242// }
4243
4244#[allow(clippy::type_complexity)]
4245pub fn open_paths(
4246 abs_paths: &[PathBuf],
4247 app_state: &Arc<AppState>,
4248 requesting_window: Option<WindowHandle<Workspace>>,
4249 cx: &mut AppContext,
4250) -> Task<
4251 anyhow::Result<(
4252 WindowHandle<Workspace>,
4253 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4254 )>,
4255> {
4256 let app_state = app_state.clone();
4257 let abs_paths = abs_paths.to_vec();
4258 // Open paths in existing workspace if possible
4259 let existing = activate_workspace_for_project(cx, {
4260 let abs_paths = abs_paths.clone();
4261 move |project, cx| project.contains_paths(&abs_paths, cx)
4262 });
4263 cx.spawn(move |mut cx| async move {
4264 if let Some(existing) = existing {
4265 // // Ok((
4266 // existing.clone(),
4267 // cx.update_window_root(&existing, |workspace, cx| {
4268 // workspace.open_paths(abs_paths, true, cx)
4269 // })?
4270 // .await,
4271 // ))
4272 todo!()
4273 } else {
4274 cx.update(move |cx| {
4275 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4276 })?
4277 .await
4278 }
4279 })
4280}
4281
4282pub fn open_new(
4283 app_state: &Arc<AppState>,
4284 cx: &mut AppContext,
4285 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4286) -> Task<()> {
4287 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4288 cx.spawn(|mut cx| async move {
4289 if let Some((workspace, opened_paths)) = task.await.log_err() {
4290 workspace
4291 .update(&mut cx, |workspace, cx| {
4292 if opened_paths.is_empty() {
4293 init(workspace, cx)
4294 }
4295 })
4296 .log_err();
4297 }
4298 })
4299}
4300
4301pub fn create_and_open_local_file(
4302 path: &'static Path,
4303 cx: &mut ViewContext<Workspace>,
4304 default_content: impl 'static + Send + FnOnce() -> Rope,
4305) -> Task<Result<Box<dyn ItemHandle>>> {
4306 cx.spawn(|workspace, mut cx| async move {
4307 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4308 if !fs.is_file(path).await {
4309 fs.create_file(path, Default::default()).await?;
4310 fs.save(path, &default_content(), Default::default())
4311 .await?;
4312 }
4313
4314 let mut items = workspace
4315 .update(&mut cx, |workspace, cx| {
4316 workspace.with_local_workspace(cx, |workspace, cx| {
4317 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4318 })
4319 })?
4320 .await?
4321 .await;
4322
4323 let item = items.pop().flatten();
4324 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4325 })
4326}
4327
4328// pub fn join_remote_project(
4329// project_id: u64,
4330// follow_user_id: u64,
4331// app_state: Arc<AppState>,
4332// cx: &mut AppContext,
4333// ) -> Task<Result<()>> {
4334// cx.spawn(|mut cx| async move {
4335// let windows = cx.windows();
4336// let existing_workspace = windows.into_iter().find_map(|window| {
4337// window.downcast::<Workspace>().and_then(|window| {
4338// window
4339// .read_root_with(&cx, |workspace, cx| {
4340// if workspace.project().read(cx).remote_id() == Some(project_id) {
4341// Some(cx.handle().downgrade())
4342// } else {
4343// None
4344// }
4345// })
4346// .unwrap_or(None)
4347// })
4348// });
4349
4350// let workspace = if let Some(existing_workspace) = existing_workspace {
4351// existing_workspace
4352// } else {
4353// let active_call = cx.read(ActiveCall::global);
4354// let room = active_call
4355// .read_with(&cx, |call, _| call.room().cloned())
4356// .ok_or_else(|| anyhow!("not in a call"))?;
4357// let project = room
4358// .update(&mut cx, |room, cx| {
4359// room.join_project(
4360// project_id,
4361// app_state.languages.clone(),
4362// app_state.fs.clone(),
4363// cx,
4364// )
4365// })
4366// .await?;
4367
4368// let window_bounds_override = window_bounds_env_override(&cx);
4369// let window = cx.add_window(
4370// (app_state.build_window_options)(
4371// window_bounds_override,
4372// None,
4373// cx.platform().as_ref(),
4374// ),
4375// |cx| Workspace::new(0, project, app_state.clone(), cx),
4376// );
4377// let workspace = window.root(&cx).unwrap();
4378// (app_state.initialize_workspace)(
4379// workspace.downgrade(),
4380// false,
4381// app_state.clone(),
4382// cx.clone(),
4383// )
4384// .await
4385// .log_err();
4386
4387// workspace.downgrade()
4388// };
4389
4390// workspace.window().activate(&mut cx);
4391// cx.platform().activate(true);
4392
4393// workspace.update(&mut cx, |workspace, cx| {
4394// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4395// let follow_peer_id = room
4396// .read(cx)
4397// .remote_participants()
4398// .iter()
4399// .find(|(_, participant)| participant.user.id == follow_user_id)
4400// .map(|(_, p)| p.peer_id)
4401// .or_else(|| {
4402// // If we couldn't follow the given user, follow the host instead.
4403// let collaborator = workspace
4404// .project()
4405// .read(cx)
4406// .collaborators()
4407// .values()
4408// .find(|collaborator| collaborator.replica_id == 0)?;
4409// Some(collaborator.peer_id)
4410// });
4411
4412// if let Some(follow_peer_id) = follow_peer_id {
4413// workspace
4414// .follow(follow_peer_id, cx)
4415// .map(|follow| follow.detach_and_log_err(cx));
4416// }
4417// }
4418// })?;
4419
4420// anyhow::Ok(())
4421// })
4422// }
4423
4424// pub fn restart(_: &Restart, cx: &mut AppContext) {
4425// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4426// cx.spawn(|mut cx| async move {
4427// let mut workspace_windows = cx
4428// .windows()
4429// .into_iter()
4430// .filter_map(|window| window.downcast::<Workspace>())
4431// .collect::<Vec<_>>();
4432
4433// // If multiple windows have unsaved changes, and need a save prompt,
4434// // prompt in the active window before switching to a different window.
4435// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4436
4437// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4438// let answer = window.prompt(
4439// PromptLevel::Info,
4440// "Are you sure you want to restart?",
4441// &["Restart", "Cancel"],
4442// &mut cx,
4443// );
4444
4445// if let Some(mut answer) = answer {
4446// let answer = answer.next().await;
4447// if answer != Some(0) {
4448// return Ok(());
4449// }
4450// }
4451// }
4452
4453// // If the user cancels any save prompt, then keep the app open.
4454// for window in workspace_windows {
4455// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4456// workspace.prepare_to_close(true, cx)
4457// }) {
4458// if !should_close.await? {
4459// return Ok(());
4460// }
4461// }
4462// }
4463// cx.platform().restart();
4464// anyhow::Ok(())
4465// })
4466// .detach_and_log_err(cx);
4467// }
4468
4469fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4470 let mut parts = value.split(',');
4471 let x: usize = parts.next()?.parse().ok()?;
4472 let y: usize = parts.next()?.parse().ok()?;
4473 Some(point((x as f64).into(), (y as f64).into()))
4474}
4475
4476fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4477 let mut parts = value.split(',');
4478 let width: usize = parts.next()?.parse().ok()?;
4479 let height: usize = parts.next()?.parse().ok()?;
4480 Some(size((width as f64).into(), (height as f64).into()))
4481}
4482
4483// #[cfg(test)]
4484// mod tests {
4485// use super::*;
4486// use crate::{
4487// dock::test::TestPanel,
4488// item::test::{TestItem, TestItemEvent, TestProjectItem},
4489// };
4490// use fs::FakeFs;
4491// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4492// use project::{Project, ProjectEntryId};
4493// use serde_json::json;
4494// use settings::SettingsStore;
4495// use std::{cell::RefCell, rc::Rc};
4496
4497// #[gpui::test]
4498// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4499// init_test(cx);
4500
4501// let fs = FakeFs::new(cx.background());
4502// let project = Project::test(fs, [], cx).await;
4503// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4504// let workspace = window.root(cx);
4505
4506// // Adding an item with no ambiguity renders the tab without detail.
4507// let item1 = window.build_view(cx, |_| {
4508// let mut item = TestItem::new();
4509// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4510// item
4511// });
4512// workspace.update(cx, |workspace, cx| {
4513// workspace.add_item(Box::new(item1.clone()), cx);
4514// });
4515// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4516
4517// // Adding an item that creates ambiguity increases the level of detail on
4518// // both tabs.
4519// let item2 = window.build_view(cx, |_| {
4520// let mut item = TestItem::new();
4521// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4522// item
4523// });
4524// workspace.update(cx, |workspace, cx| {
4525// workspace.add_item(Box::new(item2.clone()), cx);
4526// });
4527// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4528// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4529
4530// // Adding an item that creates ambiguity increases the level of detail only
4531// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4532// // we stop at the highest detail available.
4533// let item3 = window.build_view(cx, |_| {
4534// let mut item = TestItem::new();
4535// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4536// item
4537// });
4538// workspace.update(cx, |workspace, cx| {
4539// workspace.add_item(Box::new(item3.clone()), cx);
4540// });
4541// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4542// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4543// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4544// }
4545
4546// #[gpui::test]
4547// async fn test_tracking_active_path(cx: &mut TestAppContext) {
4548// init_test(cx);
4549
4550// let fs = FakeFs::new(cx.background());
4551// fs.insert_tree(
4552// "/root1",
4553// json!({
4554// "one.txt": "",
4555// "two.txt": "",
4556// }),
4557// )
4558// .await;
4559// fs.insert_tree(
4560// "/root2",
4561// json!({
4562// "three.txt": "",
4563// }),
4564// )
4565// .await;
4566
4567// let project = Project::test(fs, ["root1".as_ref()], cx).await;
4568// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4569// let workspace = window.root(cx);
4570// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4571// let worktree_id = project.read_with(cx, |project, cx| {
4572// project.worktrees().next().unwrap().read(cx).id()
4573// });
4574
4575// let item1 = window.build_view(cx, |cx| {
4576// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4577// });
4578// let item2 = window.build_view(cx, |cx| {
4579// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4580// });
4581
4582// // Add an item to an empty pane
4583// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4584// project.read_with(cx, |project, cx| {
4585// assert_eq!(
4586// project.active_entry(),
4587// project
4588// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4589// .map(|e| e.id)
4590// );
4591// });
4592// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4593
4594// // Add a second item to a non-empty pane
4595// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4596// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4597// project.read_with(cx, |project, cx| {
4598// assert_eq!(
4599// project.active_entry(),
4600// project
4601// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4602// .map(|e| e.id)
4603// );
4604// });
4605
4606// // Close the active item
4607// pane.update(cx, |pane, cx| {
4608// pane.close_active_item(&Default::default(), cx).unwrap()
4609// })
4610// .await
4611// .unwrap();
4612// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4613// project.read_with(cx, |project, cx| {
4614// assert_eq!(
4615// project.active_entry(),
4616// project
4617// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4618// .map(|e| e.id)
4619// );
4620// });
4621
4622// // Add a project folder
4623// project
4624// .update(cx, |project, cx| {
4625// project.find_or_create_local_worktree("/root2", true, cx)
4626// })
4627// .await
4628// .unwrap();
4629// assert_eq!(
4630// window.current_title(cx).as_deref(),
4631// Some("one.txt β root1, root2")
4632// );
4633
4634// // Remove a project folder
4635// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4636// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4637// }
4638
4639// #[gpui::test]
4640// async fn test_close_window(cx: &mut TestAppContext) {
4641// init_test(cx);
4642
4643// let fs = FakeFs::new(cx.background());
4644// fs.insert_tree("/root", json!({ "one": "" })).await;
4645
4646// let project = Project::test(fs, ["root".as_ref()], cx).await;
4647// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4648// let workspace = window.root(cx);
4649
4650// // When there are no dirty items, there's nothing to do.
4651// let item1 = window.build_view(cx, |_| TestItem::new());
4652// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4653// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4654// assert!(task.await.unwrap());
4655
4656// // When there are dirty untitled items, prompt to save each one. If the user
4657// // cancels any prompt, then abort.
4658// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
4659// let item3 = window.build_view(cx, |cx| {
4660// TestItem::new()
4661// .with_dirty(true)
4662// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4663// });
4664// workspace.update(cx, |w, cx| {
4665// w.add_item(Box::new(item2.clone()), cx);
4666// w.add_item(Box::new(item3.clone()), cx);
4667// });
4668// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4669// cx.foreground().run_until_parked();
4670// window.simulate_prompt_answer(2, cx); // cancel save all
4671// cx.foreground().run_until_parked();
4672// window.simulate_prompt_answer(2, cx); // cancel save all
4673// cx.foreground().run_until_parked();
4674// assert!(!window.has_pending_prompt(cx));
4675// assert!(!task.await.unwrap());
4676// }
4677
4678// #[gpui::test]
4679// async fn test_close_pane_items(cx: &mut TestAppContext) {
4680// init_test(cx);
4681
4682// let fs = FakeFs::new(cx.background());
4683
4684// let project = Project::test(fs, None, cx).await;
4685// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4686// let workspace = window.root(cx);
4687
4688// let item1 = window.build_view(cx, |cx| {
4689// TestItem::new()
4690// .with_dirty(true)
4691// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4692// });
4693// let item2 = window.build_view(cx, |cx| {
4694// TestItem::new()
4695// .with_dirty(true)
4696// .with_conflict(true)
4697// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4698// });
4699// let item3 = window.build_view(cx, |cx| {
4700// TestItem::new()
4701// .with_dirty(true)
4702// .with_conflict(true)
4703// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4704// });
4705// let item4 = window.build_view(cx, |cx| {
4706// TestItem::new()
4707// .with_dirty(true)
4708// .with_project_items(&[TestProjectItem::new_untitled(cx)])
4709// });
4710// let pane = workspace.update(cx, |workspace, cx| {
4711// workspace.add_item(Box::new(item1.clone()), cx);
4712// workspace.add_item(Box::new(item2.clone()), cx);
4713// workspace.add_item(Box::new(item3.clone()), cx);
4714// workspace.add_item(Box::new(item4.clone()), cx);
4715// workspace.active_pane().clone()
4716// });
4717
4718// let close_items = pane.update(cx, |pane, cx| {
4719// pane.activate_item(1, true, true, cx);
4720// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4721// let item1_id = item1.id();
4722// let item3_id = item3.id();
4723// let item4_id = item4.id();
4724// pane.close_items(cx, SaveIntent::Close, move |id| {
4725// [item1_id, item3_id, item4_id].contains(&id)
4726// })
4727// });
4728// cx.foreground().run_until_parked();
4729
4730// assert!(window.has_pending_prompt(cx));
4731// // Ignore "Save all" prompt
4732// window.simulate_prompt_answer(2, cx);
4733// cx.foreground().run_until_parked();
4734// // There's a prompt to save item 1.
4735// pane.read_with(cx, |pane, _| {
4736// assert_eq!(pane.items_len(), 4);
4737// assert_eq!(pane.active_item().unwrap().id(), item1.id());
4738// });
4739// // Confirm saving item 1.
4740// window.simulate_prompt_answer(0, cx);
4741// cx.foreground().run_until_parked();
4742
4743// // Item 1 is saved. There's a prompt to save item 3.
4744// pane.read_with(cx, |pane, cx| {
4745// assert_eq!(item1.read(cx).save_count, 1);
4746// assert_eq!(item1.read(cx).save_as_count, 0);
4747// assert_eq!(item1.read(cx).reload_count, 0);
4748// assert_eq!(pane.items_len(), 3);
4749// assert_eq!(pane.active_item().unwrap().id(), item3.id());
4750// });
4751// assert!(window.has_pending_prompt(cx));
4752
4753// // Cancel saving item 3.
4754// window.simulate_prompt_answer(1, cx);
4755// cx.foreground().run_until_parked();
4756
4757// // Item 3 is reloaded. There's a prompt to save item 4.
4758// pane.read_with(cx, |pane, cx| {
4759// assert_eq!(item3.read(cx).save_count, 0);
4760// assert_eq!(item3.read(cx).save_as_count, 0);
4761// assert_eq!(item3.read(cx).reload_count, 1);
4762// assert_eq!(pane.items_len(), 2);
4763// assert_eq!(pane.active_item().unwrap().id(), item4.id());
4764// });
4765// assert!(window.has_pending_prompt(cx));
4766
4767// // Confirm saving item 4.
4768// window.simulate_prompt_answer(0, cx);
4769// cx.foreground().run_until_parked();
4770
4771// // There's a prompt for a path for item 4.
4772// cx.simulate_new_path_selection(|_| Some(Default::default()));
4773// close_items.await.unwrap();
4774
4775// // The requested items are closed.
4776// pane.read_with(cx, |pane, cx| {
4777// assert_eq!(item4.read(cx).save_count, 0);
4778// assert_eq!(item4.read(cx).save_as_count, 1);
4779// assert_eq!(item4.read(cx).reload_count, 0);
4780// assert_eq!(pane.items_len(), 1);
4781// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4782// });
4783// }
4784
4785// #[gpui::test]
4786// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4787// init_test(cx);
4788
4789// let fs = FakeFs::new(cx.background());
4790
4791// let project = Project::test(fs, [], cx).await;
4792// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4793// let workspace = window.root(cx);
4794
4795// // Create several workspace items with single project entries, and two
4796// // workspace items with multiple project entries.
4797// let single_entry_items = (0..=4)
4798// .map(|project_entry_id| {
4799// window.build_view(cx, |cx| {
4800// TestItem::new()
4801// .with_dirty(true)
4802// .with_project_items(&[TestProjectItem::new(
4803// project_entry_id,
4804// &format!("{project_entry_id}.txt"),
4805// cx,
4806// )])
4807// })
4808// })
4809// .collect::<Vec<_>>();
4810// let item_2_3 = window.build_view(cx, |cx| {
4811// TestItem::new()
4812// .with_dirty(true)
4813// .with_singleton(false)
4814// .with_project_items(&[
4815// single_entry_items[2].read(cx).project_items[0].clone(),
4816// single_entry_items[3].read(cx).project_items[0].clone(),
4817// ])
4818// });
4819// let item_3_4 = window.build_view(cx, |cx| {
4820// TestItem::new()
4821// .with_dirty(true)
4822// .with_singleton(false)
4823// .with_project_items(&[
4824// single_entry_items[3].read(cx).project_items[0].clone(),
4825// single_entry_items[4].read(cx).project_items[0].clone(),
4826// ])
4827// });
4828
4829// // Create two panes that contain the following project entries:
4830// // left pane:
4831// // multi-entry items: (2, 3)
4832// // single-entry items: 0, 1, 2, 3, 4
4833// // right pane:
4834// // single-entry items: 1
4835// // multi-entry items: (3, 4)
4836// let left_pane = workspace.update(cx, |workspace, cx| {
4837// let left_pane = workspace.active_pane().clone();
4838// workspace.add_item(Box::new(item_2_3.clone()), cx);
4839// for item in single_entry_items {
4840// workspace.add_item(Box::new(item), cx);
4841// }
4842// left_pane.update(cx, |pane, cx| {
4843// pane.activate_item(2, true, true, cx);
4844// });
4845
4846// workspace
4847// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4848// .unwrap();
4849
4850// left_pane
4851// });
4852
4853// //Need to cause an effect flush in order to respect new focus
4854// workspace.update(cx, |workspace, cx| {
4855// workspace.add_item(Box::new(item_3_4.clone()), cx);
4856// cx.focus(&left_pane);
4857// });
4858
4859// // When closing all of the items in the left pane, we should be prompted twice:
4860// // once for project entry 0, and once for project entry 2. After those two
4861// // prompts, the task should complete.
4862
4863// let close = left_pane.update(cx, |pane, cx| {
4864// pane.close_items(cx, SaveIntent::Close, move |_| true)
4865// });
4866// cx.foreground().run_until_parked();
4867// // Discard "Save all" prompt
4868// window.simulate_prompt_answer(2, cx);
4869
4870// cx.foreground().run_until_parked();
4871// left_pane.read_with(cx, |pane, cx| {
4872// assert_eq!(
4873// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4874// &[ProjectEntryId::from_proto(0)]
4875// );
4876// });
4877// window.simulate_prompt_answer(0, cx);
4878
4879// cx.foreground().run_until_parked();
4880// left_pane.read_with(cx, |pane, cx| {
4881// assert_eq!(
4882// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4883// &[ProjectEntryId::from_proto(2)]
4884// );
4885// });
4886// window.simulate_prompt_answer(0, cx);
4887
4888// cx.foreground().run_until_parked();
4889// close.await.unwrap();
4890// left_pane.read_with(cx, |pane, _| {
4891// assert_eq!(pane.items_len(), 0);
4892// });
4893// }
4894
4895// #[gpui::test]
4896// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4897// init_test(cx);
4898
4899// let fs = FakeFs::new(cx.background());
4900
4901// let project = Project::test(fs, [], cx).await;
4902// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4903// let workspace = window.root(cx);
4904// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4905
4906// let item = window.build_view(cx, |cx| {
4907// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4908// });
4909// let item_id = item.id();
4910// workspace.update(cx, |workspace, cx| {
4911// workspace.add_item(Box::new(item.clone()), cx);
4912// });
4913
4914// // Autosave on window change.
4915// item.update(cx, |item, cx| {
4916// cx.update_global(|settings: &mut SettingsStore, cx| {
4917// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4918// settings.autosave = Some(AutosaveSetting::OnWindowChange);
4919// })
4920// });
4921// item.is_dirty = true;
4922// });
4923
4924// // Deactivating the window saves the file.
4925// window.simulate_deactivation(cx);
4926// deterministic.run_until_parked();
4927// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4928
4929// // Autosave on focus change.
4930// item.update(cx, |item, cx| {
4931// cx.focus_self();
4932// cx.update_global(|settings: &mut SettingsStore, cx| {
4933// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4934// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4935// })
4936// });
4937// item.is_dirty = true;
4938// });
4939
4940// // Blurring the item saves the file.
4941// item.update(cx, |_, cx| cx.blur());
4942// deterministic.run_until_parked();
4943// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4944
4945// // Deactivating the window still saves the file.
4946// window.simulate_activation(cx);
4947// item.update(cx, |item, cx| {
4948// cx.focus_self();
4949// item.is_dirty = true;
4950// });
4951// window.simulate_deactivation(cx);
4952
4953// deterministic.run_until_parked();
4954// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4955
4956// // Autosave after delay.
4957// item.update(cx, |item, cx| {
4958// cx.update_global(|settings: &mut SettingsStore, cx| {
4959// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4960// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4961// })
4962// });
4963// item.is_dirty = true;
4964// cx.emit(TestItemEvent::Edit);
4965// });
4966
4967// // Delay hasn't fully expired, so the file is still dirty and unsaved.
4968// deterministic.advance_clock(Duration::from_millis(250));
4969// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4970
4971// // After delay expires, the file is saved.
4972// deterministic.advance_clock(Duration::from_millis(250));
4973// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4974
4975// // Autosave on focus change, ensuring closing the tab counts as such.
4976// item.update(cx, |item, cx| {
4977// cx.update_global(|settings: &mut SettingsStore, cx| {
4978// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4979// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4980// })
4981// });
4982// item.is_dirty = true;
4983// });
4984
4985// pane.update(cx, |pane, cx| {
4986// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4987// })
4988// .await
4989// .unwrap();
4990// assert!(!window.has_pending_prompt(cx));
4991// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4992
4993// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4994// workspace.update(cx, |workspace, cx| {
4995// workspace.add_item(Box::new(item.clone()), cx);
4996// });
4997// item.update(cx, |item, cx| {
4998// item.project_items[0].update(cx, |item, _| {
4999// item.entry_id = None;
5000// });
5001// item.is_dirty = true;
5002// cx.blur();
5003// });
5004// deterministic.run_until_parked();
5005// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5006
5007// // Ensure autosave is prevented for deleted files also when closing the buffer.
5008// let _close_items = pane.update(cx, |pane, cx| {
5009// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5010// });
5011// deterministic.run_until_parked();
5012// assert!(window.has_pending_prompt(cx));
5013// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5014// }
5015
5016// #[gpui::test]
5017// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5018// init_test(cx);
5019
5020// let fs = FakeFs::new(cx.background());
5021
5022// let project = Project::test(fs, [], cx).await;
5023// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5024// let workspace = window.root(cx);
5025
5026// let item = window.build_view(cx, |cx| {
5027// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5028// });
5029// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5030// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5031// let toolbar_notify_count = Rc::new(RefCell::new(0));
5032
5033// workspace.update(cx, |workspace, cx| {
5034// workspace.add_item(Box::new(item.clone()), cx);
5035// let toolbar_notification_count = toolbar_notify_count.clone();
5036// cx.observe(&toolbar, move |_, _, _| {
5037// *toolbar_notification_count.borrow_mut() += 1
5038// })
5039// .detach();
5040// });
5041
5042// pane.read_with(cx, |pane, _| {
5043// assert!(!pane.can_navigate_backward());
5044// assert!(!pane.can_navigate_forward());
5045// });
5046
5047// item.update(cx, |item, cx| {
5048// item.set_state("one".to_string(), cx);
5049// });
5050
5051// // Toolbar must be notified to re-render the navigation buttons
5052// assert_eq!(*toolbar_notify_count.borrow(), 1);
5053
5054// pane.read_with(cx, |pane, _| {
5055// assert!(pane.can_navigate_backward());
5056// assert!(!pane.can_navigate_forward());
5057// });
5058
5059// workspace
5060// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5061// .await
5062// .unwrap();
5063
5064// assert_eq!(*toolbar_notify_count.borrow(), 3);
5065// pane.read_with(cx, |pane, _| {
5066// assert!(!pane.can_navigate_backward());
5067// assert!(pane.can_navigate_forward());
5068// });
5069// }
5070
5071// #[gpui::test]
5072// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5073// init_test(cx);
5074// let fs = FakeFs::new(cx.background());
5075
5076// let project = Project::test(fs, [], cx).await;
5077// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5078// let workspace = window.root(cx);
5079
5080// let panel = workspace.update(cx, |workspace, cx| {
5081// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5082// workspace.add_panel(panel.clone(), cx);
5083
5084// workspace
5085// .right_dock()
5086// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5087
5088// panel
5089// });
5090
5091// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5092// pane.update(cx, |pane, cx| {
5093// let item = cx.build_view(|_| TestItem::new());
5094// pane.add_item(Box::new(item), true, true, None, cx);
5095// });
5096
5097// // Transfer focus from center to panel
5098// workspace.update(cx, |workspace, cx| {
5099// workspace.toggle_panel_focus::<TestPanel>(cx);
5100// });
5101
5102// workspace.read_with(cx, |workspace, cx| {
5103// assert!(workspace.right_dock().read(cx).is_open());
5104// assert!(!panel.is_zoomed(cx));
5105// assert!(panel.has_focus(cx));
5106// });
5107
5108// // Transfer focus from panel to center
5109// workspace.update(cx, |workspace, cx| {
5110// workspace.toggle_panel_focus::<TestPanel>(cx);
5111// });
5112
5113// workspace.read_with(cx, |workspace, cx| {
5114// assert!(workspace.right_dock().read(cx).is_open());
5115// assert!(!panel.is_zoomed(cx));
5116// assert!(!panel.has_focus(cx));
5117// });
5118
5119// // Close the dock
5120// workspace.update(cx, |workspace, cx| {
5121// workspace.toggle_dock(DockPosition::Right, cx);
5122// });
5123
5124// workspace.read_with(cx, |workspace, cx| {
5125// assert!(!workspace.right_dock().read(cx).is_open());
5126// assert!(!panel.is_zoomed(cx));
5127// assert!(!panel.has_focus(cx));
5128// });
5129
5130// // Open the dock
5131// workspace.update(cx, |workspace, cx| {
5132// workspace.toggle_dock(DockPosition::Right, cx);
5133// });
5134
5135// workspace.read_with(cx, |workspace, cx| {
5136// assert!(workspace.right_dock().read(cx).is_open());
5137// assert!(!panel.is_zoomed(cx));
5138// assert!(panel.has_focus(cx));
5139// });
5140
5141// // Focus and zoom panel
5142// panel.update(cx, |panel, cx| {
5143// cx.focus_self();
5144// panel.set_zoomed(true, cx)
5145// });
5146
5147// workspace.read_with(cx, |workspace, cx| {
5148// assert!(workspace.right_dock().read(cx).is_open());
5149// assert!(panel.is_zoomed(cx));
5150// assert!(panel.has_focus(cx));
5151// });
5152
5153// // Transfer focus to the center closes the dock
5154// workspace.update(cx, |workspace, cx| {
5155// workspace.toggle_panel_focus::<TestPanel>(cx);
5156// });
5157
5158// workspace.read_with(cx, |workspace, cx| {
5159// assert!(!workspace.right_dock().read(cx).is_open());
5160// assert!(panel.is_zoomed(cx));
5161// assert!(!panel.has_focus(cx));
5162// });
5163
5164// // Transferring focus back to the panel keeps it zoomed
5165// workspace.update(cx, |workspace, cx| {
5166// workspace.toggle_panel_focus::<TestPanel>(cx);
5167// });
5168
5169// workspace.read_with(cx, |workspace, cx| {
5170// assert!(workspace.right_dock().read(cx).is_open());
5171// assert!(panel.is_zoomed(cx));
5172// assert!(panel.has_focus(cx));
5173// });
5174
5175// // Close the dock while it is zoomed
5176// workspace.update(cx, |workspace, cx| {
5177// workspace.toggle_dock(DockPosition::Right, cx)
5178// });
5179
5180// workspace.read_with(cx, |workspace, cx| {
5181// assert!(!workspace.right_dock().read(cx).is_open());
5182// assert!(panel.is_zoomed(cx));
5183// assert!(workspace.zoomed.is_none());
5184// assert!(!panel.has_focus(cx));
5185// });
5186
5187// // Opening the dock, when it's zoomed, retains focus
5188// workspace.update(cx, |workspace, cx| {
5189// workspace.toggle_dock(DockPosition::Right, cx)
5190// });
5191
5192// workspace.read_with(cx, |workspace, cx| {
5193// assert!(workspace.right_dock().read(cx).is_open());
5194// assert!(panel.is_zoomed(cx));
5195// assert!(workspace.zoomed.is_some());
5196// assert!(panel.has_focus(cx));
5197// });
5198
5199// // Unzoom and close the panel, zoom the active pane.
5200// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5201// workspace.update(cx, |workspace, cx| {
5202// workspace.toggle_dock(DockPosition::Right, cx)
5203// });
5204// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5205
5206// // Opening a dock unzooms the pane.
5207// workspace.update(cx, |workspace, cx| {
5208// workspace.toggle_dock(DockPosition::Right, cx)
5209// });
5210// workspace.read_with(cx, |workspace, cx| {
5211// let pane = pane.read(cx);
5212// assert!(!pane.is_zoomed());
5213// assert!(!pane.has_focus());
5214// assert!(workspace.right_dock().read(cx).is_open());
5215// assert!(workspace.zoomed.is_none());
5216// });
5217// }
5218
5219// #[gpui::test]
5220// async fn test_panels(cx: &mut gpui::TestAppContext) {
5221// init_test(cx);
5222// let fs = FakeFs::new(cx.background());
5223
5224// let project = Project::test(fs, [], cx).await;
5225// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5226// let workspace = window.root(cx);
5227
5228// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5229// // Add panel_1 on the left, panel_2 on the right.
5230// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5231// workspace.add_panel(panel_1.clone(), cx);
5232// workspace
5233// .left_dock()
5234// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5235// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5236// workspace.add_panel(panel_2.clone(), cx);
5237// workspace
5238// .right_dock()
5239// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5240
5241// let left_dock = workspace.left_dock();
5242// assert_eq!(
5243// left_dock.read(cx).visible_panel().unwrap().id(),
5244// panel_1.id()
5245// );
5246// assert_eq!(
5247// left_dock.read(cx).active_panel_size(cx).unwrap(),
5248// panel_1.size(cx)
5249// );
5250
5251// left_dock.update(cx, |left_dock, cx| {
5252// left_dock.resize_active_panel(Some(1337.), cx)
5253// });
5254// assert_eq!(
5255// workspace
5256// .right_dock()
5257// .read(cx)
5258// .visible_panel()
5259// .unwrap()
5260// .id(),
5261// panel_2.id()
5262// );
5263
5264// (panel_1, panel_2)
5265// });
5266
5267// // Move panel_1 to the right
5268// panel_1.update(cx, |panel_1, cx| {
5269// panel_1.set_position(DockPosition::Right, cx)
5270// });
5271
5272// workspace.update(cx, |workspace, cx| {
5273// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5274// // Since it was the only panel on the left, the left dock should now be closed.
5275// assert!(!workspace.left_dock().read(cx).is_open());
5276// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5277// let right_dock = workspace.right_dock();
5278// assert_eq!(
5279// right_dock.read(cx).visible_panel().unwrap().id(),
5280// panel_1.id()
5281// );
5282// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5283
5284// // Now we move panel_2Β to the left
5285// panel_2.set_position(DockPosition::Left, cx);
5286// });
5287
5288// workspace.update(cx, |workspace, cx| {
5289// // Since panel_2 was not visible on the right, we don't open the left dock.
5290// assert!(!workspace.left_dock().read(cx).is_open());
5291// // And the right dock is unaffected in it's displaying of panel_1
5292// assert!(workspace.right_dock().read(cx).is_open());
5293// assert_eq!(
5294// workspace
5295// .right_dock()
5296// .read(cx)
5297// .visible_panel()
5298// .unwrap()
5299// .id(),
5300// panel_1.id()
5301// );
5302// });
5303
5304// // Move panel_1 back to the left
5305// panel_1.update(cx, |panel_1, cx| {
5306// panel_1.set_position(DockPosition::Left, cx)
5307// });
5308
5309// workspace.update(cx, |workspace, cx| {
5310// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5311// let left_dock = workspace.left_dock();
5312// assert!(left_dock.read(cx).is_open());
5313// assert_eq!(
5314// left_dock.read(cx).visible_panel().unwrap().id(),
5315// panel_1.id()
5316// );
5317// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5318// // And right the dock should be closed as it no longer has any panels.
5319// assert!(!workspace.right_dock().read(cx).is_open());
5320
5321// // Now we move panel_1 to the bottom
5322// panel_1.set_position(DockPosition::Bottom, cx);
5323// });
5324
5325// workspace.update(cx, |workspace, cx| {
5326// // Since panel_1 was visible on the left, we close the left dock.
5327// assert!(!workspace.left_dock().read(cx).is_open());
5328// // The bottom dock is sized based on the panel's default size,
5329// // since the panel orientation changed from vertical to horizontal.
5330// let bottom_dock = workspace.bottom_dock();
5331// assert_eq!(
5332// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5333// panel_1.size(cx),
5334// );
5335// // Close bottom dock and move panel_1 back to the left.
5336// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5337// panel_1.set_position(DockPosition::Left, cx);
5338// });
5339
5340// // Emit activated event on panel 1
5341// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5342
5343// // Now the left dock is open and panel_1 is active and focused.
5344// workspace.read_with(cx, |workspace, cx| {
5345// let left_dock = workspace.left_dock();
5346// assert!(left_dock.read(cx).is_open());
5347// assert_eq!(
5348// left_dock.read(cx).visible_panel().unwrap().id(),
5349// panel_1.id()
5350// );
5351// assert!(panel_1.is_focused(cx));
5352// });
5353
5354// // Emit closed event on panel 2, which is not active
5355// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5356
5357// // Wo don't close the left dock, because panel_2 wasn't the active panel
5358// workspace.read_with(cx, |workspace, cx| {
5359// let left_dock = workspace.left_dock();
5360// assert!(left_dock.read(cx).is_open());
5361// assert_eq!(
5362// left_dock.read(cx).visible_panel().unwrap().id(),
5363// panel_1.id()
5364// );
5365// });
5366
5367// // Emitting a ZoomIn event shows the panel as zoomed.
5368// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5369// workspace.read_with(cx, |workspace, _| {
5370// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5371// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5372// });
5373
5374// // Move panel to another dock while it is zoomed
5375// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5376// workspace.read_with(cx, |workspace, _| {
5377// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5378// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5379// });
5380
5381// // If focus is transferred to another view that's not a panel or another pane, we still show
5382// // the panel as zoomed.
5383// let focus_receiver = window.build_view(cx, |_| EmptyView);
5384// focus_receiver.update(cx, |_, cx| cx.focus_self());
5385// workspace.read_with(cx, |workspace, _| {
5386// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5387// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5388// });
5389
5390// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5391// workspace.update(cx, |_, cx| cx.focus_self());
5392// workspace.read_with(cx, |workspace, _| {
5393// assert_eq!(workspace.zoomed, None);
5394// assert_eq!(workspace.zoomed_position, None);
5395// });
5396
5397// // If focus is transferred again to another view that's not a panel or a pane, we won't
5398// // show the panel as zoomed because it wasn't zoomed before.
5399// focus_receiver.update(cx, |_, cx| cx.focus_self());
5400// workspace.read_with(cx, |workspace, _| {
5401// assert_eq!(workspace.zoomed, None);
5402// assert_eq!(workspace.zoomed_position, None);
5403// });
5404
5405// // When focus is transferred back to the panel, it is zoomed again.
5406// panel_1.update(cx, |_, cx| cx.focus_self());
5407// workspace.read_with(cx, |workspace, _| {
5408// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5409// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5410// });
5411
5412// // Emitting a ZoomOut event unzooms the panel.
5413// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5414// workspace.read_with(cx, |workspace, _| {
5415// assert_eq!(workspace.zoomed, None);
5416// assert_eq!(workspace.zoomed_position, None);
5417// });
5418
5419// // Emit closed event on panel 1, which is active
5420// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5421
5422// // Now the left dock is closed, because panel_1 was the active panel
5423// workspace.read_with(cx, |workspace, cx| {
5424// let right_dock = workspace.right_dock();
5425// assert!(!right_dock.read(cx).is_open());
5426// });
5427// }
5428
5429// pub fn init_test(cx: &mut TestAppContext) {
5430// cx.foreground().forbid_parking();
5431// cx.update(|cx| {
5432// cx.set_global(SettingsStore::test(cx));
5433// theme::init((), cx);
5434// language::init(cx);
5435// crate::init_settings(cx);
5436// Project::init_settings(cx);
5437// });
5438// }
5439// }