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