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