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