workspace.rs

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