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