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