workspace.rs

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