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