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