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