workspace.rs

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