workspace.rs

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