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