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