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