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::{DirectoryLister, 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(
 609        &mut Workspace,
 610        DirectoryLister,
 611        &mut ViewContext<Workspace>,
 612    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>,
 613>;
 614
 615/// Collects everything project-related for a certain window opened.
 616/// In some way, is a counterpart of a window, as the [`WindowHandle`] could be downcast into `Workspace`.
 617///
 618/// A `Workspace` usually consists of 1 or more projects, a central pane group, 3 docks and a status bar.
 619/// The `Workspace` owns everybody's state and serves as a default, "global context",
 620/// that can be used to register a global action to be triggered from any place in the window.
 621pub struct Workspace {
 622    weak_self: WeakView<Self>,
 623    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 624    zoomed: Option<AnyWeakView>,
 625    zoomed_position: Option<DockPosition>,
 626    center: PaneGroup,
 627    left_dock: View<Dock>,
 628    bottom_dock: View<Dock>,
 629    right_dock: View<Dock>,
 630    panes: Vec<View<Pane>>,
 631    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 632    active_pane: View<Pane>,
 633    last_active_center_pane: Option<WeakView<Pane>>,
 634    last_active_view_id: Option<proto::ViewId>,
 635    status_bar: View<StatusBar>,
 636    modal_layer: View<ModalLayer>,
 637    titlebar_item: Option<AnyView>,
 638    notifications: Vec<(NotificationId, Box<dyn NotificationHandle>)>,
 639    project: Model<Project>,
 640    follower_states: HashMap<PeerId, FollowerState>,
 641    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 642    window_edited: bool,
 643    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 644    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 645    database_id: Option<WorkspaceId>,
 646    app_state: Arc<AppState>,
 647    dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
 648    _subscriptions: Vec<Subscription>,
 649    _apply_leader_updates: Task<Result<()>>,
 650    _observe_current_user: Task<Result<()>>,
 651    _schedule_serialize: Option<Task<()>>,
 652    pane_history_timestamp: Arc<AtomicUsize>,
 653    bounds: Bounds<Pixels>,
 654    centered_layout: bool,
 655    bounds_save_task_queued: Option<Task<()>>,
 656    on_prompt_for_new_path: Option<PromptForNewPath>,
 657    on_prompt_for_open_path: Option<PromptForOpenPath>,
 658    render_disconnected_overlay:
 659        Option<Box<dyn Fn(&mut Self, &mut ViewContext<Self>) -> AnyElement>>,
 660}
 661
 662impl EventEmitter<Event> for Workspace {}
 663
 664#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 665pub struct ViewId {
 666    pub creator: PeerId,
 667    pub id: u64,
 668}
 669
 670struct FollowerState {
 671    center_pane: View<Pane>,
 672    dock_pane: Option<View<Pane>>,
 673    active_view_id: Option<ViewId>,
 674    items_by_leader_view_id: HashMap<ViewId, FollowerView>,
 675}
 676
 677struct FollowerView {
 678    view: Box<dyn FollowableItemHandle>,
 679    location: Option<proto::PanelId>,
 680}
 681
 682impl Workspace {
 683    const DEFAULT_PADDING: f32 = 0.2;
 684    const MAX_PADDING: f32 = 0.4;
 685
 686    pub fn new(
 687        workspace_id: Option<WorkspaceId>,
 688        project: Model<Project>,
 689        app_state: Arc<AppState>,
 690        cx: &mut ViewContext<Self>,
 691    ) -> Self {
 692        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 693        cx.subscribe(&project, move |this, _, event, cx| {
 694            match event {
 695                project::Event::RemoteIdChanged(_) => {
 696                    this.update_window_title(cx);
 697                }
 698
 699                project::Event::CollaboratorLeft(peer_id) => {
 700                    this.collaborator_left(*peer_id, cx);
 701                }
 702
 703                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 704                    this.update_window_title(cx);
 705                    this.serialize_workspace(cx);
 706                }
 707
 708                project::Event::DisconnectedFromHost => {
 709                    this.update_window_edited(cx);
 710                    let leaders_to_unfollow =
 711                        this.follower_states.keys().copied().collect::<Vec<_>>();
 712                    for leader_id in leaders_to_unfollow {
 713                        this.unfollow(leader_id, cx);
 714                    }
 715                }
 716
 717                project::Event::Closed => {
 718                    cx.remove_window();
 719                }
 720
 721                project::Event::DeletedEntry(entry_id) => {
 722                    for pane in this.panes.iter() {
 723                        pane.update(cx, |pane, cx| {
 724                            pane.handle_deleted_project_item(*entry_id, cx)
 725                        });
 726                    }
 727                }
 728
 729                project::Event::Notification(message) => {
 730                    struct ProjectNotification;
 731
 732                    this.show_notification(
 733                        NotificationId::unique::<ProjectNotification>(),
 734                        cx,
 735                        |cx| cx.new_view(|_| MessageNotification::new(message.clone())),
 736                    )
 737                }
 738
 739                project::Event::LanguageServerPrompt(request) => {
 740                    struct LanguageServerPrompt;
 741
 742                    let mut hasher = DefaultHasher::new();
 743                    request.lsp_name.as_str().hash(&mut hasher);
 744                    let id = hasher.finish();
 745
 746                    this.show_notification(
 747                        NotificationId::identified::<LanguageServerPrompt>(id as usize),
 748                        cx,
 749                        |cx| {
 750                            cx.new_view(|_| {
 751                                notifications::LanguageServerPrompt::new(request.clone())
 752                            })
 753                        },
 754                    );
 755                }
 756
 757                _ => {}
 758            }
 759            cx.notify()
 760        })
 761        .detach();
 762
 763        cx.on_focus_lost(|this, cx| {
 764            let focus_handle = this.focus_handle(cx);
 765            cx.focus(&focus_handle);
 766        })
 767        .detach();
 768
 769        let weak_handle = cx.view().downgrade();
 770        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 771
 772        let center_pane = cx.new_view(|cx| {
 773            Pane::new(
 774                weak_handle.clone(),
 775                project.clone(),
 776                pane_history_timestamp.clone(),
 777                None,
 778                NewFile.boxed_clone(),
 779                cx,
 780            )
 781        });
 782        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 783
 784        cx.focus_view(&center_pane);
 785        cx.emit(Event::PaneAdded(center_pane.clone()));
 786
 787        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 788        app_state.workspace_store.update(cx, |store, _| {
 789            store.workspaces.insert(window_handle);
 790        });
 791
 792        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 793        let mut connection_status = app_state.client.status();
 794        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 795            current_user.next().await;
 796            connection_status.next().await;
 797            let mut stream =
 798                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 799
 800            while stream.recv().await.is_some() {
 801                this.update(&mut cx, |_, cx| cx.notify())?;
 802            }
 803            anyhow::Ok(())
 804        });
 805
 806        // All leader updates are enqueued and then processed in a single task, so
 807        // that each asynchronous operation can be run in order.
 808        let (leader_updates_tx, mut leader_updates_rx) =
 809            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 810        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 811            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 812                Self::process_leader_update(&this, leader_id, update, &mut cx)
 813                    .await
 814                    .log_err();
 815            }
 816
 817            Ok(())
 818        });
 819
 820        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 821
 822        let left_dock = Dock::new(DockPosition::Left, cx);
 823        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 824        let right_dock = Dock::new(DockPosition::Right, cx);
 825        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 826        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 827        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 828        let status_bar = cx.new_view(|cx| {
 829            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 830            status_bar.add_left_item(left_dock_buttons, cx);
 831            status_bar.add_right_item(right_dock_buttons, cx);
 832            status_bar.add_right_item(bottom_dock_buttons, cx);
 833            status_bar
 834        });
 835
 836        let modal_layer = cx.new_view(|_| ModalLayer::new());
 837
 838        let mut active_call = None;
 839        if let Some(call) = ActiveCall::try_global(cx) {
 840            let call = call.clone();
 841            let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
 842            active_call = Some((call, subscriptions));
 843        }
 844
 845        let subscriptions = vec![
 846            cx.observe_window_activation(Self::on_window_activation_changed),
 847            cx.observe_window_bounds(move |this, cx| {
 848                if this.bounds_save_task_queued.is_some() {
 849                    return;
 850                }
 851                this.bounds_save_task_queued = Some(cx.spawn(|this, mut cx| async move {
 852                    cx.background_executor()
 853                        .timer(Duration::from_millis(100))
 854                        .await;
 855                    this.update(&mut cx, |this, cx| {
 856                        if let Some(display) = cx.display() {
 857                            if let Some(display_uuid) = display.uuid().ok() {
 858                                let window_bounds = cx.window_bounds();
 859                                if let Some(database_id) = workspace_id {
 860                                    cx.background_executor()
 861                                        .spawn(DB.set_window_open_status(
 862                                            database_id,
 863                                            SerializedWindowBounds(window_bounds),
 864                                            display_uuid,
 865                                        ))
 866                                        .detach_and_log_err(cx);
 867                                }
 868                            }
 869                        }
 870                        this.bounds_save_task_queued.take();
 871                    })
 872                    .ok();
 873                }));
 874                cx.notify();
 875            }),
 876            cx.observe_window_appearance(|_, cx| {
 877                let window_appearance = cx.appearance();
 878
 879                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 880
 881                ThemeSettings::reload_current_theme(cx);
 882            }),
 883            cx.observe(&left_dock, |this, _, cx| {
 884                this.serialize_workspace(cx);
 885                cx.notify();
 886            }),
 887            cx.observe(&bottom_dock, |this, _, cx| {
 888                this.serialize_workspace(cx);
 889                cx.notify();
 890            }),
 891            cx.observe(&right_dock, |this, _, cx| {
 892                this.serialize_workspace(cx);
 893                cx.notify();
 894            }),
 895            cx.on_release(|this, window, cx| {
 896                this.app_state.workspace_store.update(cx, |store, _| {
 897                    let window = window.downcast::<Self>().unwrap();
 898                    store.workspaces.remove(&window);
 899                })
 900            }),
 901        ];
 902
 903        cx.defer(|this, cx| {
 904            this.update_window_title(cx);
 905        });
 906        Workspace {
 907            weak_self: weak_handle.clone(),
 908            zoomed: None,
 909            zoomed_position: None,
 910            center: PaneGroup::new(center_pane.clone()),
 911            panes: vec![center_pane.clone()],
 912            panes_by_item: Default::default(),
 913            active_pane: center_pane.clone(),
 914            last_active_center_pane: Some(center_pane.downgrade()),
 915            last_active_view_id: None,
 916            status_bar,
 917            modal_layer,
 918            titlebar_item: None,
 919            notifications: Default::default(),
 920            left_dock,
 921            bottom_dock,
 922            right_dock,
 923            project: project.clone(),
 924            follower_states: Default::default(),
 925            last_leaders_by_pane: Default::default(),
 926            dispatching_keystrokes: Default::default(),
 927            window_edited: false,
 928            active_call,
 929            database_id: workspace_id,
 930            app_state,
 931            _observe_current_user,
 932            _apply_leader_updates,
 933            _schedule_serialize: None,
 934            leader_updates_tx,
 935            _subscriptions: subscriptions,
 936            pane_history_timestamp,
 937            workspace_actions: Default::default(),
 938            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 939            bounds: Default::default(),
 940            centered_layout: false,
 941            bounds_save_task_queued: None,
 942            on_prompt_for_new_path: None,
 943            on_prompt_for_open_path: None,
 944            render_disconnected_overlay: None,
 945        }
 946    }
 947
 948    pub fn new_local(
 949        abs_paths: Vec<PathBuf>,
 950        app_state: Arc<AppState>,
 951        requesting_window: Option<WindowHandle<Workspace>>,
 952        cx: &mut AppContext,
 953    ) -> Task<
 954        anyhow::Result<(
 955            WindowHandle<Workspace>,
 956            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 957        )>,
 958    > {
 959        let project_handle = Project::local(
 960            app_state.client.clone(),
 961            app_state.node_runtime.clone(),
 962            app_state.user_store.clone(),
 963            app_state.languages.clone(),
 964            app_state.fs.clone(),
 965            cx,
 966        );
 967
 968        cx.spawn(|mut cx| async move {
 969            let serialized_workspace: Option<SerializedWorkspace> =
 970                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 971
 972            let mut paths_to_open = abs_paths;
 973
 974            let paths_order = serialized_workspace
 975                .as_ref()
 976                .map(|ws| &ws.location)
 977                .and_then(|loc| match loc {
 978                    SerializedWorkspaceLocation::Local(_, order) => Some(order.order()),
 979                    _ => None,
 980                });
 981
 982            if let Some(paths_order) = paths_order {
 983                paths_to_open = paths_order
 984                    .iter()
 985                    .filter_map(|i| paths_to_open.get(*i).cloned())
 986                    .collect::<Vec<_>>();
 987                if paths_order.iter().enumerate().any(|(i, &j)| i != j) {
 988                    project_handle
 989                        .update(&mut cx, |project, _| {
 990                            project.set_worktrees_reordered(true);
 991                        })
 992                        .log_err();
 993                }
 994            }
 995
 996            // Get project paths for all of the abs_paths
 997            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 998            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 999                Vec::with_capacity(paths_to_open.len());
1000            for path in paths_to_open.into_iter() {
1001                if let Some((worktree, project_entry)) = cx
1002                    .update(|cx| {
1003                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
1004                    })?
1005                    .await
1006                    .log_err()
1007                {
1008                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
1009                    project_paths.push((path, Some(project_entry)));
1010                } else {
1011                    project_paths.push((path, None));
1012                }
1013            }
1014
1015            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
1016                serialized_workspace.id
1017            } else {
1018                DB.next_id().await.unwrap_or_else(|_| Default::default())
1019            };
1020
1021            let window = if let Some(window) = requesting_window {
1022                cx.update_window(window.into(), |_, cx| {
1023                    cx.replace_root_view(|cx| {
1024                        Workspace::new(
1025                            Some(workspace_id),
1026                            project_handle.clone(),
1027                            app_state.clone(),
1028                            cx,
1029                        )
1030                    });
1031                })?;
1032                window
1033            } else {
1034                let window_bounds_override = window_bounds_env_override();
1035
1036                let (window_bounds, display) = if let Some(bounds) = window_bounds_override {
1037                    (Some(WindowBounds::Windowed(bounds)), None)
1038                } else {
1039                    let restorable_bounds = serialized_workspace
1040                        .as_ref()
1041                        .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
1042                        .or_else(|| {
1043                            let (display, window_bounds) = DB.last_window().log_err()?;
1044                            Some((display?, window_bounds?))
1045                        });
1046
1047                    if let Some((serialized_display, serialized_status)) = restorable_bounds {
1048                        (Some(serialized_status.0), Some(serialized_display))
1049                    } else {
1050                        (None, None)
1051                    }
1052                };
1053
1054                // Use the serialized workspace to construct the new window
1055                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
1056                options.window_bounds = window_bounds;
1057                let centered_layout = serialized_workspace
1058                    .as_ref()
1059                    .map(|w| w.centered_layout)
1060                    .unwrap_or(false);
1061                cx.open_window(options, {
1062                    let app_state = app_state.clone();
1063                    let project_handle = project_handle.clone();
1064                    move |cx| {
1065                        cx.new_view(|cx| {
1066                            let mut workspace =
1067                                Workspace::new(Some(workspace_id), project_handle, app_state, cx);
1068                            workspace.centered_layout = centered_layout;
1069                            workspace
1070                        })
1071                    }
1072                })?
1073            };
1074
1075            notify_if_database_failed(window, &mut cx);
1076            let opened_items = window
1077                .update(&mut cx, |_workspace, cx| {
1078                    open_items(serialized_workspace, project_paths, app_state, cx)
1079                })?
1080                .await
1081                .unwrap_or_default();
1082
1083            window
1084                .update(&mut cx, |_, cx| cx.activate_window())
1085                .log_err();
1086            Ok((window, opened_items))
1087        })
1088    }
1089
1090    pub fn weak_handle(&self) -> WeakView<Self> {
1091        self.weak_self.clone()
1092    }
1093
1094    pub fn left_dock(&self) -> &View<Dock> {
1095        &self.left_dock
1096    }
1097
1098    pub fn bottom_dock(&self) -> &View<Dock> {
1099        &self.bottom_dock
1100    }
1101
1102    pub fn right_dock(&self) -> &View<Dock> {
1103        &self.right_dock
1104    }
1105
1106    pub fn is_edited(&self) -> bool {
1107        self.window_edited
1108    }
1109
1110    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
1111        let focus_handle = panel.focus_handle(cx);
1112        cx.on_focus_in(&focus_handle, Self::handle_panel_focused)
1113            .detach();
1114
1115        let dock = match panel.position(cx) {
1116            DockPosition::Left => &self.left_dock,
1117            DockPosition::Bottom => &self.bottom_dock,
1118            DockPosition::Right => &self.right_dock,
1119        };
1120
1121        dock.update(cx, |dock, cx| {
1122            dock.add_panel(panel, self.weak_self.clone(), cx)
1123        });
1124    }
1125
1126    pub fn status_bar(&self) -> &View<StatusBar> {
1127        &self.status_bar
1128    }
1129
1130    pub fn app_state(&self) -> &Arc<AppState> {
1131        &self.app_state
1132    }
1133
1134    pub fn user_store(&self) -> &Model<UserStore> {
1135        &self.app_state.user_store
1136    }
1137
1138    pub fn project(&self) -> &Model<Project> {
1139        &self.project
1140    }
1141
1142    pub fn recent_navigation_history(
1143        &self,
1144        limit: Option<usize>,
1145        cx: &AppContext,
1146    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1147        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1148        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1149        for pane in &self.panes {
1150            let pane = pane.read(cx);
1151            pane.nav_history()
1152                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1153                    if let Some(fs_path) = &fs_path {
1154                        abs_paths_opened
1155                            .entry(fs_path.clone())
1156                            .or_default()
1157                            .insert(project_path.clone());
1158                    }
1159                    let timestamp = entry.timestamp;
1160                    match history.entry(project_path) {
1161                        hash_map::Entry::Occupied(mut entry) => {
1162                            let (_, old_timestamp) = entry.get();
1163                            if &timestamp > old_timestamp {
1164                                entry.insert((fs_path, timestamp));
1165                            }
1166                        }
1167                        hash_map::Entry::Vacant(entry) => {
1168                            entry.insert((fs_path, timestamp));
1169                        }
1170                    }
1171                });
1172        }
1173
1174        history
1175            .into_iter()
1176            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1177            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1178            .rev()
1179            .filter(|(history_path, abs_path)| {
1180                let latest_project_path_opened = abs_path
1181                    .as_ref()
1182                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1183                    .and_then(|project_paths| {
1184                        project_paths
1185                            .iter()
1186                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1187                    });
1188
1189                match latest_project_path_opened {
1190                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1191                    None => true,
1192                }
1193            })
1194            .take(limit.unwrap_or(usize::MAX))
1195            .collect()
1196    }
1197
1198    fn navigate_history(
1199        &mut self,
1200        pane: WeakView<Pane>,
1201        mode: NavigationMode,
1202        cx: &mut ViewContext<Workspace>,
1203    ) -> Task<Result<()>> {
1204        let to_load = if let Some(pane) = pane.upgrade() {
1205            pane.update(cx, |pane, cx| {
1206                pane.focus(cx);
1207                loop {
1208                    // Retrieve the weak item handle from the history.
1209                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1210
1211                    // If the item is still present in this pane, then activate it.
1212                    if let Some(index) = entry
1213                        .item
1214                        .upgrade()
1215                        .and_then(|v| pane.index_for_item(v.as_ref()))
1216                    {
1217                        let prev_active_item_index = pane.active_item_index();
1218                        pane.nav_history_mut().set_mode(mode);
1219                        pane.activate_item(index, true, true, cx);
1220                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1221
1222                        let mut navigated = prev_active_item_index != pane.active_item_index();
1223                        if let Some(data) = entry.data {
1224                            navigated |= pane.active_item()?.navigate(data, cx);
1225                        }
1226
1227                        if navigated {
1228                            break None;
1229                        }
1230                    }
1231                    // If the item is no longer present in this pane, then retrieve its
1232                    // project path in order to reopen it.
1233                    else {
1234                        break pane
1235                            .nav_history()
1236                            .path_for_item(entry.item.id())
1237                            .map(|(project_path, _)| (project_path, entry));
1238                    }
1239                }
1240            })
1241        } else {
1242            None
1243        };
1244
1245        if let Some((project_path, entry)) = to_load {
1246            // If the item was no longer present, then load it again from its previous path.
1247            let task = self.load_path(project_path, cx);
1248            cx.spawn(|workspace, mut cx| async move {
1249                let task = task.await;
1250                let mut navigated = false;
1251                if let Some((project_entry_id, build_item)) = task.log_err() {
1252                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1253                        pane.nav_history_mut().set_mode(mode);
1254                        pane.active_item().map(|p| p.item_id())
1255                    })?;
1256
1257                    pane.update(&mut cx, |pane, cx| {
1258                        let item = pane.open_item(
1259                            project_entry_id,
1260                            true,
1261                            entry.is_preview,
1262                            cx,
1263                            build_item,
1264                        );
1265                        navigated |= Some(item.item_id()) != prev_active_item_id;
1266                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1267                        if let Some(data) = entry.data {
1268                            navigated |= item.navigate(data, cx);
1269                        }
1270                    })?;
1271                }
1272
1273                if !navigated {
1274                    workspace
1275                        .update(&mut cx, |workspace, cx| {
1276                            Self::navigate_history(workspace, pane, mode, cx)
1277                        })?
1278                        .await?;
1279                }
1280
1281                Ok(())
1282            })
1283        } else {
1284            Task::ready(Ok(()))
1285        }
1286    }
1287
1288    pub fn go_back(
1289        &mut self,
1290        pane: WeakView<Pane>,
1291        cx: &mut ViewContext<Workspace>,
1292    ) -> Task<Result<()>> {
1293        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1294    }
1295
1296    pub fn go_forward(
1297        &mut self,
1298        pane: WeakView<Pane>,
1299        cx: &mut ViewContext<Workspace>,
1300    ) -> Task<Result<()>> {
1301        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1302    }
1303
1304    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1305        self.navigate_history(
1306            self.active_pane().downgrade(),
1307            NavigationMode::ReopeningClosedItem,
1308            cx,
1309        )
1310    }
1311
1312    pub fn client(&self) -> &Arc<Client> {
1313        &self.app_state.client
1314    }
1315
1316    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1317        self.titlebar_item = Some(item);
1318        cx.notify();
1319    }
1320
1321    pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
1322        self.on_prompt_for_new_path = Some(prompt)
1323    }
1324
1325    pub fn set_prompt_for_open_path(&mut self, prompt: PromptForOpenPath) {
1326        self.on_prompt_for_open_path = Some(prompt)
1327    }
1328
1329    pub fn set_render_disconnected_overlay(
1330        &mut self,
1331        render: impl Fn(&mut Self, &mut ViewContext<Self>) -> AnyElement + 'static,
1332    ) {
1333        self.render_disconnected_overlay = Some(Box::new(render))
1334    }
1335
1336    pub fn prompt_for_open_path(
1337        &mut self,
1338        path_prompt_options: PathPromptOptions,
1339        lister: DirectoryLister,
1340        cx: &mut ViewContext<Self>,
1341    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
1342        if !lister.is_local(cx) || !WorkspaceSettings::get_global(cx).use_system_path_prompts {
1343            let prompt = self.on_prompt_for_open_path.take().unwrap();
1344            let rx = prompt(self, lister, cx);
1345            self.on_prompt_for_open_path = Some(prompt);
1346            rx
1347        } else {
1348            let (tx, rx) = oneshot::channel();
1349            let abs_path = cx.prompt_for_paths(path_prompt_options);
1350
1351            cx.spawn(|this, mut cx| async move {
1352                let Ok(result) = abs_path.await else {
1353                    return Ok(());
1354                };
1355
1356                match result {
1357                    Ok(result) => {
1358                        tx.send(result).log_err();
1359                    }
1360                    Err(err) => {
1361                        let rx = this.update(&mut cx, |this, cx| {
1362                            this.show_portal_error(err.to_string(), cx);
1363                            let prompt = this.on_prompt_for_open_path.take().unwrap();
1364                            let rx = prompt(this, lister, cx);
1365                            this.on_prompt_for_open_path = Some(prompt);
1366                            rx
1367                        })?;
1368                        if let Ok(path) = rx.await {
1369                            tx.send(path).log_err();
1370                        }
1371                    }
1372                };
1373                anyhow::Ok(())
1374            })
1375            .detach();
1376
1377            rx
1378        }
1379    }
1380
1381    pub fn prompt_for_new_path(
1382        &mut self,
1383        cx: &mut ViewContext<Self>,
1384    ) -> oneshot::Receiver<Option<ProjectPath>> {
1385        if self.project.read(cx).is_remote()
1386            || !WorkspaceSettings::get_global(cx).use_system_path_prompts
1387        {
1388            let prompt = self.on_prompt_for_new_path.take().unwrap();
1389            let rx = prompt(self, cx);
1390            self.on_prompt_for_new_path = Some(prompt);
1391            rx
1392        } else {
1393            let start_abs_path = self
1394                .project
1395                .update(cx, |project, cx| {
1396                    let worktree = project.visible_worktrees(cx).next()?;
1397                    Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1398                })
1399                .unwrap_or_else(|| Path::new("").into());
1400
1401            let (tx, rx) = oneshot::channel();
1402            let abs_path = cx.prompt_for_new_path(&start_abs_path);
1403            cx.spawn(|this, mut cx| async move {
1404                let abs_path = match abs_path.await? {
1405                    Ok(path) => path,
1406                    Err(err) => {
1407                        let rx = this.update(&mut cx, |this, cx| {
1408                            this.show_portal_error(err.to_string(), cx);
1409
1410                            let prompt = this.on_prompt_for_new_path.take().unwrap();
1411                            let rx = prompt(this, cx);
1412                            this.on_prompt_for_new_path = Some(prompt);
1413                            rx
1414                        })?;
1415                        if let Ok(path) = rx.await {
1416                            tx.send(path).log_err();
1417                        }
1418                        return anyhow::Ok(());
1419                    }
1420                };
1421
1422                let project_path = abs_path.and_then(|abs_path| {
1423                    this.update(&mut cx, |this, cx| {
1424                        this.project.update(cx, |project, cx| {
1425                            project.find_or_create_worktree(abs_path, true, cx)
1426                        })
1427                    })
1428                    .ok()
1429                });
1430
1431                if let Some(project_path) = project_path {
1432                    let (worktree, path) = project_path.await?;
1433                    let worktree_id = worktree.read_with(&cx, |worktree, _| worktree.id())?;
1434                    tx.send(Some(ProjectPath {
1435                        worktree_id,
1436                        path: path.into(),
1437                    }))
1438                    .ok();
1439                } else {
1440                    tx.send(None).ok();
1441                }
1442                anyhow::Ok(())
1443            })
1444            .detach_and_log_err(cx);
1445
1446            rx
1447        }
1448    }
1449
1450    pub fn titlebar_item(&self) -> Option<AnyView> {
1451        self.titlebar_item.clone()
1452    }
1453
1454    /// Call the given callback with a workspace whose project is local.
1455    ///
1456    /// If the given workspace has a local project, then it will be passed
1457    /// to the callback. Otherwise, a new empty window will be created.
1458    pub fn with_local_workspace<T, F>(
1459        &mut self,
1460        cx: &mut ViewContext<Self>,
1461        callback: F,
1462    ) -> Task<Result<T>>
1463    where
1464        T: 'static,
1465        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1466    {
1467        if self.project.read(cx).is_local() {
1468            Task::Ready(Some(Ok(callback(self, cx))))
1469        } else {
1470            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1471            cx.spawn(|_vh, mut cx| async move {
1472                let (workspace, _) = task.await?;
1473                workspace.update(&mut cx, callback)
1474            })
1475        }
1476    }
1477
1478    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1479        self.project.read(cx).worktrees()
1480    }
1481
1482    pub fn visible_worktrees<'a>(
1483        &self,
1484        cx: &'a AppContext,
1485    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1486        self.project.read(cx).visible_worktrees(cx)
1487    }
1488
1489    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1490        let futures = self
1491            .worktrees(cx)
1492            .filter_map(|worktree| worktree.read(cx).as_local())
1493            .map(|worktree| worktree.scan_complete())
1494            .collect::<Vec<_>>();
1495        async move {
1496            for future in futures {
1497                future.await;
1498            }
1499        }
1500    }
1501
1502    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1503        cx.defer(|cx| {
1504            cx.windows().iter().find(|window| {
1505                window
1506                    .update(cx, |_, window| {
1507                        if window.is_window_active() {
1508                            //This can only get called when the window's project connection has been lost
1509                            //so we don't need to prompt the user for anything and instead just close the window
1510                            window.remove_window();
1511                            true
1512                        } else {
1513                            false
1514                        }
1515                    })
1516                    .unwrap_or(false)
1517            });
1518        });
1519    }
1520
1521    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1522        let window = cx.window_handle();
1523        let prepare = self.prepare_to_close(false, cx);
1524        cx.spawn(|_, mut cx| async move {
1525            if prepare.await? {
1526                window.update(&mut cx, |_, cx| {
1527                    cx.remove_window();
1528                })?;
1529            }
1530            anyhow::Ok(())
1531        })
1532        .detach_and_log_err(cx)
1533    }
1534
1535    pub fn prepare_to_close(
1536        &mut self,
1537        quitting: bool,
1538        cx: &mut ViewContext<Self>,
1539    ) -> Task<Result<bool>> {
1540        let active_call = self.active_call().cloned();
1541        let window = cx.window_handle();
1542
1543        cx.spawn(|this, mut cx| async move {
1544            let workspace_count = (*cx).update(|cx| {
1545                cx.windows()
1546                    .iter()
1547                    .filter(|window| window.downcast::<Workspace>().is_some())
1548                    .count()
1549            })?;
1550
1551            if let Some(active_call) = active_call {
1552                if !quitting
1553                    && workspace_count == 1
1554                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1555                {
1556                    let answer = window.update(&mut cx, |_, cx| {
1557                        cx.prompt(
1558                            PromptLevel::Warning,
1559                            "Do you want to leave the current call?",
1560                            None,
1561                            &["Close window and hang up", "Cancel"],
1562                        )
1563                    })?;
1564
1565                    if answer.await.log_err() == Some(1) {
1566                        return anyhow::Ok(false);
1567                    } else {
1568                        active_call
1569                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1570                            .await
1571                            .log_err();
1572                    }
1573                }
1574            }
1575
1576            this.update(&mut cx, |this, cx| {
1577                this.save_all_internal(SaveIntent::Close, cx)
1578            })?
1579            .await
1580        })
1581    }
1582
1583    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1584        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1585            .detach_and_log_err(cx);
1586    }
1587
1588    fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
1589        let mut keystrokes: Vec<Keystroke> = action
1590            .0
1591            .split(' ')
1592            .flat_map(|k| Keystroke::parse(k).log_err())
1593            .collect();
1594        keystrokes.reverse();
1595
1596        self.dispatching_keystrokes
1597            .borrow_mut()
1598            .append(&mut keystrokes);
1599
1600        let keystrokes = self.dispatching_keystrokes.clone();
1601        cx.window_context()
1602            .spawn(|mut cx| async move {
1603                // limit to 100 keystrokes to avoid infinite recursion.
1604                for _ in 0..100 {
1605                    let Some(keystroke) = keystrokes.borrow_mut().pop() else {
1606                        return Ok(());
1607                    };
1608                    cx.update(|cx| {
1609                        let focused = cx.focused();
1610                        cx.dispatch_keystroke(keystroke.clone());
1611                        if cx.focused() != focused {
1612                            // dispatch_keystroke may cause the focus to change.
1613                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
1614                            // And we need that to happen before the next keystroke to keep vim mode happy...
1615                            // (Note that the tests always do this implicitly, so you must manually test with something like:
1616                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
1617                            // )
1618                            cx.draw();
1619                        }
1620                    })?;
1621                }
1622                keystrokes.borrow_mut().clear();
1623                Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
1624            })
1625            .detach_and_log_err(cx);
1626    }
1627
1628    fn save_all_internal(
1629        &mut self,
1630        mut save_intent: SaveIntent,
1631        cx: &mut ViewContext<Self>,
1632    ) -> Task<Result<bool>> {
1633        if self.project.read(cx).is_disconnected() {
1634            return Task::ready(Ok(true));
1635        }
1636        let dirty_items = self
1637            .panes
1638            .iter()
1639            .flat_map(|pane| {
1640                pane.read(cx).items().filter_map(|item| {
1641                    if item.is_dirty(cx) {
1642                        Some((pane.downgrade(), item.boxed_clone()))
1643                    } else {
1644                        None
1645                    }
1646                })
1647            })
1648            .collect::<Vec<_>>();
1649
1650        let project = self.project.clone();
1651        cx.spawn(|workspace, mut cx| async move {
1652            // Override save mode and display "Save all files" prompt
1653            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1654                let answer = workspace.update(&mut cx, |_, cx| {
1655                    let (prompt, detail) = Pane::file_names_for_prompt(
1656                        &mut dirty_items.iter().map(|(_, handle)| handle),
1657                        dirty_items.len(),
1658                        cx,
1659                    );
1660                    cx.prompt(
1661                        PromptLevel::Warning,
1662                        &prompt,
1663                        Some(&detail),
1664                        &["Save all", "Discard all", "Cancel"],
1665                    )
1666                })?;
1667                match answer.await.log_err() {
1668                    Some(0) => save_intent = SaveIntent::SaveAll,
1669                    Some(1) => save_intent = SaveIntent::Skip,
1670                    _ => {}
1671                }
1672            }
1673            for (pane, item) in dirty_items {
1674                let (singleton, project_entry_ids) =
1675                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1676                if singleton || !project_entry_ids.is_empty() {
1677                    if let Some(ix) =
1678                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1679                    {
1680                        if !Pane::save_item(
1681                            project.clone(),
1682                            &pane,
1683                            ix,
1684                            &*item,
1685                            save_intent,
1686                            &mut cx,
1687                        )
1688                        .await?
1689                        {
1690                            return Ok(false);
1691                        }
1692                    }
1693                }
1694            }
1695            Ok(true)
1696        })
1697    }
1698
1699    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1700        self.client()
1701            .telemetry()
1702            .report_app_event("open project".to_string());
1703        let paths = self.prompt_for_open_path(
1704            PathPromptOptions {
1705                files: true,
1706                directories: true,
1707                multiple: true,
1708            },
1709            DirectoryLister::Local(self.app_state.fs.clone()),
1710            cx,
1711        );
1712
1713        cx.spawn(|this, mut cx| async move {
1714            let Some(paths) = paths.await.log_err().flatten() else {
1715                return;
1716            };
1717
1718            if let Some(task) = this
1719                .update(&mut cx, |this, cx| {
1720                    this.open_workspace_for_paths(false, paths, cx)
1721                })
1722                .log_err()
1723            {
1724                task.await.log_err();
1725            }
1726        })
1727        .detach()
1728    }
1729
1730    pub fn open_workspace_for_paths(
1731        &mut self,
1732        replace_current_window: bool,
1733        paths: Vec<PathBuf>,
1734        cx: &mut ViewContext<Self>,
1735    ) -> Task<Result<()>> {
1736        let window = cx.window_handle().downcast::<Self>();
1737        let is_remote = self.project.read(cx).is_remote();
1738        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1739        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1740
1741        let window_to_replace = if replace_current_window {
1742            window
1743        } else if is_remote || has_worktree || has_dirty_items {
1744            None
1745        } else {
1746            window
1747        };
1748        let app_state = self.app_state.clone();
1749
1750        cx.spawn(|_, mut cx| async move {
1751            cx.update(|cx| {
1752                open_paths(
1753                    &paths,
1754                    app_state,
1755                    OpenOptions {
1756                        replace_window: window_to_replace,
1757                        ..Default::default()
1758                    },
1759                    cx,
1760                )
1761            })?
1762            .await?;
1763            Ok(())
1764        })
1765    }
1766
1767    #[allow(clippy::type_complexity)]
1768    pub fn open_paths(
1769        &mut self,
1770        mut abs_paths: Vec<PathBuf>,
1771        visible: OpenVisible,
1772        pane: Option<WeakView<Pane>>,
1773        cx: &mut ViewContext<Self>,
1774    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1775        log::info!("open paths {abs_paths:?}");
1776
1777        let fs = self.app_state.fs.clone();
1778
1779        // Sort the paths to ensure we add worktrees for parents before their children.
1780        abs_paths.sort_unstable();
1781        cx.spawn(move |this, mut cx| async move {
1782            let mut tasks = Vec::with_capacity(abs_paths.len());
1783
1784            for abs_path in &abs_paths {
1785                let visible = match visible {
1786                    OpenVisible::All => Some(true),
1787                    OpenVisible::None => Some(false),
1788                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1789                        Some(Some(metadata)) => Some(!metadata.is_dir),
1790                        Some(None) => Some(true),
1791                        None => None,
1792                    },
1793                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1794                        Some(Some(metadata)) => Some(metadata.is_dir),
1795                        Some(None) => Some(false),
1796                        None => None,
1797                    },
1798                };
1799                let project_path = match visible {
1800                    Some(visible) => match this
1801                        .update(&mut cx, |this, cx| {
1802                            Workspace::project_path_for_path(
1803                                this.project.clone(),
1804                                abs_path,
1805                                visible,
1806                                cx,
1807                            )
1808                        })
1809                        .log_err()
1810                    {
1811                        Some(project_path) => project_path.await.log_err(),
1812                        None => None,
1813                    },
1814                    None => None,
1815                };
1816
1817                let this = this.clone();
1818                let abs_path = abs_path.clone();
1819                let fs = fs.clone();
1820                let pane = pane.clone();
1821                let task = cx.spawn(move |mut cx| async move {
1822                    let (worktree, project_path) = project_path?;
1823                    if fs.is_dir(&abs_path).await {
1824                        this.update(&mut cx, |workspace, cx| {
1825                            let worktree = worktree.read(cx);
1826                            let worktree_abs_path = worktree.abs_path();
1827                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1828                                worktree.root_entry()
1829                            } else {
1830                                abs_path
1831                                    .strip_prefix(worktree_abs_path.as_ref())
1832                                    .ok()
1833                                    .and_then(|relative_path| {
1834                                        worktree.entry_for_path(relative_path)
1835                                    })
1836                            }
1837                            .map(|entry| entry.id);
1838                            if let Some(entry_id) = entry_id {
1839                                workspace.project.update(cx, |_, cx| {
1840                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1841                                })
1842                            }
1843                        })
1844                        .log_err()?;
1845                        None
1846                    } else {
1847                        Some(
1848                            this.update(&mut cx, |this, cx| {
1849                                this.open_path(project_path, pane, true, cx)
1850                            })
1851                            .log_err()?
1852                            .await,
1853                        )
1854                    }
1855                });
1856                tasks.push(task);
1857            }
1858
1859            futures::future::join_all(tasks).await
1860        })
1861    }
1862
1863    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1864        let project = self.project.read(cx);
1865        if project.is_remote() && project.dev_server_project_id().is_none() {
1866            self.show_error(
1867                &anyhow!("You cannot add folders to someone else's project"),
1868                cx,
1869            );
1870            return;
1871        }
1872        let paths = self.prompt_for_open_path(
1873            PathPromptOptions {
1874                files: false,
1875                directories: true,
1876                multiple: true,
1877            },
1878            DirectoryLister::Project(self.project.clone()),
1879            cx,
1880        );
1881        cx.spawn(|this, mut cx| async move {
1882            if let Some(paths) = paths.await.log_err().flatten() {
1883                let results = this
1884                    .update(&mut cx, |this, cx| {
1885                        this.open_paths(paths, OpenVisible::All, None, cx)
1886                    })?
1887                    .await;
1888                for result in results.into_iter().flatten() {
1889                    result.log_err();
1890                }
1891            }
1892            anyhow::Ok(())
1893        })
1894        .detach_and_log_err(cx);
1895    }
1896
1897    fn project_path_for_path(
1898        project: Model<Project>,
1899        abs_path: &Path,
1900        visible: bool,
1901        cx: &mut AppContext,
1902    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1903        let entry = project.update(cx, |project, cx| {
1904            project.find_or_create_worktree(abs_path, visible, cx)
1905        });
1906        cx.spawn(|mut cx| async move {
1907            let (worktree, path) = entry.await?;
1908            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1909            Ok((
1910                worktree,
1911                ProjectPath {
1912                    worktree_id,
1913                    path: path.into(),
1914                },
1915            ))
1916        })
1917    }
1918
1919    pub fn items<'a>(
1920        &'a self,
1921        cx: &'a AppContext,
1922    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1923        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1924    }
1925
1926    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1927        self.items_of_type(cx).max_by_key(|item| item.item_id())
1928    }
1929
1930    pub fn items_of_type<'a, T: Item>(
1931        &'a self,
1932        cx: &'a AppContext,
1933    ) -> impl 'a + Iterator<Item = View<T>> {
1934        self.panes
1935            .iter()
1936            .flat_map(|pane| pane.read(cx).items_of_type())
1937    }
1938
1939    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1940        self.active_pane().read(cx).active_item()
1941    }
1942
1943    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1944        let item = self.active_item(cx)?;
1945        item.to_any().downcast::<I>().ok()
1946    }
1947
1948    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1949        self.active_item(cx).and_then(|item| item.project_path(cx))
1950    }
1951
1952    pub fn save_active_item(
1953        &mut self,
1954        save_intent: SaveIntent,
1955        cx: &mut WindowContext,
1956    ) -> Task<Result<()>> {
1957        let project = self.project.clone();
1958        let pane = self.active_pane();
1959        let item_ix = pane.read(cx).active_item_index();
1960        let item = pane.read(cx).active_item();
1961        let pane = pane.downgrade();
1962
1963        cx.spawn(|mut cx| async move {
1964            if let Some(item) = item {
1965                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1966                    .await
1967                    .map(|_| ())
1968            } else {
1969                Ok(())
1970            }
1971        })
1972    }
1973
1974    pub fn close_inactive_items_and_panes(
1975        &mut self,
1976        action: &CloseInactiveTabsAndPanes,
1977        cx: &mut ViewContext<Self>,
1978    ) {
1979        if let Some(task) =
1980            self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1981        {
1982            task.detach_and_log_err(cx)
1983        }
1984    }
1985
1986    pub fn close_all_items_and_panes(
1987        &mut self,
1988        action: &CloseAllItemsAndPanes,
1989        cx: &mut ViewContext<Self>,
1990    ) {
1991        if let Some(task) =
1992            self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1993        {
1994            task.detach_and_log_err(cx)
1995        }
1996    }
1997
1998    fn close_all_internal(
1999        &mut self,
2000        retain_active_pane: bool,
2001        save_intent: SaveIntent,
2002        cx: &mut ViewContext<Self>,
2003    ) -> Option<Task<Result<()>>> {
2004        let current_pane = self.active_pane();
2005
2006        let mut tasks = Vec::new();
2007
2008        if retain_active_pane {
2009            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
2010                pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
2011            }) {
2012                tasks.push(current_pane_close);
2013            };
2014        }
2015
2016        for pane in self.panes() {
2017            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
2018                continue;
2019            }
2020
2021            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
2022                pane.close_all_items(
2023                    &CloseAllItems {
2024                        save_intent: Some(save_intent),
2025                    },
2026                    cx,
2027                )
2028            }) {
2029                tasks.push(close_pane_items)
2030            }
2031        }
2032
2033        if tasks.is_empty() {
2034            None
2035        } else {
2036            Some(cx.spawn(|_, _| async move {
2037                for task in tasks {
2038                    task.await?
2039                }
2040                Ok(())
2041            }))
2042        }
2043    }
2044
2045    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
2046        let dock = match dock_side {
2047            DockPosition::Left => &self.left_dock,
2048            DockPosition::Bottom => &self.bottom_dock,
2049            DockPosition::Right => &self.right_dock,
2050        };
2051        let mut focus_center = false;
2052        let mut reveal_dock = false;
2053        dock.update(cx, |dock, cx| {
2054            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
2055            let was_visible = dock.is_open() && !other_is_zoomed;
2056            dock.set_open(!was_visible, cx);
2057
2058            if let Some(active_panel) = dock.active_panel() {
2059                if was_visible {
2060                    if active_panel.focus_handle(cx).contains_focused(cx) {
2061                        focus_center = true;
2062                    }
2063                } else {
2064                    let focus_handle = &active_panel.focus_handle(cx);
2065                    cx.focus(focus_handle);
2066                    reveal_dock = true;
2067                }
2068            }
2069        });
2070
2071        if reveal_dock {
2072            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
2073        }
2074
2075        if focus_center {
2076            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2077        }
2078
2079        cx.notify();
2080        self.serialize_workspace(cx);
2081    }
2082
2083    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
2084        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
2085
2086        for dock in docks {
2087            dock.update(cx, |dock, cx| {
2088                dock.set_open(false, cx);
2089            });
2090        }
2091
2092        cx.focus_self();
2093        cx.notify();
2094        self.serialize_workspace(cx);
2095    }
2096
2097    /// Transfer focus to the panel of the given type.
2098    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
2099        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
2100        panel.to_any().downcast().ok()
2101    }
2102
2103    /// Focus the panel of the given type if it isn't already focused. If it is
2104    /// already focused, then transfer focus back to the workspace center.
2105    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
2106        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
2107            !panel.focus_handle(cx).contains_focused(cx)
2108        });
2109    }
2110
2111    pub fn activate_panel_for_proto_id(
2112        &mut self,
2113        panel_id: PanelId,
2114        cx: &mut ViewContext<Self>,
2115    ) -> Option<Arc<dyn PanelHandle>> {
2116        let mut panel = None;
2117        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
2118            if let Some(panel_index) = dock.read(cx).panel_index_for_proto_id(panel_id) {
2119                panel = dock.update(cx, |dock, cx| {
2120                    dock.activate_panel(panel_index, cx);
2121                    dock.set_open(true, cx);
2122                    dock.active_panel().cloned()
2123                });
2124                break;
2125            }
2126        }
2127
2128        if panel.is_some() {
2129            cx.notify();
2130            self.serialize_workspace(cx);
2131        }
2132
2133        panel
2134    }
2135
2136    /// Focus or unfocus the given panel type, depending on the given callback.
2137    fn focus_or_unfocus_panel<T: Panel>(
2138        &mut self,
2139        cx: &mut ViewContext<Self>,
2140        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
2141    ) -> Option<Arc<dyn PanelHandle>> {
2142        let mut result_panel = None;
2143        let mut serialize = false;
2144        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
2145            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2146                let mut focus_center = false;
2147                let panel = dock.update(cx, |dock, cx| {
2148                    dock.activate_panel(panel_index, cx);
2149
2150                    let panel = dock.active_panel().cloned();
2151                    if let Some(panel) = panel.as_ref() {
2152                        if should_focus(&**panel, cx) {
2153                            dock.set_open(true, cx);
2154                            panel.focus_handle(cx).focus(cx);
2155                        } else {
2156                            focus_center = true;
2157                        }
2158                    }
2159                    panel
2160                });
2161
2162                if focus_center {
2163                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2164                }
2165
2166                result_panel = panel;
2167                serialize = true;
2168                break;
2169            }
2170        }
2171
2172        if serialize {
2173            self.serialize_workspace(cx);
2174        }
2175
2176        cx.notify();
2177        result_panel
2178    }
2179
2180    /// Open the panel of the given type
2181    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
2182        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
2183            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
2184                dock.update(cx, |dock, cx| {
2185                    dock.activate_panel(panel_index, cx);
2186                    dock.set_open(true, cx);
2187                });
2188            }
2189        }
2190    }
2191
2192    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
2193        [&self.left_dock, &self.bottom_dock, &self.right_dock]
2194            .iter()
2195            .find_map(|dock| dock.read(cx).panel::<T>())
2196    }
2197
2198    fn dismiss_zoomed_items_to_reveal(
2199        &mut self,
2200        dock_to_reveal: Option<DockPosition>,
2201        cx: &mut ViewContext<Self>,
2202    ) {
2203        // If a center pane is zoomed, unzoom it.
2204        for pane in &self.panes {
2205            if pane != &self.active_pane || dock_to_reveal.is_some() {
2206                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2207            }
2208        }
2209
2210        // If another dock is zoomed, hide it.
2211        let mut focus_center = false;
2212        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
2213            dock.update(cx, |dock, cx| {
2214                if Some(dock.position()) != dock_to_reveal {
2215                    if let Some(panel) = dock.active_panel() {
2216                        if panel.is_zoomed(cx) {
2217                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
2218                            dock.set_open(false, cx);
2219                        }
2220                    }
2221                }
2222            });
2223        }
2224
2225        if focus_center {
2226            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
2227        }
2228
2229        if self.zoomed_position != dock_to_reveal {
2230            self.zoomed = None;
2231            self.zoomed_position = None;
2232            cx.emit(Event::ZoomChanged);
2233        }
2234
2235        cx.notify();
2236    }
2237
2238    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2239        let pane = cx.new_view(|cx| {
2240            Pane::new(
2241                self.weak_handle(),
2242                self.project.clone(),
2243                self.pane_history_timestamp.clone(),
2244                None,
2245                NewFile.boxed_clone(),
2246                cx,
2247            )
2248        });
2249        cx.subscribe(&pane, Self::handle_pane_event).detach();
2250        self.panes.push(pane.clone());
2251        cx.focus_view(&pane);
2252        cx.emit(Event::PaneAdded(pane.clone()));
2253        pane
2254    }
2255
2256    pub fn add_item_to_center(
2257        &mut self,
2258        item: Box<dyn ItemHandle>,
2259        cx: &mut ViewContext<Self>,
2260    ) -> bool {
2261        if let Some(center_pane) = self.last_active_center_pane.clone() {
2262            if let Some(center_pane) = center_pane.upgrade() {
2263                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2264                true
2265            } else {
2266                false
2267            }
2268        } else {
2269            false
2270        }
2271    }
2272
2273    pub fn add_item_to_active_pane(
2274        &mut self,
2275        item: Box<dyn ItemHandle>,
2276        destination_index: Option<usize>,
2277        cx: &mut WindowContext,
2278    ) {
2279        self.add_item(self.active_pane.clone(), item, destination_index, cx)
2280    }
2281
2282    pub fn add_item(
2283        &mut self,
2284        pane: View<Pane>,
2285        item: Box<dyn ItemHandle>,
2286        destination_index: Option<usize>,
2287        cx: &mut WindowContext,
2288    ) {
2289        if let Some(text) = item.telemetry_event_text(cx) {
2290            self.client()
2291                .telemetry()
2292                .report_app_event(format!("{}: open", text));
2293        }
2294
2295        pane.update(cx, |pane, cx| {
2296            pane.add_item(item, true, true, destination_index, cx)
2297        });
2298    }
2299
2300    pub fn split_item(
2301        &mut self,
2302        split_direction: SplitDirection,
2303        item: Box<dyn ItemHandle>,
2304        cx: &mut ViewContext<Self>,
2305    ) {
2306        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2307        self.add_item(new_pane, item, None, cx);
2308    }
2309
2310    pub fn open_abs_path(
2311        &mut self,
2312        abs_path: PathBuf,
2313        visible: bool,
2314        cx: &mut ViewContext<Self>,
2315    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2316        cx.spawn(|workspace, mut cx| async move {
2317            let open_paths_task_result = workspace
2318                .update(&mut cx, |workspace, cx| {
2319                    workspace.open_paths(
2320                        vec![abs_path.clone()],
2321                        if visible {
2322                            OpenVisible::All
2323                        } else {
2324                            OpenVisible::None
2325                        },
2326                        None,
2327                        cx,
2328                    )
2329                })
2330                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2331                .await;
2332            anyhow::ensure!(
2333                open_paths_task_result.len() == 1,
2334                "open abs path {abs_path:?} task returned incorrect number of results"
2335            );
2336            match open_paths_task_result
2337                .into_iter()
2338                .next()
2339                .expect("ensured single task result")
2340            {
2341                Some(open_result) => {
2342                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2343                }
2344                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2345            }
2346        })
2347    }
2348
2349    pub fn split_abs_path(
2350        &mut self,
2351        abs_path: PathBuf,
2352        visible: bool,
2353        cx: &mut ViewContext<Self>,
2354    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2355        let project_path_task =
2356            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2357        cx.spawn(|this, mut cx| async move {
2358            let (_, path) = project_path_task.await?;
2359            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2360                .await
2361        })
2362    }
2363
2364    pub fn open_path(
2365        &mut self,
2366        path: impl Into<ProjectPath>,
2367        pane: Option<WeakView<Pane>>,
2368        focus_item: bool,
2369        cx: &mut WindowContext,
2370    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2371        self.open_path_preview(path, pane, focus_item, false, cx)
2372    }
2373
2374    pub fn open_path_preview(
2375        &mut self,
2376        path: impl Into<ProjectPath>,
2377        pane: Option<WeakView<Pane>>,
2378        focus_item: bool,
2379        allow_preview: bool,
2380        cx: &mut WindowContext,
2381    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2382        let pane = pane.unwrap_or_else(|| {
2383            self.last_active_center_pane.clone().unwrap_or_else(|| {
2384                self.panes
2385                    .first()
2386                    .expect("There must be an active pane")
2387                    .downgrade()
2388            })
2389        });
2390
2391        let task = self.load_path(path.into(), cx);
2392        cx.spawn(move |mut cx| async move {
2393            let (project_entry_id, build_item) = task.await?;
2394            pane.update(&mut cx, |pane, cx| {
2395                pane.open_item(project_entry_id, focus_item, allow_preview, cx, build_item)
2396            })
2397        })
2398    }
2399
2400    pub fn split_path(
2401        &mut self,
2402        path: impl Into<ProjectPath>,
2403        cx: &mut ViewContext<Self>,
2404    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2405        self.split_path_preview(path, false, cx)
2406    }
2407
2408    pub fn split_path_preview(
2409        &mut self,
2410        path: impl Into<ProjectPath>,
2411        allow_preview: bool,
2412        cx: &mut ViewContext<Self>,
2413    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2414        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2415            self.panes
2416                .first()
2417                .expect("There must be an active pane")
2418                .downgrade()
2419        });
2420
2421        if let Member::Pane(center_pane) = &self.center.root {
2422            if center_pane.read(cx).items_len() == 0 {
2423                return self.open_path(path, Some(pane), true, cx);
2424            }
2425        }
2426
2427        let task = self.load_path(path.into(), cx);
2428        cx.spawn(|this, mut cx| async move {
2429            let (project_entry_id, build_item) = task.await?;
2430            this.update(&mut cx, move |this, cx| -> Option<_> {
2431                let pane = pane.upgrade()?;
2432                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2433                new_pane.update(cx, |new_pane, cx| {
2434                    Some(new_pane.open_item(project_entry_id, true, allow_preview, cx, build_item))
2435                })
2436            })
2437            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2438        })
2439    }
2440
2441    fn load_path(
2442        &mut self,
2443        path: ProjectPath,
2444        cx: &mut WindowContext,
2445    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
2446        let project = self.project().clone();
2447        let project_item_builders = cx.default_global::<ProjectItemOpeners>().clone();
2448        let Some(open_project_item) = project_item_builders
2449            .iter()
2450            .rev()
2451            .find_map(|open_project_item| open_project_item(&project, &path, cx))
2452        else {
2453            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
2454        };
2455        open_project_item
2456    }
2457
2458    pub fn open_project_item<T>(
2459        &mut self,
2460        pane: View<Pane>,
2461        project_item: Model<T::Item>,
2462        cx: &mut ViewContext<Self>,
2463    ) -> View<T>
2464    where
2465        T: ProjectItem,
2466    {
2467        use project::Item as _;
2468
2469        let entry_id = project_item.read(cx).entry_id(cx);
2470        if let Some(item) = entry_id
2471            .and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
2472            .and_then(|item| item.downcast())
2473        {
2474            self.activate_item(&item, cx);
2475            return item;
2476        }
2477
2478        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2479
2480        let item_id = item.item_id();
2481        let mut destination_index = None;
2482        pane.update(cx, |pane, cx| {
2483            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
2484                if let Some(preview_item_id) = pane.preview_item_id() {
2485                    if preview_item_id != item_id {
2486                        destination_index = pane.close_current_preview_item(cx);
2487                    }
2488                }
2489            }
2490            pane.set_preview_item_id(Some(item.item_id()), cx)
2491        });
2492
2493        self.add_item(pane, Box::new(item.clone()), destination_index, cx);
2494        item
2495    }
2496
2497    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2498        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2499            self.active_pane.update(cx, |pane, cx| {
2500                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2501            });
2502        }
2503    }
2504
2505    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
2506        let result = self.panes.iter().find_map(|pane| {
2507            pane.read(cx)
2508                .index_for_item(item)
2509                .map(|ix| (pane.clone(), ix))
2510        });
2511        if let Some((pane, ix)) = result {
2512            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2513            true
2514        } else {
2515            false
2516        }
2517    }
2518
2519    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2520        let panes = self.center.panes();
2521        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2522            cx.focus_view(&pane);
2523        } else {
2524            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2525        }
2526    }
2527
2528    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2529        let panes = self.center.panes();
2530        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2531            let next_ix = (ix + 1) % panes.len();
2532            let next_pane = panes[next_ix].clone();
2533            cx.focus_view(&next_pane);
2534        }
2535    }
2536
2537    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2538        let panes = self.center.panes();
2539        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2540            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2541            let prev_pane = panes[prev_ix].clone();
2542            cx.focus_view(&prev_pane);
2543        }
2544    }
2545
2546    pub fn activate_pane_in_direction(
2547        &mut self,
2548        direction: SplitDirection,
2549        cx: &mut WindowContext,
2550    ) {
2551        use ActivateInDirectionTarget as Target;
2552        enum Origin {
2553            LeftDock,
2554            RightDock,
2555            BottomDock,
2556            Center,
2557        }
2558
2559        let origin: Origin = [
2560            (&self.left_dock, Origin::LeftDock),
2561            (&self.right_dock, Origin::RightDock),
2562            (&self.bottom_dock, Origin::BottomDock),
2563        ]
2564        .into_iter()
2565        .find_map(|(dock, origin)| {
2566            if dock.focus_handle(cx).contains_focused(cx) && dock.read(cx).is_open() {
2567                Some(origin)
2568            } else {
2569                None
2570            }
2571        })
2572        .unwrap_or(Origin::Center);
2573
2574        let get_last_active_pane = || {
2575            self.last_active_center_pane.as_ref().and_then(|p| {
2576                let p = p.upgrade()?;
2577                (p.read(cx).items_len() != 0).then_some(p)
2578            })
2579        };
2580
2581        let try_dock =
2582            |dock: &View<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
2583
2584        let target = match (origin, direction) {
2585            // We're in the center, so we first try to go to a different pane,
2586            // otherwise try to go to a dock.
2587            (Origin::Center, direction) => {
2588                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2589                    Some(Target::Pane(pane))
2590                } else {
2591                    match direction {
2592                        SplitDirection::Up => None,
2593                        SplitDirection::Down => try_dock(&self.bottom_dock),
2594                        SplitDirection::Left => try_dock(&self.left_dock),
2595                        SplitDirection::Right => try_dock(&self.right_dock),
2596                    }
2597                }
2598            }
2599
2600            (Origin::LeftDock, SplitDirection::Right) => {
2601                if let Some(last_active_pane) = get_last_active_pane() {
2602                    Some(Target::Pane(last_active_pane))
2603                } else {
2604                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
2605                }
2606            }
2607
2608            (Origin::LeftDock, SplitDirection::Down)
2609            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
2610
2611            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
2612            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
2613            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
2614
2615            (Origin::RightDock, SplitDirection::Left) => {
2616                if let Some(last_active_pane) = get_last_active_pane() {
2617                    Some(Target::Pane(last_active_pane))
2618                } else {
2619                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
2620                }
2621            }
2622
2623            _ => None,
2624        };
2625
2626        match target {
2627            Some(ActivateInDirectionTarget::Pane(pane)) => cx.focus_view(&pane),
2628            Some(ActivateInDirectionTarget::Dock(dock)) => {
2629                if let Some(panel) = dock.read(cx).active_panel() {
2630                    panel.focus_handle(cx).focus(cx);
2631                } else {
2632                    log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.read(cx).position());
2633                }
2634            }
2635            None => {}
2636        }
2637    }
2638
2639    pub fn find_pane_in_direction(
2640        &mut self,
2641        direction: SplitDirection,
2642        cx: &WindowContext,
2643    ) -> Option<View<Pane>> {
2644        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2645            return None;
2646        };
2647        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2648        let center = match cursor {
2649            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2650            _ => bounding_box.center(),
2651        };
2652
2653        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2654
2655        let target = match direction {
2656            SplitDirection::Left => {
2657                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2658            }
2659            SplitDirection::Right => {
2660                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2661            }
2662            SplitDirection::Up => {
2663                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2664            }
2665            SplitDirection::Down => {
2666                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2667            }
2668        };
2669        self.center.pane_at_pixel_position(target).cloned()
2670    }
2671
2672    pub fn swap_pane_in_direction(
2673        &mut self,
2674        direction: SplitDirection,
2675        cx: &mut ViewContext<Self>,
2676    ) {
2677        if let Some(to) = self
2678            .find_pane_in_direction(direction, cx)
2679            .map(|pane| pane.clone())
2680        {
2681            self.center.swap(&self.active_pane.clone(), &to);
2682            cx.notify();
2683        }
2684    }
2685
2686    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2687        // This is explicitly hoisted out of the following check for pane identity as
2688        // terminal panel panes are not registered as a center panes.
2689        self.status_bar.update(cx, |status_bar, cx| {
2690            status_bar.set_active_pane(&pane, cx);
2691        });
2692        if self.active_pane != pane {
2693            self.active_pane = pane.clone();
2694            self.active_item_path_changed(cx);
2695            self.last_active_center_pane = Some(pane.downgrade());
2696        }
2697
2698        self.dismiss_zoomed_items_to_reveal(None, cx);
2699        if pane.read(cx).is_zoomed() {
2700            self.zoomed = Some(pane.downgrade().into());
2701        } else {
2702            self.zoomed = None;
2703        }
2704        self.zoomed_position = None;
2705        cx.emit(Event::ZoomChanged);
2706        self.update_active_view_for_followers(cx);
2707        pane.model.update(cx, |pane, _| {
2708            pane.track_alternate_file_items();
2709        });
2710
2711        cx.notify();
2712    }
2713
2714    fn handle_panel_focused(&mut self, cx: &mut ViewContext<Self>) {
2715        self.update_active_view_for_followers(cx);
2716    }
2717
2718    fn handle_pane_event(
2719        &mut self,
2720        pane: View<Pane>,
2721        event: &pane::Event,
2722        cx: &mut ViewContext<Self>,
2723    ) {
2724        match event {
2725            pane::Event::AddItem { item } => {
2726                item.added_to_pane(self, pane, cx);
2727                cx.emit(Event::ItemAdded);
2728            }
2729            pane::Event::Split(direction) => {
2730                self.split_and_clone(pane, *direction, cx);
2731            }
2732            pane::Event::Remove => self.remove_pane(pane, cx),
2733            pane::Event::ActivateItem { local } => {
2734                pane.model.update(cx, |pane, _| {
2735                    pane.track_alternate_file_items();
2736                });
2737                if *local {
2738                    self.unfollow_in_pane(&pane, cx);
2739                }
2740                if &pane == self.active_pane() {
2741                    self.active_item_path_changed(cx);
2742                    self.update_active_view_for_followers(cx);
2743                }
2744            }
2745            pane::Event::ChangeItemTitle => {
2746                if pane == self.active_pane {
2747                    self.active_item_path_changed(cx);
2748                }
2749                self.update_window_edited(cx);
2750            }
2751            pane::Event::RemoveItem { item_id } => {
2752                cx.emit(Event::ActiveItemChanged);
2753                self.update_window_edited(cx);
2754                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2755                    if entry.get().entity_id() == pane.entity_id() {
2756                        entry.remove();
2757                    }
2758                }
2759            }
2760            pane::Event::Focus => {
2761                self.handle_pane_focused(pane.clone(), cx);
2762            }
2763            pane::Event::ZoomIn => {
2764                if pane == self.active_pane {
2765                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2766                    if pane.read(cx).has_focus(cx) {
2767                        self.zoomed = Some(pane.downgrade().into());
2768                        self.zoomed_position = None;
2769                        cx.emit(Event::ZoomChanged);
2770                    }
2771                    cx.notify();
2772                }
2773            }
2774            pane::Event::ZoomOut => {
2775                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2776                if self.zoomed_position.is_none() {
2777                    self.zoomed = None;
2778                    cx.emit(Event::ZoomChanged);
2779                }
2780                cx.notify();
2781            }
2782        }
2783
2784        self.serialize_workspace(cx);
2785    }
2786
2787    pub fn unfollow_in_pane(
2788        &mut self,
2789        pane: &View<Pane>,
2790        cx: &mut ViewContext<Workspace>,
2791    ) -> Option<PeerId> {
2792        let leader_id = self.leader_for_pane(pane)?;
2793        self.unfollow(leader_id, cx);
2794        Some(leader_id)
2795    }
2796
2797    pub fn split_pane(
2798        &mut self,
2799        pane_to_split: View<Pane>,
2800        split_direction: SplitDirection,
2801        cx: &mut ViewContext<Self>,
2802    ) -> View<Pane> {
2803        let new_pane = self.add_pane(cx);
2804        self.center
2805            .split(&pane_to_split, &new_pane, split_direction)
2806            .unwrap();
2807        cx.notify();
2808        new_pane
2809    }
2810
2811    pub fn split_and_clone(
2812        &mut self,
2813        pane: View<Pane>,
2814        direction: SplitDirection,
2815        cx: &mut ViewContext<Self>,
2816    ) -> Option<View<Pane>> {
2817        let item = pane.read(cx).active_item()?;
2818        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2819            let new_pane = self.add_pane(cx);
2820            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2821            self.center.split(&pane, &new_pane, direction).unwrap();
2822            Some(new_pane)
2823        } else {
2824            None
2825        };
2826        cx.notify();
2827        maybe_pane_handle
2828    }
2829
2830    pub fn split_pane_with_item(
2831        &mut self,
2832        pane_to_split: WeakView<Pane>,
2833        split_direction: SplitDirection,
2834        from: WeakView<Pane>,
2835        item_id_to_move: EntityId,
2836        cx: &mut ViewContext<Self>,
2837    ) {
2838        let Some(pane_to_split) = pane_to_split.upgrade() else {
2839            return;
2840        };
2841        let Some(from) = from.upgrade() else {
2842            return;
2843        };
2844
2845        let new_pane = self.add_pane(cx);
2846        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2847        self.center
2848            .split(&pane_to_split, &new_pane, split_direction)
2849            .unwrap();
2850        cx.notify();
2851    }
2852
2853    pub fn split_pane_with_project_entry(
2854        &mut self,
2855        pane_to_split: WeakView<Pane>,
2856        split_direction: SplitDirection,
2857        project_entry: ProjectEntryId,
2858        cx: &mut ViewContext<Self>,
2859    ) -> Option<Task<Result<()>>> {
2860        let pane_to_split = pane_to_split.upgrade()?;
2861        let new_pane = self.add_pane(cx);
2862        self.center
2863            .split(&pane_to_split, &new_pane, split_direction)
2864            .unwrap();
2865
2866        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2867        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2868        Some(cx.foreground_executor().spawn(async move {
2869            task.await?;
2870            Ok(())
2871        }))
2872    }
2873
2874    pub fn move_item(
2875        &mut self,
2876        source: View<Pane>,
2877        destination: View<Pane>,
2878        item_id_to_move: EntityId,
2879        destination_index: usize,
2880        cx: &mut ViewContext<Self>,
2881    ) {
2882        let Some((item_ix, item_handle)) = source
2883            .read(cx)
2884            .items()
2885            .enumerate()
2886            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2887        else {
2888            // Tab was closed during drag
2889            return;
2890        };
2891
2892        let item_handle = item_handle.clone();
2893
2894        if source != destination {
2895            // Close item from previous pane
2896            source.update(cx, |source, cx| {
2897                source.remove_item(item_ix, false, true, cx);
2898            });
2899        }
2900
2901        // This automatically removes duplicate items in the pane
2902        destination.update(cx, |destination, cx| {
2903            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2904            destination.focus(cx)
2905        });
2906    }
2907
2908    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2909        if self.center.remove(&pane).unwrap() {
2910            self.force_remove_pane(&pane, cx);
2911            self.unfollow_in_pane(&pane, cx);
2912            self.last_leaders_by_pane.remove(&pane.downgrade());
2913            for removed_item in pane.read(cx).items() {
2914                self.panes_by_item.remove(&removed_item.item_id());
2915            }
2916
2917            cx.notify();
2918        } else {
2919            self.active_item_path_changed(cx);
2920        }
2921        cx.emit(Event::PaneRemoved);
2922    }
2923
2924    pub fn panes(&self) -> &[View<Pane>] {
2925        &self.panes
2926    }
2927
2928    pub fn active_pane(&self) -> &View<Pane> {
2929        &self.active_pane
2930    }
2931
2932    pub fn adjacent_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
2933        self.find_pane_in_direction(SplitDirection::Right, cx)
2934            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
2935            .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx))
2936            .clone()
2937    }
2938
2939    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2940        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2941        weak_pane.upgrade()
2942    }
2943
2944    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2945        self.follower_states.retain(|leader_id, state| {
2946            if *leader_id == peer_id {
2947                for item in state.items_by_leader_view_id.values() {
2948                    item.view.set_leader_peer_id(None, cx);
2949                }
2950                false
2951            } else {
2952                true
2953            }
2954        });
2955        cx.notify();
2956    }
2957
2958    pub fn start_following(
2959        &mut self,
2960        leader_id: PeerId,
2961        cx: &mut ViewContext<Self>,
2962    ) -> Option<Task<Result<()>>> {
2963        let pane = self.active_pane().clone();
2964
2965        self.last_leaders_by_pane
2966            .insert(pane.downgrade(), leader_id);
2967        self.unfollow(leader_id, cx);
2968        self.unfollow_in_pane(&pane, cx);
2969        self.follower_states.insert(
2970            leader_id,
2971            FollowerState {
2972                center_pane: pane.clone(),
2973                dock_pane: None,
2974                active_view_id: None,
2975                items_by_leader_view_id: Default::default(),
2976            },
2977        );
2978        cx.notify();
2979
2980        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2981        let project_id = self.project.read(cx).remote_id();
2982        let request = self.app_state.client.request(proto::Follow {
2983            room_id,
2984            project_id,
2985            leader_id: Some(leader_id),
2986        });
2987
2988        Some(cx.spawn(|this, mut cx| async move {
2989            let response = request.await?;
2990            this.update(&mut cx, |this, _| {
2991                let state = this
2992                    .follower_states
2993                    .get_mut(&leader_id)
2994                    .ok_or_else(|| anyhow!("following interrupted"))?;
2995                state.active_view_id = response
2996                    .active_view
2997                    .as_ref()
2998                    .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
2999                Ok::<_, anyhow::Error>(())
3000            })??;
3001            if let Some(view) = response.active_view {
3002                Self::add_view_from_leader(this.clone(), leader_id, &view, &mut cx).await?;
3003            }
3004            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
3005            Ok(())
3006        }))
3007    }
3008
3009    pub fn follow_next_collaborator(
3010        &mut self,
3011        _: &FollowNextCollaborator,
3012        cx: &mut ViewContext<Self>,
3013    ) {
3014        let collaborators = self.project.read(cx).collaborators();
3015        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
3016            let mut collaborators = collaborators.keys().copied();
3017            for peer_id in collaborators.by_ref() {
3018                if peer_id == leader_id {
3019                    break;
3020                }
3021            }
3022            collaborators.next()
3023        } else if let Some(last_leader_id) =
3024            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
3025        {
3026            if collaborators.contains_key(last_leader_id) {
3027                Some(*last_leader_id)
3028            } else {
3029                None
3030            }
3031        } else {
3032            None
3033        };
3034
3035        let pane = self.active_pane.clone();
3036        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
3037        else {
3038            return;
3039        };
3040        if self.unfollow_in_pane(&pane, cx) == Some(leader_id) {
3041            return;
3042        }
3043        if let Some(task) = self.start_following(leader_id, cx) {
3044            task.detach_and_log_err(cx)
3045        }
3046    }
3047
3048    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
3049        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
3050            return;
3051        };
3052        let room = room.read(cx);
3053        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
3054            return;
3055        };
3056
3057        let project = self.project.read(cx);
3058
3059        let other_project_id = match remote_participant.location {
3060            call::ParticipantLocation::External => None,
3061            call::ParticipantLocation::UnsharedProject => None,
3062            call::ParticipantLocation::SharedProject { project_id } => {
3063                if Some(project_id) == project.remote_id() {
3064                    None
3065                } else {
3066                    Some(project_id)
3067                }
3068            }
3069        };
3070
3071        // if they are active in another project, follow there.
3072        if let Some(project_id) = other_project_id {
3073            let app_state = self.app_state.clone();
3074            crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
3075                .detach_and_log_err(cx);
3076        }
3077
3078        // if you're already following, find the right pane and focus it.
3079        if let Some(follower_state) = self.follower_states.get(&leader_id) {
3080            cx.focus_view(&follower_state.pane());
3081            return;
3082        }
3083
3084        // Otherwise, follow.
3085        if let Some(task) = self.start_following(leader_id, cx) {
3086            task.detach_and_log_err(cx)
3087        }
3088    }
3089
3090    pub fn unfollow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3091        cx.notify();
3092        let state = self.follower_states.remove(&leader_id)?;
3093        for (_, item) in state.items_by_leader_view_id {
3094            item.view.set_leader_peer_id(None, cx);
3095        }
3096
3097        let project_id = self.project.read(cx).remote_id();
3098        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3099        self.app_state
3100            .client
3101            .send(proto::Unfollow {
3102                room_id,
3103                project_id,
3104                leader_id: Some(leader_id),
3105            })
3106            .log_err();
3107
3108        Some(())
3109    }
3110
3111    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
3112        self.follower_states.contains_key(&peer_id)
3113    }
3114
3115    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
3116        cx.emit(Event::ActiveItemChanged);
3117        let active_entry = self.active_project_path(cx);
3118        self.project
3119            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
3120
3121        self.update_window_title(cx);
3122    }
3123
3124    fn update_window_title(&mut self, cx: &mut WindowContext) {
3125        let project = self.project().read(cx);
3126        let mut title = String::new();
3127
3128        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
3129            let filename = path
3130                .path
3131                .file_name()
3132                .map(|s| s.to_string_lossy())
3133                .or_else(|| {
3134                    Some(Cow::Borrowed(
3135                        project
3136                            .worktree_for_id(path.worktree_id, cx)?
3137                            .read(cx)
3138                            .root_name(),
3139                    ))
3140                });
3141
3142            if let Some(filename) = filename {
3143                title.push_str(filename.as_ref());
3144                title.push_str("");
3145            }
3146        }
3147
3148        for (i, name) in project.worktree_root_names(cx).enumerate() {
3149            if i > 0 {
3150                title.push_str(", ");
3151            }
3152            title.push_str(name);
3153        }
3154
3155        if title.is_empty() {
3156            title = "empty project".to_string();
3157        }
3158
3159        if project.is_remote() {
3160            title.push_str("");
3161        } else if project.is_shared() {
3162            title.push_str("");
3163        }
3164
3165        cx.set_window_title(&title);
3166    }
3167
3168    fn update_window_edited(&mut self, cx: &mut WindowContext) {
3169        let is_edited = !self.project.read(cx).is_disconnected()
3170            && self
3171                .items(cx)
3172                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
3173        if is_edited != self.window_edited {
3174            self.window_edited = is_edited;
3175            cx.set_window_edited(self.window_edited)
3176        }
3177    }
3178
3179    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
3180        if self.notifications.is_empty() {
3181            None
3182        } else {
3183            Some(
3184                div()
3185                    .absolute()
3186                    .right_3()
3187                    .bottom_3()
3188                    .w_112()
3189                    .h_full()
3190                    .flex()
3191                    .flex_col()
3192                    .justify_end()
3193                    .gap_2()
3194                    .children(
3195                        self.notifications
3196                            .iter()
3197                            .map(|(_, notification)| notification.to_any()),
3198                    ),
3199            )
3200        }
3201    }
3202
3203    // RPC handlers
3204
3205    fn active_view_for_follower(
3206        &self,
3207        follower_project_id: Option<u64>,
3208        cx: &mut ViewContext<Self>,
3209    ) -> Option<proto::View> {
3210        let (item, panel_id) = self.active_item_for_followers(cx);
3211        let item = item?;
3212        let leader_id = self
3213            .pane_for(&*item)
3214            .and_then(|pane| self.leader_for_pane(&pane));
3215
3216        let item_handle = item.to_followable_item_handle(cx)?;
3217        let id = item_handle.remote_id(&self.app_state.client, cx)?;
3218        let variant = item_handle.to_state_proto(cx)?;
3219
3220        if item_handle.is_project_item(cx)
3221            && (follower_project_id.is_none()
3222                || follower_project_id != self.project.read(cx).remote_id())
3223        {
3224            return None;
3225        }
3226
3227        Some(proto::View {
3228            id: Some(id.to_proto()),
3229            leader_id,
3230            variant: Some(variant),
3231            panel_id: panel_id.map(|id| id as i32),
3232        })
3233    }
3234
3235    fn handle_follow(
3236        &mut self,
3237        follower_project_id: Option<u64>,
3238        cx: &mut ViewContext<Self>,
3239    ) -> proto::FollowResponse {
3240        let active_view = self.active_view_for_follower(follower_project_id, cx);
3241
3242        cx.notify();
3243        proto::FollowResponse {
3244            // TODO: Remove after version 0.145.x stabilizes.
3245            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
3246            views: active_view.iter().cloned().collect(),
3247            active_view,
3248        }
3249    }
3250
3251    fn handle_update_followers(
3252        &mut self,
3253        leader_id: PeerId,
3254        message: proto::UpdateFollowers,
3255        _cx: &mut ViewContext<Self>,
3256    ) {
3257        self.leader_updates_tx
3258            .unbounded_send((leader_id, message))
3259            .ok();
3260    }
3261
3262    async fn process_leader_update(
3263        this: &WeakView<Self>,
3264        leader_id: PeerId,
3265        update: proto::UpdateFollowers,
3266        cx: &mut AsyncWindowContext,
3267    ) -> Result<()> {
3268        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
3269            proto::update_followers::Variant::CreateView(view) => {
3270                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
3271                let should_add_view = this.update(cx, |this, _| {
3272                    if let Some(state) = this.follower_states.get_mut(&leader_id) {
3273                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
3274                    } else {
3275                        anyhow::Ok(false)
3276                    }
3277                })??;
3278
3279                if should_add_view {
3280                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
3281                }
3282            }
3283            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
3284                let should_add_view = this.update(cx, |this, _| {
3285                    if let Some(state) = this.follower_states.get_mut(&leader_id) {
3286                        state.active_view_id = update_active_view
3287                            .view
3288                            .as_ref()
3289                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3290
3291                        if state.active_view_id.is_some_and(|view_id| {
3292                            !state.items_by_leader_view_id.contains_key(&view_id)
3293                        }) {
3294                            anyhow::Ok(true)
3295                        } else {
3296                            anyhow::Ok(false)
3297                        }
3298                    } else {
3299                        anyhow::Ok(false)
3300                    }
3301                })??;
3302
3303                if should_add_view {
3304                    if let Some(view) = update_active_view.view {
3305                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
3306                    }
3307                }
3308            }
3309            proto::update_followers::Variant::UpdateView(update_view) => {
3310                let variant = update_view
3311                    .variant
3312                    .ok_or_else(|| anyhow!("missing update view variant"))?;
3313                let id = update_view
3314                    .id
3315                    .ok_or_else(|| anyhow!("missing update view id"))?;
3316                let mut tasks = Vec::new();
3317                this.update(cx, |this, cx| {
3318                    let project = this.project.clone();
3319                    if let Some(state) = this.follower_states.get(&leader_id) {
3320                        let view_id = ViewId::from_proto(id.clone())?;
3321                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
3322                            tasks.push(item.view.apply_update_proto(&project, variant.clone(), cx));
3323                        }
3324                    }
3325                    anyhow::Ok(())
3326                })??;
3327                try_join_all(tasks).await.log_err();
3328            }
3329        }
3330        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
3331        Ok(())
3332    }
3333
3334    async fn add_view_from_leader(
3335        this: WeakView<Self>,
3336        leader_id: PeerId,
3337        view: &proto::View,
3338        cx: &mut AsyncWindowContext,
3339    ) -> Result<()> {
3340        let this = this.upgrade().context("workspace dropped")?;
3341
3342        let Some(id) = view.id.clone() else {
3343            return Err(anyhow!("no id for view"));
3344        };
3345        let id = ViewId::from_proto(id)?;
3346        let panel_id = view.panel_id.and_then(|id| proto::PanelId::from_i32(id));
3347
3348        let pane = this.update(cx, |this, _cx| {
3349            let state = this
3350                .follower_states
3351                .get(&leader_id)
3352                .context("stopped following")?;
3353            anyhow::Ok(state.pane().clone())
3354        })??;
3355        let existing_item = pane.update(cx, |pane, cx| {
3356            let client = this.read(cx).client().clone();
3357            pane.items().find_map(|item| {
3358                let item = item.to_followable_item_handle(cx)?;
3359                if item.remote_id(&client, cx) == Some(id) {
3360                    Some(item)
3361                } else {
3362                    None
3363                }
3364            })
3365        })?;
3366        let item = if let Some(existing_item) = existing_item {
3367            existing_item
3368        } else {
3369            let variant = view.variant.clone();
3370            if variant.is_none() {
3371                Err(anyhow!("missing view variant"))?;
3372            }
3373
3374            let task = cx.update(|cx| {
3375                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, cx)
3376            })?;
3377
3378            let Some(task) = task else {
3379                return Err(anyhow!(
3380                    "failed to construct view from leader (maybe from a different version of zed?)"
3381                ));
3382            };
3383
3384            let mut new_item = task.await?;
3385            pane.update(cx, |pane, cx| {
3386                let mut item_ix_to_remove = None;
3387                for (ix, item) in pane.items().enumerate() {
3388                    if let Some(item) = item.to_followable_item_handle(cx) {
3389                        match new_item.dedup(item.as_ref(), cx) {
3390                            Some(item::Dedup::KeepExisting) => {
3391                                new_item =
3392                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
3393                                break;
3394                            }
3395                            Some(item::Dedup::ReplaceExisting) => {
3396                                item_ix_to_remove = Some(ix);
3397                                break;
3398                            }
3399                            None => {}
3400                        }
3401                    }
3402                }
3403
3404                if let Some(ix) = item_ix_to_remove {
3405                    pane.remove_item(ix, false, false, cx);
3406                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), cx);
3407                }
3408            })?;
3409
3410            new_item
3411        };
3412
3413        this.update(cx, |this, cx| {
3414            let state = this.follower_states.get_mut(&leader_id)?;
3415            item.set_leader_peer_id(Some(leader_id), cx);
3416            state.items_by_leader_view_id.insert(
3417                id,
3418                FollowerView {
3419                    view: item,
3420                    location: panel_id,
3421                },
3422            );
3423
3424            Some(())
3425        })?;
3426
3427        Ok(())
3428    }
3429
3430    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3431        let mut is_project_item = true;
3432        let mut update = proto::UpdateActiveView::default();
3433        if cx.is_window_active() {
3434            let (active_item, panel_id) = self.active_item_for_followers(cx);
3435
3436            if let Some(item) = active_item {
3437                if item.focus_handle(cx).contains_focused(cx) {
3438                    let leader_id = self
3439                        .pane_for(&*item)
3440                        .and_then(|pane| self.leader_for_pane(&pane));
3441
3442                    if let Some(item) = item.to_followable_item_handle(cx) {
3443                        let id = item
3444                            .remote_id(&self.app_state.client, cx)
3445                            .map(|id| id.to_proto());
3446
3447                        if let Some(id) = id.clone() {
3448                            if let Some(variant) = item.to_state_proto(cx) {
3449                                let view = Some(proto::View {
3450                                    id: Some(id.clone()),
3451                                    leader_id,
3452                                    variant: Some(variant),
3453                                    panel_id: panel_id.map(|id| id as i32),
3454                                });
3455
3456                                is_project_item = item.is_project_item(cx);
3457                                update = proto::UpdateActiveView {
3458                                    view,
3459                                    // TODO: Remove after version 0.145.x stabilizes.
3460                                    id: Some(id.clone()),
3461                                    leader_id,
3462                                };
3463                            }
3464                        };
3465                    }
3466                }
3467            }
3468        }
3469
3470        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
3471        if active_view_id != self.last_active_view_id.as_ref() {
3472            self.last_active_view_id = active_view_id.cloned();
3473            self.update_followers(
3474                is_project_item,
3475                proto::update_followers::Variant::UpdateActiveView(update),
3476                cx,
3477            );
3478        }
3479    }
3480
3481    fn active_item_for_followers(
3482        &self,
3483        cx: &mut WindowContext,
3484    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
3485        let mut active_item = None;
3486        let mut panel_id = None;
3487        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
3488            if dock.focus_handle(cx).contains_focused(cx) {
3489                if let Some(panel) = dock.read(cx).active_panel() {
3490                    if let Some(pane) = panel.pane(cx) {
3491                        if let Some(item) = pane.read(cx).active_item() {
3492                            active_item = Some(item);
3493                            panel_id = panel.remote_id();
3494                            break;
3495                        }
3496                    }
3497                }
3498            }
3499        }
3500
3501        if active_item.is_none() {
3502            active_item = self.active_pane().read(cx).active_item();
3503        }
3504        (active_item, panel_id)
3505    }
3506
3507    fn update_followers(
3508        &self,
3509        project_only: bool,
3510        update: proto::update_followers::Variant,
3511        cx: &mut WindowContext,
3512    ) -> Option<()> {
3513        // If this update only applies to for followers in the current project,
3514        // then skip it unless this project is shared. If it applies to all
3515        // followers, regardless of project, then set `project_id` to none,
3516        // indicating that it goes to all followers.
3517        let project_id = if project_only {
3518            Some(self.project.read(cx).remote_id()?)
3519        } else {
3520            None
3521        };
3522        self.app_state().workspace_store.update(cx, |store, cx| {
3523            store.update_followers(project_id, update, cx)
3524        })
3525    }
3526
3527    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3528        self.follower_states.iter().find_map(|(leader_id, state)| {
3529            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
3530                Some(*leader_id)
3531            } else {
3532                None
3533            }
3534        })
3535    }
3536
3537    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3538        cx.notify();
3539
3540        let call = self.active_call()?;
3541        let room = call.read(cx).room()?.read(cx);
3542        let participant = room.remote_participant_for_peer_id(leader_id)?;
3543
3544        let leader_in_this_app;
3545        let leader_in_this_project;
3546        match participant.location {
3547            call::ParticipantLocation::SharedProject { project_id } => {
3548                leader_in_this_app = true;
3549                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3550            }
3551            call::ParticipantLocation::UnsharedProject => {
3552                leader_in_this_app = true;
3553                leader_in_this_project = false;
3554            }
3555            call::ParticipantLocation::External => {
3556                leader_in_this_app = false;
3557                leader_in_this_project = false;
3558            }
3559        };
3560
3561        let state = self.follower_states.get(&leader_id)?;
3562        let mut item_to_activate = None;
3563        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3564            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3565                if leader_in_this_project || !item.view.is_project_item(cx) {
3566                    item_to_activate = Some((item.location, item.view.boxed_clone()));
3567                }
3568            }
3569        } else if let Some(shared_screen) =
3570            self.shared_screen_for_peer(leader_id, &state.center_pane, cx)
3571        {
3572            item_to_activate = Some((None, Box::new(shared_screen)));
3573        }
3574
3575        let (panel_id, item) = item_to_activate?;
3576
3577        let mut transfer_focus = state.center_pane.read(cx).has_focus(cx);
3578        let pane;
3579        if let Some(panel_id) = panel_id {
3580            pane = self.activate_panel_for_proto_id(panel_id, cx)?.pane(cx)?;
3581            let state = self.follower_states.get_mut(&leader_id)?;
3582            state.dock_pane = Some(pane.clone());
3583        } else {
3584            pane = state.center_pane.clone();
3585            let state = self.follower_states.get_mut(&leader_id)?;
3586            if let Some(dock_pane) = state.dock_pane.take() {
3587                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(cx);
3588            }
3589        }
3590
3591        pane.update(cx, |pane, cx| {
3592            let focus_active_item = pane.has_focus(cx) || transfer_focus;
3593            if let Some(index) = pane.index_for_item(item.as_ref()) {
3594                pane.activate_item(index, false, false, cx);
3595            } else {
3596                pane.add_item(item.boxed_clone(), false, false, None, cx)
3597            }
3598
3599            if focus_active_item {
3600                pane.focus_active_item(cx)
3601            }
3602        });
3603
3604        None
3605    }
3606
3607    fn shared_screen_for_peer(
3608        &self,
3609        peer_id: PeerId,
3610        pane: &View<Pane>,
3611        cx: &mut WindowContext,
3612    ) -> Option<View<SharedScreen>> {
3613        let call = self.active_call()?;
3614        let room = call.read(cx).room()?.read(cx);
3615        let participant = room.remote_participant_for_peer_id(peer_id)?;
3616        let track = participant.video_tracks.values().next()?.clone();
3617        let user = participant.user.clone();
3618
3619        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3620            if item.read(cx).peer_id == peer_id {
3621                return Some(item);
3622            }
3623        }
3624
3625        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3626    }
3627
3628    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3629        if cx.is_window_active() {
3630            self.update_active_view_for_followers(cx);
3631
3632            if let Some(database_id) = self.database_id {
3633                cx.background_executor()
3634                    .spawn(persistence::DB.update_timestamp(database_id))
3635                    .detach();
3636            }
3637        } else {
3638            for pane in &self.panes {
3639                pane.update(cx, |pane, cx| {
3640                    if let Some(item) = pane.active_item() {
3641                        item.workspace_deactivated(cx);
3642                    }
3643                    for item in pane.items() {
3644                        if matches!(
3645                            item.workspace_settings(cx).autosave,
3646                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3647                        ) {
3648                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3649                                .detach_and_log_err(cx);
3650                        }
3651                    }
3652                });
3653            }
3654        }
3655    }
3656
3657    fn active_call(&self) -> Option<&Model<ActiveCall>> {
3658        self.active_call.as_ref().map(|(call, _)| call)
3659    }
3660
3661    fn on_active_call_event(
3662        &mut self,
3663        _: Model<ActiveCall>,
3664        event: &call::room::Event,
3665        cx: &mut ViewContext<Self>,
3666    ) {
3667        match event {
3668            call::room::Event::ParticipantLocationChanged { participant_id }
3669            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3670                self.leader_updated(*participant_id, cx);
3671            }
3672            _ => {}
3673        }
3674    }
3675
3676    pub fn database_id(&self) -> Option<WorkspaceId> {
3677        self.database_id
3678    }
3679
3680    fn local_paths(&self, cx: &AppContext) -> Option<Vec<Arc<Path>>> {
3681        let project = self.project().read(cx);
3682
3683        if project.is_local() {
3684            Some(
3685                project
3686                    .visible_worktrees(cx)
3687                    .map(|worktree| worktree.read(cx).abs_path())
3688                    .collect::<Vec<_>>(),
3689            )
3690        } else {
3691            None
3692        }
3693    }
3694
3695    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3696        match member {
3697            Member::Axis(PaneAxis { members, .. }) => {
3698                for child in members.iter() {
3699                    self.remove_panes(child.clone(), cx)
3700                }
3701            }
3702            Member::Pane(pane) => {
3703                self.force_remove_pane(&pane, cx);
3704            }
3705        }
3706    }
3707
3708    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3709        self.panes.retain(|p| p != pane);
3710        self.panes
3711            .last()
3712            .unwrap()
3713            .update(cx, |pane, cx| pane.focus(cx));
3714        if self.last_active_center_pane == Some(pane.downgrade()) {
3715            self.last_active_center_pane = None;
3716        }
3717        cx.notify();
3718    }
3719
3720    fn serialize_workspace(&mut self, cx: &mut ViewContext<Self>) {
3721        if self._schedule_serialize.is_none() {
3722            self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3723                cx.background_executor()
3724                    .timer(Duration::from_millis(100))
3725                    .await;
3726                this.update(&mut cx, |this, cx| {
3727                    this.serialize_workspace_internal(cx).detach();
3728                    this._schedule_serialize.take();
3729                })
3730                .log_err();
3731            }));
3732        }
3733    }
3734
3735    fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
3736        let Some(database_id) = self.database_id() else {
3737            return Task::ready(());
3738        };
3739
3740        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3741            let (items, active) = {
3742                let pane = pane_handle.read(cx);
3743                let active_item_id = pane.active_item().map(|item| item.item_id());
3744                (
3745                    pane.items()
3746                        .filter_map(|item_handle| {
3747                            Some(SerializedItem {
3748                                kind: Arc::from(item_handle.serialized_item_kind()?),
3749                                item_id: item_handle.item_id().as_u64(),
3750                                active: Some(item_handle.item_id()) == active_item_id,
3751                                preview: pane.is_active_preview_item(item_handle.item_id()),
3752                            })
3753                        })
3754                        .collect::<Vec<_>>(),
3755                    pane.has_focus(cx),
3756                )
3757            };
3758
3759            SerializedPane::new(items, active)
3760        }
3761
3762        fn build_serialized_pane_group(
3763            pane_group: &Member,
3764            cx: &WindowContext,
3765        ) -> SerializedPaneGroup {
3766            match pane_group {
3767                Member::Axis(PaneAxis {
3768                    axis,
3769                    members,
3770                    flexes,
3771                    bounding_boxes: _,
3772                }) => SerializedPaneGroup::Group {
3773                    axis: SerializedAxis(*axis),
3774                    children: members
3775                        .iter()
3776                        .map(|member| build_serialized_pane_group(member, cx))
3777                        .collect::<Vec<_>>(),
3778                    flexes: Some(flexes.lock().clone()),
3779                },
3780                Member::Pane(pane_handle) => {
3781                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3782                }
3783            }
3784        }
3785
3786        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3787            let left_dock = this.left_dock.read(cx);
3788            let left_visible = left_dock.is_open();
3789            let left_active_panel = left_dock
3790                .visible_panel()
3791                .map(|panel| panel.persistent_name().to_string());
3792            let left_dock_zoom = left_dock
3793                .visible_panel()
3794                .map(|panel| panel.is_zoomed(cx))
3795                .unwrap_or(false);
3796
3797            let right_dock = this.right_dock.read(cx);
3798            let right_visible = right_dock.is_open();
3799            let right_active_panel = right_dock
3800                .visible_panel()
3801                .map(|panel| panel.persistent_name().to_string());
3802            let right_dock_zoom = right_dock
3803                .visible_panel()
3804                .map(|panel| panel.is_zoomed(cx))
3805                .unwrap_or(false);
3806
3807            let bottom_dock = this.bottom_dock.read(cx);
3808            let bottom_visible = bottom_dock.is_open();
3809            let bottom_active_panel = bottom_dock
3810                .visible_panel()
3811                .map(|panel| panel.persistent_name().to_string());
3812            let bottom_dock_zoom = bottom_dock
3813                .visible_panel()
3814                .map(|panel| panel.is_zoomed(cx))
3815                .unwrap_or(false);
3816
3817            DockStructure {
3818                left: DockData {
3819                    visible: left_visible,
3820                    active_panel: left_active_panel,
3821                    zoom: left_dock_zoom,
3822                },
3823                right: DockData {
3824                    visible: right_visible,
3825                    active_panel: right_active_panel,
3826                    zoom: right_dock_zoom,
3827                },
3828                bottom: DockData {
3829                    visible: bottom_visible,
3830                    active_panel: bottom_active_panel,
3831                    zoom: bottom_dock_zoom,
3832                },
3833            }
3834        }
3835
3836        let location = if let Some(local_paths) = self.local_paths(cx) {
3837            if !local_paths.is_empty() {
3838                let (order, paths): (Vec<_>, Vec<_>) = local_paths
3839                    .iter()
3840                    .enumerate()
3841                    .sorted_by(|a, b| a.1.cmp(b.1))
3842                    .unzip();
3843
3844                Some(SerializedWorkspaceLocation::Local(
3845                    LocalPaths::new(paths),
3846                    LocalPathsOrder::new(order),
3847                ))
3848            } else {
3849                None
3850            }
3851        } else if let Some(dev_server_project_id) = self.project().read(cx).dev_server_project_id()
3852        {
3853            let store = dev_server_projects::Store::global(cx).read(cx);
3854            maybe!({
3855                let project = store.dev_server_project(dev_server_project_id)?;
3856                let dev_server = store.dev_server(project.dev_server_id)?;
3857
3858                let dev_server_project = SerializedDevServerProject {
3859                    id: dev_server_project_id,
3860                    dev_server_name: dev_server.name.to_string(),
3861                    paths: project.paths.iter().map(|path| path.clone()).collect(),
3862                };
3863                Some(SerializedWorkspaceLocation::DevServer(dev_server_project))
3864            })
3865        } else {
3866            None
3867        };
3868
3869        // don't save workspace state for the empty workspace.
3870        if let Some(location) = location {
3871            let center_group = build_serialized_pane_group(&self.center.root, cx);
3872            let docks = build_serialized_docks(self, cx);
3873            let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
3874            let serialized_workspace = SerializedWorkspace {
3875                id: database_id,
3876                location,
3877                center_group,
3878                window_bounds,
3879                display: Default::default(),
3880                docks,
3881                centered_layout: self.centered_layout,
3882            };
3883            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
3884        }
3885        Task::ready(())
3886    }
3887
3888    pub(crate) fn load_workspace(
3889        serialized_workspace: SerializedWorkspace,
3890        paths_to_open: Vec<Option<ProjectPath>>,
3891        cx: &mut ViewContext<Workspace>,
3892    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3893        cx.spawn(|workspace, mut cx| async move {
3894            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3895
3896            let mut center_group = None;
3897            let mut center_items = None;
3898
3899            // Traverse the splits tree and add to things
3900            if let Some((group, active_pane, items)) = serialized_workspace
3901                .center_group
3902                .deserialize(
3903                    &project,
3904                    serialized_workspace.id,
3905                    workspace.clone(),
3906                    &mut cx,
3907                )
3908                .await
3909            {
3910                center_items = Some(items);
3911                center_group = Some((group, active_pane))
3912            }
3913
3914            let mut items_by_project_path = cx.update(|cx| {
3915                center_items
3916                    .unwrap_or_default()
3917                    .into_iter()
3918                    .filter_map(|item| {
3919                        let item = item?;
3920                        let project_path = item.project_path(cx)?;
3921                        Some((project_path, item))
3922                    })
3923                    .collect::<HashMap<_, _>>()
3924            })?;
3925
3926            let opened_items = paths_to_open
3927                .into_iter()
3928                .map(|path_to_open| {
3929                    path_to_open
3930                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3931                })
3932                .collect::<Vec<_>>();
3933
3934            // Remove old panes from workspace panes list
3935            workspace.update(&mut cx, |workspace, cx| {
3936                if let Some((center_group, active_pane)) = center_group {
3937                    workspace.remove_panes(workspace.center.root.clone(), cx);
3938
3939                    // Swap workspace center group
3940                    workspace.center = PaneGroup::with_root(center_group);
3941                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3942                    if let Some(active_pane) = active_pane {
3943                        workspace.active_pane = active_pane;
3944                        cx.focus_self();
3945                    } else {
3946                        workspace.active_pane = workspace.center.first_pane().clone();
3947                    }
3948                }
3949
3950                let docks = serialized_workspace.docks;
3951
3952                for (dock, serialized_dock) in [
3953                    (&mut workspace.right_dock, docks.right),
3954                    (&mut workspace.left_dock, docks.left),
3955                    (&mut workspace.bottom_dock, docks.bottom),
3956                ]
3957                .iter_mut()
3958                {
3959                    dock.update(cx, |dock, cx| {
3960                        dock.serialized_dock = Some(serialized_dock.clone());
3961                        dock.restore_state(cx);
3962                    });
3963                }
3964
3965                cx.notify();
3966            })?;
3967
3968            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3969            workspace
3970                .update(&mut cx, |workspace, cx| {
3971                    workspace.serialize_workspace_internal(cx).detach();
3972                })
3973                .ok();
3974
3975            Ok(opened_items)
3976        })
3977    }
3978
3979    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3980        self.add_workspace_actions_listeners(div, cx)
3981            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3982            .on_action(cx.listener(Self::close_all_items_and_panes))
3983            .on_action(cx.listener(Self::save_all))
3984            .on_action(cx.listener(Self::send_keystrokes))
3985            .on_action(cx.listener(Self::add_folder_to_project))
3986            .on_action(cx.listener(Self::follow_next_collaborator))
3987            .on_action(cx.listener(Self::open))
3988            .on_action(cx.listener(Self::close_window))
3989            .on_action(cx.listener(Self::activate_pane_at_index))
3990            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3991                let pane = workspace.active_pane().clone();
3992                workspace.unfollow_in_pane(&pane, cx);
3993            }))
3994            .on_action(cx.listener(|workspace, action: &Save, cx| {
3995                workspace
3996                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3997                    .detach_and_log_err(cx);
3998            }))
3999            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
4000                workspace
4001                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
4002                    .detach_and_log_err(cx);
4003            }))
4004            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
4005                workspace
4006                    .save_active_item(SaveIntent::SaveAs, cx)
4007                    .detach_and_log_err(cx);
4008            }))
4009            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
4010                workspace.activate_previous_pane(cx)
4011            }))
4012            .on_action(
4013                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
4014            )
4015            .on_action(
4016                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
4017                    workspace.activate_pane_in_direction(action.0, cx)
4018                }),
4019            )
4020            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
4021                workspace.swap_pane_in_direction(action.0, cx)
4022            }))
4023            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
4024                this.toggle_dock(DockPosition::Left, cx);
4025            }))
4026            .on_action(
4027                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
4028                    workspace.toggle_dock(DockPosition::Right, cx);
4029                }),
4030            )
4031            .on_action(
4032                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
4033                    workspace.toggle_dock(DockPosition::Bottom, cx);
4034                }),
4035            )
4036            .on_action(
4037                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
4038                    workspace.close_all_docks(cx);
4039                }),
4040            )
4041            .on_action(
4042                cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| {
4043                    workspace.clear_all_notifications(cx);
4044                }),
4045            )
4046            .on_action(
4047                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
4048                    workspace.reopen_closed_item(cx).detach();
4049                }),
4050            )
4051            .on_action(cx.listener(Workspace::toggle_centered_layout))
4052    }
4053
4054    #[cfg(any(test, feature = "test-support"))]
4055    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
4056        use node_runtime::FakeNodeRuntime;
4057
4058        let client = project.read(cx).client();
4059        let user_store = project.read(cx).user_store();
4060
4061        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
4062        cx.activate_window();
4063        let app_state = Arc::new(AppState {
4064            languages: project.read(cx).languages().clone(),
4065            workspace_store,
4066            client,
4067            user_store,
4068            fs: project.read(cx).fs().clone(),
4069            build_window_options: |_, _| Default::default(),
4070            node_runtime: FakeNodeRuntime::new(),
4071        });
4072        let workspace = Self::new(Default::default(), project, app_state, cx);
4073        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
4074        workspace
4075    }
4076
4077    pub fn register_action<A: Action>(
4078        &mut self,
4079        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
4080    ) -> &mut Self {
4081        let callback = Arc::new(callback);
4082
4083        self.workspace_actions.push(Box::new(move |div, cx| {
4084            let callback = callback.clone();
4085            div.on_action(
4086                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
4087            )
4088        }));
4089        self
4090    }
4091
4092    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
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_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}