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            let item = state
3046                .active_view_id
3047                .and_then(|id| state.items_by_leader_view_id.get(&id));
3048            let shared_screen = self.shared_screen_for_peer(leader_id, pane, cx);
3049
3050            if leader_in_this_app {
3051                if let Some(item) = item {
3052                    if leader_in_this_project || !item.is_project_item(cx) {
3053                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3054                    }
3055                } else if let Some(shared_screen) = shared_screen {
3056                    items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3057                }
3058            } else if let Some(shared_screen) = shared_screen {
3059                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3060            }
3061        }
3062
3063        for (pane, item) in items_to_activate {
3064            let pane_was_focused = pane.read(cx).has_focus();
3065            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3066                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3067            } else {
3068                pane.update(cx, |pane, cx| {
3069                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3070                });
3071            }
3072
3073            if pane_was_focused {
3074                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3075            }
3076        }
3077
3078        None
3079    }
3080
3081    fn shared_screen_for_peer(
3082        &self,
3083        peer_id: PeerId,
3084        pane: &ViewHandle<Pane>,
3085        cx: &mut ViewContext<Self>,
3086    ) -> Option<ViewHandle<SharedScreen>> {
3087        let call = self.active_call()?;
3088        let room = call.read(cx).room()?.read(cx);
3089        let participant = room.remote_participant_for_peer_id(peer_id)?;
3090        let track = participant.video_tracks.values().next()?.clone();
3091        let user = participant.user.clone();
3092
3093        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3094            if item.read(cx).peer_id == peer_id {
3095                return Some(item);
3096            }
3097        }
3098
3099        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3100    }
3101
3102    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3103        if active {
3104            cx.background()
3105                .spawn(persistence::DB.update_timestamp(self.database_id()))
3106                .detach();
3107        } else {
3108            for pane in &self.panes {
3109                pane.update(cx, |pane, cx| {
3110                    if let Some(item) = pane.active_item() {
3111                        item.workspace_deactivated(cx);
3112                    }
3113                    if matches!(
3114                        settings::get::<WorkspaceSettings>(cx).autosave,
3115                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3116                    ) {
3117                        for item in pane.items() {
3118                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3119                                .detach_and_log_err(cx);
3120                        }
3121                    }
3122                });
3123            }
3124        }
3125    }
3126
3127    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3128        self.active_call.as_ref().map(|(call, _)| call)
3129    }
3130
3131    fn on_active_call_event(
3132        &mut self,
3133        _: ModelHandle<ActiveCall>,
3134        event: &call::room::Event,
3135        cx: &mut ViewContext<Self>,
3136    ) {
3137        match event {
3138            call::room::Event::ParticipantLocationChanged { participant_id }
3139            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3140                self.leader_updated(*participant_id, cx);
3141            }
3142            _ => {}
3143        }
3144    }
3145
3146    pub fn database_id(&self) -> WorkspaceId {
3147        self.database_id
3148    }
3149
3150    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3151        let project = self.project().read(cx);
3152
3153        if project.is_local() {
3154            Some(
3155                project
3156                    .visible_worktrees(cx)
3157                    .map(|worktree| worktree.read(cx).abs_path())
3158                    .collect::<Vec<_>>()
3159                    .into(),
3160            )
3161        } else {
3162            None
3163        }
3164    }
3165
3166    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3167        match member {
3168            Member::Axis(PaneAxis { members, .. }) => {
3169                for child in members.iter() {
3170                    self.remove_panes(child.clone(), cx)
3171                }
3172            }
3173            Member::Pane(pane) => {
3174                self.force_remove_pane(&pane, cx);
3175            }
3176        }
3177    }
3178
3179    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3180        self.panes.retain(|p| p != pane);
3181        cx.focus(self.panes.last().unwrap());
3182        if self.last_active_center_pane == Some(pane.downgrade()) {
3183            self.last_active_center_pane = None;
3184        }
3185        cx.notify();
3186    }
3187
3188    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3189        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3190            cx.background().timer(Duration::from_millis(100)).await;
3191            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3192                .ok();
3193        }));
3194    }
3195
3196    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3197        fn serialize_pane_handle(
3198            pane_handle: &ViewHandle<Pane>,
3199            cx: &AppContext,
3200        ) -> SerializedPane {
3201            let (items, active) = {
3202                let pane = pane_handle.read(cx);
3203                let active_item_id = pane.active_item().map(|item| item.id());
3204                (
3205                    pane.items()
3206                        .filter_map(|item_handle| {
3207                            Some(SerializedItem {
3208                                kind: Arc::from(item_handle.serialized_item_kind()?),
3209                                item_id: item_handle.id(),
3210                                active: Some(item_handle.id()) == active_item_id,
3211                            })
3212                        })
3213                        .collect::<Vec<_>>(),
3214                    pane.has_focus(),
3215                )
3216            };
3217
3218            SerializedPane::new(items, active)
3219        }
3220
3221        fn build_serialized_pane_group(
3222            pane_group: &Member,
3223            cx: &AppContext,
3224        ) -> SerializedPaneGroup {
3225            match pane_group {
3226                Member::Axis(PaneAxis {
3227                    axis,
3228                    members,
3229                    flexes,
3230                    bounding_boxes: _,
3231                }) => SerializedPaneGroup::Group {
3232                    axis: *axis,
3233                    children: members
3234                        .iter()
3235                        .map(|member| build_serialized_pane_group(member, cx))
3236                        .collect::<Vec<_>>(),
3237                    flexes: Some(flexes.borrow().clone()),
3238                },
3239                Member::Pane(pane_handle) => {
3240                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3241                }
3242            }
3243        }
3244
3245        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3246            let left_dock = this.left_dock.read(cx);
3247            let left_visible = left_dock.is_open();
3248            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3249                Some(
3250                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3251                        .to_string(),
3252                )
3253            });
3254            let left_dock_zoom = left_dock
3255                .visible_panel()
3256                .map(|panel| panel.is_zoomed(cx))
3257                .unwrap_or(false);
3258
3259            let right_dock = this.right_dock.read(cx);
3260            let right_visible = right_dock.is_open();
3261            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3262                Some(
3263                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3264                        .to_string(),
3265                )
3266            });
3267            let right_dock_zoom = right_dock
3268                .visible_panel()
3269                .map(|panel| panel.is_zoomed(cx))
3270                .unwrap_or(false);
3271
3272            let bottom_dock = this.bottom_dock.read(cx);
3273            let bottom_visible = bottom_dock.is_open();
3274            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3275                Some(
3276                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3277                        .to_string(),
3278                )
3279            });
3280            let bottom_dock_zoom = bottom_dock
3281                .visible_panel()
3282                .map(|panel| panel.is_zoomed(cx))
3283                .unwrap_or(false);
3284
3285            DockStructure {
3286                left: DockData {
3287                    visible: left_visible,
3288                    active_panel: left_active_panel,
3289                    zoom: left_dock_zoom,
3290                },
3291                right: DockData {
3292                    visible: right_visible,
3293                    active_panel: right_active_panel,
3294                    zoom: right_dock_zoom,
3295                },
3296                bottom: DockData {
3297                    visible: bottom_visible,
3298                    active_panel: bottom_active_panel,
3299                    zoom: bottom_dock_zoom,
3300                },
3301            }
3302        }
3303
3304        if let Some(location) = self.location(cx) {
3305            // Load bearing special case:
3306            //  - with_local_workspace() relies on this to not have other stuff open
3307            //    when you open your log
3308            if !location.paths().is_empty() {
3309                let center_group = build_serialized_pane_group(&self.center.root, cx);
3310                let docks = build_serialized_docks(self, cx);
3311
3312                let serialized_workspace = SerializedWorkspace {
3313                    id: self.database_id,
3314                    location,
3315                    center_group,
3316                    bounds: Default::default(),
3317                    display: Default::default(),
3318                    docks,
3319                };
3320
3321                cx.background()
3322                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3323                    .detach();
3324            }
3325        }
3326    }
3327
3328    pub(crate) fn load_workspace(
3329        workspace: WeakViewHandle<Workspace>,
3330        serialized_workspace: SerializedWorkspace,
3331        paths_to_open: Vec<Option<ProjectPath>>,
3332        cx: &mut AppContext,
3333    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3334        cx.spawn(|mut cx| async move {
3335            let result = async_iife! {{
3336                let (project, old_center_pane) =
3337                workspace.read_with(&cx, |workspace, _| {
3338                    (
3339                        workspace.project().clone(),
3340                        workspace.last_active_center_pane.clone(),
3341                    )
3342                })?;
3343
3344                let mut center_items = None;
3345                let mut center_group = None;
3346                // Traverse the splits tree and add to things
3347                if let Some((group, active_pane, items)) = serialized_workspace
3348                        .center_group
3349                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3350                        .await {
3351                    center_items = Some(items);
3352                    center_group = Some((group, active_pane))
3353                }
3354
3355                let resulting_list = cx.read(|cx| {
3356                    let mut opened_items = center_items
3357                        .unwrap_or_default()
3358                        .into_iter()
3359                        .filter_map(|item| {
3360                            let item = item?;
3361                            let project_path = item.project_path(cx)?;
3362                            Some((project_path, item))
3363                        })
3364                        .collect::<HashMap<_, _>>();
3365
3366                    paths_to_open
3367                        .into_iter()
3368                        .map(|path_to_open| {
3369                            path_to_open.map(|path_to_open| {
3370                                Ok(opened_items.remove(&path_to_open))
3371                            })
3372                            .transpose()
3373                            .map(|item| item.flatten())
3374                            .transpose()
3375                        })
3376                        .collect::<Vec<_>>()
3377                });
3378
3379                // Remove old panes from workspace panes list
3380                workspace.update(&mut cx, |workspace, cx| {
3381                    if let Some((center_group, active_pane)) = center_group {
3382                        workspace.remove_panes(workspace.center.root.clone(), cx);
3383
3384                        // Swap workspace center group
3385                        workspace.center = PaneGroup::with_root(center_group);
3386
3387                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3388                        cx.focus_self();
3389
3390                        if let Some(active_pane) = active_pane {
3391                            cx.focus(&active_pane);
3392                        } else {
3393                            cx.focus(workspace.panes.last().unwrap());
3394                        }
3395                    } else {
3396                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3397                        if let Some(old_center_handle) = old_center_handle {
3398                            cx.focus(&old_center_handle)
3399                        } else {
3400                            cx.focus_self()
3401                        }
3402                    }
3403
3404                    let docks = serialized_workspace.docks;
3405                    workspace.left_dock.update(cx, |dock, cx| {
3406                        dock.set_open(docks.left.visible, cx);
3407                        if let Some(active_panel) = docks.left.active_panel {
3408                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3409                                dock.activate_panel(ix, cx);
3410                            }
3411                        }
3412                                dock.active_panel()
3413                                    .map(|panel| {
3414                                        panel.set_zoomed(docks.left.zoom, cx)
3415                                    });
3416                                if docks.left.visible && docks.left.zoom {
3417                                    cx.focus_self()
3418                                }
3419                    });
3420                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3421                    workspace.right_dock.update(cx, |dock, cx| {
3422                        dock.set_open(docks.right.visible, cx);
3423                        if let Some(active_panel) = docks.right.active_panel {
3424                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3425                                dock.activate_panel(ix, cx);
3426
3427                            }
3428                        }
3429                                dock.active_panel()
3430                                    .map(|panel| {
3431                                        panel.set_zoomed(docks.right.zoom, cx)
3432                                    });
3433
3434                                if docks.right.visible && docks.right.zoom {
3435                                    cx.focus_self()
3436                                }
3437                    });
3438                    workspace.bottom_dock.update(cx, |dock, cx| {
3439                        dock.set_open(docks.bottom.visible, cx);
3440                        if let Some(active_panel) = docks.bottom.active_panel {
3441                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3442                                dock.activate_panel(ix, cx);
3443                            }
3444                        }
3445
3446                        dock.active_panel()
3447                            .map(|panel| {
3448                                panel.set_zoomed(docks.bottom.zoom, cx)
3449                            });
3450
3451                        if docks.bottom.visible && docks.bottom.zoom {
3452                            cx.focus_self()
3453                        }
3454                    });
3455
3456
3457                    cx.notify();
3458                })?;
3459
3460                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3461                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3462
3463                Ok::<_, anyhow::Error>(resulting_list)
3464            }};
3465
3466            result.await.unwrap_or_default()
3467        })
3468    }
3469
3470    #[cfg(any(test, feature = "test-support"))]
3471    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3472        let client = project.read(cx).client();
3473        let user_store = project.read(cx).user_store();
3474
3475        let channel_store =
3476            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3477        let app_state = Arc::new(AppState {
3478            languages: project.read(cx).languages().clone(),
3479            client,
3480            user_store,
3481            channel_store,
3482            fs: project.read(cx).fs().clone(),
3483            build_window_options: |_, _, _| Default::default(),
3484            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3485            background_actions: || &[],
3486        });
3487        Self::new(0, project, app_state, cx)
3488    }
3489
3490    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3491        let dock = match position {
3492            DockPosition::Left => &self.left_dock,
3493            DockPosition::Right => &self.right_dock,
3494            DockPosition::Bottom => &self.bottom_dock,
3495        };
3496        let active_panel = dock.read(cx).visible_panel()?;
3497        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3498            dock.read(cx).render_placeholder(cx)
3499        } else {
3500            ChildView::new(dock, cx).into_any()
3501        };
3502
3503        Some(
3504            element
3505                .constrained()
3506                .dynamically(move |constraint, _, cx| match position {
3507                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3508                        Vector2F::new(20., constraint.min.y()),
3509                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3510                    ),
3511                    DockPosition::Bottom => SizeConstraint::new(
3512                        Vector2F::new(constraint.min.x(), 20.),
3513                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3514                    ),
3515                })
3516                .into_any(),
3517        )
3518    }
3519}
3520
3521fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3522    ZED_WINDOW_POSITION
3523        .zip(*ZED_WINDOW_SIZE)
3524        .map(|(position, size)| {
3525            WindowBounds::Fixed(RectF::new(
3526                cx.platform().screens()[0].bounds().origin() + position,
3527                size,
3528            ))
3529        })
3530}
3531
3532async fn open_items(
3533    serialized_workspace: Option<SerializedWorkspace>,
3534    workspace: &WeakViewHandle<Workspace>,
3535    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3536    app_state: Arc<AppState>,
3537    mut cx: AsyncAppContext,
3538) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3539    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3540
3541    if let Some(serialized_workspace) = serialized_workspace {
3542        let workspace = workspace.clone();
3543        let restored_items = cx
3544            .update(|cx| {
3545                Workspace::load_workspace(
3546                    workspace,
3547                    serialized_workspace,
3548                    project_paths_to_open
3549                        .iter()
3550                        .map(|(_, project_path)| project_path)
3551                        .cloned()
3552                        .collect(),
3553                    cx,
3554                )
3555            })
3556            .await;
3557
3558        let restored_project_paths = cx.read(|cx| {
3559            restored_items
3560                .iter()
3561                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3562                .collect::<HashSet<_>>()
3563        });
3564
3565        opened_items = restored_items;
3566        project_paths_to_open
3567            .iter_mut()
3568            .for_each(|(_, project_path)| {
3569                if let Some(project_path_to_open) = project_path {
3570                    if restored_project_paths.contains(project_path_to_open) {
3571                        *project_path = None;
3572                    }
3573                }
3574            });
3575    } else {
3576        for _ in 0..project_paths_to_open.len() {
3577            opened_items.push(None);
3578        }
3579    }
3580    assert!(opened_items.len() == project_paths_to_open.len());
3581
3582    let tasks =
3583        project_paths_to_open
3584            .into_iter()
3585            .enumerate()
3586            .map(|(i, (abs_path, project_path))| {
3587                let workspace = workspace.clone();
3588                cx.spawn(|mut cx| {
3589                    let fs = app_state.fs.clone();
3590                    async move {
3591                        let file_project_path = project_path?;
3592                        if fs.is_file(&abs_path).await {
3593                            Some((
3594                                i,
3595                                workspace
3596                                    .update(&mut cx, |workspace, cx| {
3597                                        workspace.open_path(file_project_path, None, true, cx)
3598                                    })
3599                                    .log_err()?
3600                                    .await,
3601                            ))
3602                        } else {
3603                            None
3604                        }
3605                    }
3606                })
3607            });
3608
3609    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3610        .await
3611        .into_iter()
3612    {
3613        if let Some((i, path_open_result)) = maybe_opened_path {
3614            opened_items[i] = Some(path_open_result);
3615        }
3616    }
3617
3618    opened_items
3619}
3620
3621fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3622    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3623    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3624    const MESSAGE_ID: usize = 2;
3625
3626    if workspace
3627        .read_with(cx, |workspace, cx| {
3628            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3629        })
3630        .unwrap_or(false)
3631    {
3632        return;
3633    }
3634
3635    if db::kvp::KEY_VALUE_STORE
3636        .read_kvp(NEW_DOCK_HINT_KEY)
3637        .ok()
3638        .flatten()
3639        .is_some()
3640    {
3641        if !workspace
3642            .read_with(cx, |workspace, cx| {
3643                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3644            })
3645            .unwrap_or(false)
3646        {
3647            cx.update(|cx| {
3648                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3649                    let entry = tracker
3650                        .entry(TypeId::of::<MessageNotification>())
3651                        .or_default();
3652                    if !entry.contains(&MESSAGE_ID) {
3653                        entry.push(MESSAGE_ID);
3654                    }
3655                });
3656            });
3657        }
3658
3659        return;
3660    }
3661
3662    cx.spawn(|_| async move {
3663        db::kvp::KEY_VALUE_STORE
3664            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3665            .await
3666            .ok();
3667    })
3668    .detach();
3669
3670    workspace
3671        .update(cx, |workspace, cx| {
3672            workspace.show_notification_once(2, cx, |cx| {
3673                cx.add_view(|_| {
3674                    MessageNotification::new_element(|text, _| {
3675                        Text::new(
3676                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3677                            text,
3678                        )
3679                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3680                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3681                                .theme
3682                                .editor
3683                                .document_highlight_read_background;
3684
3685                            cx.scene().push_quad(gpui::Quad {
3686                                bounds,
3687                                background: Some(code_span_background_color),
3688                                border: Default::default(),
3689                                corner_radii: (2.0).into(),
3690                            })
3691                        })
3692                        .into_any()
3693                    })
3694                    .with_click_message("Read more about the new panel system")
3695                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3696                })
3697            })
3698        })
3699        .ok();
3700}
3701
3702fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3703    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3704
3705    workspace
3706        .update(cx, |workspace, cx| {
3707            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3708                workspace.show_notification_once(0, cx, |cx| {
3709                    cx.add_view(|_| {
3710                        MessageNotification::new("Failed to load the database file.")
3711                            .with_click_message("Click to let us know about this error")
3712                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3713                    })
3714                });
3715            }
3716        })
3717        .log_err();
3718}
3719
3720impl Entity for Workspace {
3721    type Event = Event;
3722}
3723
3724impl View for Workspace {
3725    fn ui_name() -> &'static str {
3726        "Workspace"
3727    }
3728
3729    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3730        let theme = theme::current(cx).clone();
3731        Stack::new()
3732            .with_child(
3733                Flex::column()
3734                    .with_child(self.render_titlebar(&theme, cx))
3735                    .with_child(
3736                        Stack::new()
3737                            .with_child({
3738                                let project = self.project.clone();
3739                                Flex::row()
3740                                    .with_children(self.render_dock(DockPosition::Left, cx))
3741                                    .with_child(
3742                                        Flex::column()
3743                                            .with_child(
3744                                                FlexItem::new(
3745                                                    self.center.render(
3746                                                        &project,
3747                                                        &theme,
3748                                                        &self.follower_states_by_leader,
3749                                                        self.active_call(),
3750                                                        self.active_pane(),
3751                                                        self.zoomed
3752                                                            .as_ref()
3753                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3754                                                            .as_ref(),
3755                                                        &self.app_state,
3756                                                        cx,
3757                                                    ),
3758                                                )
3759                                                .flex(1., true),
3760                                            )
3761                                            .with_children(
3762                                                self.render_dock(DockPosition::Bottom, cx),
3763                                            )
3764                                            .flex(1., true),
3765                                    )
3766                                    .with_children(self.render_dock(DockPosition::Right, cx))
3767                            })
3768                            .with_child(Overlay::new(
3769                                Stack::new()
3770                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3771                                        enum ZoomBackground {}
3772                                        let zoomed = zoomed.upgrade(cx)?;
3773
3774                                        let mut foreground_style =
3775                                            theme.workspace.zoomed_pane_foreground;
3776                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3777                                            foreground_style =
3778                                                theme.workspace.zoomed_panel_foreground;
3779                                            let margin = foreground_style.margin.top;
3780                                            let border = foreground_style.border.top;
3781
3782                                            // Only include a margin and border on the opposite side.
3783                                            foreground_style.margin.top = 0.;
3784                                            foreground_style.margin.left = 0.;
3785                                            foreground_style.margin.bottom = 0.;
3786                                            foreground_style.margin.right = 0.;
3787                                            foreground_style.border.top = false;
3788                                            foreground_style.border.left = false;
3789                                            foreground_style.border.bottom = false;
3790                                            foreground_style.border.right = false;
3791                                            match zoomed_dock_position {
3792                                                DockPosition::Left => {
3793                                                    foreground_style.margin.right = margin;
3794                                                    foreground_style.border.right = border;
3795                                                }
3796                                                DockPosition::Right => {
3797                                                    foreground_style.margin.left = margin;
3798                                                    foreground_style.border.left = border;
3799                                                }
3800                                                DockPosition::Bottom => {
3801                                                    foreground_style.margin.top = margin;
3802                                                    foreground_style.border.top = border;
3803                                                }
3804                                            }
3805                                        }
3806
3807                                        Some(
3808                                            ChildView::new(&zoomed, cx)
3809                                                .contained()
3810                                                .with_style(foreground_style)
3811                                                .aligned()
3812                                                .contained()
3813                                                .with_style(theme.workspace.zoomed_background)
3814                                                .mouse::<ZoomBackground>(0)
3815                                                .capture_all()
3816                                                .on_down(
3817                                                    MouseButton::Left,
3818                                                    |_, this: &mut Self, cx| {
3819                                                        this.zoom_out(cx);
3820                                                    },
3821                                                ),
3822                                        )
3823                                    }))
3824                                    .with_children(self.modal.as_ref().map(|modal| {
3825                                        // Prevent clicks within the modal from falling
3826                                        // through to the rest of the workspace.
3827                                        enum ModalBackground {}
3828                                        MouseEventHandler::new::<ModalBackground, _>(
3829                                            0,
3830                                            cx,
3831                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3832                                        )
3833                                        .on_click(MouseButton::Left, |_, _, _| {})
3834                                        .contained()
3835                                        .with_style(theme.workspace.modal)
3836                                        .aligned()
3837                                        .top()
3838                                    }))
3839                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3840                            ))
3841                            .provide_resize_bounds::<WorkspaceBounds>()
3842                            .flex(1.0, true),
3843                    )
3844                    .with_child(ChildView::new(&self.status_bar, cx))
3845                    .contained()
3846                    .with_background_color(theme.workspace.background),
3847            )
3848            .with_children(DragAndDrop::render(cx))
3849            .with_children(self.render_disconnected_overlay(cx))
3850            .into_any_named("workspace")
3851    }
3852
3853    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3854        if cx.is_self_focused() {
3855            cx.focus(&self.active_pane);
3856        }
3857    }
3858
3859    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3860        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3861    }
3862}
3863
3864impl ViewId {
3865    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3866        Ok(Self {
3867            creator: message
3868                .creator
3869                .ok_or_else(|| anyhow!("creator is missing"))?,
3870            id: message.id,
3871        })
3872    }
3873
3874    pub(crate) fn to_proto(&self) -> proto::ViewId {
3875        proto::ViewId {
3876            creator: Some(self.creator),
3877            id: self.id,
3878        }
3879    }
3880}
3881
3882pub trait WorkspaceHandle {
3883    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3884}
3885
3886impl WorkspaceHandle for ViewHandle<Workspace> {
3887    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3888        self.read(cx)
3889            .worktrees(cx)
3890            .flat_map(|worktree| {
3891                let worktree_id = worktree.read(cx).id();
3892                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3893                    worktree_id,
3894                    path: f.path.clone(),
3895                })
3896            })
3897            .collect::<Vec<_>>()
3898    }
3899}
3900
3901impl std::fmt::Debug for OpenPaths {
3902    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3903        f.debug_struct("OpenPaths")
3904            .field("paths", &self.paths)
3905            .finish()
3906    }
3907}
3908
3909pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3910
3911pub fn activate_workspace_for_project(
3912    cx: &mut AsyncAppContext,
3913    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3914) -> Option<WeakViewHandle<Workspace>> {
3915    for window in cx.windows() {
3916        let handle = window
3917            .update(cx, |cx| {
3918                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3919                    let project = workspace_handle.read(cx).project.clone();
3920                    if project.update(cx, &predicate) {
3921                        cx.activate_window();
3922                        return Some(workspace_handle.clone());
3923                    }
3924                }
3925                None
3926            })
3927            .flatten();
3928
3929        if let Some(handle) = handle {
3930            return Some(handle.downgrade());
3931        }
3932    }
3933    None
3934}
3935
3936pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3937    DB.last_workspace().await.log_err().flatten()
3938}
3939
3940#[allow(clippy::type_complexity)]
3941pub fn open_paths(
3942    abs_paths: &[PathBuf],
3943    app_state: &Arc<AppState>,
3944    requesting_window: Option<WindowHandle<Workspace>>,
3945    cx: &mut AppContext,
3946) -> Task<
3947    Result<(
3948        WeakViewHandle<Workspace>,
3949        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3950    )>,
3951> {
3952    let app_state = app_state.clone();
3953    let abs_paths = abs_paths.to_vec();
3954    cx.spawn(|mut cx| async move {
3955        // Open paths in existing workspace if possible
3956        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3957            project.contains_paths(&abs_paths, cx)
3958        });
3959
3960        if let Some(existing) = existing {
3961            Ok((
3962                existing.clone(),
3963                existing
3964                    .update(&mut cx, |workspace, cx| {
3965                        workspace.open_paths(abs_paths, true, cx)
3966                    })?
3967                    .await,
3968            ))
3969        } else {
3970            Ok(cx
3971                .update(|cx| {
3972                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
3973                })
3974                .await)
3975        }
3976    })
3977}
3978
3979pub fn open_new(
3980    app_state: &Arc<AppState>,
3981    cx: &mut AppContext,
3982    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3983) -> Task<()> {
3984    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3985    cx.spawn(|mut cx| async move {
3986        let (workspace, opened_paths) = task.await;
3987
3988        workspace
3989            .update(&mut cx, |workspace, cx| {
3990                if opened_paths.is_empty() {
3991                    init(workspace, cx)
3992                }
3993            })
3994            .log_err();
3995    })
3996}
3997
3998pub fn create_and_open_local_file(
3999    path: &'static Path,
4000    cx: &mut ViewContext<Workspace>,
4001    default_content: impl 'static + Send + FnOnce() -> Rope,
4002) -> Task<Result<Box<dyn ItemHandle>>> {
4003    cx.spawn(|workspace, mut cx| async move {
4004        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4005        if !fs.is_file(path).await {
4006            fs.create_file(path, Default::default()).await?;
4007            fs.save(path, &default_content(), Default::default())
4008                .await?;
4009        }
4010
4011        let mut items = workspace
4012            .update(&mut cx, |workspace, cx| {
4013                workspace.with_local_workspace(cx, |workspace, cx| {
4014                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4015                })
4016            })?
4017            .await?
4018            .await;
4019
4020        let item = items.pop().flatten();
4021        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4022    })
4023}
4024
4025pub fn join_remote_project(
4026    project_id: u64,
4027    follow_user_id: u64,
4028    app_state: Arc<AppState>,
4029    cx: &mut AppContext,
4030) -> Task<Result<()>> {
4031    cx.spawn(|mut cx| async move {
4032        let existing_workspace = cx
4033            .windows()
4034            .into_iter()
4035            .find_map(|window| {
4036                window.downcast::<Workspace>().and_then(|window| {
4037                    window.read_root_with(&cx, |workspace, cx| {
4038                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4039                            Some(cx.handle().downgrade())
4040                        } else {
4041                            None
4042                        }
4043                    })
4044                })
4045            })
4046            .flatten();
4047
4048        let workspace = if let Some(existing_workspace) = existing_workspace {
4049            existing_workspace
4050        } else {
4051            let active_call = cx.read(ActiveCall::global);
4052            let room = active_call
4053                .read_with(&cx, |call, _| call.room().cloned())
4054                .ok_or_else(|| anyhow!("not in a call"))?;
4055            let project = room
4056                .update(&mut cx, |room, cx| {
4057                    room.join_project(
4058                        project_id,
4059                        app_state.languages.clone(),
4060                        app_state.fs.clone(),
4061                        cx,
4062                    )
4063                })
4064                .await?;
4065
4066            let window_bounds_override = window_bounds_env_override(&cx);
4067            let window = cx.add_window(
4068                (app_state.build_window_options)(
4069                    window_bounds_override,
4070                    None,
4071                    cx.platform().as_ref(),
4072                ),
4073                |cx| Workspace::new(0, project, app_state.clone(), cx),
4074            );
4075            let workspace = window.root(&cx).unwrap();
4076            (app_state.initialize_workspace)(
4077                workspace.downgrade(),
4078                false,
4079                app_state.clone(),
4080                cx.clone(),
4081            )
4082            .await
4083            .log_err();
4084
4085            workspace.downgrade()
4086        };
4087
4088        workspace.window().activate(&mut cx);
4089        cx.platform().activate(true);
4090
4091        workspace.update(&mut cx, |workspace, cx| {
4092            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4093                let follow_peer_id = room
4094                    .read(cx)
4095                    .remote_participants()
4096                    .iter()
4097                    .find(|(_, participant)| participant.user.id == follow_user_id)
4098                    .map(|(_, p)| p.peer_id)
4099                    .or_else(|| {
4100                        // If we couldn't follow the given user, follow the host instead.
4101                        let collaborator = workspace
4102                            .project()
4103                            .read(cx)
4104                            .collaborators()
4105                            .values()
4106                            .find(|collaborator| collaborator.replica_id == 0)?;
4107                        Some(collaborator.peer_id)
4108                    });
4109
4110                if let Some(follow_peer_id) = follow_peer_id {
4111                    if !workspace.is_being_followed(follow_peer_id) {
4112                        workspace
4113                            .toggle_follow(follow_peer_id, cx)
4114                            .map(|follow| follow.detach_and_log_err(cx));
4115                    }
4116                }
4117            }
4118        })?;
4119
4120        anyhow::Ok(())
4121    })
4122}
4123
4124pub fn restart(_: &Restart, cx: &mut AppContext) {
4125    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4126    cx.spawn(|mut cx| async move {
4127        let mut workspace_windows = cx
4128            .windows()
4129            .into_iter()
4130            .filter_map(|window| window.downcast::<Workspace>())
4131            .collect::<Vec<_>>();
4132
4133        // If multiple windows have unsaved changes, and need a save prompt,
4134        // prompt in the active window before switching to a different window.
4135        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4136
4137        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4138            let answer = window.prompt(
4139                PromptLevel::Info,
4140                "Are you sure you want to restart?",
4141                &["Restart", "Cancel"],
4142                &mut cx,
4143            );
4144
4145            if let Some(mut answer) = answer {
4146                let answer = answer.next().await;
4147                if answer != Some(0) {
4148                    return Ok(());
4149                }
4150            }
4151        }
4152
4153        // If the user cancels any save prompt, then keep the app open.
4154        for window in workspace_windows {
4155            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4156                workspace.prepare_to_close(true, cx)
4157            }) {
4158                if !should_close.await? {
4159                    return Ok(());
4160                }
4161            }
4162        }
4163        cx.platform().restart();
4164        anyhow::Ok(())
4165    })
4166    .detach_and_log_err(cx);
4167}
4168
4169fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4170    let mut parts = value.split(',');
4171    let width: usize = parts.next()?.parse().ok()?;
4172    let height: usize = parts.next()?.parse().ok()?;
4173    Some(vec2f(width as f32, height as f32))
4174}
4175
4176#[cfg(test)]
4177mod tests {
4178    use super::*;
4179    use crate::{
4180        dock::test::{TestPanel, TestPanelEvent},
4181        item::test::{TestItem, TestItemEvent, TestProjectItem},
4182    };
4183    use fs::FakeFs;
4184    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4185    use project::{Project, ProjectEntryId};
4186    use serde_json::json;
4187    use settings::SettingsStore;
4188    use std::{cell::RefCell, rc::Rc};
4189
4190    #[gpui::test]
4191    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4192        init_test(cx);
4193
4194        let fs = FakeFs::new(cx.background());
4195        let project = Project::test(fs, [], cx).await;
4196        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4197        let workspace = window.root(cx);
4198
4199        // Adding an item with no ambiguity renders the tab without detail.
4200        let item1 = window.add_view(cx, |_| {
4201            let mut item = TestItem::new();
4202            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4203            item
4204        });
4205        workspace.update(cx, |workspace, cx| {
4206            workspace.add_item(Box::new(item1.clone()), cx);
4207        });
4208        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4209
4210        // Adding an item that creates ambiguity increases the level of detail on
4211        // both tabs.
4212        let item2 = window.add_view(cx, |_| {
4213            let mut item = TestItem::new();
4214            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4215            item
4216        });
4217        workspace.update(cx, |workspace, cx| {
4218            workspace.add_item(Box::new(item2.clone()), cx);
4219        });
4220        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4221        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4222
4223        // Adding an item that creates ambiguity increases the level of detail only
4224        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4225        // we stop at the highest detail available.
4226        let item3 = window.add_view(cx, |_| {
4227            let mut item = TestItem::new();
4228            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4229            item
4230        });
4231        workspace.update(cx, |workspace, cx| {
4232            workspace.add_item(Box::new(item3.clone()), cx);
4233        });
4234        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4235        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4236        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4237    }
4238
4239    #[gpui::test]
4240    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4241        init_test(cx);
4242
4243        let fs = FakeFs::new(cx.background());
4244        fs.insert_tree(
4245            "/root1",
4246            json!({
4247                "one.txt": "",
4248                "two.txt": "",
4249            }),
4250        )
4251        .await;
4252        fs.insert_tree(
4253            "/root2",
4254            json!({
4255                "three.txt": "",
4256            }),
4257        )
4258        .await;
4259
4260        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4261        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4262        let workspace = window.root(cx);
4263        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4264        let worktree_id = project.read_with(cx, |project, cx| {
4265            project.worktrees(cx).next().unwrap().read(cx).id()
4266        });
4267
4268        let item1 = window.add_view(cx, |cx| {
4269            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4270        });
4271        let item2 = window.add_view(cx, |cx| {
4272            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4273        });
4274
4275        // Add an item to an empty pane
4276        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4277        project.read_with(cx, |project, cx| {
4278            assert_eq!(
4279                project.active_entry(),
4280                project
4281                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4282                    .map(|e| e.id)
4283            );
4284        });
4285        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4286
4287        // Add a second item to a non-empty pane
4288        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4289        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4290        project.read_with(cx, |project, cx| {
4291            assert_eq!(
4292                project.active_entry(),
4293                project
4294                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4295                    .map(|e| e.id)
4296            );
4297        });
4298
4299        // Close the active item
4300        pane.update(cx, |pane, cx| {
4301            pane.close_active_item(&Default::default(), cx).unwrap()
4302        })
4303        .await
4304        .unwrap();
4305        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4306        project.read_with(cx, |project, cx| {
4307            assert_eq!(
4308                project.active_entry(),
4309                project
4310                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4311                    .map(|e| e.id)
4312            );
4313        });
4314
4315        // Add a project folder
4316        project
4317            .update(cx, |project, cx| {
4318                project.find_or_create_local_worktree("/root2", true, cx)
4319            })
4320            .await
4321            .unwrap();
4322        assert_eq!(
4323            window.current_title(cx).as_deref(),
4324            Some("one.txt β€” root1, root2")
4325        );
4326
4327        // Remove a project folder
4328        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4329        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4330    }
4331
4332    #[gpui::test]
4333    async fn test_close_window(cx: &mut TestAppContext) {
4334        init_test(cx);
4335
4336        let fs = FakeFs::new(cx.background());
4337        fs.insert_tree("/root", json!({ "one": "" })).await;
4338
4339        let project = Project::test(fs, ["root".as_ref()], cx).await;
4340        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4341        let workspace = window.root(cx);
4342
4343        // When there are no dirty items, there's nothing to do.
4344        let item1 = window.add_view(cx, |_| TestItem::new());
4345        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4346        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4347        assert!(task.await.unwrap());
4348
4349        // When there are dirty untitled items, prompt to save each one. If the user
4350        // cancels any prompt, then abort.
4351        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4352        let item3 = window.add_view(cx, |cx| {
4353            TestItem::new()
4354                .with_dirty(true)
4355                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4356        });
4357        workspace.update(cx, |w, cx| {
4358            w.add_item(Box::new(item2.clone()), cx);
4359            w.add_item(Box::new(item3.clone()), cx);
4360        });
4361        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4362        cx.foreground().run_until_parked();
4363        window.simulate_prompt_answer(2, cx); // cancel save all
4364        cx.foreground().run_until_parked();
4365        window.simulate_prompt_answer(2, cx); // cancel save all
4366        cx.foreground().run_until_parked();
4367        assert!(!window.has_pending_prompt(cx));
4368        assert!(!task.await.unwrap());
4369    }
4370
4371    #[gpui::test]
4372    async fn test_close_pane_items(cx: &mut TestAppContext) {
4373        init_test(cx);
4374
4375        let fs = FakeFs::new(cx.background());
4376
4377        let project = Project::test(fs, None, cx).await;
4378        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4379        let workspace = window.root(cx);
4380
4381        let item1 = window.add_view(cx, |cx| {
4382            TestItem::new()
4383                .with_dirty(true)
4384                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4385        });
4386        let item2 = window.add_view(cx, |cx| {
4387            TestItem::new()
4388                .with_dirty(true)
4389                .with_conflict(true)
4390                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4391        });
4392        let item3 = window.add_view(cx, |cx| {
4393            TestItem::new()
4394                .with_dirty(true)
4395                .with_conflict(true)
4396                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4397        });
4398        let item4 = window.add_view(cx, |cx| {
4399            TestItem::new()
4400                .with_dirty(true)
4401                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4402        });
4403        let pane = workspace.update(cx, |workspace, cx| {
4404            workspace.add_item(Box::new(item1.clone()), cx);
4405            workspace.add_item(Box::new(item2.clone()), cx);
4406            workspace.add_item(Box::new(item3.clone()), cx);
4407            workspace.add_item(Box::new(item4.clone()), cx);
4408            workspace.active_pane().clone()
4409        });
4410
4411        let close_items = pane.update(cx, |pane, cx| {
4412            pane.activate_item(1, true, true, cx);
4413            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4414            let item1_id = item1.id();
4415            let item3_id = item3.id();
4416            let item4_id = item4.id();
4417            pane.close_items(cx, SaveIntent::Close, move |id| {
4418                [item1_id, item3_id, item4_id].contains(&id)
4419            })
4420        });
4421        cx.foreground().run_until_parked();
4422
4423        assert!(window.has_pending_prompt(cx));
4424        // Ignore "Save all" prompt
4425        window.simulate_prompt_answer(2, cx);
4426        cx.foreground().run_until_parked();
4427        // There's a prompt to save item 1.
4428        pane.read_with(cx, |pane, _| {
4429            assert_eq!(pane.items_len(), 4);
4430            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4431        });
4432        // Confirm saving item 1.
4433        window.simulate_prompt_answer(0, cx);
4434        cx.foreground().run_until_parked();
4435
4436        // Item 1 is saved. There's a prompt to save item 3.
4437        pane.read_with(cx, |pane, cx| {
4438            assert_eq!(item1.read(cx).save_count, 1);
4439            assert_eq!(item1.read(cx).save_as_count, 0);
4440            assert_eq!(item1.read(cx).reload_count, 0);
4441            assert_eq!(pane.items_len(), 3);
4442            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4443        });
4444        assert!(window.has_pending_prompt(cx));
4445
4446        // Cancel saving item 3.
4447        window.simulate_prompt_answer(1, cx);
4448        cx.foreground().run_until_parked();
4449
4450        // Item 3 is reloaded. There's a prompt to save item 4.
4451        pane.read_with(cx, |pane, cx| {
4452            assert_eq!(item3.read(cx).save_count, 0);
4453            assert_eq!(item3.read(cx).save_as_count, 0);
4454            assert_eq!(item3.read(cx).reload_count, 1);
4455            assert_eq!(pane.items_len(), 2);
4456            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4457        });
4458        assert!(window.has_pending_prompt(cx));
4459
4460        // Confirm saving item 4.
4461        window.simulate_prompt_answer(0, cx);
4462        cx.foreground().run_until_parked();
4463
4464        // There's a prompt for a path for item 4.
4465        cx.simulate_new_path_selection(|_| Some(Default::default()));
4466        close_items.await.unwrap();
4467
4468        // The requested items are closed.
4469        pane.read_with(cx, |pane, cx| {
4470            assert_eq!(item4.read(cx).save_count, 0);
4471            assert_eq!(item4.read(cx).save_as_count, 1);
4472            assert_eq!(item4.read(cx).reload_count, 0);
4473            assert_eq!(pane.items_len(), 1);
4474            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4475        });
4476    }
4477
4478    #[gpui::test]
4479    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4480        init_test(cx);
4481
4482        let fs = FakeFs::new(cx.background());
4483
4484        let project = Project::test(fs, [], cx).await;
4485        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4486        let workspace = window.root(cx);
4487
4488        // Create several workspace items with single project entries, and two
4489        // workspace items with multiple project entries.
4490        let single_entry_items = (0..=4)
4491            .map(|project_entry_id| {
4492                window.add_view(cx, |cx| {
4493                    TestItem::new()
4494                        .with_dirty(true)
4495                        .with_project_items(&[TestProjectItem::new(
4496                            project_entry_id,
4497                            &format!("{project_entry_id}.txt"),
4498                            cx,
4499                        )])
4500                })
4501            })
4502            .collect::<Vec<_>>();
4503        let item_2_3 = window.add_view(cx, |cx| {
4504            TestItem::new()
4505                .with_dirty(true)
4506                .with_singleton(false)
4507                .with_project_items(&[
4508                    single_entry_items[2].read(cx).project_items[0].clone(),
4509                    single_entry_items[3].read(cx).project_items[0].clone(),
4510                ])
4511        });
4512        let item_3_4 = window.add_view(cx, |cx| {
4513            TestItem::new()
4514                .with_dirty(true)
4515                .with_singleton(false)
4516                .with_project_items(&[
4517                    single_entry_items[3].read(cx).project_items[0].clone(),
4518                    single_entry_items[4].read(cx).project_items[0].clone(),
4519                ])
4520        });
4521
4522        // Create two panes that contain the following project entries:
4523        //   left pane:
4524        //     multi-entry items:   (2, 3)
4525        //     single-entry items:  0, 1, 2, 3, 4
4526        //   right pane:
4527        //     single-entry items:  1
4528        //     multi-entry items:   (3, 4)
4529        let left_pane = workspace.update(cx, |workspace, cx| {
4530            let left_pane = workspace.active_pane().clone();
4531            workspace.add_item(Box::new(item_2_3.clone()), cx);
4532            for item in single_entry_items {
4533                workspace.add_item(Box::new(item), cx);
4534            }
4535            left_pane.update(cx, |pane, cx| {
4536                pane.activate_item(2, true, true, cx);
4537            });
4538
4539            workspace
4540                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4541                .unwrap();
4542
4543            left_pane
4544        });
4545
4546        //Need to cause an effect flush in order to respect new focus
4547        workspace.update(cx, |workspace, cx| {
4548            workspace.add_item(Box::new(item_3_4.clone()), cx);
4549            cx.focus(&left_pane);
4550        });
4551
4552        // When closing all of the items in the left pane, we should be prompted twice:
4553        // once for project entry 0, and once for project entry 2. After those two
4554        // prompts, the task should complete.
4555
4556        let close = left_pane.update(cx, |pane, cx| {
4557            pane.close_items(cx, SaveIntent::Close, move |_| true)
4558        });
4559        cx.foreground().run_until_parked();
4560        // Discard "Save all" prompt
4561        window.simulate_prompt_answer(2, cx);
4562
4563        cx.foreground().run_until_parked();
4564        left_pane.read_with(cx, |pane, cx| {
4565            assert_eq!(
4566                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4567                &[ProjectEntryId::from_proto(0)]
4568            );
4569        });
4570        window.simulate_prompt_answer(0, cx);
4571
4572        cx.foreground().run_until_parked();
4573        left_pane.read_with(cx, |pane, cx| {
4574            assert_eq!(
4575                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4576                &[ProjectEntryId::from_proto(2)]
4577            );
4578        });
4579        window.simulate_prompt_answer(0, cx);
4580
4581        cx.foreground().run_until_parked();
4582        close.await.unwrap();
4583        left_pane.read_with(cx, |pane, _| {
4584            assert_eq!(pane.items_len(), 0);
4585        });
4586    }
4587
4588    #[gpui::test]
4589    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4590        init_test(cx);
4591
4592        let fs = FakeFs::new(cx.background());
4593
4594        let project = Project::test(fs, [], cx).await;
4595        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4596        let workspace = window.root(cx);
4597        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4598
4599        let item = window.add_view(cx, |cx| {
4600            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4601        });
4602        let item_id = item.id();
4603        workspace.update(cx, |workspace, cx| {
4604            workspace.add_item(Box::new(item.clone()), cx);
4605        });
4606
4607        // Autosave on window change.
4608        item.update(cx, |item, cx| {
4609            cx.update_global(|settings: &mut SettingsStore, cx| {
4610                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4611                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4612                })
4613            });
4614            item.is_dirty = true;
4615        });
4616
4617        // Deactivating the window saves the file.
4618        window.simulate_deactivation(cx);
4619        deterministic.run_until_parked();
4620        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4621
4622        // Autosave on focus change.
4623        item.update(cx, |item, cx| {
4624            cx.focus_self();
4625            cx.update_global(|settings: &mut SettingsStore, cx| {
4626                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4627                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4628                })
4629            });
4630            item.is_dirty = true;
4631        });
4632
4633        // Blurring the item saves the file.
4634        item.update(cx, |_, cx| cx.blur());
4635        deterministic.run_until_parked();
4636        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4637
4638        // Deactivating the window still saves the file.
4639        window.simulate_activation(cx);
4640        item.update(cx, |item, cx| {
4641            cx.focus_self();
4642            item.is_dirty = true;
4643        });
4644        window.simulate_deactivation(cx);
4645
4646        deterministic.run_until_parked();
4647        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4648
4649        // Autosave after delay.
4650        item.update(cx, |item, cx| {
4651            cx.update_global(|settings: &mut SettingsStore, cx| {
4652                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4653                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4654                })
4655            });
4656            item.is_dirty = true;
4657            cx.emit(TestItemEvent::Edit);
4658        });
4659
4660        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4661        deterministic.advance_clock(Duration::from_millis(250));
4662        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4663
4664        // After delay expires, the file is saved.
4665        deterministic.advance_clock(Duration::from_millis(250));
4666        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4667
4668        // Autosave on focus change, ensuring closing the tab counts as such.
4669        item.update(cx, |item, cx| {
4670            cx.update_global(|settings: &mut SettingsStore, cx| {
4671                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4672                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4673                })
4674            });
4675            item.is_dirty = true;
4676        });
4677
4678        pane.update(cx, |pane, cx| {
4679            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4680        })
4681        .await
4682        .unwrap();
4683        assert!(!window.has_pending_prompt(cx));
4684        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4685
4686        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4687        workspace.update(cx, |workspace, cx| {
4688            workspace.add_item(Box::new(item.clone()), cx);
4689        });
4690        item.update(cx, |item, cx| {
4691            item.project_items[0].update(cx, |item, _| {
4692                item.entry_id = None;
4693            });
4694            item.is_dirty = true;
4695            cx.blur();
4696        });
4697        deterministic.run_until_parked();
4698        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4699
4700        // Ensure autosave is prevented for deleted files also when closing the buffer.
4701        let _close_items = pane.update(cx, |pane, cx| {
4702            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4703        });
4704        deterministic.run_until_parked();
4705        assert!(window.has_pending_prompt(cx));
4706        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4707    }
4708
4709    #[gpui::test]
4710    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4711        init_test(cx);
4712
4713        let fs = FakeFs::new(cx.background());
4714
4715        let project = Project::test(fs, [], cx).await;
4716        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4717        let workspace = window.root(cx);
4718
4719        let item = window.add_view(cx, |cx| {
4720            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4721        });
4722        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4723        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4724        let toolbar_notify_count = Rc::new(RefCell::new(0));
4725
4726        workspace.update(cx, |workspace, cx| {
4727            workspace.add_item(Box::new(item.clone()), cx);
4728            let toolbar_notification_count = toolbar_notify_count.clone();
4729            cx.observe(&toolbar, move |_, _, _| {
4730                *toolbar_notification_count.borrow_mut() += 1
4731            })
4732            .detach();
4733        });
4734
4735        pane.read_with(cx, |pane, _| {
4736            assert!(!pane.can_navigate_backward());
4737            assert!(!pane.can_navigate_forward());
4738        });
4739
4740        item.update(cx, |item, cx| {
4741            item.set_state("one".to_string(), cx);
4742        });
4743
4744        // Toolbar must be notified to re-render the navigation buttons
4745        assert_eq!(*toolbar_notify_count.borrow(), 1);
4746
4747        pane.read_with(cx, |pane, _| {
4748            assert!(pane.can_navigate_backward());
4749            assert!(!pane.can_navigate_forward());
4750        });
4751
4752        workspace
4753            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4754            .await
4755            .unwrap();
4756
4757        assert_eq!(*toolbar_notify_count.borrow(), 3);
4758        pane.read_with(cx, |pane, _| {
4759            assert!(!pane.can_navigate_backward());
4760            assert!(pane.can_navigate_forward());
4761        });
4762    }
4763
4764    #[gpui::test]
4765    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4766        init_test(cx);
4767        let fs = FakeFs::new(cx.background());
4768
4769        let project = Project::test(fs, [], cx).await;
4770        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4771        let workspace = window.root(cx);
4772
4773        let panel = workspace.update(cx, |workspace, cx| {
4774            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4775            workspace.add_panel(panel.clone(), cx);
4776
4777            workspace
4778                .right_dock()
4779                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4780
4781            panel
4782        });
4783
4784        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4785        pane.update(cx, |pane, cx| {
4786            let item = cx.add_view(|_| TestItem::new());
4787            pane.add_item(Box::new(item), true, true, None, cx);
4788        });
4789
4790        // Transfer focus from center to panel
4791        workspace.update(cx, |workspace, cx| {
4792            workspace.toggle_panel_focus::<TestPanel>(cx);
4793        });
4794
4795        workspace.read_with(cx, |workspace, cx| {
4796            assert!(workspace.right_dock().read(cx).is_open());
4797            assert!(!panel.is_zoomed(cx));
4798            assert!(panel.has_focus(cx));
4799        });
4800
4801        // Transfer focus from panel to center
4802        workspace.update(cx, |workspace, cx| {
4803            workspace.toggle_panel_focus::<TestPanel>(cx);
4804        });
4805
4806        workspace.read_with(cx, |workspace, cx| {
4807            assert!(workspace.right_dock().read(cx).is_open());
4808            assert!(!panel.is_zoomed(cx));
4809            assert!(!panel.has_focus(cx));
4810        });
4811
4812        // Close the dock
4813        workspace.update(cx, |workspace, cx| {
4814            workspace.toggle_dock(DockPosition::Right, cx);
4815        });
4816
4817        workspace.read_with(cx, |workspace, cx| {
4818            assert!(!workspace.right_dock().read(cx).is_open());
4819            assert!(!panel.is_zoomed(cx));
4820            assert!(!panel.has_focus(cx));
4821        });
4822
4823        // Open the dock
4824        workspace.update(cx, |workspace, cx| {
4825            workspace.toggle_dock(DockPosition::Right, cx);
4826        });
4827
4828        workspace.read_with(cx, |workspace, cx| {
4829            assert!(workspace.right_dock().read(cx).is_open());
4830            assert!(!panel.is_zoomed(cx));
4831            assert!(panel.has_focus(cx));
4832        });
4833
4834        // Focus and zoom panel
4835        panel.update(cx, |panel, cx| {
4836            cx.focus_self();
4837            panel.set_zoomed(true, cx)
4838        });
4839
4840        workspace.read_with(cx, |workspace, cx| {
4841            assert!(workspace.right_dock().read(cx).is_open());
4842            assert!(panel.is_zoomed(cx));
4843            assert!(panel.has_focus(cx));
4844        });
4845
4846        // Transfer focus to the center closes the dock
4847        workspace.update(cx, |workspace, cx| {
4848            workspace.toggle_panel_focus::<TestPanel>(cx);
4849        });
4850
4851        workspace.read_with(cx, |workspace, cx| {
4852            assert!(!workspace.right_dock().read(cx).is_open());
4853            assert!(panel.is_zoomed(cx));
4854            assert!(!panel.has_focus(cx));
4855        });
4856
4857        // Transferring focus back to the panel keeps it zoomed
4858        workspace.update(cx, |workspace, cx| {
4859            workspace.toggle_panel_focus::<TestPanel>(cx);
4860        });
4861
4862        workspace.read_with(cx, |workspace, cx| {
4863            assert!(workspace.right_dock().read(cx).is_open());
4864            assert!(panel.is_zoomed(cx));
4865            assert!(panel.has_focus(cx));
4866        });
4867
4868        // Close the dock while it is zoomed
4869        workspace.update(cx, |workspace, cx| {
4870            workspace.toggle_dock(DockPosition::Right, cx)
4871        });
4872
4873        workspace.read_with(cx, |workspace, cx| {
4874            assert!(!workspace.right_dock().read(cx).is_open());
4875            assert!(panel.is_zoomed(cx));
4876            assert!(workspace.zoomed.is_none());
4877            assert!(!panel.has_focus(cx));
4878        });
4879
4880        // Opening the dock, when it's zoomed, retains focus
4881        workspace.update(cx, |workspace, cx| {
4882            workspace.toggle_dock(DockPosition::Right, cx)
4883        });
4884
4885        workspace.read_with(cx, |workspace, cx| {
4886            assert!(workspace.right_dock().read(cx).is_open());
4887            assert!(panel.is_zoomed(cx));
4888            assert!(workspace.zoomed.is_some());
4889            assert!(panel.has_focus(cx));
4890        });
4891
4892        // Unzoom and close the panel, zoom the active pane.
4893        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4894        workspace.update(cx, |workspace, cx| {
4895            workspace.toggle_dock(DockPosition::Right, cx)
4896        });
4897        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4898
4899        // Opening a dock unzooms the pane.
4900        workspace.update(cx, |workspace, cx| {
4901            workspace.toggle_dock(DockPosition::Right, cx)
4902        });
4903        workspace.read_with(cx, |workspace, cx| {
4904            let pane = pane.read(cx);
4905            assert!(!pane.is_zoomed());
4906            assert!(!pane.has_focus());
4907            assert!(workspace.right_dock().read(cx).is_open());
4908            assert!(workspace.zoomed.is_none());
4909        });
4910    }
4911
4912    #[gpui::test]
4913    async fn test_panels(cx: &mut gpui::TestAppContext) {
4914        init_test(cx);
4915        let fs = FakeFs::new(cx.background());
4916
4917        let project = Project::test(fs, [], cx).await;
4918        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4919        let workspace = window.root(cx);
4920
4921        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4922            // Add panel_1 on the left, panel_2 on the right.
4923            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4924            workspace.add_panel(panel_1.clone(), cx);
4925            workspace
4926                .left_dock()
4927                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4928            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4929            workspace.add_panel(panel_2.clone(), cx);
4930            workspace
4931                .right_dock()
4932                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4933
4934            let left_dock = workspace.left_dock();
4935            assert_eq!(
4936                left_dock.read(cx).visible_panel().unwrap().id(),
4937                panel_1.id()
4938            );
4939            assert_eq!(
4940                left_dock.read(cx).active_panel_size(cx).unwrap(),
4941                panel_1.size(cx)
4942            );
4943
4944            left_dock.update(cx, |left_dock, cx| {
4945                left_dock.resize_active_panel(Some(1337.), cx)
4946            });
4947            assert_eq!(
4948                workspace
4949                    .right_dock()
4950                    .read(cx)
4951                    .visible_panel()
4952                    .unwrap()
4953                    .id(),
4954                panel_2.id()
4955            );
4956
4957            (panel_1, panel_2)
4958        });
4959
4960        // Move panel_1 to the right
4961        panel_1.update(cx, |panel_1, cx| {
4962            panel_1.set_position(DockPosition::Right, cx)
4963        });
4964
4965        workspace.update(cx, |workspace, cx| {
4966            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4967            // Since it was the only panel on the left, the left dock should now be closed.
4968            assert!(!workspace.left_dock().read(cx).is_open());
4969            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4970            let right_dock = workspace.right_dock();
4971            assert_eq!(
4972                right_dock.read(cx).visible_panel().unwrap().id(),
4973                panel_1.id()
4974            );
4975            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4976
4977            // Now we move panel_2Β to the left
4978            panel_2.set_position(DockPosition::Left, cx);
4979        });
4980
4981        workspace.update(cx, |workspace, cx| {
4982            // Since panel_2 was not visible on the right, we don't open the left dock.
4983            assert!(!workspace.left_dock().read(cx).is_open());
4984            // And the right dock is unaffected in it's displaying of panel_1
4985            assert!(workspace.right_dock().read(cx).is_open());
4986            assert_eq!(
4987                workspace
4988                    .right_dock()
4989                    .read(cx)
4990                    .visible_panel()
4991                    .unwrap()
4992                    .id(),
4993                panel_1.id()
4994            );
4995        });
4996
4997        // Move panel_1 back to the left
4998        panel_1.update(cx, |panel_1, cx| {
4999            panel_1.set_position(DockPosition::Left, cx)
5000        });
5001
5002        workspace.update(cx, |workspace, cx| {
5003            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5004            let left_dock = workspace.left_dock();
5005            assert!(left_dock.read(cx).is_open());
5006            assert_eq!(
5007                left_dock.read(cx).visible_panel().unwrap().id(),
5008                panel_1.id()
5009            );
5010            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5011            // And right the dock should be closed as it no longer has any panels.
5012            assert!(!workspace.right_dock().read(cx).is_open());
5013
5014            // Now we move panel_1 to the bottom
5015            panel_1.set_position(DockPosition::Bottom, cx);
5016        });
5017
5018        workspace.update(cx, |workspace, cx| {
5019            // Since panel_1 was visible on the left, we close the left dock.
5020            assert!(!workspace.left_dock().read(cx).is_open());
5021            // The bottom dock is sized based on the panel's default size,
5022            // since the panel orientation changed from vertical to horizontal.
5023            let bottom_dock = workspace.bottom_dock();
5024            assert_eq!(
5025                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5026                panel_1.size(cx),
5027            );
5028            // Close bottom dock and move panel_1 back to the left.
5029            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5030            panel_1.set_position(DockPosition::Left, cx);
5031        });
5032
5033        // Emit activated event on panel 1
5034        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5035
5036        // Now the left dock is open and panel_1 is active and focused.
5037        workspace.read_with(cx, |workspace, cx| {
5038            let left_dock = workspace.left_dock();
5039            assert!(left_dock.read(cx).is_open());
5040            assert_eq!(
5041                left_dock.read(cx).visible_panel().unwrap().id(),
5042                panel_1.id()
5043            );
5044            assert!(panel_1.is_focused(cx));
5045        });
5046
5047        // Emit closed event on panel 2, which is not active
5048        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5049
5050        // Wo don't close the left dock, because panel_2 wasn't the active panel
5051        workspace.read_with(cx, |workspace, cx| {
5052            let left_dock = workspace.left_dock();
5053            assert!(left_dock.read(cx).is_open());
5054            assert_eq!(
5055                left_dock.read(cx).visible_panel().unwrap().id(),
5056                panel_1.id()
5057            );
5058        });
5059
5060        // Emitting a ZoomIn event shows the panel as zoomed.
5061        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5062        workspace.read_with(cx, |workspace, _| {
5063            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5064            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5065        });
5066
5067        // Move panel to another dock while it is zoomed
5068        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5069        workspace.read_with(cx, |workspace, _| {
5070            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5071            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5072        });
5073
5074        // If focus is transferred to another view that's not a panel or another pane, we still show
5075        // the panel as zoomed.
5076        let focus_receiver = window.add_view(cx, |_| EmptyView);
5077        focus_receiver.update(cx, |_, cx| cx.focus_self());
5078        workspace.read_with(cx, |workspace, _| {
5079            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5080            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5081        });
5082
5083        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5084        workspace.update(cx, |_, cx| cx.focus_self());
5085        workspace.read_with(cx, |workspace, _| {
5086            assert_eq!(workspace.zoomed, None);
5087            assert_eq!(workspace.zoomed_position, None);
5088        });
5089
5090        // If focus is transferred again to another view that's not a panel or a pane, we won't
5091        // show the panel as zoomed because it wasn't zoomed before.
5092        focus_receiver.update(cx, |_, cx| cx.focus_self());
5093        workspace.read_with(cx, |workspace, _| {
5094            assert_eq!(workspace.zoomed, None);
5095            assert_eq!(workspace.zoomed_position, None);
5096        });
5097
5098        // When focus is transferred back to the panel, it is zoomed again.
5099        panel_1.update(cx, |_, cx| cx.focus_self());
5100        workspace.read_with(cx, |workspace, _| {
5101            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5102            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5103        });
5104
5105        // Emitting a ZoomOut event unzooms the panel.
5106        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5107        workspace.read_with(cx, |workspace, _| {
5108            assert_eq!(workspace.zoomed, None);
5109            assert_eq!(workspace.zoomed_position, None);
5110        });
5111
5112        // Emit closed event on panel 1, which is active
5113        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5114
5115        // Now the left dock is closed, because panel_1 was the active panel
5116        workspace.read_with(cx, |workspace, cx| {
5117            let right_dock = workspace.right_dock();
5118            assert!(!right_dock.read(cx).is_open());
5119        });
5120    }
5121
5122    pub fn init_test(cx: &mut TestAppContext) {
5123        cx.foreground().forbid_parking();
5124        cx.update(|cx| {
5125            cx.set_global(SettingsStore::test(cx));
5126            theme::init((), cx);
5127            language::init(cx);
5128            crate::init_settings(cx);
5129            Project::init_settings(cx);
5130        });
5131    }
5132}