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