workspace.rs

    1pub mod dock;
    2pub mod history_manager;
    3pub mod item;
    4mod modal_layer;
    5pub mod notifications;
    6pub mod pane;
    7pub mod pane_group;
    8mod persistence;
    9pub mod searchable;
   10pub mod shared_screen;
   11mod status_bar;
   12pub mod tasks;
   13mod theme_preview;
   14mod toast_layer;
   15mod toolbar;
   16mod workspace_settings;
   17
   18pub use toast_layer::{ToastAction, ToastLayer, ToastView};
   19
   20use anyhow::{Context as _, Result, anyhow};
   21use call::{ActiveCall, call_settings::CallSettings};
   22use client::{
   23    ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore,
   24    proto::{self, ErrorCode, PanelId, PeerId},
   25};
   26use collections::{HashMap, HashSet, hash_map};
   27pub use dock::Panel;
   28use dock::{Dock, DockPosition, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE};
   29use futures::{
   30    Future, FutureExt, StreamExt,
   31    channel::{
   32        mpsc::{self, UnboundedReceiver, UnboundedSender},
   33        oneshot,
   34    },
   35    future::try_join_all,
   36};
   37use gpui::{
   38    Action, AnyEntity, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds, Context,
   39    CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
   40    Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton,
   41    PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task,
   42    Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId, WindowOptions, actions, canvas,
   43    point, relative, size, transparent_black,
   44};
   45pub use history_manager::*;
   46pub use item::{
   47    FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
   48    ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
   49};
   50use itertools::Itertools;
   51use language::{Buffer, LanguageRegistry, Rope};
   52pub use modal_layer::*;
   53use node_runtime::NodeRuntime;
   54use notifications::{
   55    DetachAndPromptErr, Notifications, dismiss_app_notification,
   56    simple_message_notification::MessageNotification,
   57};
   58pub use pane::*;
   59pub use pane_group::*;
   60use persistence::{
   61    DB, SerializedWindowBounds,
   62    model::{SerializedSshProject, SerializedWorkspace},
   63};
   64pub use persistence::{
   65    DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items,
   66    model::{ItemId, LocalPaths, SerializedWorkspaceLocation},
   67};
   68use postage::stream::Stream;
   69use project::{
   70    DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
   71    debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
   72};
   73use remote::{SshClientDelegate, SshConnectionOptions, ssh_session::ConnectionIdentifier};
   74use schemars::JsonSchema;
   75use serde::Deserialize;
   76use session::AppSession;
   77use settings::Settings;
   78use shared_screen::SharedScreen;
   79use sqlez::{
   80    bindable::{Bind, Column, StaticColumnCount},
   81    statement::Statement,
   82};
   83use status_bar::StatusBar;
   84pub use status_bar::StatusItemView;
   85use std::{
   86    any::TypeId,
   87    borrow::Cow,
   88    cell::RefCell,
   89    cmp,
   90    collections::hash_map::DefaultHasher,
   91    env,
   92    hash::{Hash, Hasher},
   93    path::{Path, PathBuf},
   94    process::ExitStatus,
   95    rc::Rc,
   96    sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
   97    time::Duration,
   98};
   99use task::{DebugScenario, SpawnInTerminal, TaskContext};
  100use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
  101pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  102pub use ui;
  103use ui::{Window, prelude::*};
  104use util::{ResultExt, TryFutureExt, paths::SanitizedPath, serde::default_true};
  105use uuid::Uuid;
  106pub use workspace_settings::{
  107    AutosaveSetting, BottomDockLayout, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
  108};
  109use zed_actions::{Spawn, feedback::FileBugReport};
  110
  111use crate::notifications::NotificationId;
  112use crate::persistence::{
  113    SerializedAxis,
  114    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  115};
  116
  117pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200);
  118
  119static ZED_WINDOW_SIZE: LazyLock<Option<Size<Pixels>>> = LazyLock::new(|| {
  120    env::var("ZED_WINDOW_SIZE")
  121        .ok()
  122        .as_deref()
  123        .and_then(parse_pixel_size_env_var)
  124});
  125
  126static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| {
  127    env::var("ZED_WINDOW_POSITION")
  128        .ok()
  129        .as_deref()
  130        .and_then(parse_pixel_position_env_var)
  131});
  132
  133pub trait TerminalProvider {
  134    fn spawn(
  135        &self,
  136        task: SpawnInTerminal,
  137        window: &mut Window,
  138        cx: &mut App,
  139    ) -> Task<Option<Result<ExitStatus>>>;
  140}
  141
  142pub trait DebuggerProvider {
  143    // `active_buffer` is used to resolve build task's name against language-specific tasks.
  144    fn start_session(
  145        &self,
  146        definition: DebugScenario,
  147        task_context: TaskContext,
  148        active_buffer: Option<Entity<Buffer>>,
  149        worktree_id: Option<WorktreeId>,
  150        window: &mut Window,
  151        cx: &mut App,
  152    );
  153
  154    fn spawn_task_or_modal(
  155        &self,
  156        workspace: &mut Workspace,
  157        action: &Spawn,
  158        window: &mut Window,
  159        cx: &mut Context<Workspace>,
  160    );
  161
  162    fn task_scheduled(&self, cx: &mut App);
  163    fn debug_scenario_scheduled(&self, cx: &mut App);
  164    fn debug_scenario_scheduled_last(&self, cx: &App) -> bool;
  165
  166    fn active_thread_state(&self, cx: &App) -> Option<ThreadStatus>;
  167}
  168
  169actions!(
  170    workspace,
  171    [
  172        ActivateNextPane,
  173        ActivatePreviousPane,
  174        ActivateNextWindow,
  175        ActivatePreviousWindow,
  176        AddFolderToProject,
  177        ClearAllNotifications,
  178        CloseActiveDock,
  179        CloseAllDocks,
  180        CloseWindow,
  181        Feedback,
  182        FollowNextCollaborator,
  183        MoveFocusedPanelToNextPosition,
  184        NewCenterTerminal,
  185        NewFile,
  186        NewFileSplitVertical,
  187        NewFileSplitHorizontal,
  188        NewSearch,
  189        NewTerminal,
  190        NewWindow,
  191        Open,
  192        OpenFiles,
  193        OpenInTerminal,
  194        OpenComponentPreview,
  195        ReloadActiveItem,
  196        SaveAs,
  197        SaveWithoutFormat,
  198        ShutdownDebugAdapters,
  199        SuppressNotification,
  200        ToggleBottomDock,
  201        ToggleCenteredLayout,
  202        ToggleLeftDock,
  203        ToggleRightDock,
  204        ToggleZoom,
  205        Unfollow,
  206        Welcome,
  207        RestoreBanner,
  208        ToggleExpandItem,
  209    ]
  210);
  211
  212#[derive(Clone, PartialEq)]
  213pub struct OpenPaths {
  214    pub paths: Vec<PathBuf>,
  215}
  216
  217#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)]
  218#[action(namespace = workspace)]
  219pub struct ActivatePane(pub usize);
  220
  221#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)]
  222#[action(namespace = workspace)]
  223#[serde(deny_unknown_fields)]
  224pub struct MoveItemToPane {
  225    pub destination: usize,
  226    #[serde(default = "default_true")]
  227    pub focus: bool,
  228    #[serde(default)]
  229    pub clone: bool,
  230}
  231
  232#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)]
  233#[action(namespace = workspace)]
  234#[serde(deny_unknown_fields)]
  235pub struct MoveItemToPaneInDirection {
  236    pub direction: SplitDirection,
  237    #[serde(default = "default_true")]
  238    pub focus: bool,
  239    #[serde(default)]
  240    pub clone: bool,
  241}
  242
  243#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Action)]
  244#[action(namespace = workspace)]
  245#[serde(deny_unknown_fields)]
  246pub struct SaveAll {
  247    pub save_intent: Option<SaveIntent>,
  248}
  249
  250#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Action)]
  251#[action(namespace = workspace)]
  252#[serde(deny_unknown_fields)]
  253pub struct Save {
  254    pub save_intent: Option<SaveIntent>,
  255}
  256
  257#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema, Action)]
  258#[action(namespace = workspace)]
  259#[serde(deny_unknown_fields)]
  260pub struct CloseAllItemsAndPanes {
  261    pub save_intent: Option<SaveIntent>,
  262}
  263
  264#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema, Action)]
  265#[action(namespace = workspace)]
  266#[serde(deny_unknown_fields)]
  267pub struct CloseInactiveTabsAndPanes {
  268    pub save_intent: Option<SaveIntent>,
  269}
  270
  271#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)]
  272#[action(namespace = workspace)]
  273pub struct SendKeystrokes(pub String);
  274
  275#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema, Action)]
  276#[action(namespace = workspace)]
  277#[serde(deny_unknown_fields)]
  278pub struct Reload {
  279    pub binary_path: Option<PathBuf>,
  280}
  281
  282actions!(
  283    project_symbols,
  284    [
  285        #[action(name = "Toggle")]
  286        ToggleProjectSymbols
  287    ]
  288);
  289
  290#[derive(Default, PartialEq, Eq, Clone, Deserialize, JsonSchema, Action)]
  291#[action(namespace = file_finder, name = "Toggle")]
  292#[serde(deny_unknown_fields)]
  293pub struct ToggleFileFinder {
  294    #[serde(default)]
  295    pub separate_history: bool,
  296}
  297
  298actions!(
  299    workspace,
  300    [
  301        ActivatePaneLeft,
  302        ActivatePaneRight,
  303        ActivatePaneUp,
  304        ActivatePaneDown,
  305        SwapPaneLeft,
  306        SwapPaneRight,
  307        SwapPaneUp,
  308        SwapPaneDown,
  309    ]
  310);
  311
  312#[derive(PartialEq, Eq, Debug)]
  313pub enum CloseIntent {
  314    /// Quit the program entirely.
  315    Quit,
  316    /// Close a window.
  317    CloseWindow,
  318    /// Replace the workspace in an existing window.
  319    ReplaceWindow,
  320}
  321
  322#[derive(Clone)]
  323pub struct Toast {
  324    id: NotificationId,
  325    msg: Cow<'static, str>,
  326    autohide: bool,
  327    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut Window, &mut App)>)>,
  328}
  329
  330impl Toast {
  331    pub fn new<I: Into<Cow<'static, str>>>(id: NotificationId, msg: I) -> Self {
  332        Toast {
  333            id,
  334            msg: msg.into(),
  335            on_click: None,
  336            autohide: false,
  337        }
  338    }
  339
  340    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
  341    where
  342        M: Into<Cow<'static, str>>,
  343        F: Fn(&mut Window, &mut App) + 'static,
  344    {
  345        self.on_click = Some((message.into(), Arc::new(on_click)));
  346        self
  347    }
  348
  349    pub fn autohide(mut self) -> Self {
  350        self.autohide = true;
  351        self
  352    }
  353}
  354
  355impl PartialEq for Toast {
  356    fn eq(&self, other: &Self) -> bool {
  357        self.id == other.id
  358            && self.msg == other.msg
  359            && self.on_click.is_some() == other.on_click.is_some()
  360    }
  361}
  362
  363#[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema, Action)]
  364#[action(namespace = workspace)]
  365#[serde(deny_unknown_fields)]
  366pub struct OpenTerminal {
  367    pub working_directory: PathBuf,
  368}
  369
  370#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
  371pub struct WorkspaceId(i64);
  372
  373impl StaticColumnCount for WorkspaceId {}
  374impl Bind for WorkspaceId {
  375    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
  376        self.0.bind(statement, start_index)
  377    }
  378}
  379impl Column for WorkspaceId {
  380    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
  381        i64::column(statement, start_index)
  382            .map(|(i, next_index)| (Self(i), next_index))
  383            .with_context(|| format!("Failed to read WorkspaceId at index {start_index}"))
  384    }
  385}
  386impl From<WorkspaceId> for i64 {
  387    fn from(val: WorkspaceId) -> Self {
  388        val.0
  389    }
  390}
  391
  392pub fn init_settings(cx: &mut App) {
  393    WorkspaceSettings::register(cx);
  394    ItemSettings::register(cx);
  395    PreviewTabsSettings::register(cx);
  396    TabBarSettings::register(cx);
  397}
  398
  399fn prompt_and_open_paths(app_state: Arc<AppState>, options: PathPromptOptions, cx: &mut App) {
  400    let paths = cx.prompt_for_paths(options);
  401    cx.spawn(
  402        async move |cx| match paths.await.anyhow().and_then(|res| res) {
  403            Ok(Some(paths)) => {
  404                cx.update(|cx| {
  405                    open_paths(&paths, app_state, OpenOptions::default(), cx).detach_and_log_err(cx)
  406                })
  407                .ok();
  408            }
  409            Ok(None) => {}
  410            Err(err) => {
  411                util::log_err(&err);
  412                cx.update(|cx| {
  413                    if let Some(workspace_window) = cx
  414                        .active_window()
  415                        .and_then(|window| window.downcast::<Workspace>())
  416                    {
  417                        workspace_window
  418                            .update(cx, |workspace, _, cx| {
  419                                workspace.show_portal_error(err.to_string(), cx);
  420                            })
  421                            .ok();
  422                    }
  423                })
  424                .ok();
  425            }
  426        },
  427    )
  428    .detach();
  429}
  430
  431pub fn init(app_state: Arc<AppState>, cx: &mut App) {
  432    init_settings(cx);
  433    component::init();
  434    theme_preview::init(cx);
  435    toast_layer::init(cx);
  436    history_manager::init(cx);
  437
  438    cx.on_action(Workspace::close_global);
  439    cx.on_action(reload);
  440
  441    cx.on_action({
  442        let app_state = Arc::downgrade(&app_state);
  443        move |_: &Open, cx: &mut App| {
  444            if let Some(app_state) = app_state.upgrade() {
  445                prompt_and_open_paths(
  446                    app_state,
  447                    PathPromptOptions {
  448                        files: true,
  449                        directories: true,
  450                        multiple: true,
  451                    },
  452                    cx,
  453                );
  454            }
  455        }
  456    });
  457    cx.on_action({
  458        let app_state = Arc::downgrade(&app_state);
  459        move |_: &OpenFiles, cx: &mut App| {
  460            let directories = cx.can_select_mixed_files_and_dirs();
  461            if let Some(app_state) = app_state.upgrade() {
  462                prompt_and_open_paths(
  463                    app_state,
  464                    PathPromptOptions {
  465                        files: true,
  466                        directories,
  467                        multiple: true,
  468                    },
  469                    cx,
  470                );
  471            }
  472        }
  473    });
  474}
  475
  476type BuildProjectItemFn =
  477    fn(AnyEntity, Entity<Project>, Option<&Pane>, &mut Window, &mut App) -> Box<dyn ItemHandle>;
  478
  479type BuildProjectItemForPathFn =
  480    fn(
  481        &Entity<Project>,
  482        &ProjectPath,
  483        &mut Window,
  484        &mut App,
  485    ) -> Option<Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>>>;
  486
  487#[derive(Clone, Default)]
  488struct ProjectItemRegistry {
  489    build_project_item_fns_by_type: HashMap<TypeId, BuildProjectItemFn>,
  490    build_project_item_for_path_fns: Vec<BuildProjectItemForPathFn>,
  491}
  492
  493impl ProjectItemRegistry {
  494    fn register<T: ProjectItem>(&mut self) {
  495        self.build_project_item_fns_by_type.insert(
  496            TypeId::of::<T::Item>(),
  497            |item, project, pane, window, cx| {
  498                let item = item.downcast().unwrap();
  499                Box::new(cx.new(|cx| T::for_project_item(project, pane, item, window, cx)))
  500                    as Box<dyn ItemHandle>
  501            },
  502        );
  503        self.build_project_item_for_path_fns
  504            .push(|project, project_path, window, cx| {
  505                let project_item =
  506                    <T::Item as project::ProjectItem>::try_open(project, project_path, cx)?;
  507                let project = project.clone();
  508                Some(window.spawn(cx, async move |cx| {
  509                    let project_item = project_item.await?;
  510                    let project_entry_id: Option<ProjectEntryId> =
  511                        project_item.read_with(cx, project::ProjectItem::entry_id)?;
  512                    let build_workspace_item = Box::new(
  513                        |pane: &mut Pane, window: &mut Window, cx: &mut Context<Pane>| {
  514                            Box::new(cx.new(|cx| {
  515                                T::for_project_item(project, Some(pane), project_item, window, cx)
  516                            })) as Box<dyn ItemHandle>
  517                        },
  518                    ) as Box<_>;
  519                    Ok((project_entry_id, build_workspace_item))
  520                }))
  521            });
  522    }
  523
  524    fn open_path(
  525        &self,
  526        project: &Entity<Project>,
  527        path: &ProjectPath,
  528        window: &mut Window,
  529        cx: &mut App,
  530    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
  531        let Some(open_project_item) = self
  532            .build_project_item_for_path_fns
  533            .iter()
  534            .rev()
  535            .find_map(|open_project_item| open_project_item(&project, &path, window, cx))
  536        else {
  537            return Task::ready(Err(anyhow!("cannot open file {:?}", path.path)));
  538        };
  539        open_project_item
  540    }
  541
  542    fn build_item<T: project::ProjectItem>(
  543        &self,
  544        item: Entity<T>,
  545        project: Entity<Project>,
  546        pane: Option<&Pane>,
  547        window: &mut Window,
  548        cx: &mut App,
  549    ) -> Option<Box<dyn ItemHandle>> {
  550        let build = self
  551            .build_project_item_fns_by_type
  552            .get(&TypeId::of::<T>())?;
  553        Some(build(item.into_any(), project, pane, window, cx))
  554    }
  555}
  556
  557type WorkspaceItemBuilder =
  558    Box<dyn FnOnce(&mut Pane, &mut Window, &mut Context<Pane>) -> Box<dyn ItemHandle>>;
  559
  560impl Global for ProjectItemRegistry {}
  561
  562/// Registers a [ProjectItem] for the app. When opening a file, all the registered
  563/// items will get a chance to open the file, starting from the project item that
  564/// was added last.
  565pub fn register_project_item<I: ProjectItem>(cx: &mut App) {
  566    cx.default_global::<ProjectItemRegistry>().register::<I>();
  567}
  568
  569#[derive(Default)]
  570pub struct FollowableViewRegistry(HashMap<TypeId, FollowableViewDescriptor>);
  571
  572struct FollowableViewDescriptor {
  573    from_state_proto: fn(
  574        Entity<Workspace>,
  575        ViewId,
  576        &mut Option<proto::view::Variant>,
  577        &mut Window,
  578        &mut App,
  579    ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>,
  580    to_followable_view: fn(&AnyView) -> Box<dyn FollowableItemHandle>,
  581}
  582
  583impl Global for FollowableViewRegistry {}
  584
  585impl FollowableViewRegistry {
  586    pub fn register<I: FollowableItem>(cx: &mut App) {
  587        cx.default_global::<Self>().0.insert(
  588            TypeId::of::<I>(),
  589            FollowableViewDescriptor {
  590                from_state_proto: |workspace, id, state, window, cx| {
  591                    I::from_state_proto(workspace, id, state, window, cx).map(|task| {
  592                        cx.foreground_executor()
  593                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
  594                    })
  595                },
  596                to_followable_view: |view| Box::new(view.clone().downcast::<I>().unwrap()),
  597            },
  598        );
  599    }
  600
  601    pub fn from_state_proto(
  602        workspace: Entity<Workspace>,
  603        view_id: ViewId,
  604        mut state: Option<proto::view::Variant>,
  605        window: &mut Window,
  606        cx: &mut App,
  607    ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>> {
  608        cx.update_default_global(|this: &mut Self, cx| {
  609            this.0.values().find_map(|descriptor| {
  610                (descriptor.from_state_proto)(workspace.clone(), view_id, &mut state, window, cx)
  611            })
  612        })
  613    }
  614
  615    pub fn to_followable_view(
  616        view: impl Into<AnyView>,
  617        cx: &App,
  618    ) -> Option<Box<dyn FollowableItemHandle>> {
  619        let this = cx.try_global::<Self>()?;
  620        let view = view.into();
  621        let descriptor = this.0.get(&view.entity_type())?;
  622        Some((descriptor.to_followable_view)(&view))
  623    }
  624}
  625
  626#[derive(Copy, Clone)]
  627struct SerializableItemDescriptor {
  628    deserialize: fn(
  629        Entity<Project>,
  630        WeakEntity<Workspace>,
  631        WorkspaceId,
  632        ItemId,
  633        &mut Window,
  634        &mut Context<Pane>,
  635    ) -> Task<Result<Box<dyn ItemHandle>>>,
  636    cleanup: fn(WorkspaceId, Vec<ItemId>, &mut Window, &mut App) -> Task<Result<()>>,
  637    view_to_serializable_item: fn(AnyView) -> Box<dyn SerializableItemHandle>,
  638}
  639
  640#[derive(Default)]
  641struct SerializableItemRegistry {
  642    descriptors_by_kind: HashMap<Arc<str>, SerializableItemDescriptor>,
  643    descriptors_by_type: HashMap<TypeId, SerializableItemDescriptor>,
  644}
  645
  646impl Global for SerializableItemRegistry {}
  647
  648impl SerializableItemRegistry {
  649    fn deserialize(
  650        item_kind: &str,
  651        project: Entity<Project>,
  652        workspace: WeakEntity<Workspace>,
  653        workspace_id: WorkspaceId,
  654        item_item: ItemId,
  655        window: &mut Window,
  656        cx: &mut Context<Pane>,
  657    ) -> Task<Result<Box<dyn ItemHandle>>> {
  658        let Some(descriptor) = Self::descriptor(item_kind, cx) else {
  659            return Task::ready(Err(anyhow!(
  660                "cannot deserialize {}, descriptor not found",
  661                item_kind
  662            )));
  663        };
  664
  665        (descriptor.deserialize)(project, workspace, workspace_id, item_item, window, cx)
  666    }
  667
  668    fn cleanup(
  669        item_kind: &str,
  670        workspace_id: WorkspaceId,
  671        loaded_items: Vec<ItemId>,
  672        window: &mut Window,
  673        cx: &mut App,
  674    ) -> Task<Result<()>> {
  675        let Some(descriptor) = Self::descriptor(item_kind, cx) else {
  676            return Task::ready(Err(anyhow!(
  677                "cannot cleanup {}, descriptor not found",
  678                item_kind
  679            )));
  680        };
  681
  682        (descriptor.cleanup)(workspace_id, loaded_items, window, cx)
  683    }
  684
  685    fn view_to_serializable_item_handle(
  686        view: AnyView,
  687        cx: &App,
  688    ) -> Option<Box<dyn SerializableItemHandle>> {
  689        let this = cx.try_global::<Self>()?;
  690        let descriptor = this.descriptors_by_type.get(&view.entity_type())?;
  691        Some((descriptor.view_to_serializable_item)(view))
  692    }
  693
  694    fn descriptor(item_kind: &str, cx: &App) -> Option<SerializableItemDescriptor> {
  695        let this = cx.try_global::<Self>()?;
  696        this.descriptors_by_kind.get(item_kind).copied()
  697    }
  698}
  699
  700pub fn register_serializable_item<I: SerializableItem>(cx: &mut App) {
  701    let serialized_item_kind = I::serialized_item_kind();
  702
  703    let registry = cx.default_global::<SerializableItemRegistry>();
  704    let descriptor = SerializableItemDescriptor {
  705        deserialize: |project, workspace, workspace_id, item_id, window, cx| {
  706            let task = I::deserialize(project, workspace, workspace_id, item_id, window, cx);
  707            cx.foreground_executor()
  708                .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
  709        },
  710        cleanup: |workspace_id, loaded_items, window, cx| {
  711            I::cleanup(workspace_id, loaded_items, window, cx)
  712        },
  713        view_to_serializable_item: |view| Box::new(view.downcast::<I>().unwrap()),
  714    };
  715    registry
  716        .descriptors_by_kind
  717        .insert(Arc::from(serialized_item_kind), descriptor);
  718    registry
  719        .descriptors_by_type
  720        .insert(TypeId::of::<I>(), descriptor);
  721}
  722
  723pub struct AppState {
  724    pub languages: Arc<LanguageRegistry>,
  725    pub client: Arc<Client>,
  726    pub user_store: Entity<UserStore>,
  727    pub workspace_store: Entity<WorkspaceStore>,
  728    pub fs: Arc<dyn fs::Fs>,
  729    pub build_window_options: fn(Option<Uuid>, &mut App) -> WindowOptions,
  730    pub node_runtime: NodeRuntime,
  731    pub session: Entity<AppSession>,
  732}
  733
  734struct GlobalAppState(Weak<AppState>);
  735
  736impl Global for GlobalAppState {}
  737
  738pub struct WorkspaceStore {
  739    workspaces: HashSet<WindowHandle<Workspace>>,
  740    client: Arc<Client>,
  741    _subscriptions: Vec<client::Subscription>,
  742}
  743
  744#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
  745pub enum CollaboratorId {
  746    PeerId(PeerId),
  747    Agent,
  748}
  749
  750impl From<PeerId> for CollaboratorId {
  751    fn from(peer_id: PeerId) -> Self {
  752        CollaboratorId::PeerId(peer_id)
  753    }
  754}
  755
  756impl From<&PeerId> for CollaboratorId {
  757    fn from(peer_id: &PeerId) -> Self {
  758        CollaboratorId::PeerId(*peer_id)
  759    }
  760}
  761
  762#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
  763struct Follower {
  764    project_id: Option<u64>,
  765    peer_id: PeerId,
  766}
  767
  768impl AppState {
  769    #[track_caller]
  770    pub fn global(cx: &App) -> Weak<Self> {
  771        cx.global::<GlobalAppState>().0.clone()
  772    }
  773    pub fn try_global(cx: &App) -> Option<Weak<Self>> {
  774        cx.try_global::<GlobalAppState>()
  775            .map(|state| state.0.clone())
  776    }
  777    pub fn set_global(state: Weak<AppState>, cx: &mut App) {
  778        cx.set_global(GlobalAppState(state));
  779    }
  780
  781    #[cfg(any(test, feature = "test-support"))]
  782    pub fn test(cx: &mut App) -> Arc<Self> {
  783        use node_runtime::NodeRuntime;
  784        use session::Session;
  785        use settings::SettingsStore;
  786
  787        if !cx.has_global::<SettingsStore>() {
  788            let settings_store = SettingsStore::test(cx);
  789            cx.set_global(settings_store);
  790        }
  791
  792        let fs = fs::FakeFs::new(cx.background_executor().clone());
  793        let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
  794        let clock = Arc::new(clock::FakeSystemClock::new());
  795        let http_client = http_client::FakeHttpClient::with_404_response();
  796        let client = Client::new(clock, http_client.clone(), cx);
  797        let session = cx.new(|cx| AppSession::new(Session::test(), cx));
  798        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
  799        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
  800
  801        theme::init(theme::LoadThemes::JustBase, cx);
  802        client::init(&client, cx);
  803        crate::init_settings(cx);
  804
  805        Arc::new(Self {
  806            client,
  807            fs,
  808            languages,
  809            user_store,
  810            workspace_store,
  811            node_runtime: NodeRuntime::unavailable(),
  812            build_window_options: |_, _| Default::default(),
  813            session,
  814        })
  815    }
  816}
  817
  818struct DelayedDebouncedEditAction {
  819    task: Option<Task<()>>,
  820    cancel_channel: Option<oneshot::Sender<()>>,
  821}
  822
  823impl DelayedDebouncedEditAction {
  824    fn new() -> DelayedDebouncedEditAction {
  825        DelayedDebouncedEditAction {
  826            task: None,
  827            cancel_channel: None,
  828        }
  829    }
  830
  831    fn fire_new<F>(
  832        &mut self,
  833        delay: Duration,
  834        window: &mut Window,
  835        cx: &mut Context<Workspace>,
  836        func: F,
  837    ) where
  838        F: 'static
  839            + Send
  840            + FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> Task<Result<()>>,
  841    {
  842        if let Some(channel) = self.cancel_channel.take() {
  843            _ = channel.send(());
  844        }
  845
  846        let (sender, mut receiver) = oneshot::channel::<()>();
  847        self.cancel_channel = Some(sender);
  848
  849        let previous_task = self.task.take();
  850        self.task = Some(cx.spawn_in(window, async move |workspace, cx| {
  851            let mut timer = cx.background_executor().timer(delay).fuse();
  852            if let Some(previous_task) = previous_task {
  853                previous_task.await;
  854            }
  855
  856            futures::select_biased! {
  857                _ = receiver => return,
  858                    _ = timer => {}
  859            }
  860
  861            if let Some(result) = workspace
  862                .update_in(cx, |workspace, window, cx| (func)(workspace, window, cx))
  863                .log_err()
  864            {
  865                result.await.log_err();
  866            }
  867        }));
  868    }
  869}
  870
  871pub enum Event {
  872    PaneAdded(Entity<Pane>),
  873    PaneRemoved,
  874    ItemAdded {
  875        item: Box<dyn ItemHandle>,
  876    },
  877    ItemRemoved,
  878    ActiveItemChanged,
  879    UserSavedItem {
  880        pane: WeakEntity<Pane>,
  881        item: Box<dyn WeakItemHandle>,
  882        save_intent: SaveIntent,
  883    },
  884    ContactRequestedJoin(u64),
  885    WorkspaceCreated(WeakEntity<Workspace>),
  886    OpenBundledFile {
  887        text: Cow<'static, str>,
  888        title: &'static str,
  889        language: &'static str,
  890    },
  891    ZoomChanged,
  892    ModalOpened,
  893    ClearActivityIndicator,
  894}
  895
  896#[derive(Debug)]
  897pub enum OpenVisible {
  898    All,
  899    None,
  900    OnlyFiles,
  901    OnlyDirectories,
  902}
  903
  904type PromptForNewPath = Box<
  905    dyn Fn(
  906        &mut Workspace,
  907        DirectoryLister,
  908        &mut Window,
  909        &mut Context<Workspace>,
  910    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>,
  911>;
  912
  913type PromptForOpenPath = Box<
  914    dyn Fn(
  915        &mut Workspace,
  916        DirectoryLister,
  917        &mut Window,
  918        &mut Context<Workspace>,
  919    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>,
  920>;
  921
  922/// Collects everything project-related for a certain window opened.
  923/// In some way, is a counterpart of a window, as the [`WindowHandle`] could be downcast into `Workspace`.
  924///
  925/// A `Workspace` usually consists of 1 or more projects, a central pane group, 3 docks and a status bar.
  926/// The `Workspace` owns everybody's state and serves as a default, "global context",
  927/// that can be used to register a global action to be triggered from any place in the window.
  928pub struct Workspace {
  929    weak_self: WeakEntity<Self>,
  930    workspace_actions: Vec<Box<dyn Fn(Div, &Workspace, &mut Window, &mut Context<Self>) -> Div>>,
  931    zoomed: Option<AnyWeakView>,
  932    previous_dock_drag_coordinates: Option<Point<Pixels>>,
  933    zoomed_position: Option<DockPosition>,
  934    center: PaneGroup,
  935    left_dock: Entity<Dock>,
  936    bottom_dock: Entity<Dock>,
  937    bottom_dock_layout: BottomDockLayout,
  938    right_dock: Entity<Dock>,
  939    panes: Vec<Entity<Pane>>,
  940    panes_by_item: HashMap<EntityId, WeakEntity<Pane>>,
  941    active_pane: Entity<Pane>,
  942    last_active_center_pane: Option<WeakEntity<Pane>>,
  943    last_active_view_id: Option<proto::ViewId>,
  944    status_bar: Entity<StatusBar>,
  945    modal_layer: Entity<ModalLayer>,
  946    toast_layer: Entity<ToastLayer>,
  947    titlebar_item: Option<AnyView>,
  948    notifications: Notifications,
  949    suppressed_notifications: HashSet<NotificationId>,
  950    project: Entity<Project>,
  951    follower_states: HashMap<CollaboratorId, FollowerState>,
  952    last_leaders_by_pane: HashMap<WeakEntity<Pane>, CollaboratorId>,
  953    window_edited: bool,
  954    dirty_items: HashMap<EntityId, Subscription>,
  955    active_call: Option<(Entity<ActiveCall>, Vec<Subscription>)>,
  956    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
  957    database_id: Option<WorkspaceId>,
  958    app_state: Arc<AppState>,
  959    dispatching_keystrokes: Rc<RefCell<(HashSet<String>, Vec<Keystroke>)>>,
  960    _subscriptions: Vec<Subscription>,
  961    _apply_leader_updates: Task<Result<()>>,
  962    _observe_current_user: Task<Result<()>>,
  963    _schedule_serialize: Option<Task<()>>,
  964    pane_history_timestamp: Arc<AtomicUsize>,
  965    bounds: Bounds<Pixels>,
  966    pub centered_layout: bool,
  967    bounds_save_task_queued: Option<Task<()>>,
  968    on_prompt_for_new_path: Option<PromptForNewPath>,
  969    on_prompt_for_open_path: Option<PromptForOpenPath>,
  970    terminal_provider: Option<Box<dyn TerminalProvider>>,
  971    debugger_provider: Option<Arc<dyn DebuggerProvider>>,
  972    serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
  973    serialized_ssh_project: Option<SerializedSshProject>,
  974    _items_serializer: Task<Result<()>>,
  975    session_id: Option<String>,
  976}
  977
  978impl EventEmitter<Event> for Workspace {}
  979
  980#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
  981pub struct ViewId {
  982    pub creator: CollaboratorId,
  983    pub id: u64,
  984}
  985
  986pub struct FollowerState {
  987    center_pane: Entity<Pane>,
  988    dock_pane: Option<Entity<Pane>>,
  989    active_view_id: Option<ViewId>,
  990    items_by_leader_view_id: HashMap<ViewId, FollowerView>,
  991}
  992
  993struct FollowerView {
  994    view: Box<dyn FollowableItemHandle>,
  995    location: Option<proto::PanelId>,
  996}
  997
  998impl Workspace {
  999    const DEFAULT_PADDING: f32 = 0.2;
 1000    const MAX_PADDING: f32 = 0.4;
 1001
 1002    pub fn new(
 1003        workspace_id: Option<WorkspaceId>,
 1004        project: Entity<Project>,
 1005        app_state: Arc<AppState>,
 1006        window: &mut Window,
 1007        cx: &mut Context<Self>,
 1008    ) -> Self {
 1009        cx.subscribe_in(&project, window, move |this, _, event, window, cx| {
 1010            match event {
 1011                project::Event::RemoteIdChanged(_) => {
 1012                    this.update_window_title(window, cx);
 1013                }
 1014
 1015                project::Event::CollaboratorLeft(peer_id) => {
 1016                    this.collaborator_left(*peer_id, window, cx);
 1017                }
 1018
 1019                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded(_) => {
 1020                    this.update_window_title(window, cx);
 1021                    this.serialize_workspace(window, cx);
 1022                    // This event could be triggered by `AddFolderToProject` or `RemoveFromProject`.
 1023                    // So we need to update the history.
 1024                    this.update_history(cx);
 1025                }
 1026
 1027                project::Event::DisconnectedFromHost => {
 1028                    this.update_window_edited(window, cx);
 1029                    let leaders_to_unfollow =
 1030                        this.follower_states.keys().copied().collect::<Vec<_>>();
 1031                    for leader_id in leaders_to_unfollow {
 1032                        this.unfollow(leader_id, window, cx);
 1033                    }
 1034                }
 1035
 1036                project::Event::DisconnectedFromSshRemote => {
 1037                    this.update_window_edited(window, cx);
 1038                }
 1039
 1040                project::Event::Closed => {
 1041                    window.remove_window();
 1042                }
 1043
 1044                project::Event::DeletedEntry(_, entry_id) => {
 1045                    for pane in this.panes.iter() {
 1046                        pane.update(cx, |pane, cx| {
 1047                            pane.handle_deleted_project_item(*entry_id, window, cx)
 1048                        });
 1049                    }
 1050                }
 1051
 1052                project::Event::Toast {
 1053                    notification_id,
 1054                    message,
 1055                } => this.show_notification(
 1056                    NotificationId::named(notification_id.clone()),
 1057                    cx,
 1058                    |cx| cx.new(|cx| MessageNotification::new(message.clone(), cx)),
 1059                ),
 1060
 1061                project::Event::HideToast { notification_id } => {
 1062                    this.dismiss_notification(&NotificationId::named(notification_id.clone()), cx)
 1063                }
 1064
 1065                project::Event::LanguageServerPrompt(request) => {
 1066                    struct LanguageServerPrompt;
 1067
 1068                    let mut hasher = DefaultHasher::new();
 1069                    request.lsp_name.as_str().hash(&mut hasher);
 1070                    let id = hasher.finish();
 1071
 1072                    this.show_notification(
 1073                        NotificationId::composite::<LanguageServerPrompt>(id as usize),
 1074                        cx,
 1075                        |cx| {
 1076                            cx.new(|cx| {
 1077                                notifications::LanguageServerPrompt::new(request.clone(), cx)
 1078                            })
 1079                        },
 1080                    );
 1081                }
 1082
 1083                project::Event::AgentLocationChanged => {
 1084                    this.handle_agent_location_changed(window, cx)
 1085                }
 1086
 1087                _ => {}
 1088            }
 1089            cx.notify()
 1090        })
 1091        .detach();
 1092
 1093        cx.subscribe_in(
 1094            &project.read(cx).breakpoint_store(),
 1095            window,
 1096            |workspace, _, event, window, cx| match event {
 1097                BreakpointStoreEvent::BreakpointsUpdated(_, _)
 1098                | BreakpointStoreEvent::BreakpointsCleared(_) => {
 1099                    workspace.serialize_workspace(window, cx);
 1100                }
 1101                BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
 1102            },
 1103        )
 1104        .detach();
 1105
 1106        cx.on_focus_lost(window, |this, window, cx| {
 1107            let focus_handle = this.focus_handle(cx);
 1108            window.focus(&focus_handle);
 1109        })
 1110        .detach();
 1111
 1112        let weak_handle = cx.entity().downgrade();
 1113        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 1114
 1115        let center_pane = cx.new(|cx| {
 1116            let mut center_pane = Pane::new(
 1117                weak_handle.clone(),
 1118                project.clone(),
 1119                pane_history_timestamp.clone(),
 1120                None,
 1121                NewFile.boxed_clone(),
 1122                window,
 1123                cx,
 1124            );
 1125            center_pane.set_can_split(Some(Arc::new(|_, _, _, _| true)));
 1126            center_pane
 1127        });
 1128        cx.subscribe_in(&center_pane, window, Self::handle_pane_event)
 1129            .detach();
 1130
 1131        window.focus(&center_pane.focus_handle(cx));
 1132
 1133        cx.emit(Event::PaneAdded(center_pane.clone()));
 1134
 1135        let window_handle = window.window_handle().downcast::<Workspace>().unwrap();
 1136        app_state.workspace_store.update(cx, |store, _| {
 1137            store.workspaces.insert(window_handle);
 1138        });
 1139
 1140        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 1141        let mut connection_status = app_state.client.status();
 1142        let _observe_current_user = cx.spawn_in(window, async move |this, cx| {
 1143            current_user.next().await;
 1144            connection_status.next().await;
 1145            let mut stream =
 1146                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 1147
 1148            while stream.recv().await.is_some() {
 1149                this.update(cx, |_, cx| cx.notify())?;
 1150            }
 1151            anyhow::Ok(())
 1152        });
 1153
 1154        // All leader updates are enqueued and then processed in a single task, so
 1155        // that each asynchronous operation can be run in order.
 1156        let (leader_updates_tx, mut leader_updates_rx) =
 1157            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 1158        let _apply_leader_updates = cx.spawn_in(window, async move |this, cx| {
 1159            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 1160                Self::process_leader_update(&this, leader_id, update, cx)
 1161                    .await
 1162                    .log_err();
 1163            }
 1164
 1165            Ok(())
 1166        });
 1167
 1168        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 1169        let modal_layer = cx.new(|_| ModalLayer::new());
 1170        let toast_layer = cx.new(|_| ToastLayer::new());
 1171        cx.subscribe(
 1172            &modal_layer,
 1173            |_, _, _: &modal_layer::ModalOpenedEvent, cx| {
 1174                cx.emit(Event::ModalOpened);
 1175            },
 1176        )
 1177        .detach();
 1178
 1179        let bottom_dock_layout = WorkspaceSettings::get_global(cx).bottom_dock_layout;
 1180        let left_dock = Dock::new(DockPosition::Left, modal_layer.clone(), window, cx);
 1181        let bottom_dock = Dock::new(DockPosition::Bottom, modal_layer.clone(), window, cx);
 1182        let right_dock = Dock::new(DockPosition::Right, modal_layer.clone(), window, cx);
 1183        let left_dock_buttons = cx.new(|cx| PanelButtons::new(left_dock.clone(), cx));
 1184        let bottom_dock_buttons = cx.new(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 1185        let right_dock_buttons = cx.new(|cx| PanelButtons::new(right_dock.clone(), cx));
 1186        let status_bar = cx.new(|cx| {
 1187            let mut status_bar = StatusBar::new(&center_pane.clone(), window, cx);
 1188            status_bar.add_left_item(left_dock_buttons, window, cx);
 1189            status_bar.add_right_item(right_dock_buttons, window, cx);
 1190            status_bar.add_right_item(bottom_dock_buttons, window, cx);
 1191            status_bar
 1192        });
 1193
 1194        let session_id = app_state.session.read(cx).id().to_owned();
 1195
 1196        let mut active_call = None;
 1197        if let Some(call) = ActiveCall::try_global(cx) {
 1198            let call = call.clone();
 1199            let subscriptions = vec![cx.subscribe_in(&call, window, Self::on_active_call_event)];
 1200            active_call = Some((call, subscriptions));
 1201        }
 1202
 1203        let (serializable_items_tx, serializable_items_rx) =
 1204            mpsc::unbounded::<Box<dyn SerializableItemHandle>>();
 1205        let _items_serializer = cx.spawn_in(window, async move |this, cx| {
 1206            Self::serialize_items(&this, serializable_items_rx, cx).await
 1207        });
 1208
 1209        let subscriptions = vec![
 1210            cx.observe_window_activation(window, Self::on_window_activation_changed),
 1211            cx.observe_window_bounds(window, move |this, window, cx| {
 1212                if this.bounds_save_task_queued.is_some() {
 1213                    return;
 1214                }
 1215                this.bounds_save_task_queued = Some(cx.spawn_in(window, async move |this, cx| {
 1216                    cx.background_executor()
 1217                        .timer(Duration::from_millis(100))
 1218                        .await;
 1219                    this.update_in(cx, |this, window, cx| {
 1220                        if let Some(display) = window.display(cx) {
 1221                            if let Ok(display_uuid) = display.uuid() {
 1222                                let window_bounds = window.inner_window_bounds();
 1223                                if let Some(database_id) = workspace_id {
 1224                                    cx.background_executor()
 1225                                        .spawn(DB.set_window_open_status(
 1226                                            database_id,
 1227                                            SerializedWindowBounds(window_bounds),
 1228                                            display_uuid,
 1229                                        ))
 1230                                        .detach_and_log_err(cx);
 1231                                }
 1232                            }
 1233                        }
 1234                        this.bounds_save_task_queued.take();
 1235                    })
 1236                    .ok();
 1237                }));
 1238                cx.notify();
 1239            }),
 1240            cx.observe_window_appearance(window, |_, window, cx| {
 1241                let window_appearance = window.appearance();
 1242
 1243                *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 1244
 1245                ThemeSettings::reload_current_theme(cx);
 1246                ThemeSettings::reload_current_icon_theme(cx);
 1247            }),
 1248            cx.on_release(move |this, cx| {
 1249                this.app_state.workspace_store.update(cx, move |store, _| {
 1250                    store.workspaces.remove(&window_handle.clone());
 1251                })
 1252            }),
 1253        ];
 1254
 1255        cx.defer_in(window, |this, window, cx| {
 1256            this.update_window_title(window, cx);
 1257            this.show_initial_notifications(cx);
 1258        });
 1259        Workspace {
 1260            weak_self: weak_handle.clone(),
 1261            zoomed: None,
 1262            zoomed_position: None,
 1263            previous_dock_drag_coordinates: None,
 1264            center: PaneGroup::new(center_pane.clone()),
 1265            panes: vec![center_pane.clone()],
 1266            panes_by_item: Default::default(),
 1267            active_pane: center_pane.clone(),
 1268            last_active_center_pane: Some(center_pane.downgrade()),
 1269            last_active_view_id: None,
 1270            status_bar,
 1271            modal_layer,
 1272            toast_layer,
 1273            titlebar_item: None,
 1274            notifications: Notifications::default(),
 1275            suppressed_notifications: HashSet::default(),
 1276            left_dock,
 1277            bottom_dock,
 1278            bottom_dock_layout,
 1279            right_dock,
 1280            project: project.clone(),
 1281            follower_states: Default::default(),
 1282            last_leaders_by_pane: Default::default(),
 1283            dispatching_keystrokes: Default::default(),
 1284            window_edited: false,
 1285            dirty_items: Default::default(),
 1286            active_call,
 1287            database_id: workspace_id,
 1288            app_state,
 1289            _observe_current_user,
 1290            _apply_leader_updates,
 1291            _schedule_serialize: None,
 1292            leader_updates_tx,
 1293            _subscriptions: subscriptions,
 1294            pane_history_timestamp,
 1295            workspace_actions: Default::default(),
 1296            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 1297            bounds: Default::default(),
 1298            centered_layout: false,
 1299            bounds_save_task_queued: None,
 1300            on_prompt_for_new_path: None,
 1301            on_prompt_for_open_path: None,
 1302            terminal_provider: None,
 1303            debugger_provider: None,
 1304            serializable_items_tx,
 1305            _items_serializer,
 1306            session_id: Some(session_id),
 1307            serialized_ssh_project: None,
 1308        }
 1309    }
 1310
 1311    pub fn new_local(
 1312        abs_paths: Vec<PathBuf>,
 1313        app_state: Arc<AppState>,
 1314        requesting_window: Option<WindowHandle<Workspace>>,
 1315        env: Option<HashMap<String, String>>,
 1316        cx: &mut App,
 1317    ) -> Task<
 1318        anyhow::Result<(
 1319            WindowHandle<Workspace>,
 1320            Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
 1321        )>,
 1322    > {
 1323        let project_handle = Project::local(
 1324            app_state.client.clone(),
 1325            app_state.node_runtime.clone(),
 1326            app_state.user_store.clone(),
 1327            app_state.languages.clone(),
 1328            app_state.fs.clone(),
 1329            env,
 1330            cx,
 1331        );
 1332
 1333        cx.spawn(async move |cx| {
 1334            let mut paths_to_open = Vec::with_capacity(abs_paths.len());
 1335            for path in abs_paths.into_iter() {
 1336                if let Some(canonical) = app_state.fs.canonicalize(&path).await.ok() {
 1337                    paths_to_open.push(canonical)
 1338                } else {
 1339                    paths_to_open.push(path)
 1340                }
 1341            }
 1342
 1343            let serialized_workspace =
 1344                persistence::DB.workspace_for_roots(paths_to_open.as_slice());
 1345
 1346            let workspace_location = serialized_workspace
 1347                .as_ref()
 1348                .map(|ws| &ws.location)
 1349                .and_then(|loc| match loc {
 1350                    SerializedWorkspaceLocation::Local(_, order) => {
 1351                        Some((loc.sorted_paths(), order.order()))
 1352                    }
 1353                    _ => None,
 1354                });
 1355
 1356            if let Some((paths, order)) = workspace_location {
 1357                paths_to_open = paths.iter().cloned().collect();
 1358
 1359                if order.iter().enumerate().any(|(i, &j)| i != j) {
 1360                    project_handle
 1361                        .update(cx, |project, cx| {
 1362                            project.set_worktrees_reordered(true, cx);
 1363                        })
 1364                        .log_err();
 1365                }
 1366            }
 1367
 1368            // Get project paths for all of the abs_paths
 1369            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 1370                Vec::with_capacity(paths_to_open.len());
 1371
 1372            for path in paths_to_open.into_iter() {
 1373                if let Some((_, project_entry)) = cx
 1374                    .update(|cx| {
 1375                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 1376                    })?
 1377                    .await
 1378                    .log_err()
 1379                {
 1380                    project_paths.push((path, Some(project_entry)));
 1381                } else {
 1382                    project_paths.push((path, None));
 1383                }
 1384            }
 1385
 1386            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 1387                serialized_workspace.id
 1388            } else {
 1389                DB.next_id().await.unwrap_or_else(|_| Default::default())
 1390            };
 1391
 1392            let toolchains = DB.toolchains(workspace_id).await?;
 1393
 1394            for (toolchain, worktree_id, path) in toolchains {
 1395                let toolchain_path = PathBuf::from(toolchain.path.clone().to_string());
 1396                if !app_state.fs.is_file(toolchain_path.as_path()).await {
 1397                    continue;
 1398                }
 1399
 1400                project_handle
 1401                    .update(cx, |this, cx| {
 1402                        this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
 1403                    })?
 1404                    .await;
 1405            }
 1406            let window = if let Some(window) = requesting_window {
 1407                let centered_layout = serialized_workspace
 1408                    .as_ref()
 1409                    .map(|w| w.centered_layout)
 1410                    .unwrap_or(false);
 1411
 1412                cx.update_window(window.into(), |_, window, cx| {
 1413                    window.replace_root(cx, |window, cx| {
 1414                        let mut workspace = Workspace::new(
 1415                            Some(workspace_id),
 1416                            project_handle.clone(),
 1417                            app_state.clone(),
 1418                            window,
 1419                            cx,
 1420                        );
 1421
 1422                        workspace.centered_layout = centered_layout;
 1423                        workspace
 1424                    });
 1425                })?;
 1426                window
 1427            } else {
 1428                let window_bounds_override = window_bounds_env_override();
 1429
 1430                let (window_bounds, display) = if let Some(bounds) = window_bounds_override {
 1431                    (Some(WindowBounds::Windowed(bounds)), None)
 1432                } else {
 1433                    let restorable_bounds = serialized_workspace
 1434                        .as_ref()
 1435                        .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
 1436                        .or_else(|| {
 1437                            let (display, window_bounds) = DB.last_window().log_err()?;
 1438                            Some((display?, window_bounds?))
 1439                        });
 1440
 1441                    if let Some((serialized_display, serialized_status)) = restorable_bounds {
 1442                        (Some(serialized_status.0), Some(serialized_display))
 1443                    } else {
 1444                        (None, None)
 1445                    }
 1446                };
 1447
 1448                // Use the serialized workspace to construct the new window
 1449                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
 1450                options.window_bounds = window_bounds;
 1451                let centered_layout = serialized_workspace
 1452                    .as_ref()
 1453                    .map(|w| w.centered_layout)
 1454                    .unwrap_or(false);
 1455                cx.open_window(options, {
 1456                    let app_state = app_state.clone();
 1457                    let project_handle = project_handle.clone();
 1458                    move |window, cx| {
 1459                        cx.new(|cx| {
 1460                            let mut workspace = Workspace::new(
 1461                                Some(workspace_id),
 1462                                project_handle,
 1463                                app_state,
 1464                                window,
 1465                                cx,
 1466                            );
 1467                            workspace.centered_layout = centered_layout;
 1468                            workspace
 1469                        })
 1470                    }
 1471                })?
 1472            };
 1473
 1474            notify_if_database_failed(window, cx);
 1475            let opened_items = window
 1476                .update(cx, |_workspace, window, cx| {
 1477                    open_items(serialized_workspace, project_paths, window, cx)
 1478                })?
 1479                .await
 1480                .unwrap_or_default();
 1481
 1482            window
 1483                .update(cx, |workspace, window, cx| {
 1484                    window.activate_window();
 1485                    workspace.update_history(cx);
 1486                })
 1487                .log_err();
 1488            Ok((window, opened_items))
 1489        })
 1490    }
 1491
 1492    pub fn weak_handle(&self) -> WeakEntity<Self> {
 1493        self.weak_self.clone()
 1494    }
 1495
 1496    pub fn left_dock(&self) -> &Entity<Dock> {
 1497        &self.left_dock
 1498    }
 1499
 1500    pub fn bottom_dock(&self) -> &Entity<Dock> {
 1501        &self.bottom_dock
 1502    }
 1503
 1504    pub fn bottom_dock_layout(&self) -> BottomDockLayout {
 1505        self.bottom_dock_layout
 1506    }
 1507
 1508    pub fn set_bottom_dock_layout(
 1509        &mut self,
 1510        layout: BottomDockLayout,
 1511        window: &mut Window,
 1512        cx: &mut Context<Self>,
 1513    ) {
 1514        let fs = self.project().read(cx).fs();
 1515        settings::update_settings_file::<WorkspaceSettings>(fs.clone(), cx, move |content, _cx| {
 1516            content.bottom_dock_layout = Some(layout);
 1517        });
 1518
 1519        self.bottom_dock_layout = layout;
 1520        cx.notify();
 1521        self.serialize_workspace(window, cx);
 1522    }
 1523
 1524    pub fn right_dock(&self) -> &Entity<Dock> {
 1525        &self.right_dock
 1526    }
 1527
 1528    pub fn all_docks(&self) -> [&Entity<Dock>; 3] {
 1529        [&self.left_dock, &self.bottom_dock, &self.right_dock]
 1530    }
 1531
 1532    pub fn dock_at_position(&self, position: DockPosition) -> &Entity<Dock> {
 1533        match position {
 1534            DockPosition::Left => &self.left_dock,
 1535            DockPosition::Bottom => &self.bottom_dock,
 1536            DockPosition::Right => &self.right_dock,
 1537        }
 1538    }
 1539
 1540    pub fn is_edited(&self) -> bool {
 1541        self.window_edited
 1542    }
 1543
 1544    pub fn add_panel<T: Panel>(
 1545        &mut self,
 1546        panel: Entity<T>,
 1547        window: &mut Window,
 1548        cx: &mut Context<Self>,
 1549    ) {
 1550        let focus_handle = panel.panel_focus_handle(cx);
 1551        cx.on_focus_in(&focus_handle, window, Self::handle_panel_focused)
 1552            .detach();
 1553
 1554        let dock_position = panel.position(window, cx);
 1555        let dock = self.dock_at_position(dock_position);
 1556
 1557        dock.update(cx, |dock, cx| {
 1558            dock.add_panel(panel, self.weak_self.clone(), window, cx)
 1559        });
 1560    }
 1561
 1562    pub fn status_bar(&self) -> &Entity<StatusBar> {
 1563        &self.status_bar
 1564    }
 1565
 1566    pub fn app_state(&self) -> &Arc<AppState> {
 1567        &self.app_state
 1568    }
 1569
 1570    pub fn user_store(&self) -> &Entity<UserStore> {
 1571        &self.app_state.user_store
 1572    }
 1573
 1574    pub fn project(&self) -> &Entity<Project> {
 1575        &self.project
 1576    }
 1577
 1578    pub fn recently_activated_items(&self, cx: &App) -> HashMap<EntityId, usize> {
 1579        let mut history: HashMap<EntityId, usize> = HashMap::default();
 1580
 1581        for pane_handle in &self.panes {
 1582            let pane = pane_handle.read(cx);
 1583
 1584            for entry in pane.activation_history() {
 1585                history.insert(
 1586                    entry.entity_id,
 1587                    history
 1588                        .get(&entry.entity_id)
 1589                        .cloned()
 1590                        .unwrap_or(0)
 1591                        .max(entry.timestamp),
 1592                );
 1593            }
 1594        }
 1595
 1596        history
 1597    }
 1598
 1599    pub fn recent_navigation_history_iter(
 1600        &self,
 1601        cx: &App,
 1602    ) -> impl Iterator<Item = (ProjectPath, Option<PathBuf>)> {
 1603        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 1604        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 1605
 1606        for pane in &self.panes {
 1607            let pane = pane.read(cx);
 1608
 1609            pane.nav_history()
 1610                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 1611                    if let Some(fs_path) = &fs_path {
 1612                        abs_paths_opened
 1613                            .entry(fs_path.clone())
 1614                            .or_default()
 1615                            .insert(project_path.clone());
 1616                    }
 1617                    let timestamp = entry.timestamp;
 1618                    match history.entry(project_path) {
 1619                        hash_map::Entry::Occupied(mut entry) => {
 1620                            let (_, old_timestamp) = entry.get();
 1621                            if &timestamp > old_timestamp {
 1622                                entry.insert((fs_path, timestamp));
 1623                            }
 1624                        }
 1625                        hash_map::Entry::Vacant(entry) => {
 1626                            entry.insert((fs_path, timestamp));
 1627                        }
 1628                    }
 1629                });
 1630
 1631            if let Some(item) = pane.active_item() {
 1632                if let Some(project_path) = item.project_path(cx) {
 1633                    let fs_path = self.project.read(cx).absolute_path(&project_path, cx);
 1634
 1635                    if let Some(fs_path) = &fs_path {
 1636                        abs_paths_opened
 1637                            .entry(fs_path.clone())
 1638                            .or_default()
 1639                            .insert(project_path.clone());
 1640                    }
 1641
 1642                    history.insert(project_path, (fs_path, std::usize::MAX));
 1643                }
 1644            }
 1645        }
 1646
 1647        history
 1648            .into_iter()
 1649            .sorted_by_key(|(_, (_, order))| *order)
 1650            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 1651            .rev()
 1652            .filter(move |(history_path, abs_path)| {
 1653                let latest_project_path_opened = abs_path
 1654                    .as_ref()
 1655                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 1656                    .and_then(|project_paths| {
 1657                        project_paths
 1658                            .iter()
 1659                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 1660                    });
 1661
 1662                match latest_project_path_opened {
 1663                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 1664                    None => true,
 1665                }
 1666            })
 1667    }
 1668
 1669    pub fn recent_navigation_history(
 1670        &self,
 1671        limit: Option<usize>,
 1672        cx: &App,
 1673    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 1674        self.recent_navigation_history_iter(cx)
 1675            .take(limit.unwrap_or(usize::MAX))
 1676            .collect()
 1677    }
 1678
 1679    fn navigate_history(
 1680        &mut self,
 1681        pane: WeakEntity<Pane>,
 1682        mode: NavigationMode,
 1683        window: &mut Window,
 1684        cx: &mut Context<Workspace>,
 1685    ) -> Task<Result<()>> {
 1686        let to_load = if let Some(pane) = pane.upgrade() {
 1687            pane.update(cx, |pane, cx| {
 1688                window.focus(&pane.focus_handle(cx));
 1689                loop {
 1690                    // Retrieve the weak item handle from the history.
 1691                    let entry = pane.nav_history_mut().pop(mode, cx)?;
 1692
 1693                    // If the item is still present in this pane, then activate it.
 1694                    if let Some(index) = entry
 1695                        .item
 1696                        .upgrade()
 1697                        .and_then(|v| pane.index_for_item(v.as_ref()))
 1698                    {
 1699                        let prev_active_item_index = pane.active_item_index();
 1700                        pane.nav_history_mut().set_mode(mode);
 1701                        pane.activate_item(index, true, true, window, cx);
 1702                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 1703
 1704                        let mut navigated = prev_active_item_index != pane.active_item_index();
 1705                        if let Some(data) = entry.data {
 1706                            navigated |= pane.active_item()?.navigate(data, window, cx);
 1707                        }
 1708
 1709                        if navigated {
 1710                            break None;
 1711                        }
 1712                    } else {
 1713                        // If the item is no longer present in this pane, then retrieve its
 1714                        // path info in order to reopen it.
 1715                        break pane
 1716                            .nav_history()
 1717                            .path_for_item(entry.item.id())
 1718                            .map(|(project_path, abs_path)| (project_path, abs_path, entry));
 1719                    }
 1720                }
 1721            })
 1722        } else {
 1723            None
 1724        };
 1725
 1726        if let Some((project_path, abs_path, entry)) = to_load {
 1727            // If the item was no longer present, then load it again from its previous path, first try the local path
 1728            let open_by_project_path = self.load_path(project_path.clone(), window, cx);
 1729
 1730            cx.spawn_in(window, async move  |workspace, cx| {
 1731                let open_by_project_path = open_by_project_path.await;
 1732                let mut navigated = false;
 1733                match open_by_project_path
 1734                    .with_context(|| format!("Navigating to {project_path:?}"))
 1735                {
 1736                    Ok((project_entry_id, build_item)) => {
 1737                        let prev_active_item_id = pane.update(cx, |pane, _| {
 1738                            pane.nav_history_mut().set_mode(mode);
 1739                            pane.active_item().map(|p| p.item_id())
 1740                        })?;
 1741
 1742                        pane.update_in(cx, |pane, window, cx| {
 1743                            let item = pane.open_item(
 1744                                project_entry_id,
 1745                                project_path,
 1746                                true,
 1747                                entry.is_preview,
 1748                                true,
 1749                                None,
 1750                                window, cx,
 1751                                build_item,
 1752                            );
 1753                            navigated |= Some(item.item_id()) != prev_active_item_id;
 1754                            pane.nav_history_mut().set_mode(NavigationMode::Normal);
 1755                            if let Some(data) = entry.data {
 1756                                navigated |= item.navigate(data, window, cx);
 1757                            }
 1758                        })?;
 1759                    }
 1760                    Err(open_by_project_path_e) => {
 1761                        // Fall back to opening by abs path, in case an external file was opened and closed,
 1762                        // and its worktree is now dropped
 1763                        if let Some(abs_path) = abs_path {
 1764                            let prev_active_item_id = pane.update(cx, |pane, _| {
 1765                                pane.nav_history_mut().set_mode(mode);
 1766                                pane.active_item().map(|p| p.item_id())
 1767                            })?;
 1768                            let open_by_abs_path = workspace.update_in(cx, |workspace, window, cx| {
 1769                                workspace.open_abs_path(abs_path.clone(), OpenOptions { visible: Some(OpenVisible::None), ..Default::default() }, window, cx)
 1770                            })?;
 1771                            match open_by_abs_path
 1772                                .await
 1773                                .with_context(|| format!("Navigating to {abs_path:?}"))
 1774                            {
 1775                                Ok(item) => {
 1776                                    pane.update_in(cx, |pane, window, cx| {
 1777                                        navigated |= Some(item.item_id()) != prev_active_item_id;
 1778                                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 1779                                        if let Some(data) = entry.data {
 1780                                            navigated |= item.navigate(data, window, cx);
 1781                                        }
 1782                                    })?;
 1783                                }
 1784                                Err(open_by_abs_path_e) => {
 1785                                    log::error!("Failed to navigate history: {open_by_project_path_e:#} and {open_by_abs_path_e:#}");
 1786                                }
 1787                            }
 1788                        }
 1789                    }
 1790                }
 1791
 1792                if !navigated {
 1793                    workspace
 1794                        .update_in(cx, |workspace, window, cx| {
 1795                            Self::navigate_history(workspace, pane, mode, window, cx)
 1796                        })?
 1797                        .await?;
 1798                }
 1799
 1800                Ok(())
 1801            })
 1802        } else {
 1803            Task::ready(Ok(()))
 1804        }
 1805    }
 1806
 1807    pub fn go_back(
 1808        &mut self,
 1809        pane: WeakEntity<Pane>,
 1810        window: &mut Window,
 1811        cx: &mut Context<Workspace>,
 1812    ) -> Task<Result<()>> {
 1813        self.navigate_history(pane, NavigationMode::GoingBack, window, cx)
 1814    }
 1815
 1816    pub fn go_forward(
 1817        &mut self,
 1818        pane: WeakEntity<Pane>,
 1819        window: &mut Window,
 1820        cx: &mut Context<Workspace>,
 1821    ) -> Task<Result<()>> {
 1822        self.navigate_history(pane, NavigationMode::GoingForward, window, cx)
 1823    }
 1824
 1825    pub fn reopen_closed_item(
 1826        &mut self,
 1827        window: &mut Window,
 1828        cx: &mut Context<Workspace>,
 1829    ) -> Task<Result<()>> {
 1830        self.navigate_history(
 1831            self.active_pane().downgrade(),
 1832            NavigationMode::ReopeningClosedItem,
 1833            window,
 1834            cx,
 1835        )
 1836    }
 1837
 1838    pub fn client(&self) -> &Arc<Client> {
 1839        &self.app_state.client
 1840    }
 1841
 1842    pub fn set_titlebar_item(&mut self, item: AnyView, _: &mut Window, cx: &mut Context<Self>) {
 1843        self.titlebar_item = Some(item);
 1844        cx.notify();
 1845    }
 1846
 1847    pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
 1848        self.on_prompt_for_new_path = Some(prompt)
 1849    }
 1850
 1851    pub fn set_prompt_for_open_path(&mut self, prompt: PromptForOpenPath) {
 1852        self.on_prompt_for_open_path = Some(prompt)
 1853    }
 1854
 1855    pub fn set_terminal_provider(&mut self, provider: impl TerminalProvider + 'static) {
 1856        self.terminal_provider = Some(Box::new(provider));
 1857    }
 1858
 1859    pub fn set_debugger_provider(&mut self, provider: impl DebuggerProvider + 'static) {
 1860        self.debugger_provider = Some(Arc::new(provider));
 1861    }
 1862
 1863    pub fn debugger_provider(&self) -> Option<Arc<dyn DebuggerProvider>> {
 1864        self.debugger_provider.clone()
 1865    }
 1866
 1867    pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
 1868        self.serialized_ssh_project.clone()
 1869    }
 1870
 1871    pub fn set_serialized_ssh_project(&mut self, serialized_ssh_project: SerializedSshProject) {
 1872        self.serialized_ssh_project = Some(serialized_ssh_project);
 1873    }
 1874
 1875    pub fn prompt_for_open_path(
 1876        &mut self,
 1877        path_prompt_options: PathPromptOptions,
 1878        lister: DirectoryLister,
 1879        window: &mut Window,
 1880        cx: &mut Context<Self>,
 1881    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
 1882        if !lister.is_local(cx) || !WorkspaceSettings::get_global(cx).use_system_path_prompts {
 1883            let prompt = self.on_prompt_for_open_path.take().unwrap();
 1884            let rx = prompt(self, lister, window, cx);
 1885            self.on_prompt_for_open_path = Some(prompt);
 1886            rx
 1887        } else {
 1888            let (tx, rx) = oneshot::channel();
 1889            let abs_path = cx.prompt_for_paths(path_prompt_options);
 1890
 1891            cx.spawn_in(window, async move |workspace, cx| {
 1892                let Ok(result) = abs_path.await else {
 1893                    return Ok(());
 1894                };
 1895
 1896                match result {
 1897                    Ok(result) => {
 1898                        tx.send(result).ok();
 1899                    }
 1900                    Err(err) => {
 1901                        let rx = workspace.update_in(cx, |workspace, window, cx| {
 1902                            workspace.show_portal_error(err.to_string(), cx);
 1903                            let prompt = workspace.on_prompt_for_open_path.take().unwrap();
 1904                            let rx = prompt(workspace, lister, window, cx);
 1905                            workspace.on_prompt_for_open_path = Some(prompt);
 1906                            rx
 1907                        })?;
 1908                        if let Ok(path) = rx.await {
 1909                            tx.send(path).ok();
 1910                        }
 1911                    }
 1912                };
 1913                anyhow::Ok(())
 1914            })
 1915            .detach();
 1916
 1917            rx
 1918        }
 1919    }
 1920
 1921    pub fn prompt_for_new_path(
 1922        &mut self,
 1923        lister: DirectoryLister,
 1924        window: &mut Window,
 1925        cx: &mut Context<Self>,
 1926    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
 1927        if self.project.read(cx).is_via_collab()
 1928            || self.project.read(cx).is_via_ssh()
 1929            || !WorkspaceSettings::get_global(cx).use_system_path_prompts
 1930        {
 1931            let prompt = self.on_prompt_for_new_path.take().unwrap();
 1932            let rx = prompt(self, lister, window, cx);
 1933            self.on_prompt_for_new_path = Some(prompt);
 1934            return rx;
 1935        }
 1936
 1937        let (tx, rx) = oneshot::channel();
 1938        cx.spawn_in(window, async move |workspace, cx| {
 1939            let abs_path = workspace.update(cx, |workspace, cx| {
 1940                let relative_to = workspace
 1941                    .most_recent_active_path(cx)
 1942                    .and_then(|p| p.parent().map(|p| p.to_path_buf()))
 1943                    .or_else(|| {
 1944                        let project = workspace.project.read(cx);
 1945                        project.visible_worktrees(cx).find_map(|worktree| {
 1946                            Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
 1947                        })
 1948                    })
 1949                    .or_else(std::env::home_dir)
 1950                    .unwrap_or_else(|| PathBuf::from(""));
 1951                cx.prompt_for_new_path(&relative_to)
 1952            })?;
 1953            let abs_path = match abs_path.await? {
 1954                Ok(path) => path,
 1955                Err(err) => {
 1956                    let rx = workspace.update_in(cx, |workspace, window, cx| {
 1957                        workspace.show_portal_error(err.to_string(), cx);
 1958
 1959                        let prompt = workspace.on_prompt_for_new_path.take().unwrap();
 1960                        let rx = prompt(workspace, lister, window, cx);
 1961                        workspace.on_prompt_for_new_path = Some(prompt);
 1962                        rx
 1963                    })?;
 1964                    if let Ok(path) = rx.await {
 1965                        tx.send(path).ok();
 1966                    }
 1967                    return anyhow::Ok(());
 1968                }
 1969            };
 1970
 1971            tx.send(abs_path.map(|path| vec![path])).ok();
 1972            anyhow::Ok(())
 1973        })
 1974        .detach();
 1975
 1976        rx
 1977    }
 1978
 1979    pub fn titlebar_item(&self) -> Option<AnyView> {
 1980        self.titlebar_item.clone()
 1981    }
 1982
 1983    /// Call the given callback with a workspace whose project is local.
 1984    ///
 1985    /// If the given workspace has a local project, then it will be passed
 1986    /// to the callback. Otherwise, a new empty window will be created.
 1987    pub fn with_local_workspace<T, F>(
 1988        &mut self,
 1989        window: &mut Window,
 1990        cx: &mut Context<Self>,
 1991        callback: F,
 1992    ) -> Task<Result<T>>
 1993    where
 1994        T: 'static,
 1995        F: 'static + FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> T,
 1996    {
 1997        if self.project.read(cx).is_local() {
 1998            Task::ready(Ok(callback(self, window, cx)))
 1999        } else {
 2000            let env = self.project.read(cx).cli_environment(cx);
 2001            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, env, cx);
 2002            cx.spawn_in(window, async move |_vh, cx| {
 2003                let (workspace, _) = task.await?;
 2004                workspace.update(cx, callback)
 2005            })
 2006        }
 2007    }
 2008
 2009    pub fn worktrees<'a>(&self, cx: &'a App) -> impl 'a + Iterator<Item = Entity<Worktree>> {
 2010        self.project.read(cx).worktrees(cx)
 2011    }
 2012
 2013    pub fn visible_worktrees<'a>(
 2014        &self,
 2015        cx: &'a App,
 2016    ) -> impl 'a + Iterator<Item = Entity<Worktree>> {
 2017        self.project.read(cx).visible_worktrees(cx)
 2018    }
 2019
 2020    #[cfg(any(test, feature = "test-support"))]
 2021    pub fn worktree_scans_complete(&self, cx: &App) -> impl Future<Output = ()> + 'static + use<> {
 2022        let futures = self
 2023            .worktrees(cx)
 2024            .filter_map(|worktree| worktree.read(cx).as_local())
 2025            .map(|worktree| worktree.scan_complete())
 2026            .collect::<Vec<_>>();
 2027        async move {
 2028            for future in futures {
 2029                future.await;
 2030            }
 2031        }
 2032    }
 2033
 2034    pub fn close_global(_: &CloseWindow, cx: &mut App) {
 2035        cx.defer(|cx| {
 2036            cx.windows().iter().find(|window| {
 2037                window
 2038                    .update(cx, |_, window, _| {
 2039                        if window.is_window_active() {
 2040                            //This can only get called when the window's project connection has been lost
 2041                            //so we don't need to prompt the user for anything and instead just close the window
 2042                            window.remove_window();
 2043                            true
 2044                        } else {
 2045                            false
 2046                        }
 2047                    })
 2048                    .unwrap_or(false)
 2049            });
 2050        });
 2051    }
 2052
 2053    pub fn close_window(&mut self, _: &CloseWindow, window: &mut Window, cx: &mut Context<Self>) {
 2054        let prepare = self.prepare_to_close(CloseIntent::CloseWindow, window, cx);
 2055        cx.spawn_in(window, async move |_, cx| {
 2056            if prepare.await? {
 2057                cx.update(|window, _cx| window.remove_window())?;
 2058            }
 2059            anyhow::Ok(())
 2060        })
 2061        .detach_and_log_err(cx)
 2062    }
 2063
 2064    pub fn move_focused_panel_to_next_position(
 2065        &mut self,
 2066        _: &MoveFocusedPanelToNextPosition,
 2067        window: &mut Window,
 2068        cx: &mut Context<Self>,
 2069    ) {
 2070        let docks = self.all_docks();
 2071        let active_dock = docks
 2072            .into_iter()
 2073            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
 2074
 2075        if let Some(dock) = active_dock {
 2076            dock.update(cx, |dock, cx| {
 2077                let active_panel = dock
 2078                    .active_panel()
 2079                    .filter(|panel| panel.panel_focus_handle(cx).contains_focused(window, cx));
 2080
 2081                if let Some(panel) = active_panel {
 2082                    panel.move_to_next_position(window, cx);
 2083                }
 2084            })
 2085        }
 2086    }
 2087
 2088    pub fn prepare_to_close(
 2089        &mut self,
 2090        close_intent: CloseIntent,
 2091        window: &mut Window,
 2092        cx: &mut Context<Self>,
 2093    ) -> Task<Result<bool>> {
 2094        let active_call = self.active_call().cloned();
 2095
 2096        // On Linux and Windows, closing the last window should restore the last workspace.
 2097        let save_last_workspace = cfg!(not(target_os = "macos"))
 2098            && close_intent != CloseIntent::ReplaceWindow
 2099            && cx.windows().len() == 1;
 2100
 2101        cx.spawn_in(window, async move |this, cx| {
 2102            let workspace_count = cx.update(|_window, cx| {
 2103                cx.windows()
 2104                    .iter()
 2105                    .filter(|window| window.downcast::<Workspace>().is_some())
 2106                    .count()
 2107            })?;
 2108
 2109            if let Some(active_call) = active_call {
 2110                if close_intent != CloseIntent::Quit
 2111                    && workspace_count == 1
 2112                    && active_call.read_with(cx, |call, _| call.room().is_some())?
 2113                {
 2114                    let answer = cx.update(|window, cx| {
 2115                        window.prompt(
 2116                            PromptLevel::Warning,
 2117                            "Do you want to leave the current call?",
 2118                            None,
 2119                            &["Close window and hang up", "Cancel"],
 2120                            cx,
 2121                        )
 2122                    })?;
 2123
 2124                    if answer.await.log_err() == Some(1) {
 2125                        return anyhow::Ok(false);
 2126                    } else {
 2127                        active_call
 2128                            .update(cx, |call, cx| call.hang_up(cx))?
 2129                            .await
 2130                            .log_err();
 2131                    }
 2132                }
 2133            }
 2134
 2135            let save_result = this
 2136                .update_in(cx, |this, window, cx| {
 2137                    this.save_all_internal(SaveIntent::Close, window, cx)
 2138                })?
 2139                .await;
 2140
 2141            // If we're not quitting, but closing, we remove the workspace from
 2142            // the current session.
 2143            if close_intent != CloseIntent::Quit
 2144                && !save_last_workspace
 2145                && save_result.as_ref().map_or(false, |&res| res)
 2146            {
 2147                this.update_in(cx, |this, window, cx| this.remove_from_session(window, cx))?
 2148                    .await;
 2149            }
 2150
 2151            save_result
 2152        })
 2153    }
 2154
 2155    fn save_all(&mut self, action: &SaveAll, window: &mut Window, cx: &mut Context<Self>) {
 2156        self.save_all_internal(
 2157            action.save_intent.unwrap_or(SaveIntent::SaveAll),
 2158            window,
 2159            cx,
 2160        )
 2161        .detach_and_log_err(cx);
 2162    }
 2163
 2164    fn send_keystrokes(
 2165        &mut self,
 2166        action: &SendKeystrokes,
 2167        window: &mut Window,
 2168        cx: &mut Context<Self>,
 2169    ) {
 2170        let mut state = self.dispatching_keystrokes.borrow_mut();
 2171        if !state.0.insert(action.0.clone()) {
 2172            cx.propagate();
 2173            return;
 2174        }
 2175        let mut keystrokes: Vec<Keystroke> = action
 2176            .0
 2177            .split(' ')
 2178            .flat_map(|k| Keystroke::parse(k).log_err())
 2179            .collect();
 2180        keystrokes.reverse();
 2181
 2182        state.1.append(&mut keystrokes);
 2183        drop(state);
 2184
 2185        let keystrokes = self.dispatching_keystrokes.clone();
 2186        window
 2187            .spawn(cx, async move |cx| {
 2188                // limit to 100 keystrokes to avoid infinite recursion.
 2189                for _ in 0..100 {
 2190                    let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
 2191                        keystrokes.borrow_mut().0.clear();
 2192                        return Ok(());
 2193                    };
 2194                    cx.update(|window, cx| {
 2195                        let focused = window.focused(cx);
 2196                        window.dispatch_keystroke(keystroke.clone(), cx);
 2197                        if window.focused(cx) != focused {
 2198                            // dispatch_keystroke may cause the focus to change.
 2199                            // draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
 2200                            // And we need that to happen before the next keystroke to keep vim mode happy...
 2201                            // (Note that the tests always do this implicitly, so you must manually test with something like:
 2202                            //   "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
 2203                            // )
 2204                            window.draw(cx).clear();
 2205                        }
 2206                    })?;
 2207                }
 2208
 2209                *keystrokes.borrow_mut() = Default::default();
 2210                anyhow::bail!("over 100 keystrokes passed to send_keystrokes");
 2211            })
 2212            .detach_and_log_err(cx);
 2213    }
 2214
 2215    fn save_all_internal(
 2216        &mut self,
 2217        mut save_intent: SaveIntent,
 2218        window: &mut Window,
 2219        cx: &mut Context<Self>,
 2220    ) -> Task<Result<bool>> {
 2221        if self.project.read(cx).is_disconnected(cx) {
 2222            return Task::ready(Ok(true));
 2223        }
 2224        let dirty_items = self
 2225            .panes
 2226            .iter()
 2227            .flat_map(|pane| {
 2228                pane.read(cx).items().filter_map(|item| {
 2229                    if item.is_dirty(cx) {
 2230                        item.tab_content_text(0, cx);
 2231                        Some((pane.downgrade(), item.boxed_clone()))
 2232                    } else {
 2233                        None
 2234                    }
 2235                })
 2236            })
 2237            .collect::<Vec<_>>();
 2238
 2239        let project = self.project.clone();
 2240        cx.spawn_in(window, async move |workspace, cx| {
 2241            let dirty_items = if save_intent == SaveIntent::Close && !dirty_items.is_empty() {
 2242                let (serialize_tasks, remaining_dirty_items) =
 2243                    workspace.update_in(cx, |workspace, window, cx| {
 2244                        let mut remaining_dirty_items = Vec::new();
 2245                        let mut serialize_tasks = Vec::new();
 2246                        for (pane, item) in dirty_items {
 2247                            if let Some(task) = item
 2248                                .to_serializable_item_handle(cx)
 2249                                .and_then(|handle| handle.serialize(workspace, true, window, cx))
 2250                            {
 2251                                serialize_tasks.push(task);
 2252                            } else {
 2253                                remaining_dirty_items.push((pane, item));
 2254                            }
 2255                        }
 2256                        (serialize_tasks, remaining_dirty_items)
 2257                    })?;
 2258
 2259                futures::future::try_join_all(serialize_tasks).await?;
 2260
 2261                if remaining_dirty_items.len() > 1 {
 2262                    let answer = workspace.update_in(cx, |_, window, cx| {
 2263                        let detail = Pane::file_names_for_prompt(
 2264                            &mut remaining_dirty_items.iter().map(|(_, handle)| handle),
 2265                            cx,
 2266                        );
 2267                        window.prompt(
 2268                            PromptLevel::Warning,
 2269                            &"Do you want to save all changes in the following files?",
 2270                            Some(&detail),
 2271                            &["Save all", "Discard all", "Cancel"],
 2272                            cx,
 2273                        )
 2274                    })?;
 2275                    match answer.await.log_err() {
 2276                        Some(0) => save_intent = SaveIntent::SaveAll,
 2277                        Some(1) => save_intent = SaveIntent::Skip,
 2278                        Some(2) => return Ok(false),
 2279                        _ => {}
 2280                    }
 2281                }
 2282
 2283                remaining_dirty_items
 2284            } else {
 2285                dirty_items
 2286            };
 2287
 2288            for (pane, item) in dirty_items {
 2289                let (singleton, project_entry_ids) =
 2290                    cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
 2291                if singleton || !project_entry_ids.is_empty() {
 2292                    if !Pane::save_item(project.clone(), &pane, &*item, save_intent, cx).await? {
 2293                        return Ok(false);
 2294                    }
 2295                }
 2296            }
 2297            Ok(true)
 2298        })
 2299    }
 2300
 2301    pub fn open_workspace_for_paths(
 2302        &mut self,
 2303        replace_current_window: bool,
 2304        paths: Vec<PathBuf>,
 2305        window: &mut Window,
 2306        cx: &mut Context<Self>,
 2307    ) -> Task<Result<()>> {
 2308        let window_handle = window.window_handle().downcast::<Self>();
 2309        let is_remote = self.project.read(cx).is_via_collab();
 2310        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
 2311        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
 2312
 2313        let window_to_replace = if replace_current_window {
 2314            window_handle
 2315        } else if is_remote || has_worktree || has_dirty_items {
 2316            None
 2317        } else {
 2318            window_handle
 2319        };
 2320        let app_state = self.app_state.clone();
 2321
 2322        cx.spawn(async move |_, cx| {
 2323            cx.update(|cx| {
 2324                open_paths(
 2325                    &paths,
 2326                    app_state,
 2327                    OpenOptions {
 2328                        replace_window: window_to_replace,
 2329                        ..Default::default()
 2330                    },
 2331                    cx,
 2332                )
 2333            })?
 2334            .await?;
 2335            Ok(())
 2336        })
 2337    }
 2338
 2339    #[allow(clippy::type_complexity)]
 2340    pub fn open_paths(
 2341        &mut self,
 2342        mut abs_paths: Vec<PathBuf>,
 2343        options: OpenOptions,
 2344        pane: Option<WeakEntity<Pane>>,
 2345        window: &mut Window,
 2346        cx: &mut Context<Self>,
 2347    ) -> Task<Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>> {
 2348        log::info!("open paths {abs_paths:?}");
 2349
 2350        let fs = self.app_state.fs.clone();
 2351
 2352        // Sort the paths to ensure we add worktrees for parents before their children.
 2353        abs_paths.sort_unstable();
 2354        cx.spawn_in(window, async move |this, cx| {
 2355            let mut tasks = Vec::with_capacity(abs_paths.len());
 2356
 2357            for abs_path in &abs_paths {
 2358                let visible = match options.visible.as_ref().unwrap_or(&OpenVisible::None) {
 2359                    OpenVisible::All => Some(true),
 2360                    OpenVisible::None => Some(false),
 2361                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
 2362                        Some(Some(metadata)) => Some(!metadata.is_dir),
 2363                        Some(None) => Some(true),
 2364                        None => None,
 2365                    },
 2366                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
 2367                        Some(Some(metadata)) => Some(metadata.is_dir),
 2368                        Some(None) => Some(false),
 2369                        None => None,
 2370                    },
 2371                };
 2372                let project_path = match visible {
 2373                    Some(visible) => match this
 2374                        .update(cx, |this, cx| {
 2375                            Workspace::project_path_for_path(
 2376                                this.project.clone(),
 2377                                abs_path,
 2378                                visible,
 2379                                cx,
 2380                            )
 2381                        })
 2382                        .log_err()
 2383                    {
 2384                        Some(project_path) => project_path.await.log_err(),
 2385                        None => None,
 2386                    },
 2387                    None => None,
 2388                };
 2389
 2390                let this = this.clone();
 2391                let abs_path: Arc<Path> = SanitizedPath::from(abs_path.clone()).into();
 2392                let fs = fs.clone();
 2393                let pane = pane.clone();
 2394                let task = cx.spawn(async move |cx| {
 2395                    let (worktree, project_path) = project_path?;
 2396                    if fs.is_dir(&abs_path).await {
 2397                        this.update(cx, |workspace, cx| {
 2398                            let worktree = worktree.read(cx);
 2399                            let worktree_abs_path = worktree.abs_path();
 2400                            let entry_id = if abs_path.as_ref() == worktree_abs_path.as_ref() {
 2401                                worktree.root_entry()
 2402                            } else {
 2403                                abs_path
 2404                                    .strip_prefix(worktree_abs_path.as_ref())
 2405                                    .ok()
 2406                                    .and_then(|relative_path| {
 2407                                        worktree.entry_for_path(relative_path)
 2408                                    })
 2409                            }
 2410                            .map(|entry| entry.id);
 2411                            if let Some(entry_id) = entry_id {
 2412                                workspace.project.update(cx, |_, cx| {
 2413                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
 2414                                })
 2415                            }
 2416                        })
 2417                        .ok()?;
 2418                        None
 2419                    } else {
 2420                        Some(
 2421                            this.update_in(cx, |this, window, cx| {
 2422                                this.open_path(
 2423                                    project_path,
 2424                                    pane,
 2425                                    options.focus.unwrap_or(true),
 2426                                    window,
 2427                                    cx,
 2428                                )
 2429                            })
 2430                            .ok()?
 2431                            .await,
 2432                        )
 2433                    }
 2434                });
 2435                tasks.push(task);
 2436            }
 2437
 2438            futures::future::join_all(tasks).await
 2439        })
 2440    }
 2441
 2442    pub fn open_resolved_path(
 2443        &mut self,
 2444        path: ResolvedPath,
 2445        window: &mut Window,
 2446        cx: &mut Context<Self>,
 2447    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 2448        match path {
 2449            ResolvedPath::ProjectPath { project_path, .. } => {
 2450                self.open_path(project_path, None, true, window, cx)
 2451            }
 2452            ResolvedPath::AbsPath { path, .. } => self.open_abs_path(
 2453                path,
 2454                OpenOptions {
 2455                    visible: Some(OpenVisible::None),
 2456                    ..Default::default()
 2457                },
 2458                window,
 2459                cx,
 2460            ),
 2461        }
 2462    }
 2463
 2464    pub fn absolute_path_of_worktree(
 2465        &self,
 2466        worktree_id: WorktreeId,
 2467        cx: &mut Context<Self>,
 2468    ) -> Option<PathBuf> {
 2469        self.project
 2470            .read(cx)
 2471            .worktree_for_id(worktree_id, cx)
 2472            // TODO: use `abs_path` or `root_dir`
 2473            .map(|wt| wt.read(cx).abs_path().as_ref().to_path_buf())
 2474    }
 2475
 2476    fn add_folder_to_project(
 2477        &mut self,
 2478        _: &AddFolderToProject,
 2479        window: &mut Window,
 2480        cx: &mut Context<Self>,
 2481    ) {
 2482        let project = self.project.read(cx);
 2483        if project.is_via_collab() {
 2484            self.show_error(
 2485                &anyhow!("You cannot add folders to someone else's project"),
 2486                cx,
 2487            );
 2488            return;
 2489        }
 2490        let paths = self.prompt_for_open_path(
 2491            PathPromptOptions {
 2492                files: false,
 2493                directories: true,
 2494                multiple: true,
 2495            },
 2496            DirectoryLister::Project(self.project.clone()),
 2497            window,
 2498            cx,
 2499        );
 2500        cx.spawn_in(window, async move |this, cx| {
 2501            if let Some(paths) = paths.await.log_err().flatten() {
 2502                let results = this
 2503                    .update_in(cx, |this, window, cx| {
 2504                        this.open_paths(
 2505                            paths,
 2506                            OpenOptions {
 2507                                visible: Some(OpenVisible::All),
 2508                                ..Default::default()
 2509                            },
 2510                            None,
 2511                            window,
 2512                            cx,
 2513                        )
 2514                    })?
 2515                    .await;
 2516                for result in results.into_iter().flatten() {
 2517                    result.log_err();
 2518                }
 2519            }
 2520            anyhow::Ok(())
 2521        })
 2522        .detach_and_log_err(cx);
 2523    }
 2524
 2525    pub fn project_path_for_path(
 2526        project: Entity<Project>,
 2527        abs_path: &Path,
 2528        visible: bool,
 2529        cx: &mut App,
 2530    ) -> Task<Result<(Entity<Worktree>, ProjectPath)>> {
 2531        let entry = project.update(cx, |project, cx| {
 2532            project.find_or_create_worktree(abs_path, visible, cx)
 2533        });
 2534        cx.spawn(async move |cx| {
 2535            let (worktree, path) = entry.await?;
 2536            let worktree_id = worktree.read_with(cx, |t, _| t.id())?;
 2537            Ok((
 2538                worktree,
 2539                ProjectPath {
 2540                    worktree_id,
 2541                    path: path.into(),
 2542                },
 2543            ))
 2544        })
 2545    }
 2546
 2547    pub fn items<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = &'a Box<dyn ItemHandle>> {
 2548        self.panes.iter().flat_map(|pane| pane.read(cx).items())
 2549    }
 2550
 2551    pub fn item_of_type<T: Item>(&self, cx: &App) -> Option<Entity<T>> {
 2552        self.items_of_type(cx).max_by_key(|item| item.item_id())
 2553    }
 2554
 2555    pub fn items_of_type<'a, T: Item>(
 2556        &'a self,
 2557        cx: &'a App,
 2558    ) -> impl 'a + Iterator<Item = Entity<T>> {
 2559        self.panes
 2560            .iter()
 2561            .flat_map(|pane| pane.read(cx).items_of_type())
 2562    }
 2563
 2564    pub fn active_item(&self, cx: &App) -> Option<Box<dyn ItemHandle>> {
 2565        self.active_pane().read(cx).active_item()
 2566    }
 2567
 2568    pub fn active_item_as<I: 'static>(&self, cx: &App) -> Option<Entity<I>> {
 2569        let item = self.active_item(cx)?;
 2570        item.to_any().downcast::<I>().ok()
 2571    }
 2572
 2573    fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
 2574        self.active_item(cx).and_then(|item| item.project_path(cx))
 2575    }
 2576
 2577    pub fn most_recent_active_path(&self, cx: &App) -> Option<PathBuf> {
 2578        self.recent_navigation_history_iter(cx)
 2579            .filter_map(|(path, abs_path)| {
 2580                let worktree = self
 2581                    .project
 2582                    .read(cx)
 2583                    .worktree_for_id(path.worktree_id, cx)?;
 2584                if worktree.read(cx).is_visible() {
 2585                    abs_path
 2586                } else {
 2587                    None
 2588                }
 2589            })
 2590            .next()
 2591    }
 2592
 2593    pub fn save_active_item(
 2594        &mut self,
 2595        save_intent: SaveIntent,
 2596        window: &mut Window,
 2597        cx: &mut App,
 2598    ) -> Task<Result<()>> {
 2599        let project = self.project.clone();
 2600        let pane = self.active_pane();
 2601        let item = pane.read(cx).active_item();
 2602        let pane = pane.downgrade();
 2603
 2604        window.spawn(cx, async move |mut cx| {
 2605            if let Some(item) = item {
 2606                Pane::save_item(project, &pane, item.as_ref(), save_intent, &mut cx)
 2607                    .await
 2608                    .map(|_| ())
 2609            } else {
 2610                Ok(())
 2611            }
 2612        })
 2613    }
 2614
 2615    pub fn close_inactive_items_and_panes(
 2616        &mut self,
 2617        action: &CloseInactiveTabsAndPanes,
 2618        window: &mut Window,
 2619        cx: &mut Context<Self>,
 2620    ) {
 2621        if let Some(task) = self.close_all_internal(
 2622            true,
 2623            action.save_intent.unwrap_or(SaveIntent::Close),
 2624            window,
 2625            cx,
 2626        ) {
 2627            task.detach_and_log_err(cx)
 2628        }
 2629    }
 2630
 2631    pub fn close_all_items_and_panes(
 2632        &mut self,
 2633        action: &CloseAllItemsAndPanes,
 2634        window: &mut Window,
 2635        cx: &mut Context<Self>,
 2636    ) {
 2637        if let Some(task) = self.close_all_internal(
 2638            false,
 2639            action.save_intent.unwrap_or(SaveIntent::Close),
 2640            window,
 2641            cx,
 2642        ) {
 2643            task.detach_and_log_err(cx)
 2644        }
 2645    }
 2646
 2647    fn close_all_internal(
 2648        &mut self,
 2649        retain_active_pane: bool,
 2650        save_intent: SaveIntent,
 2651        window: &mut Window,
 2652        cx: &mut Context<Self>,
 2653    ) -> Option<Task<Result<()>>> {
 2654        let current_pane = self.active_pane();
 2655
 2656        let mut tasks = Vec::new();
 2657
 2658        if retain_active_pane {
 2659            let current_pane_close = current_pane.update(cx, |pane, cx| {
 2660                pane.close_inactive_items(
 2661                    &CloseInactiveItems {
 2662                        save_intent: None,
 2663                        close_pinned: false,
 2664                    },
 2665                    window,
 2666                    cx,
 2667                )
 2668            });
 2669
 2670            tasks.push(current_pane_close);
 2671        }
 2672
 2673        for pane in self.panes() {
 2674            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
 2675                continue;
 2676            }
 2677
 2678            let close_pane_items = pane.update(cx, |pane: &mut Pane, cx| {
 2679                pane.close_all_items(
 2680                    &CloseAllItems {
 2681                        save_intent: Some(save_intent),
 2682                        close_pinned: false,
 2683                    },
 2684                    window,
 2685                    cx,
 2686                )
 2687            });
 2688
 2689            tasks.push(close_pane_items)
 2690        }
 2691
 2692        if tasks.is_empty() {
 2693            None
 2694        } else {
 2695            Some(cx.spawn_in(window, async move |_, _| {
 2696                for task in tasks {
 2697                    task.await?
 2698                }
 2699                Ok(())
 2700            }))
 2701        }
 2702    }
 2703
 2704    pub fn is_dock_at_position_open(&self, position: DockPosition, cx: &mut Context<Self>) -> bool {
 2705        self.dock_at_position(position).read(cx).is_open()
 2706    }
 2707
 2708    pub fn toggle_dock(
 2709        &mut self,
 2710        dock_side: DockPosition,
 2711        window: &mut Window,
 2712        cx: &mut Context<Self>,
 2713    ) {
 2714        let dock = self.dock_at_position(dock_side);
 2715        let mut focus_center = false;
 2716        let mut reveal_dock = false;
 2717        dock.update(cx, |dock, cx| {
 2718            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
 2719            let was_visible = dock.is_open() && !other_is_zoomed;
 2720            dock.set_open(!was_visible, window, cx);
 2721
 2722            if dock.active_panel().is_none() {
 2723                let Some(panel_ix) = dock
 2724                    .first_enabled_panel_idx(cx)
 2725                    .log_with_level(log::Level::Info)
 2726                else {
 2727                    return;
 2728                };
 2729                dock.activate_panel(panel_ix, window, cx);
 2730            }
 2731
 2732            if let Some(active_panel) = dock.active_panel() {
 2733                if was_visible {
 2734                    if active_panel
 2735                        .panel_focus_handle(cx)
 2736                        .contains_focused(window, cx)
 2737                    {
 2738                        focus_center = true;
 2739                    }
 2740                } else {
 2741                    let focus_handle = &active_panel.panel_focus_handle(cx);
 2742                    window.focus(focus_handle);
 2743                    reveal_dock = true;
 2744                }
 2745            }
 2746        });
 2747
 2748        if reveal_dock {
 2749            self.dismiss_zoomed_items_to_reveal(Some(dock_side), window, cx);
 2750        }
 2751
 2752        if focus_center {
 2753            self.active_pane
 2754                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
 2755        }
 2756
 2757        cx.notify();
 2758        self.serialize_workspace(window, cx);
 2759    }
 2760
 2761    fn active_dock(&self, window: &Window, cx: &Context<Self>) -> Option<&Entity<Dock>> {
 2762        self.all_docks().into_iter().find(|&dock| {
 2763            dock.read(cx).is_open() && dock.focus_handle(cx).contains_focused(window, cx)
 2764        })
 2765    }
 2766
 2767    fn close_active_dock(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 2768        if let Some(dock) = self.active_dock(window, cx) {
 2769            dock.update(cx, |dock, cx| {
 2770                dock.set_open(false, window, cx);
 2771            });
 2772        }
 2773    }
 2774
 2775    pub fn close_all_docks(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 2776        for dock in self.all_docks() {
 2777            dock.update(cx, |dock, cx| {
 2778                dock.set_open(false, window, cx);
 2779            });
 2780        }
 2781
 2782        cx.focus_self(window);
 2783        cx.notify();
 2784        self.serialize_workspace(window, cx);
 2785    }
 2786
 2787    /// Transfer focus to the panel of the given type.
 2788    pub fn focus_panel<T: Panel>(
 2789        &mut self,
 2790        window: &mut Window,
 2791        cx: &mut Context<Self>,
 2792    ) -> Option<Entity<T>> {
 2793        let panel = self.focus_or_unfocus_panel::<T>(window, cx, |_, _, _| true)?;
 2794        panel.to_any().downcast().ok()
 2795    }
 2796
 2797    /// Focus the panel of the given type if it isn't already focused. If it is
 2798    /// already focused, then transfer focus back to the workspace center.
 2799    pub fn toggle_panel_focus<T: Panel>(
 2800        &mut self,
 2801        window: &mut Window,
 2802        cx: &mut Context<Self>,
 2803    ) -> bool {
 2804        let mut did_focus_panel = false;
 2805        self.focus_or_unfocus_panel::<T>(window, cx, |panel, window, cx| {
 2806            did_focus_panel = !panel.panel_focus_handle(cx).contains_focused(window, cx);
 2807            did_focus_panel
 2808        });
 2809        did_focus_panel
 2810    }
 2811
 2812    pub fn activate_panel_for_proto_id(
 2813        &mut self,
 2814        panel_id: PanelId,
 2815        window: &mut Window,
 2816        cx: &mut Context<Self>,
 2817    ) -> Option<Arc<dyn PanelHandle>> {
 2818        let mut panel = None;
 2819        for dock in self.all_docks() {
 2820            if let Some(panel_index) = dock.read(cx).panel_index_for_proto_id(panel_id) {
 2821                panel = dock.update(cx, |dock, cx| {
 2822                    dock.activate_panel(panel_index, window, cx);
 2823                    dock.set_open(true, window, cx);
 2824                    dock.active_panel().cloned()
 2825                });
 2826                break;
 2827            }
 2828        }
 2829
 2830        if panel.is_some() {
 2831            cx.notify();
 2832            self.serialize_workspace(window, cx);
 2833        }
 2834
 2835        panel
 2836    }
 2837
 2838    /// Focus or unfocus the given panel type, depending on the given callback.
 2839    fn focus_or_unfocus_panel<T: Panel>(
 2840        &mut self,
 2841        window: &mut Window,
 2842        cx: &mut Context<Self>,
 2843        mut should_focus: impl FnMut(&dyn PanelHandle, &mut Window, &mut Context<Dock>) -> bool,
 2844    ) -> Option<Arc<dyn PanelHandle>> {
 2845        let mut result_panel = None;
 2846        let mut serialize = false;
 2847        for dock in self.all_docks() {
 2848            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
 2849                let mut focus_center = false;
 2850                let panel = dock.update(cx, |dock, cx| {
 2851                    dock.activate_panel(panel_index, window, cx);
 2852
 2853                    let panel = dock.active_panel().cloned();
 2854                    if let Some(panel) = panel.as_ref() {
 2855                        if should_focus(&**panel, window, cx) {
 2856                            dock.set_open(true, window, cx);
 2857                            panel.panel_focus_handle(cx).focus(window);
 2858                        } else {
 2859                            focus_center = true;
 2860                        }
 2861                    }
 2862                    panel
 2863                });
 2864
 2865                if focus_center {
 2866                    self.active_pane
 2867                        .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
 2868                }
 2869
 2870                result_panel = panel;
 2871                serialize = true;
 2872                break;
 2873            }
 2874        }
 2875
 2876        if serialize {
 2877            self.serialize_workspace(window, cx);
 2878        }
 2879
 2880        cx.notify();
 2881        result_panel
 2882    }
 2883
 2884    /// Open the panel of the given type
 2885    pub fn open_panel<T: Panel>(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 2886        for dock in self.all_docks() {
 2887            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
 2888                dock.update(cx, |dock, cx| {
 2889                    dock.activate_panel(panel_index, window, cx);
 2890                    dock.set_open(true, window, cx);
 2891                });
 2892            }
 2893        }
 2894    }
 2895
 2896    pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
 2897        self.all_docks()
 2898            .iter()
 2899            .find_map(|dock| dock.read(cx).panel::<T>())
 2900    }
 2901
 2902    fn dismiss_zoomed_items_to_reveal(
 2903        &mut self,
 2904        dock_to_reveal: Option<DockPosition>,
 2905        window: &mut Window,
 2906        cx: &mut Context<Self>,
 2907    ) {
 2908        // If a center pane is zoomed, unzoom it.
 2909        for pane in &self.panes {
 2910            if pane != &self.active_pane || dock_to_reveal.is_some() {
 2911                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
 2912            }
 2913        }
 2914
 2915        // If another dock is zoomed, hide it.
 2916        let mut focus_center = false;
 2917        for dock in self.all_docks() {
 2918            dock.update(cx, |dock, cx| {
 2919                if Some(dock.position()) != dock_to_reveal {
 2920                    if let Some(panel) = dock.active_panel() {
 2921                        if panel.is_zoomed(window, cx) {
 2922                            focus_center |=
 2923                                panel.panel_focus_handle(cx).contains_focused(window, cx);
 2924                            dock.set_open(false, window, cx);
 2925                        }
 2926                    }
 2927                }
 2928            });
 2929        }
 2930
 2931        if focus_center {
 2932            self.active_pane
 2933                .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)))
 2934        }
 2935
 2936        if self.zoomed_position != dock_to_reveal {
 2937            self.zoomed = None;
 2938            self.zoomed_position = None;
 2939            cx.emit(Event::ZoomChanged);
 2940        }
 2941
 2942        cx.notify();
 2943    }
 2944
 2945    fn add_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
 2946        let pane = cx.new(|cx| {
 2947            let mut pane = Pane::new(
 2948                self.weak_handle(),
 2949                self.project.clone(),
 2950                self.pane_history_timestamp.clone(),
 2951                None,
 2952                NewFile.boxed_clone(),
 2953                window,
 2954                cx,
 2955            );
 2956            pane.set_can_split(Some(Arc::new(|_, _, _, _| true)));
 2957            pane
 2958        });
 2959        cx.subscribe_in(&pane, window, Self::handle_pane_event)
 2960            .detach();
 2961        self.panes.push(pane.clone());
 2962
 2963        window.focus(&pane.focus_handle(cx));
 2964
 2965        cx.emit(Event::PaneAdded(pane.clone()));
 2966        pane
 2967    }
 2968
 2969    pub fn add_item_to_center(
 2970        &mut self,
 2971        item: Box<dyn ItemHandle>,
 2972        window: &mut Window,
 2973        cx: &mut Context<Self>,
 2974    ) -> bool {
 2975        if let Some(center_pane) = self.last_active_center_pane.clone() {
 2976            if let Some(center_pane) = center_pane.upgrade() {
 2977                center_pane.update(cx, |pane, cx| {
 2978                    pane.add_item(item, true, true, None, window, cx)
 2979                });
 2980                true
 2981            } else {
 2982                false
 2983            }
 2984        } else {
 2985            false
 2986        }
 2987    }
 2988
 2989    pub fn add_item_to_active_pane(
 2990        &mut self,
 2991        item: Box<dyn ItemHandle>,
 2992        destination_index: Option<usize>,
 2993        focus_item: bool,
 2994        window: &mut Window,
 2995        cx: &mut App,
 2996    ) {
 2997        self.add_item(
 2998            self.active_pane.clone(),
 2999            item,
 3000            destination_index,
 3001            false,
 3002            focus_item,
 3003            window,
 3004            cx,
 3005        )
 3006    }
 3007
 3008    pub fn add_item(
 3009        &mut self,
 3010        pane: Entity<Pane>,
 3011        item: Box<dyn ItemHandle>,
 3012        destination_index: Option<usize>,
 3013        activate_pane: bool,
 3014        focus_item: bool,
 3015        window: &mut Window,
 3016        cx: &mut App,
 3017    ) {
 3018        if let Some(text) = item.telemetry_event_text(cx) {
 3019            telemetry::event!(text);
 3020        }
 3021
 3022        pane.update(cx, |pane, cx| {
 3023            pane.add_item(
 3024                item,
 3025                activate_pane,
 3026                focus_item,
 3027                destination_index,
 3028                window,
 3029                cx,
 3030            )
 3031        });
 3032    }
 3033
 3034    pub fn split_item(
 3035        &mut self,
 3036        split_direction: SplitDirection,
 3037        item: Box<dyn ItemHandle>,
 3038        window: &mut Window,
 3039        cx: &mut Context<Self>,
 3040    ) {
 3041        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, window, cx);
 3042        self.add_item(new_pane, item, None, true, true, window, cx);
 3043    }
 3044
 3045    pub fn open_abs_path(
 3046        &mut self,
 3047        abs_path: PathBuf,
 3048        options: OpenOptions,
 3049        window: &mut Window,
 3050        cx: &mut Context<Self>,
 3051    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 3052        cx.spawn_in(window, async move |workspace, cx| {
 3053            let open_paths_task_result = workspace
 3054                .update_in(cx, |workspace, window, cx| {
 3055                    workspace.open_paths(vec![abs_path.clone()], options, None, window, cx)
 3056                })
 3057                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
 3058                .await;
 3059            anyhow::ensure!(
 3060                open_paths_task_result.len() == 1,
 3061                "open abs path {abs_path:?} task returned incorrect number of results"
 3062            );
 3063            match open_paths_task_result
 3064                .into_iter()
 3065                .next()
 3066                .expect("ensured single task result")
 3067            {
 3068                Some(open_result) => {
 3069                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
 3070                }
 3071                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
 3072            }
 3073        })
 3074    }
 3075
 3076    pub fn split_abs_path(
 3077        &mut self,
 3078        abs_path: PathBuf,
 3079        visible: bool,
 3080        window: &mut Window,
 3081        cx: &mut Context<Self>,
 3082    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 3083        let project_path_task =
 3084            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
 3085        cx.spawn_in(window, async move |this, cx| {
 3086            let (_, path) = project_path_task.await?;
 3087            this.update_in(cx, |this, window, cx| this.split_path(path, window, cx))?
 3088                .await
 3089        })
 3090    }
 3091
 3092    pub fn open_path(
 3093        &mut self,
 3094        path: impl Into<ProjectPath>,
 3095        pane: Option<WeakEntity<Pane>>,
 3096        focus_item: bool,
 3097        window: &mut Window,
 3098        cx: &mut App,
 3099    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 3100        self.open_path_preview(path, pane, focus_item, false, true, window, cx)
 3101    }
 3102
 3103    pub fn open_path_preview(
 3104        &mut self,
 3105        path: impl Into<ProjectPath>,
 3106        pane: Option<WeakEntity<Pane>>,
 3107        focus_item: bool,
 3108        allow_preview: bool,
 3109        activate: bool,
 3110        window: &mut Window,
 3111        cx: &mut App,
 3112    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 3113        let pane = pane.unwrap_or_else(|| {
 3114            self.last_active_center_pane.clone().unwrap_or_else(|| {
 3115                self.panes
 3116                    .first()
 3117                    .expect("There must be an active pane")
 3118                    .downgrade()
 3119            })
 3120        });
 3121
 3122        let project_path = path.into();
 3123        let task = self.load_path(project_path.clone(), window, cx);
 3124        window.spawn(cx, async move |cx| {
 3125            let (project_entry_id, build_item) = task.await?;
 3126            let result = pane.update_in(cx, |pane, window, cx| {
 3127                pane.open_item(
 3128                    project_entry_id,
 3129                    project_path,
 3130                    focus_item,
 3131                    allow_preview,
 3132                    activate,
 3133                    None,
 3134                    window,
 3135                    cx,
 3136                    build_item,
 3137                )
 3138            });
 3139            result
 3140        })
 3141    }
 3142
 3143    pub fn split_path(
 3144        &mut self,
 3145        path: impl Into<ProjectPath>,
 3146        window: &mut Window,
 3147        cx: &mut Context<Self>,
 3148    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 3149        self.split_path_preview(path, false, None, window, cx)
 3150    }
 3151
 3152    pub fn split_path_preview(
 3153        &mut self,
 3154        path: impl Into<ProjectPath>,
 3155        allow_preview: bool,
 3156        split_direction: Option<SplitDirection>,
 3157        window: &mut Window,
 3158        cx: &mut Context<Self>,
 3159    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
 3160        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
 3161            self.panes
 3162                .first()
 3163                .expect("There must be an active pane")
 3164                .downgrade()
 3165        });
 3166
 3167        if let Member::Pane(center_pane) = &self.center.root {
 3168            if center_pane.read(cx).items_len() == 0 {
 3169                return self.open_path(path, Some(pane), true, window, cx);
 3170            }
 3171        }
 3172
 3173        let project_path = path.into();
 3174        let task = self.load_path(project_path.clone(), window, cx);
 3175        cx.spawn_in(window, async move |this, cx| {
 3176            let (project_entry_id, build_item) = task.await?;
 3177            this.update_in(cx, move |this, window, cx| -> Option<_> {
 3178                let pane = pane.upgrade()?;
 3179                let new_pane = this.split_pane(
 3180                    pane,
 3181                    split_direction.unwrap_or(SplitDirection::Right),
 3182                    window,
 3183                    cx,
 3184                );
 3185                new_pane.update(cx, |new_pane, cx| {
 3186                    Some(new_pane.open_item(
 3187                        project_entry_id,
 3188                        project_path,
 3189                        true,
 3190                        allow_preview,
 3191                        true,
 3192                        None,
 3193                        window,
 3194                        cx,
 3195                        build_item,
 3196                    ))
 3197                })
 3198            })
 3199            .map(|option| option.context("pane was dropped"))?
 3200        })
 3201    }
 3202
 3203    fn load_path(
 3204        &mut self,
 3205        path: ProjectPath,
 3206        window: &mut Window,
 3207        cx: &mut App,
 3208    ) -> Task<Result<(Option<ProjectEntryId>, WorkspaceItemBuilder)>> {
 3209        let project = self.project().clone();
 3210        let registry = cx.default_global::<ProjectItemRegistry>().clone();
 3211        registry.open_path(&project, &path, window, cx)
 3212    }
 3213
 3214    pub fn find_project_item<T>(
 3215        &self,
 3216        pane: &Entity<Pane>,
 3217        project_item: &Entity<T::Item>,
 3218        cx: &App,
 3219    ) -> Option<Entity<T>>
 3220    where
 3221        T: ProjectItem,
 3222    {
 3223        use project::ProjectItem as _;
 3224        let project_item = project_item.read(cx);
 3225        let entry_id = project_item.entry_id(cx);
 3226        let project_path = project_item.project_path(cx);
 3227
 3228        let mut item = None;
 3229        if let Some(entry_id) = entry_id {
 3230            item = pane.read(cx).item_for_entry(entry_id, cx);
 3231        }
 3232        if item.is_none() {
 3233            if let Some(project_path) = project_path {
 3234                item = pane.read(cx).item_for_path(project_path, cx);
 3235            }
 3236        }
 3237
 3238        item.and_then(|item| item.downcast::<T>())
 3239    }
 3240
 3241    pub fn is_project_item_open<T>(
 3242        &self,
 3243        pane: &Entity<Pane>,
 3244        project_item: &Entity<T::Item>,
 3245        cx: &App,
 3246    ) -> bool
 3247    where
 3248        T: ProjectItem,
 3249    {
 3250        self.find_project_item::<T>(pane, project_item, cx)
 3251            .is_some()
 3252    }
 3253
 3254    pub fn open_project_item<T>(
 3255        &mut self,
 3256        pane: Entity<Pane>,
 3257        project_item: Entity<T::Item>,
 3258        activate_pane: bool,
 3259        focus_item: bool,
 3260        window: &mut Window,
 3261        cx: &mut Context<Self>,
 3262    ) -> Entity<T>
 3263    where
 3264        T: ProjectItem,
 3265    {
 3266        if let Some(item) = self.find_project_item(&pane, &project_item, cx) {
 3267            self.activate_item(&item, activate_pane, focus_item, window, cx);
 3268            return item;
 3269        }
 3270
 3271        let item = pane.update(cx, |pane, cx| {
 3272            cx.new(|cx| {
 3273                T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx)
 3274            })
 3275        });
 3276        let item_id = item.item_id();
 3277        let mut destination_index = None;
 3278        pane.update(cx, |pane, cx| {
 3279            if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
 3280                if let Some(preview_item_id) = pane.preview_item_id() {
 3281                    if preview_item_id != item_id {
 3282                        destination_index = pane.close_current_preview_item(window, cx);
 3283                    }
 3284                }
 3285            }
 3286            pane.set_preview_item_id(Some(item.item_id()), cx)
 3287        });
 3288
 3289        self.add_item(
 3290            pane,
 3291            Box::new(item.clone()),
 3292            destination_index,
 3293            activate_pane,
 3294            focus_item,
 3295            window,
 3296            cx,
 3297        );
 3298        item
 3299    }
 3300
 3301    pub fn open_shared_screen(
 3302        &mut self,
 3303        peer_id: PeerId,
 3304        window: &mut Window,
 3305        cx: &mut Context<Self>,
 3306    ) {
 3307        if let Some(shared_screen) =
 3308            self.shared_screen_for_peer(peer_id, &self.active_pane, window, cx)
 3309        {
 3310            self.active_pane.update(cx, |pane, cx| {
 3311                pane.add_item(Box::new(shared_screen), false, true, None, window, cx)
 3312            });
 3313        }
 3314    }
 3315
 3316    pub fn activate_item(
 3317        &mut self,
 3318        item: &dyn ItemHandle,
 3319        activate_pane: bool,
 3320        focus_item: bool,
 3321        window: &mut Window,
 3322        cx: &mut App,
 3323    ) -> bool {
 3324        let result = self.panes.iter().find_map(|pane| {
 3325            pane.read(cx)
 3326                .index_for_item(item)
 3327                .map(|ix| (pane.clone(), ix))
 3328        });
 3329        if let Some((pane, ix)) = result {
 3330            pane.update(cx, |pane, cx| {
 3331                pane.activate_item(ix, activate_pane, focus_item, window, cx)
 3332            });
 3333            true
 3334        } else {
 3335            false
 3336        }
 3337    }
 3338
 3339    fn activate_pane_at_index(
 3340        &mut self,
 3341        action: &ActivatePane,
 3342        window: &mut Window,
 3343        cx: &mut Context<Self>,
 3344    ) {
 3345        let panes = self.center.panes();
 3346        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
 3347            window.focus(&pane.focus_handle(cx));
 3348        } else {
 3349            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, window, cx);
 3350        }
 3351    }
 3352
 3353    fn move_item_to_pane_at_index(
 3354        &mut self,
 3355        action: &MoveItemToPane,
 3356        window: &mut Window,
 3357        cx: &mut Context<Self>,
 3358    ) {
 3359        let panes = self.center.panes();
 3360        let destination = match panes.get(action.destination) {
 3361            Some(&destination) => destination.clone(),
 3362            None => {
 3363                if !action.clone && self.active_pane.read(cx).items_len() < 2 {
 3364                    return;
 3365                }
 3366                let direction = SplitDirection::Right;
 3367                let split_off_pane = self
 3368                    .find_pane_in_direction(direction, cx)
 3369                    .unwrap_or_else(|| self.active_pane.clone());
 3370                let new_pane = self.add_pane(window, cx);
 3371                if self
 3372                    .center
 3373                    .split(&split_off_pane, &new_pane, direction)
 3374                    .log_err()
 3375                    .is_none()
 3376                {
 3377                    return;
 3378                };
 3379                new_pane
 3380            }
 3381        };
 3382
 3383        if action.clone {
 3384            clone_active_item(
 3385                self.database_id(),
 3386                &self.active_pane,
 3387                &destination,
 3388                action.focus,
 3389                window,
 3390                cx,
 3391            )
 3392        } else {
 3393            move_active_item(
 3394                &self.active_pane,
 3395                &destination,
 3396                action.focus,
 3397                true,
 3398                window,
 3399                cx,
 3400            )
 3401        }
 3402    }
 3403
 3404    pub fn activate_next_pane(&mut self, window: &mut Window, cx: &mut App) {
 3405        let panes = self.center.panes();
 3406        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
 3407            let next_ix = (ix + 1) % panes.len();
 3408            let next_pane = panes[next_ix].clone();
 3409            window.focus(&next_pane.focus_handle(cx));
 3410        }
 3411    }
 3412
 3413    pub fn activate_previous_pane(&mut self, window: &mut Window, cx: &mut App) {
 3414        let panes = self.center.panes();
 3415        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
 3416            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
 3417            let prev_pane = panes[prev_ix].clone();
 3418            window.focus(&prev_pane.focus_handle(cx));
 3419        }
 3420    }
 3421
 3422    pub fn activate_pane_in_direction(
 3423        &mut self,
 3424        direction: SplitDirection,
 3425        window: &mut Window,
 3426        cx: &mut App,
 3427    ) {
 3428        use ActivateInDirectionTarget as Target;
 3429        enum Origin {
 3430            LeftDock,
 3431            RightDock,
 3432            BottomDock,
 3433            Center,
 3434        }
 3435
 3436        let origin: Origin = [
 3437            (&self.left_dock, Origin::LeftDock),
 3438            (&self.right_dock, Origin::RightDock),
 3439            (&self.bottom_dock, Origin::BottomDock),
 3440        ]
 3441        .into_iter()
 3442        .find_map(|(dock, origin)| {
 3443            if dock.focus_handle(cx).contains_focused(window, cx) && dock.read(cx).is_open() {
 3444                Some(origin)
 3445            } else {
 3446                None
 3447            }
 3448        })
 3449        .unwrap_or(Origin::Center);
 3450
 3451        let get_last_active_pane = || {
 3452            let pane = self
 3453                .last_active_center_pane
 3454                .clone()
 3455                .unwrap_or_else(|| {
 3456                    self.panes
 3457                        .first()
 3458                        .expect("There must be an active pane")
 3459                        .downgrade()
 3460                })
 3461                .upgrade()?;
 3462            (pane.read(cx).items_len() != 0).then_some(pane)
 3463        };
 3464
 3465        let try_dock =
 3466            |dock: &Entity<Dock>| dock.read(cx).is_open().then(|| Target::Dock(dock.clone()));
 3467
 3468        let target = match (origin, direction) {
 3469            // We're in the center, so we first try to go to a different pane,
 3470            // otherwise try to go to a dock.
 3471            (Origin::Center, direction) => {
 3472                if let Some(pane) = self.find_pane_in_direction(direction, cx) {
 3473                    Some(Target::Pane(pane))
 3474                } else {
 3475                    match direction {
 3476                        SplitDirection::Up => None,
 3477                        SplitDirection::Down => try_dock(&self.bottom_dock),
 3478                        SplitDirection::Left => try_dock(&self.left_dock),
 3479                        SplitDirection::Right => try_dock(&self.right_dock),
 3480                    }
 3481                }
 3482            }
 3483
 3484            (Origin::LeftDock, SplitDirection::Right) => {
 3485                if let Some(last_active_pane) = get_last_active_pane() {
 3486                    Some(Target::Pane(last_active_pane))
 3487                } else {
 3488                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.right_dock))
 3489                }
 3490            }
 3491
 3492            (Origin::LeftDock, SplitDirection::Down)
 3493            | (Origin::RightDock, SplitDirection::Down) => try_dock(&self.bottom_dock),
 3494
 3495            (Origin::BottomDock, SplitDirection::Up) => get_last_active_pane().map(Target::Pane),
 3496            (Origin::BottomDock, SplitDirection::Left) => try_dock(&self.left_dock),
 3497            (Origin::BottomDock, SplitDirection::Right) => try_dock(&self.right_dock),
 3498
 3499            (Origin::RightDock, SplitDirection::Left) => {
 3500                if let Some(last_active_pane) = get_last_active_pane() {
 3501                    Some(Target::Pane(last_active_pane))
 3502                } else {
 3503                    try_dock(&self.bottom_dock).or_else(|| try_dock(&self.left_dock))
 3504                }
 3505            }
 3506
 3507            _ => None,
 3508        };
 3509
 3510        match target {
 3511            Some(ActivateInDirectionTarget::Pane(pane)) => {
 3512                let pane = pane.read(cx);
 3513                if let Some(item) = pane.active_item() {
 3514                    item.item_focus_handle(cx).focus(window);
 3515                } else {
 3516                    log::error!(
 3517                        "Could not find a focus target when in switching focus in {direction} direction for a pane",
 3518                    );
 3519                }
 3520            }
 3521            Some(ActivateInDirectionTarget::Dock(dock)) => {
 3522                // Defer this to avoid a panic when the dock's active panel is already on the stack.
 3523                window.defer(cx, move |window, cx| {
 3524                    let dock = dock.read(cx);
 3525                    if let Some(panel) = dock.active_panel() {
 3526                        panel.panel_focus_handle(cx).focus(window);
 3527                    } else {
 3528                        log::error!("Could not find a focus target when in switching focus in {direction} direction for a {:?} dock", dock.position());
 3529                    }
 3530                })
 3531            }
 3532            None => {}
 3533        }
 3534    }
 3535
 3536    pub fn move_item_to_pane_in_direction(
 3537        &mut self,
 3538        action: &MoveItemToPaneInDirection,
 3539        window: &mut Window,
 3540        cx: &mut Context<Self>,
 3541    ) {
 3542        let destination = match self.find_pane_in_direction(action.direction, cx) {
 3543            Some(destination) => destination,
 3544            None => {
 3545                if !action.clone && self.active_pane.read(cx).items_len() < 2 {
 3546                    return;
 3547                }
 3548                let new_pane = self.add_pane(window, cx);
 3549                if self
 3550                    .center
 3551                    .split(&self.active_pane, &new_pane, action.direction)
 3552                    .log_err()
 3553                    .is_none()
 3554                {
 3555                    return;
 3556                };
 3557                new_pane
 3558            }
 3559        };
 3560
 3561        if action.clone {
 3562            clone_active_item(
 3563                self.database_id(),
 3564                &self.active_pane,
 3565                &destination,
 3566                action.focus,
 3567                window,
 3568                cx,
 3569            )
 3570        } else {
 3571            move_active_item(
 3572                &self.active_pane,
 3573                &destination,
 3574                action.focus,
 3575                true,
 3576                window,
 3577                cx,
 3578            );
 3579        }
 3580    }
 3581
 3582    pub fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
 3583        self.center.bounding_box_for_pane(pane)
 3584    }
 3585
 3586    pub fn find_pane_in_direction(
 3587        &mut self,
 3588        direction: SplitDirection,
 3589        cx: &App,
 3590    ) -> Option<Entity<Pane>> {
 3591        self.center
 3592            .find_pane_in_direction(&self.active_pane, direction, cx)
 3593            .cloned()
 3594    }
 3595
 3596    pub fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
 3597        if let Some(to) = self.find_pane_in_direction(direction, cx) {
 3598            self.center.swap(&self.active_pane, &to);
 3599            cx.notify();
 3600        }
 3601    }
 3602
 3603    pub fn resize_pane(
 3604        &mut self,
 3605        axis: gpui::Axis,
 3606        amount: Pixels,
 3607        window: &mut Window,
 3608        cx: &mut Context<Self>,
 3609    ) {
 3610        let docks = self.all_docks();
 3611        let active_dock = docks
 3612            .into_iter()
 3613            .find(|dock| dock.focus_handle(cx).contains_focused(window, cx));
 3614
 3615        if let Some(dock) = active_dock {
 3616            let Some(panel_size) = dock.read(cx).active_panel_size(window, cx) else {
 3617                return;
 3618            };
 3619            match dock.read(cx).position() {
 3620                DockPosition::Left => resize_left_dock(panel_size + amount, self, window, cx),
 3621                DockPosition::Bottom => resize_bottom_dock(panel_size + amount, self, window, cx),
 3622                DockPosition::Right => resize_right_dock(panel_size + amount, self, window, cx),
 3623            }
 3624        } else {
 3625            self.center
 3626                .resize(&self.active_pane, axis, amount, &self.bounds);
 3627        }
 3628        cx.notify();
 3629    }
 3630
 3631    pub fn reset_pane_sizes(&mut self, cx: &mut Context<Self>) {
 3632        self.center.reset_pane_sizes();
 3633        cx.notify();
 3634    }
 3635
 3636    fn handle_pane_focused(
 3637        &mut self,
 3638        pane: Entity<Pane>,
 3639        window: &mut Window,
 3640        cx: &mut Context<Self>,
 3641    ) {
 3642        // This is explicitly hoisted out of the following check for pane identity as
 3643        // terminal panel panes are not registered as a center panes.
 3644        self.status_bar.update(cx, |status_bar, cx| {
 3645            status_bar.set_active_pane(&pane, window, cx);
 3646        });
 3647        if self.active_pane != pane {
 3648            self.set_active_pane(&pane, window, cx);
 3649        }
 3650
 3651        if self.last_active_center_pane.is_none() {
 3652            self.last_active_center_pane = Some(pane.downgrade());
 3653        }
 3654
 3655        self.dismiss_zoomed_items_to_reveal(None, window, cx);
 3656        if pane.read(cx).is_zoomed() {
 3657            self.zoomed = Some(pane.downgrade().into());
 3658        } else {
 3659            self.zoomed = None;
 3660        }
 3661        self.zoomed_position = None;
 3662        cx.emit(Event::ZoomChanged);
 3663        self.update_active_view_for_followers(window, cx);
 3664        pane.update(cx, |pane, _| {
 3665            pane.track_alternate_file_items();
 3666        });
 3667
 3668        cx.notify();
 3669    }
 3670
 3671    fn set_active_pane(
 3672        &mut self,
 3673        pane: &Entity<Pane>,
 3674        window: &mut Window,
 3675        cx: &mut Context<Self>,
 3676    ) {
 3677        self.active_pane = pane.clone();
 3678        self.active_item_path_changed(window, cx);
 3679        self.last_active_center_pane = Some(pane.downgrade());
 3680    }
 3681
 3682    fn handle_panel_focused(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 3683        self.update_active_view_for_followers(window, cx);
 3684    }
 3685
 3686    fn handle_pane_event(
 3687        &mut self,
 3688        pane: &Entity<Pane>,
 3689        event: &pane::Event,
 3690        window: &mut Window,
 3691        cx: &mut Context<Self>,
 3692    ) {
 3693        let mut serialize_workspace = true;
 3694        match event {
 3695            pane::Event::AddItem { item } => {
 3696                item.added_to_pane(self, pane.clone(), window, cx);
 3697                cx.emit(Event::ItemAdded {
 3698                    item: item.boxed_clone(),
 3699                });
 3700            }
 3701            pane::Event::Split(direction) => {
 3702                self.split_and_clone(pane.clone(), *direction, window, cx);
 3703            }
 3704            pane::Event::JoinIntoNext => {
 3705                self.join_pane_into_next(pane.clone(), window, cx);
 3706            }
 3707            pane::Event::JoinAll => {
 3708                self.join_all_panes(window, cx);
 3709            }
 3710            pane::Event::Remove { focus_on_pane } => {
 3711                self.remove_pane(pane.clone(), focus_on_pane.clone(), window, cx);
 3712            }
 3713            pane::Event::ActivateItem {
 3714                local,
 3715                focus_changed,
 3716            } => {
 3717                cx.on_next_frame(window, |_, window, _| {
 3718                    window.invalidate_character_coordinates();
 3719                });
 3720
 3721                pane.update(cx, |pane, _| {
 3722                    pane.track_alternate_file_items();
 3723                });
 3724                if *local {
 3725                    self.unfollow_in_pane(&pane, window, cx);
 3726                }
 3727                if pane == self.active_pane() {
 3728                    self.active_item_path_changed(window, cx);
 3729                    self.update_active_view_for_followers(window, cx);
 3730                }
 3731                serialize_workspace = *focus_changed || pane != self.active_pane();
 3732            }
 3733            pane::Event::UserSavedItem { item, save_intent } => {
 3734                cx.emit(Event::UserSavedItem {
 3735                    pane: pane.downgrade(),
 3736                    item: item.boxed_clone(),
 3737                    save_intent: *save_intent,
 3738                });
 3739                serialize_workspace = false;
 3740            }
 3741            pane::Event::ChangeItemTitle => {
 3742                if *pane == self.active_pane {
 3743                    self.active_item_path_changed(window, cx);
 3744                }
 3745                serialize_workspace = false;
 3746            }
 3747            pane::Event::RemoveItem { .. } => {}
 3748            pane::Event::RemovedItem { item } => {
 3749                cx.emit(Event::ActiveItemChanged);
 3750                self.update_window_edited(window, cx);
 3751                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(item.item_id()) {
 3752                    if entry.get().entity_id() == pane.entity_id() {
 3753                        entry.remove();
 3754                    }
 3755                }
 3756            }
 3757            pane::Event::Focus => {
 3758                cx.on_next_frame(window, |_, window, _| {
 3759                    window.invalidate_character_coordinates();
 3760                });
 3761                self.handle_pane_focused(pane.clone(), window, cx);
 3762            }
 3763            pane::Event::ZoomIn => {
 3764                if *pane == self.active_pane {
 3765                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
 3766                    if pane.read(cx).has_focus(window, cx) {
 3767                        self.zoomed = Some(pane.downgrade().into());
 3768                        self.zoomed_position = None;
 3769                        cx.emit(Event::ZoomChanged);
 3770                    }
 3771                    cx.notify();
 3772                }
 3773            }
 3774            pane::Event::ZoomOut => {
 3775                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
 3776                if self.zoomed_position.is_none() {
 3777                    self.zoomed = None;
 3778                    cx.emit(Event::ZoomChanged);
 3779                }
 3780                cx.notify();
 3781            }
 3782            pane::Event::ItemPinned | pane::Event::ItemUnpinned => {}
 3783        }
 3784
 3785        if serialize_workspace {
 3786            self.serialize_workspace(window, cx);
 3787        }
 3788    }
 3789
 3790    pub fn unfollow_in_pane(
 3791        &mut self,
 3792        pane: &Entity<Pane>,
 3793        window: &mut Window,
 3794        cx: &mut Context<Workspace>,
 3795    ) -> Option<CollaboratorId> {
 3796        let leader_id = self.leader_for_pane(pane)?;
 3797        self.unfollow(leader_id, window, cx);
 3798        Some(leader_id)
 3799    }
 3800
 3801    pub fn split_pane(
 3802        &mut self,
 3803        pane_to_split: Entity<Pane>,
 3804        split_direction: SplitDirection,
 3805        window: &mut Window,
 3806        cx: &mut Context<Self>,
 3807    ) -> Entity<Pane> {
 3808        let new_pane = self.add_pane(window, cx);
 3809        self.center
 3810            .split(&pane_to_split, &new_pane, split_direction)
 3811            .unwrap();
 3812        cx.notify();
 3813        new_pane
 3814    }
 3815
 3816    pub fn split_and_clone(
 3817        &mut self,
 3818        pane: Entity<Pane>,
 3819        direction: SplitDirection,
 3820        window: &mut Window,
 3821        cx: &mut Context<Self>,
 3822    ) -> Option<Entity<Pane>> {
 3823        let item = pane.read(cx).active_item()?;
 3824        let maybe_pane_handle =
 3825            if let Some(clone) = item.clone_on_split(self.database_id(), window, cx) {
 3826                let new_pane = self.add_pane(window, cx);
 3827                new_pane.update(cx, |pane, cx| {
 3828                    pane.add_item(clone, true, true, None, window, cx)
 3829                });
 3830                self.center.split(&pane, &new_pane, direction).unwrap();
 3831                Some(new_pane)
 3832            } else {
 3833                None
 3834            };
 3835        cx.notify();
 3836        maybe_pane_handle
 3837    }
 3838
 3839    pub fn split_pane_with_item(
 3840        &mut self,
 3841        pane_to_split: WeakEntity<Pane>,
 3842        split_direction: SplitDirection,
 3843        from: WeakEntity<Pane>,
 3844        item_id_to_move: EntityId,
 3845        window: &mut Window,
 3846        cx: &mut Context<Self>,
 3847    ) {
 3848        let Some(pane_to_split) = pane_to_split.upgrade() else {
 3849            return;
 3850        };
 3851        let Some(from) = from.upgrade() else {
 3852            return;
 3853        };
 3854
 3855        let new_pane = self.add_pane(window, cx);
 3856        move_item(&from, &new_pane, item_id_to_move, 0, true, window, cx);
 3857        self.center
 3858            .split(&pane_to_split, &new_pane, split_direction)
 3859            .unwrap();
 3860        cx.notify();
 3861    }
 3862
 3863    pub fn split_pane_with_project_entry(
 3864        &mut self,
 3865        pane_to_split: WeakEntity<Pane>,
 3866        split_direction: SplitDirection,
 3867        project_entry: ProjectEntryId,
 3868        window: &mut Window,
 3869        cx: &mut Context<Self>,
 3870    ) -> Option<Task<Result<()>>> {
 3871        let pane_to_split = pane_to_split.upgrade()?;
 3872        let new_pane = self.add_pane(window, cx);
 3873        self.center
 3874            .split(&pane_to_split, &new_pane, split_direction)
 3875            .unwrap();
 3876
 3877        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
 3878        let task = self.open_path(path, Some(new_pane.downgrade()), true, window, cx);
 3879        Some(cx.foreground_executor().spawn(async move {
 3880            task.await?;
 3881            Ok(())
 3882        }))
 3883    }
 3884
 3885    pub fn join_all_panes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 3886        let active_item = self.active_pane.read(cx).active_item();
 3887        for pane in &self.panes {
 3888            join_pane_into_active(&self.active_pane, pane, window, cx);
 3889        }
 3890        if let Some(active_item) = active_item {
 3891            self.activate_item(active_item.as_ref(), true, true, window, cx);
 3892        }
 3893        cx.notify();
 3894    }
 3895
 3896    pub fn join_pane_into_next(
 3897        &mut self,
 3898        pane: Entity<Pane>,
 3899        window: &mut Window,
 3900        cx: &mut Context<Self>,
 3901    ) {
 3902        let next_pane = self
 3903            .find_pane_in_direction(SplitDirection::Right, cx)
 3904            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
 3905            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
 3906            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
 3907        let Some(next_pane) = next_pane else {
 3908            return;
 3909        };
 3910        move_all_items(&pane, &next_pane, window, cx);
 3911        cx.notify();
 3912    }
 3913
 3914    fn remove_pane(
 3915        &mut self,
 3916        pane: Entity<Pane>,
 3917        focus_on: Option<Entity<Pane>>,
 3918        window: &mut Window,
 3919        cx: &mut Context<Self>,
 3920    ) {
 3921        if self.center.remove(&pane).unwrap() {
 3922            self.force_remove_pane(&pane, &focus_on, window, cx);
 3923            self.unfollow_in_pane(&pane, window, cx);
 3924            self.last_leaders_by_pane.remove(&pane.downgrade());
 3925            for removed_item in pane.read(cx).items() {
 3926                self.panes_by_item.remove(&removed_item.item_id());
 3927            }
 3928
 3929            cx.notify();
 3930        } else {
 3931            self.active_item_path_changed(window, cx);
 3932        }
 3933        cx.emit(Event::PaneRemoved);
 3934    }
 3935
 3936    pub fn panes(&self) -> &[Entity<Pane>] {
 3937        &self.panes
 3938    }
 3939
 3940    pub fn active_pane(&self) -> &Entity<Pane> {
 3941        &self.active_pane
 3942    }
 3943
 3944    pub fn focused_pane(&self, window: &Window, cx: &App) -> Entity<Pane> {
 3945        for dock in self.all_docks() {
 3946            if dock.focus_handle(cx).contains_focused(window, cx) {
 3947                if let Some(pane) = dock
 3948                    .read(cx)
 3949                    .active_panel()
 3950                    .and_then(|panel| panel.pane(cx))
 3951                {
 3952                    return pane;
 3953                }
 3954            }
 3955        }
 3956        self.active_pane().clone()
 3957    }
 3958
 3959    pub fn adjacent_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<Pane> {
 3960        self.find_pane_in_direction(SplitDirection::Right, cx)
 3961            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
 3962            .unwrap_or_else(|| {
 3963                self.split_pane(self.active_pane.clone(), SplitDirection::Right, window, cx)
 3964            })
 3965            .clone()
 3966    }
 3967
 3968    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<Entity<Pane>> {
 3969        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
 3970        weak_pane.upgrade()
 3971    }
 3972
 3973    fn collaborator_left(&mut self, peer_id: PeerId, window: &mut Window, cx: &mut Context<Self>) {
 3974        self.follower_states.retain(|leader_id, state| {
 3975            if *leader_id == CollaboratorId::PeerId(peer_id) {
 3976                for item in state.items_by_leader_view_id.values() {
 3977                    item.view.set_leader_id(None, window, cx);
 3978                }
 3979                false
 3980            } else {
 3981                true
 3982            }
 3983        });
 3984        cx.notify();
 3985    }
 3986
 3987    pub fn start_following(
 3988        &mut self,
 3989        leader_id: impl Into<CollaboratorId>,
 3990        window: &mut Window,
 3991        cx: &mut Context<Self>,
 3992    ) -> Option<Task<Result<()>>> {
 3993        let leader_id = leader_id.into();
 3994        let pane = self.active_pane().clone();
 3995
 3996        self.last_leaders_by_pane
 3997            .insert(pane.downgrade(), leader_id);
 3998        self.unfollow(leader_id, window, cx);
 3999        self.unfollow_in_pane(&pane, window, cx);
 4000        self.follower_states.insert(
 4001            leader_id,
 4002            FollowerState {
 4003                center_pane: pane.clone(),
 4004                dock_pane: None,
 4005                active_view_id: None,
 4006                items_by_leader_view_id: Default::default(),
 4007            },
 4008        );
 4009        cx.notify();
 4010
 4011        match leader_id {
 4012            CollaboratorId::PeerId(leader_peer_id) => {
 4013                let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
 4014                let project_id = self.project.read(cx).remote_id();
 4015                let request = self.app_state.client.request(proto::Follow {
 4016                    room_id,
 4017                    project_id,
 4018                    leader_id: Some(leader_peer_id),
 4019                });
 4020
 4021                Some(cx.spawn_in(window, async move |this, cx| {
 4022                    let response = request.await?;
 4023                    this.update(cx, |this, _| {
 4024                        let state = this
 4025                            .follower_states
 4026                            .get_mut(&leader_id)
 4027                            .context("following interrupted")?;
 4028                        state.active_view_id = response
 4029                            .active_view
 4030                            .as_ref()
 4031                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
 4032                        anyhow::Ok(())
 4033                    })??;
 4034                    if let Some(view) = response.active_view {
 4035                        Self::add_view_from_leader(this.clone(), leader_peer_id, &view, cx).await?;
 4036                    }
 4037                    this.update_in(cx, |this, window, cx| {
 4038                        this.leader_updated(leader_id, window, cx)
 4039                    })?;
 4040                    Ok(())
 4041                }))
 4042            }
 4043            CollaboratorId::Agent => {
 4044                self.leader_updated(leader_id, window, cx)?;
 4045                Some(Task::ready(Ok(())))
 4046            }
 4047        }
 4048    }
 4049
 4050    pub fn follow_next_collaborator(
 4051        &mut self,
 4052        _: &FollowNextCollaborator,
 4053        window: &mut Window,
 4054        cx: &mut Context<Self>,
 4055    ) {
 4056        let collaborators = self.project.read(cx).collaborators();
 4057        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
 4058            let mut collaborators = collaborators.keys().copied();
 4059            for peer_id in collaborators.by_ref() {
 4060                if CollaboratorId::PeerId(peer_id) == leader_id {
 4061                    break;
 4062                }
 4063            }
 4064            collaborators.next().map(CollaboratorId::PeerId)
 4065        } else if let Some(last_leader_id) =
 4066            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
 4067        {
 4068            match last_leader_id {
 4069                CollaboratorId::PeerId(peer_id) => {
 4070                    if collaborators.contains_key(peer_id) {
 4071                        Some(*last_leader_id)
 4072                    } else {
 4073                        None
 4074                    }
 4075                }
 4076                CollaboratorId::Agent => Some(CollaboratorId::Agent),
 4077            }
 4078        } else {
 4079            None
 4080        };
 4081
 4082        let pane = self.active_pane.clone();
 4083        let Some(leader_id) = next_leader_id.or_else(|| {
 4084            Some(CollaboratorId::PeerId(
 4085                collaborators.keys().copied().next()?,
 4086            ))
 4087        }) else {
 4088            return;
 4089        };
 4090        if self.unfollow_in_pane(&pane, window, cx) == Some(leader_id) {
 4091            return;
 4092        }
 4093        if let Some(task) = self.start_following(leader_id, window, cx) {
 4094            task.detach_and_log_err(cx)
 4095        }
 4096    }
 4097
 4098    pub fn follow(
 4099        &mut self,
 4100        leader_id: impl Into<CollaboratorId>,
 4101        window: &mut Window,
 4102        cx: &mut Context<Self>,
 4103    ) {
 4104        let leader_id = leader_id.into();
 4105
 4106        if let CollaboratorId::PeerId(peer_id) = leader_id {
 4107            let Some(room) = ActiveCall::global(cx).read(cx).room() else {
 4108                return;
 4109            };
 4110            let room = room.read(cx);
 4111            let Some(remote_participant) = room.remote_participant_for_peer_id(peer_id) else {
 4112                return;
 4113            };
 4114
 4115            let project = self.project.read(cx);
 4116
 4117            let other_project_id = match remote_participant.location {
 4118                call::ParticipantLocation::External => None,
 4119                call::ParticipantLocation::UnsharedProject => None,
 4120                call::ParticipantLocation::SharedProject { project_id } => {
 4121                    if Some(project_id) == project.remote_id() {
 4122                        None
 4123                    } else {
 4124                        Some(project_id)
 4125                    }
 4126                }
 4127            };
 4128
 4129            // if they are active in another project, follow there.
 4130            if let Some(project_id) = other_project_id {
 4131                let app_state = self.app_state.clone();
 4132                crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
 4133                    .detach_and_log_err(cx);
 4134            }
 4135        }
 4136
 4137        // if you're already following, find the right pane and focus it.
 4138        if let Some(follower_state) = self.follower_states.get(&leader_id) {
 4139            window.focus(&follower_state.pane().focus_handle(cx));
 4140
 4141            return;
 4142        }
 4143
 4144        // Otherwise, follow.
 4145        if let Some(task) = self.start_following(leader_id, window, cx) {
 4146            task.detach_and_log_err(cx)
 4147        }
 4148    }
 4149
 4150    pub fn unfollow(
 4151        &mut self,
 4152        leader_id: impl Into<CollaboratorId>,
 4153        window: &mut Window,
 4154        cx: &mut Context<Self>,
 4155    ) -> Option<()> {
 4156        cx.notify();
 4157
 4158        let leader_id = leader_id.into();
 4159        let state = self.follower_states.remove(&leader_id)?;
 4160        for (_, item) in state.items_by_leader_view_id {
 4161            item.view.set_leader_id(None, window, cx);
 4162        }
 4163
 4164        if let CollaboratorId::PeerId(leader_peer_id) = leader_id {
 4165            let project_id = self.project.read(cx).remote_id();
 4166            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
 4167            self.app_state
 4168                .client
 4169                .send(proto::Unfollow {
 4170                    room_id,
 4171                    project_id,
 4172                    leader_id: Some(leader_peer_id),
 4173                })
 4174                .log_err();
 4175        }
 4176
 4177        Some(())
 4178    }
 4179
 4180    pub fn is_being_followed(&self, id: impl Into<CollaboratorId>) -> bool {
 4181        self.follower_states.contains_key(&id.into())
 4182    }
 4183
 4184    fn active_item_path_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 4185        cx.emit(Event::ActiveItemChanged);
 4186        let active_entry = self.active_project_path(cx);
 4187        self.project
 4188            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
 4189
 4190        self.update_window_title(window, cx);
 4191    }
 4192
 4193    fn update_window_title(&mut self, window: &mut Window, cx: &mut App) {
 4194        let project = self.project().read(cx);
 4195        let mut title = String::new();
 4196
 4197        for (i, name) in project.worktree_root_names(cx).enumerate() {
 4198            if i > 0 {
 4199                title.push_str(", ");
 4200            }
 4201            title.push_str(name);
 4202        }
 4203
 4204        if title.is_empty() {
 4205            title = "empty project".to_string();
 4206        }
 4207
 4208        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
 4209            let filename = path
 4210                .path
 4211                .file_name()
 4212                .map(|s| s.to_string_lossy())
 4213                .or_else(|| {
 4214                    Some(Cow::Borrowed(
 4215                        project
 4216                            .worktree_for_id(path.worktree_id, cx)?
 4217                            .read(cx)
 4218                            .root_name(),
 4219                    ))
 4220                });
 4221
 4222            if let Some(filename) = filename {
 4223                title.push_str("");
 4224                title.push_str(filename.as_ref());
 4225            }
 4226        }
 4227
 4228        if project.is_via_collab() {
 4229            title.push_str("");
 4230        } else if project.is_shared() {
 4231            title.push_str("");
 4232        }
 4233
 4234        window.set_window_title(&title);
 4235    }
 4236
 4237    fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
 4238        let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
 4239        if is_edited != self.window_edited {
 4240            self.window_edited = is_edited;
 4241            window.set_window_edited(self.window_edited)
 4242        }
 4243    }
 4244
 4245    fn update_item_dirty_state(
 4246        &mut self,
 4247        item: &dyn ItemHandle,
 4248        window: &mut Window,
 4249        cx: &mut App,
 4250    ) {
 4251        let is_dirty = item.is_dirty(cx);
 4252        let item_id = item.item_id();
 4253        let was_dirty = self.dirty_items.contains_key(&item_id);
 4254        if is_dirty == was_dirty {
 4255            return;
 4256        }
 4257        if was_dirty {
 4258            self.dirty_items.remove(&item_id);
 4259            self.update_window_edited(window, cx);
 4260            return;
 4261        }
 4262        if let Some(window_handle) = window.window_handle().downcast::<Self>() {
 4263            let s = item.on_release(
 4264                cx,
 4265                Box::new(move |cx| {
 4266                    window_handle
 4267                        .update(cx, |this, window, cx| {
 4268                            this.dirty_items.remove(&item_id);
 4269                            this.update_window_edited(window, cx)
 4270                        })
 4271                        .ok();
 4272                }),
 4273            );
 4274            self.dirty_items.insert(item_id, s);
 4275            self.update_window_edited(window, cx);
 4276        }
 4277    }
 4278
 4279    fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
 4280        if self.notifications.is_empty() {
 4281            None
 4282        } else {
 4283            Some(
 4284                div()
 4285                    .absolute()
 4286                    .right_3()
 4287                    .bottom_3()
 4288                    .w_112()
 4289                    .h_full()
 4290                    .flex()
 4291                    .flex_col()
 4292                    .justify_end()
 4293                    .gap_2()
 4294                    .children(
 4295                        self.notifications
 4296                            .iter()
 4297                            .map(|(_, notification)| notification.clone().into_any()),
 4298                    ),
 4299            )
 4300        }
 4301    }
 4302
 4303    // RPC handlers
 4304
 4305    fn active_view_for_follower(
 4306        &self,
 4307        follower_project_id: Option<u64>,
 4308        window: &mut Window,
 4309        cx: &mut Context<Self>,
 4310    ) -> Option<proto::View> {
 4311        let (item, panel_id) = self.active_item_for_followers(window, cx);
 4312        let item = item?;
 4313        let leader_id = self
 4314            .pane_for(&*item)
 4315            .and_then(|pane| self.leader_for_pane(&pane));
 4316        let leader_peer_id = match leader_id {
 4317            Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
 4318            Some(CollaboratorId::Agent) | None => None,
 4319        };
 4320
 4321        let item_handle = item.to_followable_item_handle(cx)?;
 4322        let id = item_handle.remote_id(&self.app_state.client, window, cx)?;
 4323        let variant = item_handle.to_state_proto(window, cx)?;
 4324
 4325        if item_handle.is_project_item(window, cx)
 4326            && (follower_project_id.is_none()
 4327                || follower_project_id != self.project.read(cx).remote_id())
 4328        {
 4329            return None;
 4330        }
 4331
 4332        Some(proto::View {
 4333            id: id.to_proto(),
 4334            leader_id: leader_peer_id,
 4335            variant: Some(variant),
 4336            panel_id: panel_id.map(|id| id as i32),
 4337        })
 4338    }
 4339
 4340    fn handle_follow(
 4341        &mut self,
 4342        follower_project_id: Option<u64>,
 4343        window: &mut Window,
 4344        cx: &mut Context<Self>,
 4345    ) -> proto::FollowResponse {
 4346        let active_view = self.active_view_for_follower(follower_project_id, window, cx);
 4347
 4348        cx.notify();
 4349        proto::FollowResponse {
 4350            // TODO: Remove after version 0.145.x stabilizes.
 4351            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
 4352            views: active_view.iter().cloned().collect(),
 4353            active_view,
 4354        }
 4355    }
 4356
 4357    fn handle_update_followers(
 4358        &mut self,
 4359        leader_id: PeerId,
 4360        message: proto::UpdateFollowers,
 4361        _window: &mut Window,
 4362        _cx: &mut Context<Self>,
 4363    ) {
 4364        self.leader_updates_tx
 4365            .unbounded_send((leader_id, message))
 4366            .ok();
 4367    }
 4368
 4369    async fn process_leader_update(
 4370        this: &WeakEntity<Self>,
 4371        leader_id: PeerId,
 4372        update: proto::UpdateFollowers,
 4373        cx: &mut AsyncWindowContext,
 4374    ) -> Result<()> {
 4375        match update.variant.context("invalid update")? {
 4376            proto::update_followers::Variant::CreateView(view) => {
 4377                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
 4378                let should_add_view = this.update(cx, |this, _| {
 4379                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
 4380                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
 4381                    } else {
 4382                        anyhow::Ok(false)
 4383                    }
 4384                })??;
 4385
 4386                if should_add_view {
 4387                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
 4388                }
 4389            }
 4390            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
 4391                let should_add_view = this.update(cx, |this, _| {
 4392                    if let Some(state) = this.follower_states.get_mut(&leader_id.into()) {
 4393                        state.active_view_id = update_active_view
 4394                            .view
 4395                            .as_ref()
 4396                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
 4397
 4398                        if state.active_view_id.is_some_and(|view_id| {
 4399                            !state.items_by_leader_view_id.contains_key(&view_id)
 4400                        }) {
 4401                            anyhow::Ok(true)
 4402                        } else {
 4403                            anyhow::Ok(false)
 4404                        }
 4405                    } else {
 4406                        anyhow::Ok(false)
 4407                    }
 4408                })??;
 4409
 4410                if should_add_view {
 4411                    if let Some(view) = update_active_view.view {
 4412                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
 4413                    }
 4414                }
 4415            }
 4416            proto::update_followers::Variant::UpdateView(update_view) => {
 4417                let variant = update_view.variant.context("missing update view variant")?;
 4418                let id = update_view.id.context("missing update view id")?;
 4419                let mut tasks = Vec::new();
 4420                this.update_in(cx, |this, window, cx| {
 4421                    let project = this.project.clone();
 4422                    if let Some(state) = this.follower_states.get(&leader_id.into()) {
 4423                        let view_id = ViewId::from_proto(id.clone())?;
 4424                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
 4425                            tasks.push(item.view.apply_update_proto(
 4426                                &project,
 4427                                variant.clone(),
 4428                                window,
 4429                                cx,
 4430                            ));
 4431                        }
 4432                    }
 4433                    anyhow::Ok(())
 4434                })??;
 4435                try_join_all(tasks).await.log_err();
 4436            }
 4437        }
 4438        this.update_in(cx, |this, window, cx| {
 4439            this.leader_updated(leader_id, window, cx)
 4440        })?;
 4441        Ok(())
 4442    }
 4443
 4444    async fn add_view_from_leader(
 4445        this: WeakEntity<Self>,
 4446        leader_id: PeerId,
 4447        view: &proto::View,
 4448        cx: &mut AsyncWindowContext,
 4449    ) -> Result<()> {
 4450        let this = this.upgrade().context("workspace dropped")?;
 4451
 4452        let Some(id) = view.id.clone() else {
 4453            anyhow::bail!("no id for view");
 4454        };
 4455        let id = ViewId::from_proto(id)?;
 4456        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
 4457
 4458        let pane = this.update(cx, |this, _cx| {
 4459            let state = this
 4460                .follower_states
 4461                .get(&leader_id.into())
 4462                .context("stopped following")?;
 4463            anyhow::Ok(state.pane().clone())
 4464        })??;
 4465        let existing_item = pane.update_in(cx, |pane, window, cx| {
 4466            let client = this.read(cx).client().clone();
 4467            pane.items().find_map(|item| {
 4468                let item = item.to_followable_item_handle(cx)?;
 4469                if item.remote_id(&client, window, cx) == Some(id) {
 4470                    Some(item)
 4471                } else {
 4472                    None
 4473                }
 4474            })
 4475        })?;
 4476        let item = if let Some(existing_item) = existing_item {
 4477            existing_item
 4478        } else {
 4479            let variant = view.variant.clone();
 4480            anyhow::ensure!(variant.is_some(), "missing view variant");
 4481
 4482            let task = cx.update(|window, cx| {
 4483                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, window, cx)
 4484            })?;
 4485
 4486            let Some(task) = task else {
 4487                anyhow::bail!(
 4488                    "failed to construct view from leader (maybe from a different version of zed?)"
 4489                );
 4490            };
 4491
 4492            let mut new_item = task.await?;
 4493            pane.update_in(cx, |pane, window, cx| {
 4494                let mut item_to_remove = None;
 4495                for (ix, item) in pane.items().enumerate() {
 4496                    if let Some(item) = item.to_followable_item_handle(cx) {
 4497                        match new_item.dedup(item.as_ref(), window, cx) {
 4498                            Some(item::Dedup::KeepExisting) => {
 4499                                new_item =
 4500                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
 4501                                break;
 4502                            }
 4503                            Some(item::Dedup::ReplaceExisting) => {
 4504                                item_to_remove = Some((ix, item.item_id()));
 4505                                break;
 4506                            }
 4507                            None => {}
 4508                        }
 4509                    }
 4510                }
 4511
 4512                if let Some((ix, id)) = item_to_remove {
 4513                    pane.remove_item(id, false, false, window, cx);
 4514                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), window, cx);
 4515                }
 4516            })?;
 4517
 4518            new_item
 4519        };
 4520
 4521        this.update_in(cx, |this, window, cx| {
 4522            let state = this.follower_states.get_mut(&leader_id.into())?;
 4523            item.set_leader_id(Some(leader_id.into()), window, cx);
 4524            state.items_by_leader_view_id.insert(
 4525                id,
 4526                FollowerView {
 4527                    view: item,
 4528                    location: panel_id,
 4529                },
 4530            );
 4531
 4532            Some(())
 4533        })?;
 4534
 4535        Ok(())
 4536    }
 4537
 4538    fn handle_agent_location_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 4539        let Some(follower_state) = self.follower_states.get_mut(&CollaboratorId::Agent) else {
 4540            return;
 4541        };
 4542
 4543        if let Some(agent_location) = self.project.read(cx).agent_location() {
 4544            let buffer_entity_id = agent_location.buffer.entity_id();
 4545            let view_id = ViewId {
 4546                creator: CollaboratorId::Agent,
 4547                id: buffer_entity_id.as_u64(),
 4548            };
 4549            follower_state.active_view_id = Some(view_id);
 4550
 4551            let item = match follower_state.items_by_leader_view_id.entry(view_id) {
 4552                hash_map::Entry::Occupied(entry) => Some(entry.into_mut()),
 4553                hash_map::Entry::Vacant(entry) => {
 4554                    let existing_view =
 4555                        follower_state
 4556                            .center_pane
 4557                            .read(cx)
 4558                            .items()
 4559                            .find_map(|item| {
 4560                                let item = item.to_followable_item_handle(cx)?;
 4561                                if item.is_singleton(cx)
 4562                                    && item.project_item_model_ids(cx).as_slice()
 4563                                        == [buffer_entity_id]
 4564                                {
 4565                                    Some(item)
 4566                                } else {
 4567                                    None
 4568                                }
 4569                            });
 4570                    let view = existing_view.or_else(|| {
 4571                        agent_location.buffer.upgrade().and_then(|buffer| {
 4572                            cx.update_default_global(|registry: &mut ProjectItemRegistry, cx| {
 4573                                registry.build_item(buffer, self.project.clone(), None, window, cx)
 4574                            })?
 4575                            .to_followable_item_handle(cx)
 4576                        })
 4577                    });
 4578
 4579                    if let Some(view) = view {
 4580                        Some(entry.insert(FollowerView {
 4581                            view,
 4582                            location: None,
 4583                        }))
 4584                    } else {
 4585                        None
 4586                    }
 4587                }
 4588            };
 4589
 4590            if let Some(item) = item {
 4591                item.view
 4592                    .set_leader_id(Some(CollaboratorId::Agent), window, cx);
 4593                item.view
 4594                    .update_agent_location(agent_location.position, window, cx);
 4595            }
 4596        } else {
 4597            follower_state.active_view_id = None;
 4598        }
 4599
 4600        self.leader_updated(CollaboratorId::Agent, window, cx);
 4601    }
 4602
 4603    pub fn update_active_view_for_followers(&mut self, window: &mut Window, cx: &mut App) {
 4604        let mut is_project_item = true;
 4605        let mut update = proto::UpdateActiveView::default();
 4606        if window.is_window_active() {
 4607            let (active_item, panel_id) = self.active_item_for_followers(window, cx);
 4608
 4609            if let Some(item) = active_item {
 4610                if item.item_focus_handle(cx).contains_focused(window, cx) {
 4611                    let leader_id = self
 4612                        .pane_for(&*item)
 4613                        .and_then(|pane| self.leader_for_pane(&pane));
 4614                    let leader_peer_id = match leader_id {
 4615                        Some(CollaboratorId::PeerId(peer_id)) => Some(peer_id),
 4616                        Some(CollaboratorId::Agent) | None => None,
 4617                    };
 4618
 4619                    if let Some(item) = item.to_followable_item_handle(cx) {
 4620                        let id = item
 4621                            .remote_id(&self.app_state.client, window, cx)
 4622                            .map(|id| id.to_proto());
 4623
 4624                        if let Some(id) = id.clone() {
 4625                            if let Some(variant) = item.to_state_proto(window, cx) {
 4626                                let view = Some(proto::View {
 4627                                    id: id.clone(),
 4628                                    leader_id: leader_peer_id,
 4629                                    variant: Some(variant),
 4630                                    panel_id: panel_id.map(|id| id as i32),
 4631                                });
 4632
 4633                                is_project_item = item.is_project_item(window, cx);
 4634                                update = proto::UpdateActiveView {
 4635                                    view,
 4636                                    // TODO: Remove after version 0.145.x stabilizes.
 4637                                    id: id.clone(),
 4638                                    leader_id: leader_peer_id,
 4639                                };
 4640                            }
 4641                        };
 4642                    }
 4643                }
 4644            }
 4645        }
 4646
 4647        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
 4648        if active_view_id != self.last_active_view_id.as_ref() {
 4649            self.last_active_view_id = active_view_id.cloned();
 4650            self.update_followers(
 4651                is_project_item,
 4652                proto::update_followers::Variant::UpdateActiveView(update),
 4653                window,
 4654                cx,
 4655            );
 4656        }
 4657    }
 4658
 4659    fn active_item_for_followers(
 4660        &self,
 4661        window: &mut Window,
 4662        cx: &mut App,
 4663    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
 4664        let mut active_item = None;
 4665        let mut panel_id = None;
 4666        for dock in self.all_docks() {
 4667            if dock.focus_handle(cx).contains_focused(window, cx) {
 4668                if let Some(panel) = dock.read(cx).active_panel() {
 4669                    if let Some(pane) = panel.pane(cx) {
 4670                        if let Some(item) = pane.read(cx).active_item() {
 4671                            active_item = Some(item);
 4672                            panel_id = panel.remote_id();
 4673                            break;
 4674                        }
 4675                    }
 4676                }
 4677            }
 4678        }
 4679
 4680        if active_item.is_none() {
 4681            active_item = self.active_pane().read(cx).active_item();
 4682        }
 4683        (active_item, panel_id)
 4684    }
 4685
 4686    fn update_followers(
 4687        &self,
 4688        project_only: bool,
 4689        update: proto::update_followers::Variant,
 4690        _: &mut Window,
 4691        cx: &mut App,
 4692    ) -> Option<()> {
 4693        // If this update only applies to for followers in the current project,
 4694        // then skip it unless this project is shared. If it applies to all
 4695        // followers, regardless of project, then set `project_id` to none,
 4696        // indicating that it goes to all followers.
 4697        let project_id = if project_only {
 4698            Some(self.project.read(cx).remote_id()?)
 4699        } else {
 4700            None
 4701        };
 4702        self.app_state().workspace_store.update(cx, |store, cx| {
 4703            store.update_followers(project_id, update, cx)
 4704        })
 4705    }
 4706
 4707    pub fn leader_for_pane(&self, pane: &Entity<Pane>) -> Option<CollaboratorId> {
 4708        self.follower_states.iter().find_map(|(leader_id, state)| {
 4709            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
 4710                Some(*leader_id)
 4711            } else {
 4712                None
 4713            }
 4714        })
 4715    }
 4716
 4717    fn leader_updated(
 4718        &mut self,
 4719        leader_id: impl Into<CollaboratorId>,
 4720        window: &mut Window,
 4721        cx: &mut Context<Self>,
 4722    ) -> Option<Box<dyn ItemHandle>> {
 4723        cx.notify();
 4724
 4725        let leader_id = leader_id.into();
 4726        let (panel_id, item) = match leader_id {
 4727            CollaboratorId::PeerId(peer_id) => self.active_item_for_peer(peer_id, window, cx)?,
 4728            CollaboratorId::Agent => (None, self.active_item_for_agent()?),
 4729        };
 4730
 4731        let state = self.follower_states.get(&leader_id)?;
 4732        let mut transfer_focus = state.center_pane.read(cx).has_focus(window, cx);
 4733        let pane;
 4734        if let Some(panel_id) = panel_id {
 4735            pane = self
 4736                .activate_panel_for_proto_id(panel_id, window, cx)?
 4737                .pane(cx)?;
 4738            let state = self.follower_states.get_mut(&leader_id)?;
 4739            state.dock_pane = Some(pane.clone());
 4740        } else {
 4741            pane = state.center_pane.clone();
 4742            let state = self.follower_states.get_mut(&leader_id)?;
 4743            if let Some(dock_pane) = state.dock_pane.take() {
 4744                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(window, cx);
 4745            }
 4746        }
 4747
 4748        pane.update(cx, |pane, cx| {
 4749            let focus_active_item = pane.has_focus(window, cx) || transfer_focus;
 4750            if let Some(index) = pane.index_for_item(item.as_ref()) {
 4751                pane.activate_item(index, false, false, window, cx);
 4752            } else {
 4753                pane.add_item(item.boxed_clone(), false, false, None, window, cx)
 4754            }
 4755
 4756            if focus_active_item {
 4757                pane.focus_active_item(window, cx)
 4758            }
 4759        });
 4760
 4761        Some(item)
 4762    }
 4763
 4764    fn active_item_for_agent(&self) -> Option<Box<dyn ItemHandle>> {
 4765        let state = self.follower_states.get(&CollaboratorId::Agent)?;
 4766        let active_view_id = state.active_view_id?;
 4767        Some(
 4768            state
 4769                .items_by_leader_view_id
 4770                .get(&active_view_id)?
 4771                .view
 4772                .boxed_clone(),
 4773        )
 4774    }
 4775
 4776    fn active_item_for_peer(
 4777        &self,
 4778        peer_id: PeerId,
 4779        window: &mut Window,
 4780        cx: &mut Context<Self>,
 4781    ) -> Option<(Option<PanelId>, Box<dyn ItemHandle>)> {
 4782        let call = self.active_call()?;
 4783        let room = call.read(cx).room()?.read(cx);
 4784        let participant = room.remote_participant_for_peer_id(peer_id)?;
 4785        let leader_in_this_app;
 4786        let leader_in_this_project;
 4787        match participant.location {
 4788            call::ParticipantLocation::SharedProject { project_id } => {
 4789                leader_in_this_app = true;
 4790                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
 4791            }
 4792            call::ParticipantLocation::UnsharedProject => {
 4793                leader_in_this_app = true;
 4794                leader_in_this_project = false;
 4795            }
 4796            call::ParticipantLocation::External => {
 4797                leader_in_this_app = false;
 4798                leader_in_this_project = false;
 4799            }
 4800        };
 4801        let state = self.follower_states.get(&peer_id.into())?;
 4802        let mut item_to_activate = None;
 4803        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
 4804            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
 4805                if leader_in_this_project || !item.view.is_project_item(window, cx) {
 4806                    item_to_activate = Some((item.location, item.view.boxed_clone()));
 4807                }
 4808            }
 4809        } else if let Some(shared_screen) =
 4810            self.shared_screen_for_peer(peer_id, &state.center_pane, window, cx)
 4811        {
 4812            item_to_activate = Some((None, Box::new(shared_screen)));
 4813        }
 4814        item_to_activate
 4815    }
 4816
 4817    fn shared_screen_for_peer(
 4818        &self,
 4819        peer_id: PeerId,
 4820        pane: &Entity<Pane>,
 4821        window: &mut Window,
 4822        cx: &mut App,
 4823    ) -> Option<Entity<SharedScreen>> {
 4824        let call = self.active_call()?;
 4825        let room = call.read(cx).room()?.clone();
 4826        let participant = room.read(cx).remote_participant_for_peer_id(peer_id)?;
 4827        let track = participant.video_tracks.values().next()?.clone();
 4828        let user = participant.user.clone();
 4829
 4830        for item in pane.read(cx).items_of_type::<SharedScreen>() {
 4831            if item.read(cx).peer_id == peer_id {
 4832                return Some(item);
 4833            }
 4834        }
 4835
 4836        Some(cx.new(|cx| SharedScreen::new(track, peer_id, user.clone(), room.clone(), window, cx)))
 4837    }
 4838
 4839    pub fn on_window_activation_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 4840        if window.is_window_active() {
 4841            self.update_active_view_for_followers(window, cx);
 4842
 4843            if let Some(database_id) = self.database_id {
 4844                cx.background_spawn(persistence::DB.update_timestamp(database_id))
 4845                    .detach();
 4846            }
 4847        } else {
 4848            for pane in &self.panes {
 4849                pane.update(cx, |pane, cx| {
 4850                    if let Some(item) = pane.active_item() {
 4851                        item.workspace_deactivated(window, cx);
 4852                    }
 4853                    for item in pane.items() {
 4854                        if matches!(
 4855                            item.workspace_settings(cx).autosave,
 4856                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
 4857                        ) {
 4858                            Pane::autosave_item(item.as_ref(), self.project.clone(), window, cx)
 4859                                .detach_and_log_err(cx);
 4860                        }
 4861                    }
 4862                });
 4863            }
 4864        }
 4865    }
 4866
 4867    pub fn active_call(&self) -> Option<&Entity<ActiveCall>> {
 4868        self.active_call.as_ref().map(|(call, _)| call)
 4869    }
 4870
 4871    fn on_active_call_event(
 4872        &mut self,
 4873        _: &Entity<ActiveCall>,
 4874        event: &call::room::Event,
 4875        window: &mut Window,
 4876        cx: &mut Context<Self>,
 4877    ) {
 4878        match event {
 4879            call::room::Event::ParticipantLocationChanged { participant_id }
 4880            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
 4881                self.leader_updated(participant_id, window, cx);
 4882            }
 4883            _ => {}
 4884        }
 4885    }
 4886
 4887    pub fn database_id(&self) -> Option<WorkspaceId> {
 4888        self.database_id
 4889    }
 4890
 4891    pub fn session_id(&self) -> Option<String> {
 4892        self.session_id.clone()
 4893    }
 4894
 4895    fn local_paths(&self, cx: &App) -> Option<Vec<Arc<Path>>> {
 4896        let project = self.project().read(cx);
 4897
 4898        if project.is_local() {
 4899            Some(
 4900                project
 4901                    .visible_worktrees(cx)
 4902                    .map(|worktree| worktree.read(cx).abs_path())
 4903                    .collect::<Vec<_>>(),
 4904            )
 4905        } else {
 4906            None
 4907        }
 4908    }
 4909
 4910    fn remove_panes(&mut self, member: Member, window: &mut Window, cx: &mut Context<Workspace>) {
 4911        match member {
 4912            Member::Axis(PaneAxis { members, .. }) => {
 4913                for child in members.iter() {
 4914                    self.remove_panes(child.clone(), window, cx)
 4915                }
 4916            }
 4917            Member::Pane(pane) => {
 4918                self.force_remove_pane(&pane, &None, window, cx);
 4919            }
 4920        }
 4921    }
 4922
 4923    fn remove_from_session(&mut self, window: &mut Window, cx: &mut App) -> Task<()> {
 4924        self.session_id.take();
 4925        self.serialize_workspace_internal(window, cx)
 4926    }
 4927
 4928    fn force_remove_pane(
 4929        &mut self,
 4930        pane: &Entity<Pane>,
 4931        focus_on: &Option<Entity<Pane>>,
 4932        window: &mut Window,
 4933        cx: &mut Context<Workspace>,
 4934    ) {
 4935        self.panes.retain(|p| p != pane);
 4936        if let Some(focus_on) = focus_on {
 4937            focus_on.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
 4938        } else {
 4939            if self.active_pane() == pane {
 4940                self.panes
 4941                    .last()
 4942                    .unwrap()
 4943                    .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
 4944            }
 4945        }
 4946        if self.last_active_center_pane == Some(pane.downgrade()) {
 4947            self.last_active_center_pane = None;
 4948        }
 4949        cx.notify();
 4950    }
 4951
 4952    fn serialize_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 4953        if self._schedule_serialize.is_none() {
 4954            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
 4955                cx.background_executor()
 4956                    .timer(Duration::from_millis(100))
 4957                    .await;
 4958                this.update_in(cx, |this, window, cx| {
 4959                    this.serialize_workspace_internal(window, cx).detach();
 4960                    this._schedule_serialize.take();
 4961                })
 4962                .log_err();
 4963            }));
 4964        }
 4965    }
 4966
 4967    fn serialize_workspace_internal(&self, window: &mut Window, cx: &mut App) -> Task<()> {
 4968        let Some(database_id) = self.database_id() else {
 4969            return Task::ready(());
 4970        };
 4971
 4972        fn serialize_pane_handle(
 4973            pane_handle: &Entity<Pane>,
 4974            window: &mut Window,
 4975            cx: &mut App,
 4976        ) -> SerializedPane {
 4977            let (items, active, pinned_count) = {
 4978                let pane = pane_handle.read(cx);
 4979                let active_item_id = pane.active_item().map(|item| item.item_id());
 4980                (
 4981                    pane.items()
 4982                        .filter_map(|handle| {
 4983                            let handle = handle.to_serializable_item_handle(cx)?;
 4984
 4985                            Some(SerializedItem {
 4986                                kind: Arc::from(handle.serialized_item_kind()),
 4987                                item_id: handle.item_id().as_u64(),
 4988                                active: Some(handle.item_id()) == active_item_id,
 4989                                preview: pane.is_active_preview_item(handle.item_id()),
 4990                            })
 4991                        })
 4992                        .collect::<Vec<_>>(),
 4993                    pane.has_focus(window, cx),
 4994                    pane.pinned_count(),
 4995                )
 4996            };
 4997
 4998            SerializedPane::new(items, active, pinned_count)
 4999        }
 5000
 5001        fn build_serialized_pane_group(
 5002            pane_group: &Member,
 5003            window: &mut Window,
 5004            cx: &mut App,
 5005        ) -> SerializedPaneGroup {
 5006            match pane_group {
 5007                Member::Axis(PaneAxis {
 5008                    axis,
 5009                    members,
 5010                    flexes,
 5011                    bounding_boxes: _,
 5012                }) => SerializedPaneGroup::Group {
 5013                    axis: SerializedAxis(*axis),
 5014                    children: members
 5015                        .iter()
 5016                        .map(|member| build_serialized_pane_group(member, window, cx))
 5017                        .collect::<Vec<_>>(),
 5018                    flexes: Some(flexes.lock().clone()),
 5019                },
 5020                Member::Pane(pane_handle) => {
 5021                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, window, cx))
 5022                }
 5023            }
 5024        }
 5025
 5026        fn build_serialized_docks(
 5027            this: &Workspace,
 5028            window: &mut Window,
 5029            cx: &mut App,
 5030        ) -> DockStructure {
 5031            let left_dock = this.left_dock.read(cx);
 5032            let left_visible = left_dock.is_open();
 5033            let left_active_panel = left_dock
 5034                .active_panel()
 5035                .map(|panel| panel.persistent_name().to_string());
 5036            let left_dock_zoom = left_dock
 5037                .active_panel()
 5038                .map(|panel| panel.is_zoomed(window, cx))
 5039                .unwrap_or(false);
 5040
 5041            let right_dock = this.right_dock.read(cx);
 5042            let right_visible = right_dock.is_open();
 5043            let right_active_panel = right_dock
 5044                .active_panel()
 5045                .map(|panel| panel.persistent_name().to_string());
 5046            let right_dock_zoom = right_dock
 5047                .active_panel()
 5048                .map(|panel| panel.is_zoomed(window, cx))
 5049                .unwrap_or(false);
 5050
 5051            let bottom_dock = this.bottom_dock.read(cx);
 5052            let bottom_visible = bottom_dock.is_open();
 5053            let bottom_active_panel = bottom_dock
 5054                .active_panel()
 5055                .map(|panel| panel.persistent_name().to_string());
 5056            let bottom_dock_zoom = bottom_dock
 5057                .active_panel()
 5058                .map(|panel| panel.is_zoomed(window, cx))
 5059                .unwrap_or(false);
 5060
 5061            DockStructure {
 5062                left: DockData {
 5063                    visible: left_visible,
 5064                    active_panel: left_active_panel,
 5065                    zoom: left_dock_zoom,
 5066                },
 5067                right: DockData {
 5068                    visible: right_visible,
 5069                    active_panel: right_active_panel,
 5070                    zoom: right_dock_zoom,
 5071                },
 5072                bottom: DockData {
 5073                    visible: bottom_visible,
 5074                    active_panel: bottom_active_panel,
 5075                    zoom: bottom_dock_zoom,
 5076                },
 5077            }
 5078        }
 5079
 5080        if let Some(location) = self.serialize_workspace_location(cx) {
 5081            let breakpoints = self.project.update(cx, |project, cx| {
 5082                project
 5083                    .breakpoint_store()
 5084                    .read(cx)
 5085                    .all_source_breakpoints(cx)
 5086            });
 5087
 5088            let center_group = build_serialized_pane_group(&self.center.root, window, cx);
 5089            let docks = build_serialized_docks(self, window, cx);
 5090            let window_bounds = Some(SerializedWindowBounds(window.window_bounds()));
 5091            let serialized_workspace = SerializedWorkspace {
 5092                id: database_id,
 5093                location,
 5094                center_group,
 5095                window_bounds,
 5096                display: Default::default(),
 5097                docks,
 5098                centered_layout: self.centered_layout,
 5099                session_id: self.session_id.clone(),
 5100                breakpoints,
 5101                window_id: Some(window.window_handle().window_id().as_u64()),
 5102            };
 5103
 5104            return window.spawn(cx, async move |_| {
 5105                persistence::DB.save_workspace(serialized_workspace).await;
 5106            });
 5107        }
 5108        Task::ready(())
 5109    }
 5110
 5111    fn serialize_workspace_location(&self, cx: &App) -> Option<SerializedWorkspaceLocation> {
 5112        if let Some(ssh_project) = &self.serialized_ssh_project {
 5113            Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
 5114        } else if let Some(local_paths) = self.local_paths(cx) {
 5115            if !local_paths.is_empty() {
 5116                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
 5117            } else {
 5118                None
 5119            }
 5120        } else {
 5121            None
 5122        }
 5123    }
 5124
 5125    fn update_history(&self, cx: &mut App) {
 5126        let Some(id) = self.database_id() else {
 5127            return;
 5128        };
 5129        let Some(location) = self.serialize_workspace_location(cx) else {
 5130            return;
 5131        };
 5132        if let Some(manager) = HistoryManager::global(cx) {
 5133            manager.update(cx, |this, cx| {
 5134                this.update_history(id, HistoryManagerEntry::new(id, &location), cx);
 5135            });
 5136        }
 5137    }
 5138
 5139    async fn serialize_items(
 5140        this: &WeakEntity<Self>,
 5141        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
 5142        cx: &mut AsyncWindowContext,
 5143    ) -> Result<()> {
 5144        const CHUNK_SIZE: usize = 200;
 5145
 5146        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
 5147
 5148        while let Some(items_received) = serializable_items.next().await {
 5149            let unique_items =
 5150                items_received
 5151                    .into_iter()
 5152                    .fold(HashMap::default(), |mut acc, item| {
 5153                        acc.entry(item.item_id()).or_insert(item);
 5154                        acc
 5155                    });
 5156
 5157            // We use into_iter() here so that the references to the items are moved into
 5158            // the tasks and not kept alive while we're sleeping.
 5159            for (_, item) in unique_items.into_iter() {
 5160                if let Ok(Some(task)) = this.update_in(cx, |workspace, window, cx| {
 5161                    item.serialize(workspace, false, window, cx)
 5162                }) {
 5163                    cx.background_spawn(async move { task.await.log_err() })
 5164                        .detach();
 5165                }
 5166            }
 5167
 5168            cx.background_executor()
 5169                .timer(SERIALIZATION_THROTTLE_TIME)
 5170                .await;
 5171        }
 5172
 5173        Ok(())
 5174    }
 5175
 5176    pub(crate) fn enqueue_item_serialization(
 5177        &mut self,
 5178        item: Box<dyn SerializableItemHandle>,
 5179    ) -> Result<()> {
 5180        self.serializable_items_tx
 5181            .unbounded_send(item)
 5182            .map_err(|err| anyhow!("failed to send serializable item over channel: {err}"))
 5183    }
 5184
 5185    pub(crate) fn load_workspace(
 5186        serialized_workspace: SerializedWorkspace,
 5187        paths_to_open: Vec<Option<ProjectPath>>,
 5188        window: &mut Window,
 5189        cx: &mut Context<Workspace>,
 5190    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
 5191        cx.spawn_in(window, async move |workspace, cx| {
 5192            let project = workspace.read_with(cx, |workspace, _| workspace.project().clone())?;
 5193
 5194            let mut center_group = None;
 5195            let mut center_items = None;
 5196
 5197            // Traverse the splits tree and add to things
 5198            if let Some((group, active_pane, items)) = serialized_workspace
 5199                .center_group
 5200                .deserialize(&project, serialized_workspace.id, workspace.clone(), cx)
 5201                .await
 5202            {
 5203                center_items = Some(items);
 5204                center_group = Some((group, active_pane))
 5205            }
 5206
 5207            let mut items_by_project_path = HashMap::default();
 5208            let mut item_ids_by_kind = HashMap::default();
 5209            let mut all_deserialized_items = Vec::default();
 5210            cx.update(|_, cx| {
 5211                for item in center_items.unwrap_or_default().into_iter().flatten() {
 5212                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
 5213                        item_ids_by_kind
 5214                            .entry(serializable_item_handle.serialized_item_kind())
 5215                            .or_insert(Vec::new())
 5216                            .push(item.item_id().as_u64() as ItemId);
 5217                    }
 5218
 5219                    if let Some(project_path) = item.project_path(cx) {
 5220                        items_by_project_path.insert(project_path, item.clone());
 5221                    }
 5222                    all_deserialized_items.push(item);
 5223                }
 5224            })?;
 5225
 5226            let opened_items = paths_to_open
 5227                .into_iter()
 5228                .map(|path_to_open| {
 5229                    path_to_open
 5230                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
 5231                })
 5232                .collect::<Vec<_>>();
 5233
 5234            // Remove old panes from workspace panes list
 5235            workspace.update_in(cx, |workspace, window, cx| {
 5236                if let Some((center_group, active_pane)) = center_group {
 5237                    workspace.remove_panes(workspace.center.root.clone(), window, cx);
 5238
 5239                    // Swap workspace center group
 5240                    workspace.center = PaneGroup::with_root(center_group);
 5241                    if let Some(active_pane) = active_pane {
 5242                        workspace.set_active_pane(&active_pane, window, cx);
 5243                        cx.focus_self(window);
 5244                    } else {
 5245                        workspace.set_active_pane(&workspace.center.first_pane(), window, cx);
 5246                    }
 5247                }
 5248
 5249                let docks = serialized_workspace.docks;
 5250
 5251                for (dock, serialized_dock) in [
 5252                    (&mut workspace.right_dock, docks.right),
 5253                    (&mut workspace.left_dock, docks.left),
 5254                    (&mut workspace.bottom_dock, docks.bottom),
 5255                ]
 5256                .iter_mut()
 5257                {
 5258                    dock.update(cx, |dock, cx| {
 5259                        dock.serialized_dock = Some(serialized_dock.clone());
 5260                        dock.restore_state(window, cx);
 5261                    });
 5262                }
 5263
 5264                cx.notify();
 5265            })?;
 5266
 5267            let _ = project
 5268                .update(cx, |project, cx| {
 5269                    project
 5270                        .breakpoint_store()
 5271                        .update(cx, |breakpoint_store, cx| {
 5272                            breakpoint_store
 5273                                .with_serialized_breakpoints(serialized_workspace.breakpoints, cx)
 5274                        })
 5275                })?
 5276                .await;
 5277
 5278            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
 5279            // after loading the items, we might have different items and in order to avoid
 5280            // the database filling up, we delete items that haven't been loaded now.
 5281            //
 5282            // The items that have been loaded, have been saved after they've been added to the workspace.
 5283            let clean_up_tasks = workspace.update_in(cx, |_, window, cx| {
 5284                item_ids_by_kind
 5285                    .into_iter()
 5286                    .map(|(item_kind, loaded_items)| {
 5287                        SerializableItemRegistry::cleanup(
 5288                            item_kind,
 5289                            serialized_workspace.id,
 5290                            loaded_items,
 5291                            window,
 5292                            cx,
 5293                        )
 5294                        .log_err()
 5295                    })
 5296                    .collect::<Vec<_>>()
 5297            })?;
 5298
 5299            futures::future::join_all(clean_up_tasks).await;
 5300
 5301            workspace
 5302                .update_in(cx, |workspace, window, cx| {
 5303                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
 5304                    workspace.serialize_workspace_internal(window, cx).detach();
 5305
 5306                    // Ensure that we mark the window as edited if we did load dirty items
 5307                    workspace.update_window_edited(window, cx);
 5308                })
 5309                .ok();
 5310
 5311            Ok(opened_items)
 5312        })
 5313    }
 5314
 5315    fn actions(&self, div: Div, window: &mut Window, cx: &mut Context<Self>) -> Div {
 5316        self.add_workspace_actions_listeners(div, window, cx)
 5317            .on_action(cx.listener(Self::close_inactive_items_and_panes))
 5318            .on_action(cx.listener(Self::close_all_items_and_panes))
 5319            .on_action(cx.listener(Self::save_all))
 5320            .on_action(cx.listener(Self::send_keystrokes))
 5321            .on_action(cx.listener(Self::add_folder_to_project))
 5322            .on_action(cx.listener(Self::follow_next_collaborator))
 5323            .on_action(cx.listener(Self::close_window))
 5324            .on_action(cx.listener(Self::activate_pane_at_index))
 5325            .on_action(cx.listener(Self::move_item_to_pane_at_index))
 5326            .on_action(cx.listener(Self::move_focused_panel_to_next_position))
 5327            .on_action(cx.listener(|workspace, _: &Unfollow, window, cx| {
 5328                let pane = workspace.active_pane().clone();
 5329                workspace.unfollow_in_pane(&pane, window, cx);
 5330            }))
 5331            .on_action(cx.listener(|workspace, action: &Save, window, cx| {
 5332                workspace
 5333                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), window, cx)
 5334                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
 5335            }))
 5336            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, window, cx| {
 5337                workspace
 5338                    .save_active_item(SaveIntent::SaveWithoutFormat, window, cx)
 5339                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
 5340            }))
 5341            .on_action(cx.listener(|workspace, _: &SaveAs, window, cx| {
 5342                workspace
 5343                    .save_active_item(SaveIntent::SaveAs, window, cx)
 5344                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);
 5345            }))
 5346            .on_action(
 5347                cx.listener(|workspace, _: &ActivatePreviousPane, window, cx| {
 5348                    workspace.activate_previous_pane(window, cx)
 5349                }),
 5350            )
 5351            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
 5352                workspace.activate_next_pane(window, cx)
 5353            }))
 5354            .on_action(
 5355                cx.listener(|workspace, _: &ActivateNextWindow, _window, cx| {
 5356                    workspace.activate_next_window(cx)
 5357                }),
 5358            )
 5359            .on_action(
 5360                cx.listener(|workspace, _: &ActivatePreviousWindow, _window, cx| {
 5361                    workspace.activate_previous_window(cx)
 5362                }),
 5363            )
 5364            .on_action(cx.listener(|workspace, _: &ActivatePaneLeft, window, cx| {
 5365                workspace.activate_pane_in_direction(SplitDirection::Left, window, cx)
 5366            }))
 5367            .on_action(cx.listener(|workspace, _: &ActivatePaneRight, window, cx| {
 5368                workspace.activate_pane_in_direction(SplitDirection::Right, window, cx)
 5369            }))
 5370            .on_action(cx.listener(|workspace, _: &ActivatePaneUp, window, cx| {
 5371                workspace.activate_pane_in_direction(SplitDirection::Up, window, cx)
 5372            }))
 5373            .on_action(cx.listener(|workspace, _: &ActivatePaneDown, window, cx| {
 5374                workspace.activate_pane_in_direction(SplitDirection::Down, window, cx)
 5375            }))
 5376            .on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
 5377                workspace.activate_next_pane(window, cx)
 5378            }))
 5379            .on_action(cx.listener(
 5380                |workspace, action: &MoveItemToPaneInDirection, window, cx| {
 5381                    workspace.move_item_to_pane_in_direction(action, window, cx)
 5382                },
 5383            ))
 5384            .on_action(cx.listener(|workspace, _: &SwapPaneLeft, _, cx| {
 5385                workspace.swap_pane_in_direction(SplitDirection::Left, cx)
 5386            }))
 5387            .on_action(cx.listener(|workspace, _: &SwapPaneRight, _, cx| {
 5388                workspace.swap_pane_in_direction(SplitDirection::Right, cx)
 5389            }))
 5390            .on_action(cx.listener(|workspace, _: &SwapPaneUp, _, cx| {
 5391                workspace.swap_pane_in_direction(SplitDirection::Up, cx)
 5392            }))
 5393            .on_action(cx.listener(|workspace, _: &SwapPaneDown, _, cx| {
 5394                workspace.swap_pane_in_direction(SplitDirection::Down, cx)
 5395            }))
 5396            .on_action(cx.listener(|this, _: &ToggleLeftDock, window, cx| {
 5397                this.toggle_dock(DockPosition::Left, window, cx);
 5398            }))
 5399            .on_action(cx.listener(
 5400                |workspace: &mut Workspace, _: &ToggleRightDock, window, cx| {
 5401                    workspace.toggle_dock(DockPosition::Right, window, cx);
 5402                },
 5403            ))
 5404            .on_action(cx.listener(
 5405                |workspace: &mut Workspace, _: &ToggleBottomDock, window, cx| {
 5406                    workspace.toggle_dock(DockPosition::Bottom, window, cx);
 5407                },
 5408            ))
 5409            .on_action(cx.listener(
 5410                |workspace: &mut Workspace, _: &CloseActiveDock, window, cx| {
 5411                    workspace.close_active_dock(window, cx);
 5412                },
 5413            ))
 5414            .on_action(
 5415                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, window, cx| {
 5416                    workspace.close_all_docks(window, cx);
 5417                }),
 5418            )
 5419            .on_action(cx.listener(
 5420                |workspace: &mut Workspace, _: &ClearAllNotifications, _, cx| {
 5421                    workspace.clear_all_notifications(cx);
 5422                },
 5423            ))
 5424            .on_action(cx.listener(
 5425                |workspace: &mut Workspace, _: &SuppressNotification, _, cx| {
 5426                    if let Some((notification_id, _)) = workspace.notifications.pop() {
 5427                        workspace.suppress_notification(&notification_id, cx);
 5428                    }
 5429                },
 5430            ))
 5431            .on_action(cx.listener(
 5432                |workspace: &mut Workspace, _: &ReopenClosedItem, window, cx| {
 5433                    workspace.reopen_closed_item(window, cx).detach();
 5434                },
 5435            ))
 5436            .on_action(cx.listener(Workspace::toggle_centered_layout))
 5437            .on_action(cx.listener(Workspace::cancel))
 5438    }
 5439
 5440    #[cfg(any(test, feature = "test-support"))]
 5441    pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 5442        use node_runtime::NodeRuntime;
 5443        use session::Session;
 5444
 5445        let client = project.read(cx).client();
 5446        let user_store = project.read(cx).user_store();
 5447
 5448        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
 5449        let session = cx.new(|cx| AppSession::new(Session::test(), cx));
 5450        window.activate_window();
 5451        let app_state = Arc::new(AppState {
 5452            languages: project.read(cx).languages().clone(),
 5453            workspace_store,
 5454            client,
 5455            user_store,
 5456            fs: project.read(cx).fs().clone(),
 5457            build_window_options: |_, _| Default::default(),
 5458            node_runtime: NodeRuntime::unavailable(),
 5459            session,
 5460        });
 5461        let workspace = Self::new(Default::default(), project, app_state, window, cx);
 5462        workspace
 5463            .active_pane
 5464            .update(cx, |pane, cx| window.focus(&pane.focus_handle(cx)));
 5465        workspace
 5466    }
 5467
 5468    pub fn register_action<A: Action>(
 5469        &mut self,
 5470        callback: impl Fn(&mut Self, &A, &mut Window, &mut Context<Self>) + 'static,
 5471    ) -> &mut Self {
 5472        let callback = Arc::new(callback);
 5473
 5474        self.workspace_actions.push(Box::new(move |div, _, _, cx| {
 5475            let callback = callback.clone();
 5476            div.on_action(cx.listener(move |workspace, event, window, cx| {
 5477                (callback)(workspace, event, window, cx)
 5478            }))
 5479        }));
 5480        self
 5481    }
 5482    pub fn register_action_renderer(
 5483        &mut self,
 5484        callback: impl Fn(Div, &Workspace, &mut Window, &mut Context<Self>) -> Div + 'static,
 5485    ) -> &mut Self {
 5486        self.workspace_actions.push(Box::new(callback));
 5487        self
 5488    }
 5489
 5490    fn add_workspace_actions_listeners(
 5491        &self,
 5492        mut div: Div,
 5493        window: &mut Window,
 5494        cx: &mut Context<Self>,
 5495    ) -> Div {
 5496        for action in self.workspace_actions.iter() {
 5497            div = (action)(div, self, window, cx)
 5498        }
 5499        div
 5500    }
 5501
 5502    pub fn has_active_modal(&self, _: &mut Window, cx: &mut App) -> bool {
 5503        self.modal_layer.read(cx).has_active_modal()
 5504    }
 5505
 5506    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
 5507        self.modal_layer.read(cx).active_modal()
 5508    }
 5509
 5510    pub fn toggle_modal<V: ModalView, B>(&mut self, window: &mut Window, cx: &mut App, build: B)
 5511    where
 5512        B: FnOnce(&mut Window, &mut Context<V>) -> V,
 5513    {
 5514        self.modal_layer.update(cx, |modal_layer, cx| {
 5515            modal_layer.toggle_modal(window, cx, build)
 5516        })
 5517    }
 5518
 5519    pub fn toggle_status_toast<V: ToastView>(&mut self, entity: Entity<V>, cx: &mut App) {
 5520        self.toast_layer
 5521            .update(cx, |toast_layer, cx| toast_layer.toggle_toast(cx, entity))
 5522    }
 5523
 5524    pub fn toggle_centered_layout(
 5525        &mut self,
 5526        _: &ToggleCenteredLayout,
 5527        _: &mut Window,
 5528        cx: &mut Context<Self>,
 5529    ) {
 5530        self.centered_layout = !self.centered_layout;
 5531        if let Some(database_id) = self.database_id() {
 5532            cx.background_spawn(DB.set_centered_layout(database_id, self.centered_layout))
 5533                .detach_and_log_err(cx);
 5534        }
 5535        cx.notify();
 5536    }
 5537
 5538    fn adjust_padding(padding: Option<f32>) -> f32 {
 5539        padding
 5540            .unwrap_or(Self::DEFAULT_PADDING)
 5541            .clamp(0.0, Self::MAX_PADDING)
 5542    }
 5543
 5544    fn render_dock(
 5545        &self,
 5546        position: DockPosition,
 5547        dock: &Entity<Dock>,
 5548        window: &mut Window,
 5549        cx: &mut App,
 5550    ) -> Option<Div> {
 5551        if self.zoomed_position == Some(position) {
 5552            return None;
 5553        }
 5554
 5555        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
 5556            let pane = panel.pane(cx)?;
 5557            let follower_states = &self.follower_states;
 5558            leader_border_for_pane(follower_states, &pane, window, cx)
 5559        });
 5560
 5561        Some(
 5562            div()
 5563                .flex()
 5564                .flex_none()
 5565                .overflow_hidden()
 5566                .child(dock.clone())
 5567                .children(leader_border),
 5568        )
 5569    }
 5570
 5571    pub fn for_window(window: &mut Window, _: &mut App) -> Option<Entity<Workspace>> {
 5572        window.root().flatten()
 5573    }
 5574
 5575    pub fn zoomed_item(&self) -> Option<&AnyWeakView> {
 5576        self.zoomed.as_ref()
 5577    }
 5578
 5579    pub fn activate_next_window(&mut self, cx: &mut Context<Self>) {
 5580        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
 5581            return;
 5582        };
 5583        let windows = cx.windows();
 5584        let Some(next_window) = windows
 5585            .iter()
 5586            .cycle()
 5587            .skip_while(|window| window.window_id() != current_window_id)
 5588            .nth(1)
 5589        else {
 5590            return;
 5591        };
 5592        next_window
 5593            .update(cx, |_, window, _| window.activate_window())
 5594            .ok();
 5595    }
 5596
 5597    pub fn activate_previous_window(&mut self, cx: &mut Context<Self>) {
 5598        let Some(current_window_id) = cx.active_window().map(|a| a.window_id()) else {
 5599            return;
 5600        };
 5601        let windows = cx.windows();
 5602        let Some(prev_window) = windows
 5603            .iter()
 5604            .rev()
 5605            .cycle()
 5606            .skip_while(|window| window.window_id() != current_window_id)
 5607            .nth(1)
 5608        else {
 5609            return;
 5610        };
 5611        prev_window
 5612            .update(cx, |_, window, _| window.activate_window())
 5613            .ok();
 5614    }
 5615
 5616    pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 5617        if cx.stop_active_drag(window) {
 5618            return;
 5619        } else if let Some((notification_id, _)) = self.notifications.pop() {
 5620            dismiss_app_notification(&notification_id, cx);
 5621        } else {
 5622            cx.propagate();
 5623        }
 5624    }
 5625}
 5626
 5627fn leader_border_for_pane(
 5628    follower_states: &HashMap<CollaboratorId, FollowerState>,
 5629    pane: &Entity<Pane>,
 5630    _: &Window,
 5631    cx: &App,
 5632) -> Option<Div> {
 5633    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
 5634        if state.pane() == pane {
 5635            Some((*leader_id, state))
 5636        } else {
 5637            None
 5638        }
 5639    })?;
 5640
 5641    let mut leader_color = match leader_id {
 5642        CollaboratorId::PeerId(leader_peer_id) => {
 5643            let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
 5644            let leader = room.remote_participant_for_peer_id(leader_peer_id)?;
 5645
 5646            cx.theme()
 5647                .players()
 5648                .color_for_participant(leader.participant_index.0)
 5649                .cursor
 5650        }
 5651        CollaboratorId::Agent => cx.theme().players().agent().cursor,
 5652    };
 5653    leader_color.fade_out(0.3);
 5654    Some(
 5655        div()
 5656            .absolute()
 5657            .size_full()
 5658            .left_0()
 5659            .top_0()
 5660            .border_2()
 5661            .border_color(leader_color),
 5662    )
 5663}
 5664
 5665fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
 5666    ZED_WINDOW_POSITION
 5667        .zip(*ZED_WINDOW_SIZE)
 5668        .map(|(position, size)| Bounds {
 5669            origin: position,
 5670            size,
 5671        })
 5672}
 5673
 5674fn open_items(
 5675    serialized_workspace: Option<SerializedWorkspace>,
 5676    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
 5677    window: &mut Window,
 5678    cx: &mut Context<Workspace>,
 5679) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> + use<> {
 5680    let restored_items = serialized_workspace.map(|serialized_workspace| {
 5681        Workspace::load_workspace(
 5682            serialized_workspace,
 5683            project_paths_to_open
 5684                .iter()
 5685                .map(|(_, project_path)| project_path)
 5686                .cloned()
 5687                .collect(),
 5688            window,
 5689            cx,
 5690        )
 5691    });
 5692
 5693    cx.spawn_in(window, async move |workspace, cx| {
 5694        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
 5695
 5696        if let Some(restored_items) = restored_items {
 5697            let restored_items = restored_items.await?;
 5698
 5699            let restored_project_paths = restored_items
 5700                .iter()
 5701                .filter_map(|item| {
 5702                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
 5703                        .ok()
 5704                        .flatten()
 5705                })
 5706                .collect::<HashSet<_>>();
 5707
 5708            for restored_item in restored_items {
 5709                opened_items.push(restored_item.map(Ok));
 5710            }
 5711
 5712            project_paths_to_open
 5713                .iter_mut()
 5714                .for_each(|(_, project_path)| {
 5715                    if let Some(project_path_to_open) = project_path {
 5716                        if restored_project_paths.contains(project_path_to_open) {
 5717                            *project_path = None;
 5718                        }
 5719                    }
 5720                });
 5721        } else {
 5722            for _ in 0..project_paths_to_open.len() {
 5723                opened_items.push(None);
 5724            }
 5725        }
 5726        assert!(opened_items.len() == project_paths_to_open.len());
 5727
 5728        let tasks =
 5729            project_paths_to_open
 5730                .into_iter()
 5731                .enumerate()
 5732                .map(|(ix, (abs_path, project_path))| {
 5733                    let workspace = workspace.clone();
 5734                    cx.spawn(async move |cx| {
 5735                        let file_project_path = project_path?;
 5736                        let abs_path_task = workspace.update(cx, |workspace, cx| {
 5737                            workspace.project().update(cx, |project, cx| {
 5738                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
 5739                            })
 5740                        });
 5741
 5742                        // We only want to open file paths here. If one of the items
 5743                        // here is a directory, it was already opened further above
 5744                        // with a `find_or_create_worktree`.
 5745                        if let Ok(task) = abs_path_task {
 5746                            if task.await.map_or(true, |p| p.is_file()) {
 5747                                return Some((
 5748                                    ix,
 5749                                    workspace
 5750                                        .update_in(cx, |workspace, window, cx| {
 5751                                            workspace.open_path(
 5752                                                file_project_path,
 5753                                                None,
 5754                                                true,
 5755                                                window,
 5756                                                cx,
 5757                                            )
 5758                                        })
 5759                                        .log_err()?
 5760                                        .await,
 5761                                ));
 5762                            }
 5763                        }
 5764                        None
 5765                    })
 5766                });
 5767
 5768        let tasks = tasks.collect::<Vec<_>>();
 5769
 5770        let tasks = futures::future::join_all(tasks);
 5771        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
 5772            opened_items[ix] = Some(path_open_result);
 5773        }
 5774
 5775        Ok(opened_items)
 5776    })
 5777}
 5778
 5779enum ActivateInDirectionTarget {
 5780    Pane(Entity<Pane>),
 5781    Dock(Entity<Dock>),
 5782}
 5783
 5784fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncApp) {
 5785    workspace
 5786        .update(cx, |workspace, _, cx| {
 5787            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
 5788                struct DatabaseFailedNotification;
 5789
 5790                workspace.show_notification(
 5791                    NotificationId::unique::<DatabaseFailedNotification>(),
 5792                    cx,
 5793                    |cx| {
 5794                        cx.new(|cx| {
 5795                            MessageNotification::new("Failed to load the database file.", cx)
 5796                                .primary_message("File an Issue")
 5797                                .primary_icon(IconName::Plus)
 5798                                .primary_on_click(|window, cx| {
 5799                                    window.dispatch_action(Box::new(FileBugReport), cx)
 5800                                })
 5801                        })
 5802                    },
 5803                );
 5804            }
 5805        })
 5806        .log_err();
 5807}
 5808
 5809impl Focusable for Workspace {
 5810    fn focus_handle(&self, cx: &App) -> FocusHandle {
 5811        self.active_pane.focus_handle(cx)
 5812    }
 5813}
 5814
 5815#[derive(Clone)]
 5816struct DraggedDock(DockPosition);
 5817
 5818impl Render for DraggedDock {
 5819    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 5820        gpui::Empty
 5821    }
 5822}
 5823
 5824impl Render for Workspace {
 5825    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 5826        let mut context = KeyContext::new_with_defaults();
 5827        context.add("Workspace");
 5828        context.set("keyboard_layout", cx.keyboard_layout().name().to_string());
 5829        if let Some(status) = self
 5830            .debugger_provider
 5831            .as_ref()
 5832            .and_then(|provider| provider.active_thread_state(cx))
 5833        {
 5834            match status {
 5835                ThreadStatus::Running | ThreadStatus::Stepping => {
 5836                    context.add("debugger_running");
 5837                }
 5838                ThreadStatus::Stopped => context.add("debugger_stopped"),
 5839                ThreadStatus::Exited | ThreadStatus::Ended => {}
 5840            }
 5841        }
 5842
 5843        let centered_layout = self.centered_layout
 5844            && self.center.panes().len() == 1
 5845            && self.active_item(cx).is_some();
 5846        let render_padding = |size| {
 5847            (size > 0.0).then(|| {
 5848                div()
 5849                    .h_full()
 5850                    .w(relative(size))
 5851                    .bg(cx.theme().colors().editor_background)
 5852                    .border_color(cx.theme().colors().pane_group_border)
 5853            })
 5854        };
 5855        let paddings = if centered_layout {
 5856            let settings = WorkspaceSettings::get_global(cx).centered_layout;
 5857            (
 5858                render_padding(Self::adjust_padding(settings.left_padding)),
 5859                render_padding(Self::adjust_padding(settings.right_padding)),
 5860            )
 5861        } else {
 5862            (None, None)
 5863        };
 5864        let ui_font = theme::setup_ui_font(window, cx);
 5865
 5866        let theme = cx.theme().clone();
 5867        let colors = theme.colors();
 5868        let notification_entities = self
 5869            .notifications
 5870            .iter()
 5871            .map(|(_, notification)| notification.entity_id())
 5872            .collect::<Vec<_>>();
 5873
 5874        client_side_decorations(
 5875            self.actions(div(), window, cx)
 5876                .key_context(context)
 5877                .relative()
 5878                .size_full()
 5879                .flex()
 5880                .flex_col()
 5881                .font(ui_font)
 5882                .gap_0()
 5883                .justify_start()
 5884                .items_start()
 5885                .text_color(colors.text)
 5886                .overflow_hidden()
 5887                .children(self.titlebar_item.clone())
 5888                .on_modifiers_changed(move |_, _, cx| {
 5889                    for &id in &notification_entities {
 5890                        cx.notify(id);
 5891                    }
 5892                })
 5893                .child(
 5894                    div()
 5895                        .size_full()
 5896                        .relative()
 5897                        .flex_1()
 5898                        .flex()
 5899                        .flex_col()
 5900                        .child(
 5901                            div()
 5902                                .id("workspace")
 5903                                .bg(colors.background)
 5904                                .relative()
 5905                                .flex_1()
 5906                                .w_full()
 5907                                .flex()
 5908                                .flex_col()
 5909                                .overflow_hidden()
 5910                                .border_t_1()
 5911                                .border_b_1()
 5912                                .border_color(colors.border)
 5913                                .child({
 5914                                    let this = cx.entity().clone();
 5915                                    canvas(
 5916                                        move |bounds, window, cx| {
 5917                                            this.update(cx, |this, cx| {
 5918                                                let bounds_changed = this.bounds != bounds;
 5919                                                this.bounds = bounds;
 5920
 5921                                                if bounds_changed {
 5922                                                    this.left_dock.update(cx, |dock, cx| {
 5923                                                        dock.clamp_panel_size(
 5924                                                            bounds.size.width,
 5925                                                            window,
 5926                                                            cx,
 5927                                                        )
 5928                                                    });
 5929
 5930                                                    this.right_dock.update(cx, |dock, cx| {
 5931                                                        dock.clamp_panel_size(
 5932                                                            bounds.size.width,
 5933                                                            window,
 5934                                                            cx,
 5935                                                        )
 5936                                                    });
 5937
 5938                                                    this.bottom_dock.update(cx, |dock, cx| {
 5939                                                        dock.clamp_panel_size(
 5940                                                            bounds.size.height,
 5941                                                            window,
 5942                                                            cx,
 5943                                                        )
 5944                                                    });
 5945                                                }
 5946                                            })
 5947                                        },
 5948                                        |_, _, _, _| {},
 5949                                    )
 5950                                    .absolute()
 5951                                    .size_full()
 5952                                })
 5953                                .when(self.zoomed.is_none(), |this| {
 5954                                    this.on_drag_move(cx.listener(
 5955                                        move |workspace,
 5956                                              e: &DragMoveEvent<DraggedDock>,
 5957                                              window,
 5958                                              cx| {
 5959                                            if workspace.previous_dock_drag_coordinates
 5960                                                != Some(e.event.position)
 5961                                            {
 5962                                                workspace.previous_dock_drag_coordinates =
 5963                                                    Some(e.event.position);
 5964                                                match e.drag(cx).0 {
 5965                                                    DockPosition::Left => {
 5966                                                        resize_left_dock(
 5967                                                            e.event.position.x
 5968                                                                - workspace.bounds.left(),
 5969                                                            workspace,
 5970                                                            window,
 5971                                                            cx,
 5972                                                        );
 5973                                                    }
 5974                                                    DockPosition::Right => {
 5975                                                        resize_right_dock(
 5976                                                            workspace.bounds.right()
 5977                                                                - e.event.position.x,
 5978                                                            workspace,
 5979                                                            window,
 5980                                                            cx,
 5981                                                        );
 5982                                                    }
 5983                                                    DockPosition::Bottom => {
 5984                                                        resize_bottom_dock(
 5985                                                            workspace.bounds.bottom()
 5986                                                                - e.event.position.y,
 5987                                                            workspace,
 5988                                                            window,
 5989                                                            cx,
 5990                                                        );
 5991                                                    }
 5992                                                };
 5993                                                workspace.serialize_workspace(window, cx);
 5994                                            }
 5995                                        },
 5996                                    ))
 5997                                })
 5998                                .child({
 5999                                    match self.bottom_dock_layout {
 6000                                        BottomDockLayout::Full => div()
 6001                                            .flex()
 6002                                            .flex_col()
 6003                                            .h_full()
 6004                                            .child(
 6005                                                div()
 6006                                                    .flex()
 6007                                                    .flex_row()
 6008                                                    .flex_1()
 6009                                                    .overflow_hidden()
 6010                                                    .children(self.render_dock(
 6011                                                        DockPosition::Left,
 6012                                                        &self.left_dock,
 6013                                                        window,
 6014                                                        cx,
 6015                                                    ))
 6016                                                    .child(
 6017                                                        div()
 6018                                                            .flex()
 6019                                                            .flex_col()
 6020                                                            .flex_1()
 6021                                                            .overflow_hidden()
 6022                                                            .child(
 6023                                                                h_flex()
 6024                                                                    .flex_1()
 6025                                                                    .when_some(
 6026                                                                        paddings.0,
 6027                                                                        |this, p| {
 6028                                                                            this.child(
 6029                                                                                p.border_r_1(),
 6030                                                                            )
 6031                                                                        },
 6032                                                                    )
 6033                                                                    .child(self.center.render(
 6034                                                                        self.zoomed.as_ref(),
 6035                                                                        &PaneRenderContext {
 6036                                                                            follower_states:
 6037                                                                                &self.follower_states,
 6038                                                                            active_call: self.active_call(),
 6039                                                                            active_pane: &self.active_pane,
 6040                                                                            app_state: &self.app_state,
 6041                                                                            project: &self.project,
 6042                                                                            workspace: &self.weak_self,
 6043                                                                        },
 6044                                                                        window,
 6045                                                                        cx,
 6046                                                                    ))
 6047                                                                    .when_some(
 6048                                                                        paddings.1,
 6049                                                                        |this, p| {
 6050                                                                            this.child(
 6051                                                                                p.border_l_1(),
 6052                                                                            )
 6053                                                                        },
 6054                                                                    ),
 6055                                                            ),
 6056                                                    )
 6057                                                    .children(self.render_dock(
 6058                                                        DockPosition::Right,
 6059                                                        &self.right_dock,
 6060                                                        window,
 6061                                                        cx,
 6062                                                    )),
 6063                                            )
 6064                                            .child(div().w_full().children(self.render_dock(
 6065                                                DockPosition::Bottom,
 6066                                                &self.bottom_dock,
 6067                                                window,
 6068                                                cx
 6069                                            ))),
 6070
 6071                                        BottomDockLayout::LeftAligned => div()
 6072                                            .flex()
 6073                                            .flex_row()
 6074                                            .h_full()
 6075                                            .child(
 6076                                                div()
 6077                                                    .flex()
 6078                                                    .flex_col()
 6079                                                    .flex_1()
 6080                                                    .h_full()
 6081                                                    .child(
 6082                                                        div()
 6083                                                            .flex()
 6084                                                            .flex_row()
 6085                                                            .flex_1()
 6086                                                            .children(self.render_dock(DockPosition::Left, &self.left_dock, window, cx))
 6087                                                            .child(
 6088                                                                div()
 6089                                                                    .flex()
 6090                                                                    .flex_col()
 6091                                                                    .flex_1()
 6092                                                                    .overflow_hidden()
 6093                                                                    .child(
 6094                                                                        h_flex()
 6095                                                                            .flex_1()
 6096                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
 6097                                                                            .child(self.center.render(
 6098                                                                                self.zoomed.as_ref(),
 6099                                                                                &PaneRenderContext {
 6100                                                                                    follower_states:
 6101                                                                                        &self.follower_states,
 6102                                                                                    active_call: self.active_call(),
 6103                                                                                    active_pane: &self.active_pane,
 6104                                                                                    app_state: &self.app_state,
 6105                                                                                    project: &self.project,
 6106                                                                                    workspace: &self.weak_self,
 6107                                                                                },
 6108                                                                                window,
 6109                                                                                cx,
 6110                                                                            ))
 6111                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
 6112                                                                    )
 6113                                                            )
 6114                                                    )
 6115                                                    .child(
 6116                                                        div()
 6117                                                            .w_full()
 6118                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
 6119                                                    ),
 6120                                            )
 6121                                            .children(self.render_dock(
 6122                                                DockPosition::Right,
 6123                                                &self.right_dock,
 6124                                                window,
 6125                                                cx,
 6126                                            )),
 6127
 6128                                        BottomDockLayout::RightAligned => div()
 6129                                            .flex()
 6130                                            .flex_row()
 6131                                            .h_full()
 6132                                            .children(self.render_dock(
 6133                                                DockPosition::Left,
 6134                                                &self.left_dock,
 6135                                                window,
 6136                                                cx,
 6137                                            ))
 6138                                            .child(
 6139                                                div()
 6140                                                    .flex()
 6141                                                    .flex_col()
 6142                                                    .flex_1()
 6143                                                    .h_full()
 6144                                                    .child(
 6145                                                        div()
 6146                                                            .flex()
 6147                                                            .flex_row()
 6148                                                            .flex_1()
 6149                                                            .child(
 6150                                                                div()
 6151                                                                    .flex()
 6152                                                                    .flex_col()
 6153                                                                    .flex_1()
 6154                                                                    .overflow_hidden()
 6155                                                                    .child(
 6156                                                                        h_flex()
 6157                                                                            .flex_1()
 6158                                                                            .when_some(paddings.0, |this, p| this.child(p.border_r_1()))
 6159                                                                            .child(self.center.render(
 6160                                                                                self.zoomed.as_ref(),
 6161                                                                                &PaneRenderContext {
 6162                                                                                    follower_states:
 6163                                                                                        &self.follower_states,
 6164                                                                                    active_call: self.active_call(),
 6165                                                                                    active_pane: &self.active_pane,
 6166                                                                                    app_state: &self.app_state,
 6167                                                                                    project: &self.project,
 6168                                                                                    workspace: &self.weak_self,
 6169                                                                                },
 6170                                                                                window,
 6171                                                                                cx,
 6172                                                                            ))
 6173                                                                            .when_some(paddings.1, |this, p| this.child(p.border_l_1())),
 6174                                                                    )
 6175                                                            )
 6176                                                            .children(self.render_dock(DockPosition::Right, &self.right_dock, window, cx))
 6177                                                    )
 6178                                                    .child(
 6179                                                        div()
 6180                                                            .w_full()
 6181                                                            .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx))
 6182                                                    ),
 6183                                            ),
 6184
 6185                                        BottomDockLayout::Contained => div()
 6186                                            .flex()
 6187                                            .flex_row()
 6188                                            .h_full()
 6189                                            .children(self.render_dock(
 6190                                                DockPosition::Left,
 6191                                                &self.left_dock,
 6192                                                window,
 6193                                                cx,
 6194                                            ))
 6195                                            .child(
 6196                                                div()
 6197                                                    .flex()
 6198                                                    .flex_col()
 6199                                                    .flex_1()
 6200                                                    .overflow_hidden()
 6201                                                    .child(
 6202                                                        h_flex()
 6203                                                            .flex_1()
 6204                                                            .when_some(paddings.0, |this, p| {
 6205                                                                this.child(p.border_r_1())
 6206                                                            })
 6207                                                            .child(self.center.render(
 6208                                                                self.zoomed.as_ref(),
 6209                                                                &PaneRenderContext {
 6210                                                                    follower_states:
 6211                                                                        &self.follower_states,
 6212                                                                    active_call: self.active_call(),
 6213                                                                    active_pane: &self.active_pane,
 6214                                                                    app_state: &self.app_state,
 6215                                                                    project: &self.project,
 6216                                                                    workspace: &self.weak_self,
 6217                                                                },
 6218                                                                window,
 6219                                                                cx,
 6220                                                            ))
 6221                                                            .when_some(paddings.1, |this, p| {
 6222                                                                this.child(p.border_l_1())
 6223                                                            }),
 6224                                                    )
 6225                                                    .children(self.render_dock(
 6226                                                        DockPosition::Bottom,
 6227                                                        &self.bottom_dock,
 6228                                                        window,
 6229                                                        cx,
 6230                                                    )),
 6231                                            )
 6232                                            .children(self.render_dock(
 6233                                                DockPosition::Right,
 6234                                                &self.right_dock,
 6235                                                window,
 6236                                                cx,
 6237                                            )),
 6238                                    }
 6239                                })
 6240                                .children(self.zoomed.as_ref().and_then(|view| {
 6241                                    let zoomed_view = view.upgrade()?;
 6242                                    let div = div()
 6243                                        .occlude()
 6244                                        .absolute()
 6245                                        .overflow_hidden()
 6246                                        .border_color(colors.border)
 6247                                        .bg(colors.background)
 6248                                        .child(zoomed_view)
 6249                                        .inset_0()
 6250                                        .shadow_lg();
 6251
 6252                                    Some(match self.zoomed_position {
 6253                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
 6254                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
 6255                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
 6256                                        None => {
 6257                                            div.top_2().bottom_2().left_2().right_2().border_1()
 6258                                        }
 6259                                    })
 6260                                }))
 6261                                .children(self.render_notifications(window, cx)),
 6262                        )
 6263                        .child(self.status_bar.clone())
 6264                        .child(self.modal_layer.clone())
 6265                        .child(self.toast_layer.clone()),
 6266                ),
 6267            window,
 6268            cx,
 6269        )
 6270    }
 6271}
 6272
 6273fn resize_bottom_dock(
 6274    new_size: Pixels,
 6275    workspace: &mut Workspace,
 6276    window: &mut Window,
 6277    cx: &mut App,
 6278) {
 6279    let size =
 6280        new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE - workspace.bounds.top());
 6281    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
 6282        if WorkspaceSettings::get_global(cx)
 6283            .resize_all_panels_in_dock
 6284            .contains(&DockPosition::Bottom)
 6285        {
 6286            bottom_dock.resize_all_panels(Some(size), window, cx);
 6287        } else {
 6288            bottom_dock.resize_active_panel(Some(size), window, cx);
 6289        }
 6290    });
 6291}
 6292
 6293fn resize_right_dock(
 6294    new_size: Pixels,
 6295    workspace: &mut Workspace,
 6296    window: &mut Window,
 6297    cx: &mut App,
 6298) {
 6299    let mut size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
 6300    workspace.left_dock.read_with(cx, |left_dock, cx| {
 6301        let left_dock_size = left_dock
 6302            .active_panel_size(window, cx)
 6303            .unwrap_or(Pixels(0.0));
 6304        if left_dock_size + size > workspace.bounds.right() {
 6305            size = workspace.bounds.right() - left_dock_size
 6306        }
 6307    });
 6308    workspace.right_dock.update(cx, |right_dock, cx| {
 6309        if WorkspaceSettings::get_global(cx)
 6310            .resize_all_panels_in_dock
 6311            .contains(&DockPosition::Right)
 6312        {
 6313            right_dock.resize_all_panels(Some(size), window, cx);
 6314        } else {
 6315            right_dock.resize_active_panel(Some(size), window, cx);
 6316        }
 6317    });
 6318}
 6319
 6320fn resize_left_dock(
 6321    new_size: Pixels,
 6322    workspace: &mut Workspace,
 6323    window: &mut Window,
 6324    cx: &mut App,
 6325) {
 6326    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
 6327
 6328    workspace.left_dock.update(cx, |left_dock, cx| {
 6329        if WorkspaceSettings::get_global(cx)
 6330            .resize_all_panels_in_dock
 6331            .contains(&DockPosition::Left)
 6332        {
 6333            left_dock.resize_all_panels(Some(size), window, cx);
 6334        } else {
 6335            left_dock.resize_active_panel(Some(size), window, cx);
 6336        }
 6337    });
 6338}
 6339
 6340impl WorkspaceStore {
 6341    pub fn new(client: Arc<Client>, cx: &mut Context<Self>) -> Self {
 6342        Self {
 6343            workspaces: Default::default(),
 6344            _subscriptions: vec![
 6345                client.add_request_handler(cx.weak_entity(), Self::handle_follow),
 6346                client.add_message_handler(cx.weak_entity(), Self::handle_update_followers),
 6347            ],
 6348            client,
 6349        }
 6350    }
 6351
 6352    pub fn update_followers(
 6353        &self,
 6354        project_id: Option<u64>,
 6355        update: proto::update_followers::Variant,
 6356        cx: &App,
 6357    ) -> Option<()> {
 6358        let active_call = ActiveCall::try_global(cx)?;
 6359        let room_id = active_call.read(cx).room()?.read(cx).id();
 6360        self.client
 6361            .send(proto::UpdateFollowers {
 6362                room_id,
 6363                project_id,
 6364                variant: Some(update),
 6365            })
 6366            .log_err()
 6367    }
 6368
 6369    pub async fn handle_follow(
 6370        this: Entity<Self>,
 6371        envelope: TypedEnvelope<proto::Follow>,
 6372        mut cx: AsyncApp,
 6373    ) -> Result<proto::FollowResponse> {
 6374        this.update(&mut cx, |this, cx| {
 6375            let follower = Follower {
 6376                project_id: envelope.payload.project_id,
 6377                peer_id: envelope.original_sender_id()?,
 6378            };
 6379
 6380            let mut response = proto::FollowResponse::default();
 6381            this.workspaces.retain(|workspace| {
 6382                workspace
 6383                    .update(cx, |workspace, window, cx| {
 6384                        let handler_response =
 6385                            workspace.handle_follow(follower.project_id, window, cx);
 6386                        if let Some(active_view) = handler_response.active_view.clone() {
 6387                            if workspace.project.read(cx).remote_id() == follower.project_id {
 6388                                response.active_view = Some(active_view)
 6389                            }
 6390                        }
 6391                    })
 6392                    .is_ok()
 6393            });
 6394
 6395            Ok(response)
 6396        })?
 6397    }
 6398
 6399    async fn handle_update_followers(
 6400        this: Entity<Self>,
 6401        envelope: TypedEnvelope<proto::UpdateFollowers>,
 6402        mut cx: AsyncApp,
 6403    ) -> Result<()> {
 6404        let leader_id = envelope.original_sender_id()?;
 6405        let update = envelope.payload;
 6406
 6407        this.update(&mut cx, |this, cx| {
 6408            this.workspaces.retain(|workspace| {
 6409                workspace
 6410                    .update(cx, |workspace, window, cx| {
 6411                        let project_id = workspace.project.read(cx).remote_id();
 6412                        if update.project_id != project_id && update.project_id.is_some() {
 6413                            return;
 6414                        }
 6415                        workspace.handle_update_followers(leader_id, update.clone(), window, cx);
 6416                    })
 6417                    .is_ok()
 6418            });
 6419            Ok(())
 6420        })?
 6421    }
 6422}
 6423
 6424impl ViewId {
 6425    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
 6426        Ok(Self {
 6427            creator: message
 6428                .creator
 6429                .map(CollaboratorId::PeerId)
 6430                .context("creator is missing")?,
 6431            id: message.id,
 6432        })
 6433    }
 6434
 6435    pub(crate) fn to_proto(self) -> Option<proto::ViewId> {
 6436        if let CollaboratorId::PeerId(peer_id) = self.creator {
 6437            Some(proto::ViewId {
 6438                creator: Some(peer_id),
 6439                id: self.id,
 6440            })
 6441        } else {
 6442            None
 6443        }
 6444    }
 6445}
 6446
 6447impl FollowerState {
 6448    fn pane(&self) -> &Entity<Pane> {
 6449        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
 6450    }
 6451}
 6452
 6453pub trait WorkspaceHandle {
 6454    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath>;
 6455}
 6456
 6457impl WorkspaceHandle for Entity<Workspace> {
 6458    fn file_project_paths(&self, cx: &App) -> Vec<ProjectPath> {
 6459        self.read(cx)
 6460            .worktrees(cx)
 6461            .flat_map(|worktree| {
 6462                let worktree_id = worktree.read(cx).id();
 6463                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
 6464                    worktree_id,
 6465                    path: f.path.clone(),
 6466                })
 6467            })
 6468            .collect::<Vec<_>>()
 6469    }
 6470}
 6471
 6472impl std::fmt::Debug for OpenPaths {
 6473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 6474        f.debug_struct("OpenPaths")
 6475            .field("paths", &self.paths)
 6476            .finish()
 6477    }
 6478}
 6479
 6480pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
 6481    DB.last_workspace().await.log_err().flatten()
 6482}
 6483
 6484pub fn last_session_workspace_locations(
 6485    last_session_id: &str,
 6486    last_session_window_stack: Option<Vec<WindowId>>,
 6487) -> Option<Vec<SerializedWorkspaceLocation>> {
 6488    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
 6489        .log_err()
 6490}
 6491
 6492actions!(
 6493    collab,
 6494    [
 6495        /// Opens the channel notes for the current call.
 6496        ///
 6497        /// If you want to open a specific channel, use `zed::OpenZedUrl` with a channel notes URL -
 6498        /// can be copied via "Copy link to section" in the context menu of the channel notes
 6499        /// buffer. These URLs look like `https://zed.dev/channel/channel-name-CHANNEL_ID/notes`.
 6500        OpenChannelNotes,
 6501        Mute,
 6502        Deafen,
 6503        LeaveCall,
 6504        ShareProject,
 6505        ScreenShare
 6506    ]
 6507);
 6508actions!(zed, [OpenLog]);
 6509
 6510async fn join_channel_internal(
 6511    channel_id: ChannelId,
 6512    app_state: &Arc<AppState>,
 6513    requesting_window: Option<WindowHandle<Workspace>>,
 6514    active_call: &Entity<ActiveCall>,
 6515    cx: &mut AsyncApp,
 6516) -> Result<bool> {
 6517    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
 6518        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
 6519            return (false, None);
 6520        };
 6521
 6522        let already_in_channel = room.channel_id() == Some(channel_id);
 6523        let should_prompt = room.is_sharing_project()
 6524            && !room.remote_participants().is_empty()
 6525            && !already_in_channel;
 6526        let open_room = if already_in_channel {
 6527            active_call.room().cloned()
 6528        } else {
 6529            None
 6530        };
 6531        (should_prompt, open_room)
 6532    })?;
 6533
 6534    if let Some(room) = open_room {
 6535        let task = room.update(cx, |room, cx| {
 6536            if let Some((project, host)) = room.most_active_project(cx) {
 6537                return Some(join_in_room_project(project, host, app_state.clone(), cx));
 6538            }
 6539
 6540            None
 6541        })?;
 6542        if let Some(task) = task {
 6543            task.await?;
 6544        }
 6545        return anyhow::Ok(true);
 6546    }
 6547
 6548    if should_prompt {
 6549        if let Some(workspace) = requesting_window {
 6550            let answer = workspace
 6551                .update(cx, |_, window, cx| {
 6552                    window.prompt(
 6553                        PromptLevel::Warning,
 6554                        "Do you want to switch channels?",
 6555                        Some("Leaving this call will unshare your current project."),
 6556                        &["Yes, Join Channel", "Cancel"],
 6557                        cx,
 6558                    )
 6559                })?
 6560                .await;
 6561
 6562            if answer == Ok(1) {
 6563                return Ok(false);
 6564            }
 6565        } else {
 6566            return Ok(false); // unreachable!() hopefully
 6567        }
 6568    }
 6569
 6570    let client = cx.update(|cx| active_call.read(cx).client())?;
 6571
 6572    let mut client_status = client.status();
 6573
 6574    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
 6575    'outer: loop {
 6576        let Some(status) = client_status.recv().await else {
 6577            anyhow::bail!("error connecting");
 6578        };
 6579
 6580        match status {
 6581            Status::Connecting
 6582            | Status::Authenticating
 6583            | Status::Reconnecting
 6584            | Status::Reauthenticating => continue,
 6585            Status::Connected { .. } => break 'outer,
 6586            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
 6587            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
 6588            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
 6589                return Err(ErrorCode::Disconnected.into());
 6590            }
 6591        }
 6592    }
 6593
 6594    let room = active_call
 6595        .update(cx, |active_call, cx| {
 6596            active_call.join_channel(channel_id, cx)
 6597        })?
 6598        .await?;
 6599
 6600    let Some(room) = room else {
 6601        return anyhow::Ok(true);
 6602    };
 6603
 6604    room.update(cx, |room, _| room.room_update_completed())?
 6605        .await;
 6606
 6607    let task = room.update(cx, |room, cx| {
 6608        if let Some((project, host)) = room.most_active_project(cx) {
 6609            return Some(join_in_room_project(project, host, app_state.clone(), cx));
 6610        }
 6611
 6612        // If you are the first to join a channel, see if you should share your project.
 6613        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
 6614            if let Some(workspace) = requesting_window {
 6615                let project = workspace.update(cx, |workspace, _, cx| {
 6616                    let project = workspace.project.read(cx);
 6617
 6618                    if !CallSettings::get_global(cx).share_on_join {
 6619                        return None;
 6620                    }
 6621
 6622                    if (project.is_local() || project.is_via_ssh())
 6623                        && project.visible_worktrees(cx).any(|tree| {
 6624                            tree.read(cx)
 6625                                .root_entry()
 6626                                .map_or(false, |entry| entry.is_dir())
 6627                        })
 6628                    {
 6629                        Some(workspace.project.clone())
 6630                    } else {
 6631                        None
 6632                    }
 6633                });
 6634                if let Ok(Some(project)) = project {
 6635                    return Some(cx.spawn(async move |room, cx| {
 6636                        room.update(cx, |room, cx| room.share_project(project, cx))?
 6637                            .await?;
 6638                        Ok(())
 6639                    }));
 6640                }
 6641            }
 6642        }
 6643
 6644        None
 6645    })?;
 6646    if let Some(task) = task {
 6647        task.await?;
 6648        return anyhow::Ok(true);
 6649    }
 6650    anyhow::Ok(false)
 6651}
 6652
 6653pub fn join_channel(
 6654    channel_id: ChannelId,
 6655    app_state: Arc<AppState>,
 6656    requesting_window: Option<WindowHandle<Workspace>>,
 6657    cx: &mut App,
 6658) -> Task<Result<()>> {
 6659    let active_call = ActiveCall::global(cx);
 6660    cx.spawn(async move |cx| {
 6661        let result = join_channel_internal(
 6662            channel_id,
 6663            &app_state,
 6664            requesting_window,
 6665            &active_call,
 6666             cx,
 6667        )
 6668            .await;
 6669
 6670        // join channel succeeded, and opened a window
 6671        if matches!(result, Ok(true)) {
 6672            return anyhow::Ok(());
 6673        }
 6674
 6675        // find an existing workspace to focus and show call controls
 6676        let mut active_window =
 6677            requesting_window.or_else(|| activate_any_workspace_window( cx));
 6678        if active_window.is_none() {
 6679            // no open workspaces, make one to show the error in (blergh)
 6680            let (window_handle, _) = cx
 6681                .update(|cx| {
 6682                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
 6683                })?
 6684                .await?;
 6685
 6686            if result.is_ok() {
 6687                cx.update(|cx| {
 6688                    cx.dispatch_action(&OpenChannelNotes);
 6689                }).log_err();
 6690            }
 6691
 6692            active_window = Some(window_handle);
 6693        }
 6694
 6695        if let Err(err) = result {
 6696            log::error!("failed to join channel: {}", err);
 6697            if let Some(active_window) = active_window {
 6698                active_window
 6699                    .update(cx, |_, window, cx| {
 6700                        let detail: SharedString = match err.error_code() {
 6701                            ErrorCode::SignedOut => {
 6702                                "Please sign in to continue.".into()
 6703                            }
 6704                            ErrorCode::UpgradeRequired => {
 6705                                "Your are running an unsupported version of Zed. Please update to continue.".into()
 6706                            }
 6707                            ErrorCode::NoSuchChannel => {
 6708                                "No matching channel was found. Please check the link and try again.".into()
 6709                            }
 6710                            ErrorCode::Forbidden => {
 6711                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
 6712                            }
 6713                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
 6714                            _ => format!("{}\n\nPlease try again.", err).into(),
 6715                        };
 6716                        window.prompt(
 6717                            PromptLevel::Critical,
 6718                            "Failed to join channel",
 6719                            Some(&detail),
 6720                            &["Ok"],
 6721                        cx)
 6722                    })?
 6723                    .await
 6724                    .ok();
 6725            }
 6726        }
 6727
 6728        // return ok, we showed the error to the user.
 6729        anyhow::Ok(())
 6730    })
 6731}
 6732
 6733pub async fn get_any_active_workspace(
 6734    app_state: Arc<AppState>,
 6735    mut cx: AsyncApp,
 6736) -> anyhow::Result<WindowHandle<Workspace>> {
 6737    // find an existing workspace to focus and show call controls
 6738    let active_window = activate_any_workspace_window(&mut cx);
 6739    if active_window.is_none() {
 6740        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
 6741            .await?;
 6742    }
 6743    activate_any_workspace_window(&mut cx).context("could not open zed")
 6744}
 6745
 6746fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Workspace>> {
 6747    cx.update(|cx| {
 6748        if let Some(workspace_window) = cx
 6749            .active_window()
 6750            .and_then(|window| window.downcast::<Workspace>())
 6751        {
 6752            return Some(workspace_window);
 6753        }
 6754
 6755        for window in cx.windows() {
 6756            if let Some(workspace_window) = window.downcast::<Workspace>() {
 6757                workspace_window
 6758                    .update(cx, |_, window, _| window.activate_window())
 6759                    .ok();
 6760                return Some(workspace_window);
 6761            }
 6762        }
 6763        None
 6764    })
 6765    .ok()
 6766    .flatten()
 6767}
 6768
 6769pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
 6770    cx.windows()
 6771        .into_iter()
 6772        .filter_map(|window| window.downcast::<Workspace>())
 6773        .filter(|workspace| {
 6774            workspace
 6775                .read(cx)
 6776                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
 6777        })
 6778        .collect()
 6779}
 6780
 6781#[derive(Default)]
 6782pub struct OpenOptions {
 6783    pub visible: Option<OpenVisible>,
 6784    pub focus: Option<bool>,
 6785    pub open_new_workspace: Option<bool>,
 6786    pub replace_window: Option<WindowHandle<Workspace>>,
 6787    pub env: Option<HashMap<String, String>>,
 6788}
 6789
 6790#[allow(clippy::type_complexity)]
 6791pub fn open_paths(
 6792    abs_paths: &[PathBuf],
 6793    app_state: Arc<AppState>,
 6794    open_options: OpenOptions,
 6795    cx: &mut App,
 6796) -> Task<
 6797    anyhow::Result<(
 6798        WindowHandle<Workspace>,
 6799        Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>>,
 6800    )>,
 6801> {
 6802    let abs_paths = abs_paths.to_vec();
 6803    let mut existing = None;
 6804    let mut best_match = None;
 6805    let mut open_visible = OpenVisible::All;
 6806
 6807    cx.spawn(async move |cx| {
 6808        if open_options.open_new_workspace != Some(true) {
 6809            let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path));
 6810            let all_metadatas = futures::future::join_all(all_paths)
 6811                .await
 6812                .into_iter()
 6813                .filter_map(|result| result.ok().flatten())
 6814                .collect::<Vec<_>>();
 6815
 6816            cx.update(|cx| {
 6817                for window in local_workspace_windows(&cx) {
 6818                    if let Ok(workspace) = window.read(&cx) {
 6819                        let m = workspace.project.read(&cx).visibility_for_paths(
 6820                            &abs_paths,
 6821                            &all_metadatas,
 6822                            open_options.open_new_workspace == None,
 6823                            cx,
 6824                        );
 6825                        if m > best_match {
 6826                            existing = Some(window);
 6827                            best_match = m;
 6828                        } else if best_match.is_none()
 6829                            && open_options.open_new_workspace == Some(false)
 6830                        {
 6831                            existing = Some(window)
 6832                        }
 6833                    }
 6834                }
 6835            })?;
 6836
 6837            if open_options.open_new_workspace.is_none() && existing.is_none() {
 6838                if all_metadatas.iter().all(|file| !file.is_dir) {
 6839                    cx.update(|cx| {
 6840                        if let Some(window) = cx
 6841                            .active_window()
 6842                            .and_then(|window| window.downcast::<Workspace>())
 6843                        {
 6844                            if let Ok(workspace) = window.read(cx) {
 6845                                let project = workspace.project().read(cx);
 6846                                if project.is_local() && !project.is_via_collab() {
 6847                                    existing = Some(window);
 6848                                    open_visible = OpenVisible::None;
 6849                                    return;
 6850                                }
 6851                            }
 6852                        }
 6853                        for window in local_workspace_windows(cx) {
 6854                            if let Ok(workspace) = window.read(cx) {
 6855                                let project = workspace.project().read(cx);
 6856                                if project.is_via_collab() {
 6857                                    continue;
 6858                                }
 6859                                existing = Some(window);
 6860                                open_visible = OpenVisible::None;
 6861                                break;
 6862                            }
 6863                        }
 6864                    })?;
 6865                }
 6866            }
 6867        }
 6868
 6869        if let Some(existing) = existing {
 6870            let open_task = existing
 6871                .update(cx, |workspace, window, cx| {
 6872                    window.activate_window();
 6873                    workspace.open_paths(
 6874                        abs_paths,
 6875                        OpenOptions {
 6876                            visible: Some(open_visible),
 6877                            ..Default::default()
 6878                        },
 6879                        None,
 6880                        window,
 6881                        cx,
 6882                    )
 6883                })?
 6884                .await;
 6885
 6886            _ = existing.update(cx, |workspace, _, cx| {
 6887                for item in open_task.iter().flatten() {
 6888                    if let Err(e) = item {
 6889                        workspace.show_error(&e, cx);
 6890                    }
 6891                }
 6892            });
 6893
 6894            Ok((existing, open_task))
 6895        } else {
 6896            cx.update(move |cx| {
 6897                Workspace::new_local(
 6898                    abs_paths,
 6899                    app_state.clone(),
 6900                    open_options.replace_window,
 6901                    open_options.env,
 6902                    cx,
 6903                )
 6904            })?
 6905            .await
 6906        }
 6907    })
 6908}
 6909
 6910pub fn open_new(
 6911    open_options: OpenOptions,
 6912    app_state: Arc<AppState>,
 6913    cx: &mut App,
 6914    init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
 6915) -> Task<anyhow::Result<()>> {
 6916    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
 6917    cx.spawn(async move |cx| {
 6918        let (workspace, opened_paths) = task.await?;
 6919        workspace.update(cx, |workspace, window, cx| {
 6920            if opened_paths.is_empty() {
 6921                init(workspace, window, cx)
 6922            }
 6923        })?;
 6924        Ok(())
 6925    })
 6926}
 6927
 6928pub fn create_and_open_local_file(
 6929    path: &'static Path,
 6930    window: &mut Window,
 6931    cx: &mut Context<Workspace>,
 6932    default_content: impl 'static + Send + FnOnce() -> Rope,
 6933) -> Task<Result<Box<dyn ItemHandle>>> {
 6934    cx.spawn_in(window, async move |workspace, cx| {
 6935        let fs = workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
 6936        if !fs.is_file(path).await {
 6937            fs.create_file(path, Default::default()).await?;
 6938            fs.save(path, &default_content(), Default::default())
 6939                .await?;
 6940        }
 6941
 6942        let mut items = workspace
 6943            .update_in(cx, |workspace, window, cx| {
 6944                workspace.with_local_workspace(window, cx, |workspace, window, cx| {
 6945                    workspace.open_paths(
 6946                        vec![path.to_path_buf()],
 6947                        OpenOptions {
 6948                            visible: Some(OpenVisible::None),
 6949                            ..Default::default()
 6950                        },
 6951                        None,
 6952                        window,
 6953                        cx,
 6954                    )
 6955                })
 6956            })?
 6957            .await?
 6958            .await;
 6959
 6960        let item = items.pop().flatten();
 6961        item.with_context(|| format!("path {path:?} is not a file"))?
 6962    })
 6963}
 6964
 6965pub fn open_ssh_project_with_new_connection(
 6966    window: WindowHandle<Workspace>,
 6967    connection_options: SshConnectionOptions,
 6968    cancel_rx: oneshot::Receiver<()>,
 6969    delegate: Arc<dyn SshClientDelegate>,
 6970    app_state: Arc<AppState>,
 6971    paths: Vec<PathBuf>,
 6972    cx: &mut App,
 6973) -> Task<Result<()>> {
 6974    cx.spawn(async move |cx| {
 6975        let (serialized_ssh_project, workspace_id, serialized_workspace) =
 6976            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
 6977
 6978        let session = match cx
 6979            .update(|cx| {
 6980                remote::SshRemoteClient::new(
 6981                    ConnectionIdentifier::Workspace(workspace_id.0),
 6982                    connection_options,
 6983                    cancel_rx,
 6984                    delegate,
 6985                    cx,
 6986                )
 6987            })?
 6988            .await?
 6989        {
 6990            Some(result) => result,
 6991            None => return Ok(()),
 6992        };
 6993
 6994        let project = cx.update(|cx| {
 6995            project::Project::ssh(
 6996                session,
 6997                app_state.client.clone(),
 6998                app_state.node_runtime.clone(),
 6999                app_state.user_store.clone(),
 7000                app_state.languages.clone(),
 7001                app_state.fs.clone(),
 7002                cx,
 7003            )
 7004        })?;
 7005
 7006        open_ssh_project_inner(
 7007            project,
 7008            paths,
 7009            serialized_ssh_project,
 7010            workspace_id,
 7011            serialized_workspace,
 7012            app_state,
 7013            window,
 7014            cx,
 7015        )
 7016        .await
 7017    })
 7018}
 7019
 7020pub fn open_ssh_project_with_existing_connection(
 7021    connection_options: SshConnectionOptions,
 7022    project: Entity<Project>,
 7023    paths: Vec<PathBuf>,
 7024    app_state: Arc<AppState>,
 7025    window: WindowHandle<Workspace>,
 7026    cx: &mut AsyncApp,
 7027) -> Task<Result<()>> {
 7028    cx.spawn(async move |cx| {
 7029        let (serialized_ssh_project, workspace_id, serialized_workspace) =
 7030            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
 7031
 7032        open_ssh_project_inner(
 7033            project,
 7034            paths,
 7035            serialized_ssh_project,
 7036            workspace_id,
 7037            serialized_workspace,
 7038            app_state,
 7039            window,
 7040            cx,
 7041        )
 7042        .await
 7043    })
 7044}
 7045
 7046async fn open_ssh_project_inner(
 7047    project: Entity<Project>,
 7048    paths: Vec<PathBuf>,
 7049    serialized_ssh_project: SerializedSshProject,
 7050    workspace_id: WorkspaceId,
 7051    serialized_workspace: Option<SerializedWorkspace>,
 7052    app_state: Arc<AppState>,
 7053    window: WindowHandle<Workspace>,
 7054    cx: &mut AsyncApp,
 7055) -> Result<()> {
 7056    let toolchains = DB.toolchains(workspace_id).await?;
 7057    for (toolchain, worktree_id, path) in toolchains {
 7058        project
 7059            .update(cx, |this, cx| {
 7060                this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
 7061            })?
 7062            .await;
 7063    }
 7064    let mut project_paths_to_open = vec![];
 7065    let mut project_path_errors = vec![];
 7066
 7067    for path in paths {
 7068        let result = cx
 7069            .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
 7070            .await;
 7071        match result {
 7072            Ok((_, project_path)) => {
 7073                project_paths_to_open.push((path.clone(), Some(project_path)));
 7074            }
 7075            Err(error) => {
 7076                project_path_errors.push(error);
 7077            }
 7078        };
 7079    }
 7080
 7081    if project_paths_to_open.is_empty() {
 7082        return Err(project_path_errors.pop().context("no paths given")?);
 7083    }
 7084
 7085    cx.update_window(window.into(), |_, window, cx| {
 7086        window.replace_root(cx, |window, cx| {
 7087            telemetry::event!("SSH Project Opened");
 7088
 7089            let mut workspace =
 7090                Workspace::new(Some(workspace_id), project, app_state.clone(), window, cx);
 7091            workspace.set_serialized_ssh_project(serialized_ssh_project);
 7092            workspace.update_history(cx);
 7093
 7094            if let Some(ref serialized) = serialized_workspace {
 7095                workspace.centered_layout = serialized.centered_layout;
 7096            }
 7097
 7098            workspace
 7099        });
 7100    })?;
 7101
 7102    window
 7103        .update(cx, |_, window, cx| {
 7104            window.activate_window();
 7105            open_items(serialized_workspace, project_paths_to_open, window, cx)
 7106        })?
 7107        .await?;
 7108
 7109    window.update(cx, |workspace, _, cx| {
 7110        for error in project_path_errors {
 7111            if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
 7112                if let Some(path) = error.error_tag("path") {
 7113                    workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
 7114                }
 7115            } else {
 7116                workspace.show_error(&error, cx)
 7117            }
 7118        }
 7119    })?;
 7120
 7121    Ok(())
 7122}
 7123
 7124fn serialize_ssh_project(
 7125    connection_options: SshConnectionOptions,
 7126    paths: Vec<PathBuf>,
 7127    cx: &AsyncApp,
 7128) -> Task<
 7129    Result<(
 7130        SerializedSshProject,
 7131        WorkspaceId,
 7132        Option<SerializedWorkspace>,
 7133    )>,
 7134> {
 7135    cx.background_spawn(async move {
 7136        let serialized_ssh_project = persistence::DB
 7137            .get_or_create_ssh_project(
 7138                connection_options.host.clone(),
 7139                connection_options.port,
 7140                paths
 7141                    .iter()
 7142                    .map(|path| path.to_string_lossy().to_string())
 7143                    .collect::<Vec<_>>(),
 7144                connection_options.username.clone(),
 7145            )
 7146            .await?;
 7147
 7148        let serialized_workspace =
 7149            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
 7150
 7151        let workspace_id = if let Some(workspace_id) =
 7152            serialized_workspace.as_ref().map(|workspace| workspace.id)
 7153        {
 7154            workspace_id
 7155        } else {
 7156            persistence::DB.next_id().await?
 7157        };
 7158
 7159        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
 7160    })
 7161}
 7162
 7163pub fn join_in_room_project(
 7164    project_id: u64,
 7165    follow_user_id: u64,
 7166    app_state: Arc<AppState>,
 7167    cx: &mut App,
 7168) -> Task<Result<()>> {
 7169    let windows = cx.windows();
 7170    cx.spawn(async move |cx| {
 7171        let existing_workspace = windows.into_iter().find_map(|window_handle| {
 7172            window_handle
 7173                .downcast::<Workspace>()
 7174                .and_then(|window_handle| {
 7175                    window_handle
 7176                        .update(cx, |workspace, _window, cx| {
 7177                            if workspace.project().read(cx).remote_id() == Some(project_id) {
 7178                                Some(window_handle)
 7179                            } else {
 7180                                None
 7181                            }
 7182                        })
 7183                        .unwrap_or(None)
 7184                })
 7185        });
 7186
 7187        let workspace = if let Some(existing_workspace) = existing_workspace {
 7188            existing_workspace
 7189        } else {
 7190            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
 7191            let room = active_call
 7192                .read_with(cx, |call, _| call.room().cloned())?
 7193                .context("not in a call")?;
 7194            let project = room
 7195                .update(cx, |room, cx| {
 7196                    room.join_project(
 7197                        project_id,
 7198                        app_state.languages.clone(),
 7199                        app_state.fs.clone(),
 7200                        cx,
 7201                    )
 7202                })?
 7203                .await?;
 7204
 7205            let window_bounds_override = window_bounds_env_override();
 7206            cx.update(|cx| {
 7207                let mut options = (app_state.build_window_options)(None, cx);
 7208                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
 7209                cx.open_window(options, |window, cx| {
 7210                    cx.new(|cx| {
 7211                        Workspace::new(Default::default(), project, app_state.clone(), window, cx)
 7212                    })
 7213                })
 7214            })??
 7215        };
 7216
 7217        workspace.update(cx, |workspace, window, cx| {
 7218            cx.activate(true);
 7219            window.activate_window();
 7220
 7221            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
 7222                let follow_peer_id = room
 7223                    .read(cx)
 7224                    .remote_participants()
 7225                    .iter()
 7226                    .find(|(_, participant)| participant.user.id == follow_user_id)
 7227                    .map(|(_, p)| p.peer_id)
 7228                    .or_else(|| {
 7229                        // If we couldn't follow the given user, follow the host instead.
 7230                        let collaborator = workspace
 7231                            .project()
 7232                            .read(cx)
 7233                            .collaborators()
 7234                            .values()
 7235                            .find(|collaborator| collaborator.is_host)?;
 7236                        Some(collaborator.peer_id)
 7237                    });
 7238
 7239                if let Some(follow_peer_id) = follow_peer_id {
 7240                    workspace.follow(follow_peer_id, window, cx);
 7241                }
 7242            }
 7243        })?;
 7244
 7245        anyhow::Ok(())
 7246    })
 7247}
 7248
 7249pub fn reload(reload: &Reload, cx: &mut App) {
 7250    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
 7251    let mut workspace_windows = cx
 7252        .windows()
 7253        .into_iter()
 7254        .filter_map(|window| window.downcast::<Workspace>())
 7255        .collect::<Vec<_>>();
 7256
 7257    // If multiple windows have unsaved changes, and need a save prompt,
 7258    // prompt in the active window before switching to a different window.
 7259    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
 7260
 7261    let mut prompt = None;
 7262    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
 7263        prompt = window
 7264            .update(cx, |_, window, cx| {
 7265                window.prompt(
 7266                    PromptLevel::Info,
 7267                    "Are you sure you want to restart?",
 7268                    None,
 7269                    &["Restart", "Cancel"],
 7270                    cx,
 7271                )
 7272            })
 7273            .ok();
 7274    }
 7275
 7276    let binary_path = reload.binary_path.clone();
 7277    cx.spawn(async move |cx| {
 7278        if let Some(prompt) = prompt {
 7279            let answer = prompt.await?;
 7280            if answer != 0 {
 7281                return Ok(());
 7282            }
 7283        }
 7284
 7285        // If the user cancels any save prompt, then keep the app open.
 7286        for window in workspace_windows {
 7287            if let Ok(should_close) = window.update(cx, |workspace, window, cx| {
 7288                workspace.prepare_to_close(CloseIntent::Quit, window, cx)
 7289            }) {
 7290                if !should_close.await? {
 7291                    return Ok(());
 7292                }
 7293            }
 7294        }
 7295
 7296        cx.update(|cx| cx.restart(binary_path))
 7297    })
 7298    .detach_and_log_err(cx);
 7299}
 7300
 7301fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
 7302    let mut parts = value.split(',');
 7303    let x: usize = parts.next()?.parse().ok()?;
 7304    let y: usize = parts.next()?.parse().ok()?;
 7305    Some(point(px(x as f32), px(y as f32)))
 7306}
 7307
 7308fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
 7309    let mut parts = value.split(',');
 7310    let width: usize = parts.next()?.parse().ok()?;
 7311    let height: usize = parts.next()?.parse().ok()?;
 7312    Some(size(px(width as f32), px(height as f32)))
 7313}
 7314
 7315pub fn client_side_decorations(
 7316    element: impl IntoElement,
 7317    window: &mut Window,
 7318    cx: &mut App,
 7319) -> Stateful<Div> {
 7320    const BORDER_SIZE: Pixels = px(1.0);
 7321    let decorations = window.window_decorations();
 7322
 7323    if matches!(decorations, Decorations::Client { .. }) {
 7324        window.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
 7325    }
 7326
 7327    struct GlobalResizeEdge(ResizeEdge);
 7328    impl Global for GlobalResizeEdge {}
 7329
 7330    div()
 7331        .id("window-backdrop")
 7332        .bg(transparent_black())
 7333        .map(|div| match decorations {
 7334            Decorations::Server => div,
 7335            Decorations::Client { tiling, .. } => div
 7336                .when(!(tiling.top || tiling.right), |div| {
 7337                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7338                })
 7339                .when(!(tiling.top || tiling.left), |div| {
 7340                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7341                })
 7342                .when(!(tiling.bottom || tiling.right), |div| {
 7343                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7344                })
 7345                .when(!(tiling.bottom || tiling.left), |div| {
 7346                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7347                })
 7348                .when(!tiling.top, |div| {
 7349                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7350                })
 7351                .when(!tiling.bottom, |div| {
 7352                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7353                })
 7354                .when(!tiling.left, |div| {
 7355                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7356                })
 7357                .when(!tiling.right, |div| {
 7358                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
 7359                })
 7360                .on_mouse_move(move |e, window, cx| {
 7361                    let size = window.window_bounds().get_bounds().size;
 7362                    let pos = e.position;
 7363
 7364                    let new_edge =
 7365                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
 7366
 7367                    let edge = cx.try_global::<GlobalResizeEdge>();
 7368                    if new_edge != edge.map(|edge| edge.0) {
 7369                        window
 7370                            .window_handle()
 7371                            .update(cx, |workspace, _, cx| {
 7372                                cx.notify(workspace.entity_id());
 7373                            })
 7374                            .ok();
 7375                    }
 7376                })
 7377                .on_mouse_down(MouseButton::Left, move |e, window, _| {
 7378                    let size = window.window_bounds().get_bounds().size;
 7379                    let pos = e.position;
 7380
 7381                    let edge = match resize_edge(
 7382                        pos,
 7383                        theme::CLIENT_SIDE_DECORATION_SHADOW,
 7384                        size,
 7385                        tiling,
 7386                    ) {
 7387                        Some(value) => value,
 7388                        None => return,
 7389                    };
 7390
 7391                    window.start_window_resize(edge);
 7392                }),
 7393        })
 7394        .size_full()
 7395        .child(
 7396            div()
 7397                .cursor(CursorStyle::Arrow)
 7398                .map(|div| match decorations {
 7399                    Decorations::Server => div,
 7400                    Decorations::Client { tiling } => div
 7401                        .border_color(cx.theme().colors().border)
 7402                        .when(!(tiling.top || tiling.right), |div| {
 7403                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7404                        })
 7405                        .when(!(tiling.top || tiling.left), |div| {
 7406                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7407                        })
 7408                        .when(!(tiling.bottom || tiling.right), |div| {
 7409                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7410                        })
 7411                        .when(!(tiling.bottom || tiling.left), |div| {
 7412                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
 7413                        })
 7414                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
 7415                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
 7416                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
 7417                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
 7418                        .when(!tiling.is_tiled(), |div| {
 7419                            div.shadow(vec![gpui::BoxShadow {
 7420                                color: Hsla {
 7421                                    h: 0.,
 7422                                    s: 0.,
 7423                                    l: 0.,
 7424                                    a: 0.4,
 7425                                },
 7426                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
 7427                                spread_radius: px(0.),
 7428                                offset: point(px(0.0), px(0.0)),
 7429                            }])
 7430                        }),
 7431                })
 7432                .on_mouse_move(|_e, _, cx| {
 7433                    cx.stop_propagation();
 7434                })
 7435                .size_full()
 7436                .child(element),
 7437        )
 7438        .map(|div| match decorations {
 7439            Decorations::Server => div,
 7440            Decorations::Client { tiling, .. } => div.child(
 7441                canvas(
 7442                    |_bounds, window, _| {
 7443                        window.insert_hitbox(
 7444                            Bounds::new(
 7445                                point(px(0.0), px(0.0)),
 7446                                window.window_bounds().get_bounds().size,
 7447                            ),
 7448                            HitboxBehavior::Normal,
 7449                        )
 7450                    },
 7451                    move |_bounds, hitbox, window, cx| {
 7452                        let mouse = window.mouse_position();
 7453                        let size = window.window_bounds().get_bounds().size;
 7454                        let Some(edge) =
 7455                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
 7456                        else {
 7457                            return;
 7458                        };
 7459                        cx.set_global(GlobalResizeEdge(edge));
 7460                        window.set_cursor_style(
 7461                            match edge {
 7462                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
 7463                                ResizeEdge::Left | ResizeEdge::Right => {
 7464                                    CursorStyle::ResizeLeftRight
 7465                                }
 7466                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
 7467                                    CursorStyle::ResizeUpLeftDownRight
 7468                                }
 7469                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
 7470                                    CursorStyle::ResizeUpRightDownLeft
 7471                                }
 7472                            },
 7473                            &hitbox,
 7474                        );
 7475                    },
 7476                )
 7477                .size_full()
 7478                .absolute(),
 7479            ),
 7480        })
 7481}
 7482
 7483fn resize_edge(
 7484    pos: Point<Pixels>,
 7485    shadow_size: Pixels,
 7486    window_size: Size<Pixels>,
 7487    tiling: Tiling,
 7488) -> Option<ResizeEdge> {
 7489    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
 7490    if bounds.contains(&pos) {
 7491        return None;
 7492    }
 7493
 7494    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
 7495    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
 7496    if !tiling.top && top_left_bounds.contains(&pos) {
 7497        return Some(ResizeEdge::TopLeft);
 7498    }
 7499
 7500    let top_right_bounds = Bounds::new(
 7501        Point::new(window_size.width - corner_size.width, px(0.)),
 7502        corner_size,
 7503    );
 7504    if !tiling.top && top_right_bounds.contains(&pos) {
 7505        return Some(ResizeEdge::TopRight);
 7506    }
 7507
 7508    let bottom_left_bounds = Bounds::new(
 7509        Point::new(px(0.), window_size.height - corner_size.height),
 7510        corner_size,
 7511    );
 7512    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
 7513        return Some(ResizeEdge::BottomLeft);
 7514    }
 7515
 7516    let bottom_right_bounds = Bounds::new(
 7517        Point::new(
 7518            window_size.width - corner_size.width,
 7519            window_size.height - corner_size.height,
 7520        ),
 7521        corner_size,
 7522    );
 7523    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
 7524        return Some(ResizeEdge::BottomRight);
 7525    }
 7526
 7527    if !tiling.top && pos.y < shadow_size {
 7528        Some(ResizeEdge::Top)
 7529    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
 7530        Some(ResizeEdge::Bottom)
 7531    } else if !tiling.left && pos.x < shadow_size {
 7532        Some(ResizeEdge::Left)
 7533    } else if !tiling.right && pos.x > window_size.width - shadow_size {
 7534        Some(ResizeEdge::Right)
 7535    } else {
 7536        None
 7537    }
 7538}
 7539
 7540fn join_pane_into_active(
 7541    active_pane: &Entity<Pane>,
 7542    pane: &Entity<Pane>,
 7543    window: &mut Window,
 7544    cx: &mut App,
 7545) {
 7546    if pane == active_pane {
 7547        return;
 7548    } else if pane.read(cx).items_len() == 0 {
 7549        pane.update(cx, |_, cx| {
 7550            cx.emit(pane::Event::Remove {
 7551                focus_on_pane: None,
 7552            });
 7553        })
 7554    } else {
 7555        move_all_items(pane, active_pane, window, cx);
 7556    }
 7557}
 7558
 7559fn move_all_items(
 7560    from_pane: &Entity<Pane>,
 7561    to_pane: &Entity<Pane>,
 7562    window: &mut Window,
 7563    cx: &mut App,
 7564) {
 7565    let destination_is_different = from_pane != to_pane;
 7566    let mut moved_items = 0;
 7567    for (item_ix, item_handle) in from_pane
 7568        .read(cx)
 7569        .items()
 7570        .enumerate()
 7571        .map(|(ix, item)| (ix, item.clone()))
 7572        .collect::<Vec<_>>()
 7573    {
 7574        let ix = item_ix - moved_items;
 7575        if destination_is_different {
 7576            // Close item from previous pane
 7577            from_pane.update(cx, |source, cx| {
 7578                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), window, cx);
 7579            });
 7580            moved_items += 1;
 7581        }
 7582
 7583        // This automatically removes duplicate items in the pane
 7584        to_pane.update(cx, |destination, cx| {
 7585            destination.add_item(item_handle, true, true, None, window, cx);
 7586            window.focus(&destination.focus_handle(cx))
 7587        });
 7588    }
 7589}
 7590
 7591pub fn move_item(
 7592    source: &Entity<Pane>,
 7593    destination: &Entity<Pane>,
 7594    item_id_to_move: EntityId,
 7595    destination_index: usize,
 7596    activate: bool,
 7597    window: &mut Window,
 7598    cx: &mut App,
 7599) {
 7600    let Some((item_ix, item_handle)) = source
 7601        .read(cx)
 7602        .items()
 7603        .enumerate()
 7604        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
 7605        .map(|(ix, item)| (ix, item.clone()))
 7606    else {
 7607        // Tab was closed during drag
 7608        return;
 7609    };
 7610
 7611    if source != destination {
 7612        // Close item from previous pane
 7613        source.update(cx, |source, cx| {
 7614            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), window, cx);
 7615        });
 7616    }
 7617
 7618    // This automatically removes duplicate items in the pane
 7619    destination.update(cx, |destination, cx| {
 7620        destination.add_item_inner(
 7621            item_handle,
 7622            activate,
 7623            activate,
 7624            activate,
 7625            Some(destination_index),
 7626            window,
 7627            cx,
 7628        );
 7629        if activate {
 7630            window.focus(&destination.focus_handle(cx))
 7631        }
 7632    });
 7633}
 7634
 7635pub fn move_active_item(
 7636    source: &Entity<Pane>,
 7637    destination: &Entity<Pane>,
 7638    focus_destination: bool,
 7639    close_if_empty: bool,
 7640    window: &mut Window,
 7641    cx: &mut App,
 7642) {
 7643    if source == destination {
 7644        return;
 7645    }
 7646    let Some(active_item) = source.read(cx).active_item() else {
 7647        return;
 7648    };
 7649    source.update(cx, |source_pane, cx| {
 7650        let item_id = active_item.item_id();
 7651        source_pane.remove_item(item_id, false, close_if_empty, window, cx);
 7652        destination.update(cx, |target_pane, cx| {
 7653            target_pane.add_item(
 7654                active_item,
 7655                focus_destination,
 7656                focus_destination,
 7657                Some(target_pane.items_len()),
 7658                window,
 7659                cx,
 7660            );
 7661        });
 7662    });
 7663}
 7664
 7665pub fn clone_active_item(
 7666    workspace_id: Option<WorkspaceId>,
 7667    source: &Entity<Pane>,
 7668    destination: &Entity<Pane>,
 7669    focus_destination: bool,
 7670    window: &mut Window,
 7671    cx: &mut App,
 7672) {
 7673    if source == destination {
 7674        return;
 7675    }
 7676    let Some(active_item) = source.read(cx).active_item() else {
 7677        return;
 7678    };
 7679    destination.update(cx, |target_pane, cx| {
 7680        let Some(clone) = active_item.clone_on_split(workspace_id, window, cx) else {
 7681            return;
 7682        };
 7683        target_pane.add_item(
 7684            clone,
 7685            focus_destination,
 7686            focus_destination,
 7687            Some(target_pane.items_len()),
 7688            window,
 7689            cx,
 7690        );
 7691    });
 7692}
 7693
 7694#[derive(Debug)]
 7695pub struct WorkspacePosition {
 7696    pub window_bounds: Option<WindowBounds>,
 7697    pub display: Option<Uuid>,
 7698    pub centered_layout: bool,
 7699}
 7700
 7701pub fn ssh_workspace_position_from_db(
 7702    host: String,
 7703    port: Option<u16>,
 7704    user: Option<String>,
 7705    paths_to_open: &[PathBuf],
 7706    cx: &App,
 7707) -> Task<Result<WorkspacePosition>> {
 7708    let paths = paths_to_open
 7709        .iter()
 7710        .map(|path| path.to_string_lossy().to_string())
 7711        .collect::<Vec<_>>();
 7712
 7713    cx.background_spawn(async move {
 7714        let serialized_ssh_project = persistence::DB
 7715            .get_or_create_ssh_project(host, port, paths, user)
 7716            .await
 7717            .context("fetching serialized ssh project")?;
 7718        let serialized_workspace =
 7719            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
 7720
 7721        let (window_bounds, display) = if let Some(bounds) = window_bounds_env_override() {
 7722            (Some(WindowBounds::Windowed(bounds)), None)
 7723        } else {
 7724            let restorable_bounds = serialized_workspace
 7725                .as_ref()
 7726                .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?)))
 7727                .or_else(|| {
 7728                    let (display, window_bounds) = DB.last_window().log_err()?;
 7729                    Some((display?, window_bounds?))
 7730                });
 7731
 7732            if let Some((serialized_display, serialized_status)) = restorable_bounds {
 7733                (Some(serialized_status.0), Some(serialized_display))
 7734            } else {
 7735                (None, None)
 7736            }
 7737        };
 7738
 7739        let centered_layout = serialized_workspace
 7740            .as_ref()
 7741            .map(|w| w.centered_layout)
 7742            .unwrap_or(false);
 7743
 7744        Ok(WorkspacePosition {
 7745            window_bounds,
 7746            display,
 7747            centered_layout,
 7748        })
 7749    })
 7750}
 7751
 7752pub fn with_active_or_new_workspace(
 7753    cx: &mut App,
 7754    f: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send + 'static,
 7755) {
 7756    match cx.active_window().and_then(|w| w.downcast::<Workspace>()) {
 7757        Some(workspace) => {
 7758            cx.defer(move |cx| {
 7759                workspace
 7760                    .update(cx, |workspace, window, cx| f(workspace, window, cx))
 7761                    .log_err();
 7762            });
 7763        }
 7764        None => {
 7765            let app_state = AppState::global(cx);
 7766            if let Some(app_state) = app_state.upgrade() {
 7767                open_new(
 7768                    OpenOptions::default(),
 7769                    app_state,
 7770                    cx,
 7771                    move |workspace, window, cx| f(workspace, window, cx),
 7772                )
 7773                .detach_and_log_err(cx);
 7774            }
 7775        }
 7776    }
 7777}
 7778
 7779#[cfg(test)]
 7780mod tests {
 7781    use std::{cell::RefCell, rc::Rc};
 7782
 7783    use super::*;
 7784    use crate::{
 7785        dock::{PanelEvent, test::TestPanel},
 7786        item::{
 7787            ItemEvent,
 7788            test::{TestItem, TestProjectItem},
 7789        },
 7790    };
 7791    use fs::FakeFs;
 7792    use gpui::{
 7793        DismissEvent, Empty, EventEmitter, FocusHandle, Focusable, Render, TestAppContext,
 7794        UpdateGlobal, VisualTestContext, px,
 7795    };
 7796    use project::{Project, ProjectEntryId};
 7797    use serde_json::json;
 7798    use settings::SettingsStore;
 7799
 7800    #[gpui::test]
 7801    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
 7802        init_test(cx);
 7803
 7804        let fs = FakeFs::new(cx.executor());
 7805        let project = Project::test(fs, [], cx).await;
 7806        let (workspace, cx) =
 7807            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7808
 7809        // Adding an item with no ambiguity renders the tab without detail.
 7810        let item1 = cx.new(|cx| {
 7811            let mut item = TestItem::new(cx);
 7812            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
 7813            item
 7814        });
 7815        workspace.update_in(cx, |workspace, window, cx| {
 7816            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
 7817        });
 7818        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
 7819
 7820        // Adding an item that creates ambiguity increases the level of detail on
 7821        // both tabs.
 7822        let item2 = cx.new_window_entity(|_window, cx| {
 7823            let mut item = TestItem::new(cx);
 7824            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
 7825            item
 7826        });
 7827        workspace.update_in(cx, |workspace, window, cx| {
 7828            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 7829        });
 7830        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
 7831        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
 7832
 7833        // Adding an item that creates ambiguity increases the level of detail only
 7834        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
 7835        // we stop at the highest detail available.
 7836        let item3 = cx.new(|cx| {
 7837            let mut item = TestItem::new(cx);
 7838            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
 7839            item
 7840        });
 7841        workspace.update_in(cx, |workspace, window, cx| {
 7842            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
 7843        });
 7844        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
 7845        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
 7846        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
 7847    }
 7848
 7849    #[gpui::test]
 7850    async fn test_tracking_active_path(cx: &mut TestAppContext) {
 7851        init_test(cx);
 7852
 7853        let fs = FakeFs::new(cx.executor());
 7854        fs.insert_tree(
 7855            "/root1",
 7856            json!({
 7857                "one.txt": "",
 7858                "two.txt": "",
 7859            }),
 7860        )
 7861        .await;
 7862        fs.insert_tree(
 7863            "/root2",
 7864            json!({
 7865                "three.txt": "",
 7866            }),
 7867        )
 7868        .await;
 7869
 7870        let project = Project::test(fs, ["root1".as_ref()], cx).await;
 7871        let (workspace, cx) =
 7872            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7873        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 7874        let worktree_id = project.update(cx, |project, cx| {
 7875            project.worktrees(cx).next().unwrap().read(cx).id()
 7876        });
 7877
 7878        let item1 = cx.new(|cx| {
 7879            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
 7880        });
 7881        let item2 = cx.new(|cx| {
 7882            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
 7883        });
 7884
 7885        // Add an item to an empty pane
 7886        workspace.update_in(cx, |workspace, window, cx| {
 7887            workspace.add_item_to_active_pane(Box::new(item1), None, true, window, cx)
 7888        });
 7889        project.update(cx, |project, cx| {
 7890            assert_eq!(
 7891                project.active_entry(),
 7892                project
 7893                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
 7894                    .map(|e| e.id)
 7895            );
 7896        });
 7897        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
 7898
 7899        // Add a second item to a non-empty pane
 7900        workspace.update_in(cx, |workspace, window, cx| {
 7901            workspace.add_item_to_active_pane(Box::new(item2), None, true, window, cx)
 7902        });
 7903        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
 7904        project.update(cx, |project, cx| {
 7905            assert_eq!(
 7906                project.active_entry(),
 7907                project
 7908                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
 7909                    .map(|e| e.id)
 7910            );
 7911        });
 7912
 7913        // Close the active item
 7914        pane.update_in(cx, |pane, window, cx| {
 7915            pane.close_active_item(&Default::default(), window, cx)
 7916        })
 7917        .await
 7918        .unwrap();
 7919        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
 7920        project.update(cx, |project, cx| {
 7921            assert_eq!(
 7922                project.active_entry(),
 7923                project
 7924                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
 7925                    .map(|e| e.id)
 7926            );
 7927        });
 7928
 7929        // Add a project folder
 7930        project
 7931            .update(cx, |project, cx| {
 7932                project.find_or_create_worktree("root2", true, cx)
 7933            })
 7934            .await
 7935            .unwrap();
 7936        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
 7937
 7938        // Remove a project folder
 7939        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
 7940        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
 7941    }
 7942
 7943    #[gpui::test]
 7944    async fn test_close_window(cx: &mut TestAppContext) {
 7945        init_test(cx);
 7946
 7947        let fs = FakeFs::new(cx.executor());
 7948        fs.insert_tree("/root", json!({ "one": "" })).await;
 7949
 7950        let project = Project::test(fs, ["root".as_ref()], cx).await;
 7951        let (workspace, cx) =
 7952            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7953
 7954        // When there are no dirty items, there's nothing to do.
 7955        let item1 = cx.new(TestItem::new);
 7956        workspace.update_in(cx, |w, window, cx| {
 7957            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx)
 7958        });
 7959        let task = workspace.update_in(cx, |w, window, cx| {
 7960            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 7961        });
 7962        assert!(task.await.unwrap());
 7963
 7964        // When there are dirty untitled items, prompt to save each one. If the user
 7965        // cancels any prompt, then abort.
 7966        let item2 = cx.new(|cx| TestItem::new(cx).with_dirty(true));
 7967        let item3 = cx.new(|cx| {
 7968            TestItem::new(cx)
 7969                .with_dirty(true)
 7970                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 7971        });
 7972        workspace.update_in(cx, |w, window, cx| {
 7973            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 7974            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
 7975        });
 7976        let task = workspace.update_in(cx, |w, window, cx| {
 7977            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 7978        });
 7979        cx.executor().run_until_parked();
 7980        cx.simulate_prompt_answer("Cancel"); // cancel save all
 7981        cx.executor().run_until_parked();
 7982        assert!(!cx.has_pending_prompt());
 7983        assert!(!task.await.unwrap());
 7984    }
 7985
 7986    #[gpui::test]
 7987    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
 7988        init_test(cx);
 7989
 7990        // Register TestItem as a serializable item
 7991        cx.update(|cx| {
 7992            register_serializable_item::<TestItem>(cx);
 7993        });
 7994
 7995        let fs = FakeFs::new(cx.executor());
 7996        fs.insert_tree("/root", json!({ "one": "" })).await;
 7997
 7998        let project = Project::test(fs, ["root".as_ref()], cx).await;
 7999        let (workspace, cx) =
 8000            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 8001
 8002        // When there are dirty untitled items, but they can serialize, then there is no prompt.
 8003        let item1 = cx.new(|cx| {
 8004            TestItem::new(cx)
 8005                .with_dirty(true)
 8006                .with_serialize(|| Some(Task::ready(Ok(()))))
 8007        });
 8008        let item2 = cx.new(|cx| {
 8009            TestItem::new(cx)
 8010                .with_dirty(true)
 8011                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 8012                .with_serialize(|| Some(Task::ready(Ok(()))))
 8013        });
 8014        workspace.update_in(cx, |w, window, cx| {
 8015            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
 8016            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 8017        });
 8018        let task = workspace.update_in(cx, |w, window, cx| {
 8019            w.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 8020        });
 8021        assert!(task.await.unwrap());
 8022    }
 8023
 8024    #[gpui::test]
 8025    async fn test_close_pane_items(cx: &mut TestAppContext) {
 8026        init_test(cx);
 8027
 8028        let fs = FakeFs::new(cx.executor());
 8029
 8030        let project = Project::test(fs, None, cx).await;
 8031        let (workspace, cx) =
 8032            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8033
 8034        let item1 = cx.new(|cx| {
 8035            TestItem::new(cx)
 8036                .with_dirty(true)
 8037                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 8038        });
 8039        let item2 = cx.new(|cx| {
 8040            TestItem::new(cx)
 8041                .with_dirty(true)
 8042                .with_conflict(true)
 8043                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 8044        });
 8045        let item3 = cx.new(|cx| {
 8046            TestItem::new(cx)
 8047                .with_dirty(true)
 8048                .with_conflict(true)
 8049                .with_project_items(&[dirty_project_item(3, "3.txt", cx)])
 8050        });
 8051        let item4 = cx.new(|cx| {
 8052            TestItem::new(cx).with_dirty(true).with_project_items(&[{
 8053                let project_item = TestProjectItem::new_untitled(cx);
 8054                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
 8055                project_item
 8056            }])
 8057        });
 8058        let pane = workspace.update_in(cx, |workspace, window, cx| {
 8059            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, window, cx);
 8060            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, window, cx);
 8061            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, window, cx);
 8062            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, window, cx);
 8063            workspace.active_pane().clone()
 8064        });
 8065
 8066        let close_items = pane.update_in(cx, |pane, window, cx| {
 8067            pane.activate_item(1, true, true, window, cx);
 8068            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
 8069            let item1_id = item1.item_id();
 8070            let item3_id = item3.item_id();
 8071            let item4_id = item4.item_id();
 8072            pane.close_items(window, cx, SaveIntent::Close, move |id| {
 8073                [item1_id, item3_id, item4_id].contains(&id)
 8074            })
 8075        });
 8076        cx.executor().run_until_parked();
 8077
 8078        assert!(cx.has_pending_prompt());
 8079        cx.simulate_prompt_answer("Save all");
 8080
 8081        cx.executor().run_until_parked();
 8082
 8083        // Item 1 is saved. There's a prompt to save item 3.
 8084        pane.update(cx, |pane, cx| {
 8085            assert_eq!(item1.read(cx).save_count, 1);
 8086            assert_eq!(item1.read(cx).save_as_count, 0);
 8087            assert_eq!(item1.read(cx).reload_count, 0);
 8088            assert_eq!(pane.items_len(), 3);
 8089            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
 8090        });
 8091        assert!(cx.has_pending_prompt());
 8092
 8093        // Cancel saving item 3.
 8094        cx.simulate_prompt_answer("Discard");
 8095        cx.executor().run_until_parked();
 8096
 8097        // Item 3 is reloaded. There's a prompt to save item 4.
 8098        pane.update(cx, |pane, cx| {
 8099            assert_eq!(item3.read(cx).save_count, 0);
 8100            assert_eq!(item3.read(cx).save_as_count, 0);
 8101            assert_eq!(item3.read(cx).reload_count, 1);
 8102            assert_eq!(pane.items_len(), 2);
 8103            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
 8104        });
 8105
 8106        // There's a prompt for a path for item 4.
 8107        cx.simulate_new_path_selection(|_| Some(Default::default()));
 8108        close_items.await.unwrap();
 8109
 8110        // The requested items are closed.
 8111        pane.update(cx, |pane, cx| {
 8112            assert_eq!(item4.read(cx).save_count, 0);
 8113            assert_eq!(item4.read(cx).save_as_count, 1);
 8114            assert_eq!(item4.read(cx).reload_count, 0);
 8115            assert_eq!(pane.items_len(), 1);
 8116            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
 8117        });
 8118    }
 8119
 8120    #[gpui::test]
 8121    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
 8122        init_test(cx);
 8123
 8124        let fs = FakeFs::new(cx.executor());
 8125        let project = Project::test(fs, [], cx).await;
 8126        let (workspace, cx) =
 8127            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8128
 8129        // Create several workspace items with single project entries, and two
 8130        // workspace items with multiple project entries.
 8131        let single_entry_items = (0..=4)
 8132            .map(|project_entry_id| {
 8133                cx.new(|cx| {
 8134                    TestItem::new(cx)
 8135                        .with_dirty(true)
 8136                        .with_project_items(&[dirty_project_item(
 8137                            project_entry_id,
 8138                            &format!("{project_entry_id}.txt"),
 8139                            cx,
 8140                        )])
 8141                })
 8142            })
 8143            .collect::<Vec<_>>();
 8144        let item_2_3 = cx.new(|cx| {
 8145            TestItem::new(cx)
 8146                .with_dirty(true)
 8147                .with_singleton(false)
 8148                .with_project_items(&[
 8149                    single_entry_items[2].read(cx).project_items[0].clone(),
 8150                    single_entry_items[3].read(cx).project_items[0].clone(),
 8151                ])
 8152        });
 8153        let item_3_4 = cx.new(|cx| {
 8154            TestItem::new(cx)
 8155                .with_dirty(true)
 8156                .with_singleton(false)
 8157                .with_project_items(&[
 8158                    single_entry_items[3].read(cx).project_items[0].clone(),
 8159                    single_entry_items[4].read(cx).project_items[0].clone(),
 8160                ])
 8161        });
 8162
 8163        // Create two panes that contain the following project entries:
 8164        //   left pane:
 8165        //     multi-entry items:   (2, 3)
 8166        //     single-entry items:  0, 2, 3, 4
 8167        //   right pane:
 8168        //     single-entry items:  4, 1
 8169        //     multi-entry items:   (3, 4)
 8170        let (left_pane, right_pane) = workspace.update_in(cx, |workspace, window, cx| {
 8171            let left_pane = workspace.active_pane().clone();
 8172            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, window, cx);
 8173            workspace.add_item_to_active_pane(
 8174                single_entry_items[0].boxed_clone(),
 8175                None,
 8176                true,
 8177                window,
 8178                cx,
 8179            );
 8180            workspace.add_item_to_active_pane(
 8181                single_entry_items[2].boxed_clone(),
 8182                None,
 8183                true,
 8184                window,
 8185                cx,
 8186            );
 8187            workspace.add_item_to_active_pane(
 8188                single_entry_items[3].boxed_clone(),
 8189                None,
 8190                true,
 8191                window,
 8192                cx,
 8193            );
 8194            workspace.add_item_to_active_pane(
 8195                single_entry_items[4].boxed_clone(),
 8196                None,
 8197                true,
 8198                window,
 8199                cx,
 8200            );
 8201
 8202            let right_pane = workspace
 8203                .split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
 8204                .unwrap();
 8205
 8206            right_pane.update(cx, |pane, cx| {
 8207                pane.add_item(
 8208                    single_entry_items[1].boxed_clone(),
 8209                    true,
 8210                    true,
 8211                    None,
 8212                    window,
 8213                    cx,
 8214                );
 8215                pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
 8216            });
 8217
 8218            (left_pane, right_pane)
 8219        });
 8220
 8221        cx.focus(&right_pane);
 8222
 8223        let mut close = right_pane.update_in(cx, |pane, window, cx| {
 8224            pane.close_all_items(&CloseAllItems::default(), window, cx)
 8225                .unwrap()
 8226        });
 8227        cx.executor().run_until_parked();
 8228
 8229        let msg = cx.pending_prompt().unwrap().0;
 8230        assert!(msg.contains("1.txt"));
 8231        assert!(!msg.contains("2.txt"));
 8232        assert!(!msg.contains("3.txt"));
 8233        assert!(!msg.contains("4.txt"));
 8234
 8235        cx.simulate_prompt_answer("Cancel");
 8236        close.await;
 8237
 8238        left_pane
 8239            .update_in(cx, |left_pane, window, cx| {
 8240                left_pane.close_item_by_id(
 8241                    single_entry_items[3].entity_id(),
 8242                    SaveIntent::Skip,
 8243                    window,
 8244                    cx,
 8245                )
 8246            })
 8247            .await
 8248            .unwrap();
 8249
 8250        close = right_pane.update_in(cx, |pane, window, cx| {
 8251            pane.close_all_items(&CloseAllItems::default(), window, cx)
 8252                .unwrap()
 8253        });
 8254        cx.executor().run_until_parked();
 8255
 8256        let details = cx.pending_prompt().unwrap().1;
 8257        assert!(details.contains("1.txt"));
 8258        assert!(!details.contains("2.txt"));
 8259        assert!(details.contains("3.txt"));
 8260        // ideally this assertion could be made, but today we can only
 8261        // save whole items not project items, so the orphaned item 3 causes
 8262        // 4 to be saved too.
 8263        // assert!(!details.contains("4.txt"));
 8264
 8265        cx.simulate_prompt_answer("Save all");
 8266
 8267        cx.executor().run_until_parked();
 8268        close.await;
 8269        right_pane.read_with(cx, |pane, _| {
 8270            assert_eq!(pane.items_len(), 0);
 8271        });
 8272    }
 8273
 8274    #[gpui::test]
 8275    async fn test_autosave(cx: &mut gpui::TestAppContext) {
 8276        init_test(cx);
 8277
 8278        let fs = FakeFs::new(cx.executor());
 8279        let project = Project::test(fs, [], cx).await;
 8280        let (workspace, cx) =
 8281            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8282        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 8283
 8284        let item = cx.new(|cx| {
 8285            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 8286        });
 8287        let item_id = item.entity_id();
 8288        workspace.update_in(cx, |workspace, window, cx| {
 8289            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
 8290        });
 8291
 8292        // Autosave on window change.
 8293        item.update(cx, |item, cx| {
 8294            SettingsStore::update_global(cx, |settings, cx| {
 8295                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8296                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
 8297                })
 8298            });
 8299            item.is_dirty = true;
 8300        });
 8301
 8302        // Deactivating the window saves the file.
 8303        cx.deactivate_window();
 8304        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
 8305
 8306        // Re-activating the window doesn't save the file.
 8307        cx.update(|window, _| window.activate_window());
 8308        cx.executor().run_until_parked();
 8309        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
 8310
 8311        // Autosave on focus change.
 8312        item.update_in(cx, |item, window, cx| {
 8313            cx.focus_self(window);
 8314            SettingsStore::update_global(cx, |settings, cx| {
 8315                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8316                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
 8317                })
 8318            });
 8319            item.is_dirty = true;
 8320        });
 8321
 8322        // Blurring the item saves the file.
 8323        item.update_in(cx, |_, window, _| window.blur());
 8324        cx.executor().run_until_parked();
 8325        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
 8326
 8327        // Deactivating the window still saves the file.
 8328        item.update_in(cx, |item, window, cx| {
 8329            cx.focus_self(window);
 8330            item.is_dirty = true;
 8331        });
 8332        cx.deactivate_window();
 8333        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 8334
 8335        // Autosave after delay.
 8336        item.update(cx, |item, cx| {
 8337            SettingsStore::update_global(cx, |settings, cx| {
 8338                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8339                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
 8340                })
 8341            });
 8342            item.is_dirty = true;
 8343            cx.emit(ItemEvent::Edit);
 8344        });
 8345
 8346        // Delay hasn't fully expired, so the file is still dirty and unsaved.
 8347        cx.executor().advance_clock(Duration::from_millis(250));
 8348        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
 8349
 8350        // After delay expires, the file is saved.
 8351        cx.executor().advance_clock(Duration::from_millis(250));
 8352        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
 8353
 8354        // Autosave on focus change, ensuring closing the tab counts as such.
 8355        item.update(cx, |item, cx| {
 8356            SettingsStore::update_global(cx, |settings, cx| {
 8357                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 8358                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
 8359                })
 8360            });
 8361            item.is_dirty = true;
 8362            for project_item in &mut item.project_items {
 8363                project_item.update(cx, |project_item, _| project_item.is_dirty = true);
 8364            }
 8365        });
 8366
 8367        pane.update_in(cx, |pane, window, cx| {
 8368            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
 8369        })
 8370        .await
 8371        .unwrap();
 8372        assert!(!cx.has_pending_prompt());
 8373        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 8374
 8375        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
 8376        workspace.update_in(cx, |workspace, window, cx| {
 8377            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
 8378        });
 8379        item.update_in(cx, |item, window, cx| {
 8380            item.project_items[0].update(cx, |item, _| {
 8381                item.entry_id = None;
 8382            });
 8383            item.is_dirty = true;
 8384            window.blur();
 8385        });
 8386        cx.run_until_parked();
 8387        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 8388
 8389        // Ensure autosave is prevented for deleted files also when closing the buffer.
 8390        let _close_items = pane.update_in(cx, |pane, window, cx| {
 8391            pane.close_items(window, cx, SaveIntent::Close, move |id| id == item_id)
 8392        });
 8393        cx.run_until_parked();
 8394        assert!(cx.has_pending_prompt());
 8395        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 8396    }
 8397
 8398    #[gpui::test]
 8399    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
 8400        init_test(cx);
 8401
 8402        let fs = FakeFs::new(cx.executor());
 8403
 8404        let project = Project::test(fs, [], cx).await;
 8405        let (workspace, cx) =
 8406            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8407
 8408        let item = cx.new(|cx| {
 8409            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
 8410        });
 8411        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 8412        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
 8413        let toolbar_notify_count = Rc::new(RefCell::new(0));
 8414
 8415        workspace.update_in(cx, |workspace, window, cx| {
 8416            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, window, cx);
 8417            let toolbar_notification_count = toolbar_notify_count.clone();
 8418            cx.observe_in(&toolbar, window, move |_, _, _, _| {
 8419                *toolbar_notification_count.borrow_mut() += 1
 8420            })
 8421            .detach();
 8422        });
 8423
 8424        pane.read_with(cx, |pane, _| {
 8425            assert!(!pane.can_navigate_backward());
 8426            assert!(!pane.can_navigate_forward());
 8427        });
 8428
 8429        item.update_in(cx, |item, _, cx| {
 8430            item.set_state("one".to_string(), cx);
 8431        });
 8432
 8433        // Toolbar must be notified to re-render the navigation buttons
 8434        assert_eq!(*toolbar_notify_count.borrow(), 1);
 8435
 8436        pane.read_with(cx, |pane, _| {
 8437            assert!(pane.can_navigate_backward());
 8438            assert!(!pane.can_navigate_forward());
 8439        });
 8440
 8441        workspace
 8442            .update_in(cx, |workspace, window, cx| {
 8443                workspace.go_back(pane.downgrade(), window, cx)
 8444            })
 8445            .await
 8446            .unwrap();
 8447
 8448        assert_eq!(*toolbar_notify_count.borrow(), 2);
 8449        pane.read_with(cx, |pane, _| {
 8450            assert!(!pane.can_navigate_backward());
 8451            assert!(pane.can_navigate_forward());
 8452        });
 8453    }
 8454
 8455    #[gpui::test]
 8456    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
 8457        init_test(cx);
 8458        let fs = FakeFs::new(cx.executor());
 8459
 8460        let project = Project::test(fs, [], cx).await;
 8461        let (workspace, cx) =
 8462            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8463
 8464        let panel = workspace.update_in(cx, |workspace, window, cx| {
 8465            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
 8466            workspace.add_panel(panel.clone(), window, cx);
 8467
 8468            workspace
 8469                .right_dock()
 8470                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
 8471
 8472            panel
 8473        });
 8474
 8475        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 8476        pane.update_in(cx, |pane, window, cx| {
 8477            let item = cx.new(TestItem::new);
 8478            pane.add_item(Box::new(item), true, true, None, window, cx);
 8479        });
 8480
 8481        // Transfer focus from center to panel
 8482        workspace.update_in(cx, |workspace, window, cx| {
 8483            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8484        });
 8485
 8486        workspace.update_in(cx, |workspace, window, cx| {
 8487            assert!(workspace.right_dock().read(cx).is_open());
 8488            assert!(!panel.is_zoomed(window, cx));
 8489            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8490        });
 8491
 8492        // Transfer focus from panel to center
 8493        workspace.update_in(cx, |workspace, window, cx| {
 8494            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8495        });
 8496
 8497        workspace.update_in(cx, |workspace, window, cx| {
 8498            assert!(workspace.right_dock().read(cx).is_open());
 8499            assert!(!panel.is_zoomed(window, cx));
 8500            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8501        });
 8502
 8503        // Close the dock
 8504        workspace.update_in(cx, |workspace, window, cx| {
 8505            workspace.toggle_dock(DockPosition::Right, window, cx);
 8506        });
 8507
 8508        workspace.update_in(cx, |workspace, window, cx| {
 8509            assert!(!workspace.right_dock().read(cx).is_open());
 8510            assert!(!panel.is_zoomed(window, cx));
 8511            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8512        });
 8513
 8514        // Open the dock
 8515        workspace.update_in(cx, |workspace, window, cx| {
 8516            workspace.toggle_dock(DockPosition::Right, window, cx);
 8517        });
 8518
 8519        workspace.update_in(cx, |workspace, window, cx| {
 8520            assert!(workspace.right_dock().read(cx).is_open());
 8521            assert!(!panel.is_zoomed(window, cx));
 8522            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8523        });
 8524
 8525        // Focus and zoom panel
 8526        panel.update_in(cx, |panel, window, cx| {
 8527            cx.focus_self(window);
 8528            panel.set_zoomed(true, window, cx)
 8529        });
 8530
 8531        workspace.update_in(cx, |workspace, window, cx| {
 8532            assert!(workspace.right_dock().read(cx).is_open());
 8533            assert!(panel.is_zoomed(window, cx));
 8534            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8535        });
 8536
 8537        // Transfer focus to the center closes the dock
 8538        workspace.update_in(cx, |workspace, window, cx| {
 8539            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8540        });
 8541
 8542        workspace.update_in(cx, |workspace, window, cx| {
 8543            assert!(!workspace.right_dock().read(cx).is_open());
 8544            assert!(panel.is_zoomed(window, cx));
 8545            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8546        });
 8547
 8548        // Transferring focus back to the panel keeps it zoomed
 8549        workspace.update_in(cx, |workspace, window, cx| {
 8550            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 8551        });
 8552
 8553        workspace.update_in(cx, |workspace, window, cx| {
 8554            assert!(workspace.right_dock().read(cx).is_open());
 8555            assert!(panel.is_zoomed(window, cx));
 8556            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8557        });
 8558
 8559        // Close the dock while it is zoomed
 8560        workspace.update_in(cx, |workspace, window, cx| {
 8561            workspace.toggle_dock(DockPosition::Right, window, cx)
 8562        });
 8563
 8564        workspace.update_in(cx, |workspace, window, cx| {
 8565            assert!(!workspace.right_dock().read(cx).is_open());
 8566            assert!(panel.is_zoomed(window, cx));
 8567            assert!(workspace.zoomed.is_none());
 8568            assert!(!panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8569        });
 8570
 8571        // Opening the dock, when it's zoomed, retains focus
 8572        workspace.update_in(cx, |workspace, window, cx| {
 8573            workspace.toggle_dock(DockPosition::Right, window, cx)
 8574        });
 8575
 8576        workspace.update_in(cx, |workspace, window, cx| {
 8577            assert!(workspace.right_dock().read(cx).is_open());
 8578            assert!(panel.is_zoomed(window, cx));
 8579            assert!(workspace.zoomed.is_some());
 8580            assert!(panel.read(cx).focus_handle(cx).contains_focused(window, cx));
 8581        });
 8582
 8583        // Unzoom and close the panel, zoom the active pane.
 8584        panel.update_in(cx, |panel, window, cx| panel.set_zoomed(false, window, cx));
 8585        workspace.update_in(cx, |workspace, window, cx| {
 8586            workspace.toggle_dock(DockPosition::Right, window, cx)
 8587        });
 8588        pane.update_in(cx, |pane, window, cx| {
 8589            pane.toggle_zoom(&Default::default(), window, cx)
 8590        });
 8591
 8592        // Opening a dock unzooms the pane.
 8593        workspace.update_in(cx, |workspace, window, cx| {
 8594            workspace.toggle_dock(DockPosition::Right, window, cx)
 8595        });
 8596        workspace.update_in(cx, |workspace, window, cx| {
 8597            let pane = pane.read(cx);
 8598            assert!(!pane.is_zoomed());
 8599            assert!(!pane.focus_handle(cx).is_focused(window));
 8600            assert!(workspace.right_dock().read(cx).is_open());
 8601            assert!(workspace.zoomed.is_none());
 8602        });
 8603    }
 8604
 8605    #[gpui::test]
 8606    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
 8607        init_test(cx);
 8608
 8609        let fs = FakeFs::new(cx.executor());
 8610
 8611        let project = Project::test(fs, None, cx).await;
 8612        let (workspace, cx) =
 8613            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8614
 8615        // Let's arrange the panes like this:
 8616        //
 8617        // +-----------------------+
 8618        // |         top           |
 8619        // +------+--------+-------+
 8620        // | left | center | right |
 8621        // +------+--------+-------+
 8622        // |        bottom         |
 8623        // +-----------------------+
 8624
 8625        let top_item = cx.new(|cx| {
 8626            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
 8627        });
 8628        let bottom_item = cx.new(|cx| {
 8629            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
 8630        });
 8631        let left_item = cx.new(|cx| {
 8632            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
 8633        });
 8634        let right_item = cx.new(|cx| {
 8635            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
 8636        });
 8637        let center_item = cx.new(|cx| {
 8638            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
 8639        });
 8640
 8641        let top_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8642            let top_pane_id = workspace.active_pane().entity_id();
 8643            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, window, cx);
 8644            workspace.split_pane(
 8645                workspace.active_pane().clone(),
 8646                SplitDirection::Down,
 8647                window,
 8648                cx,
 8649            );
 8650            top_pane_id
 8651        });
 8652        let bottom_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8653            let bottom_pane_id = workspace.active_pane().entity_id();
 8654            workspace.add_item_to_active_pane(
 8655                Box::new(bottom_item.clone()),
 8656                None,
 8657                false,
 8658                window,
 8659                cx,
 8660            );
 8661            workspace.split_pane(
 8662                workspace.active_pane().clone(),
 8663                SplitDirection::Up,
 8664                window,
 8665                cx,
 8666            );
 8667            bottom_pane_id
 8668        });
 8669        let left_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8670            let left_pane_id = workspace.active_pane().entity_id();
 8671            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, window, cx);
 8672            workspace.split_pane(
 8673                workspace.active_pane().clone(),
 8674                SplitDirection::Right,
 8675                window,
 8676                cx,
 8677            );
 8678            left_pane_id
 8679        });
 8680        let right_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8681            let right_pane_id = workspace.active_pane().entity_id();
 8682            workspace.add_item_to_active_pane(
 8683                Box::new(right_item.clone()),
 8684                None,
 8685                false,
 8686                window,
 8687                cx,
 8688            );
 8689            workspace.split_pane(
 8690                workspace.active_pane().clone(),
 8691                SplitDirection::Left,
 8692                window,
 8693                cx,
 8694            );
 8695            right_pane_id
 8696        });
 8697        let center_pane_id = workspace.update_in(cx, |workspace, window, cx| {
 8698            let center_pane_id = workspace.active_pane().entity_id();
 8699            workspace.add_item_to_active_pane(
 8700                Box::new(center_item.clone()),
 8701                None,
 8702                false,
 8703                window,
 8704                cx,
 8705            );
 8706            center_pane_id
 8707        });
 8708        cx.executor().run_until_parked();
 8709
 8710        workspace.update_in(cx, |workspace, window, cx| {
 8711            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
 8712
 8713            // Join into next from center pane into right
 8714            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8715        });
 8716
 8717        workspace.update_in(cx, |workspace, window, cx| {
 8718            let active_pane = workspace.active_pane();
 8719            assert_eq!(right_pane_id, active_pane.entity_id());
 8720            assert_eq!(2, active_pane.read(cx).items_len());
 8721            let item_ids_in_pane =
 8722                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8723            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8724            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8725
 8726            // Join into next from right pane into bottom
 8727            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8728        });
 8729
 8730        workspace.update_in(cx, |workspace, window, cx| {
 8731            let active_pane = workspace.active_pane();
 8732            assert_eq!(bottom_pane_id, active_pane.entity_id());
 8733            assert_eq!(3, active_pane.read(cx).items_len());
 8734            let item_ids_in_pane =
 8735                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8736            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8737            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8738            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
 8739
 8740            // Join into next from bottom pane into left
 8741            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8742        });
 8743
 8744        workspace.update_in(cx, |workspace, window, cx| {
 8745            let active_pane = workspace.active_pane();
 8746            assert_eq!(left_pane_id, active_pane.entity_id());
 8747            assert_eq!(4, active_pane.read(cx).items_len());
 8748            let item_ids_in_pane =
 8749                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8750            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8751            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8752            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
 8753            assert!(item_ids_in_pane.contains(&left_item.item_id()));
 8754
 8755            // Join into next from left pane into top
 8756            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx);
 8757        });
 8758
 8759        workspace.update_in(cx, |workspace, window, cx| {
 8760            let active_pane = workspace.active_pane();
 8761            assert_eq!(top_pane_id, active_pane.entity_id());
 8762            assert_eq!(5, active_pane.read(cx).items_len());
 8763            let item_ids_in_pane =
 8764                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
 8765            assert!(item_ids_in_pane.contains(&center_item.item_id()));
 8766            assert!(item_ids_in_pane.contains(&right_item.item_id()));
 8767            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
 8768            assert!(item_ids_in_pane.contains(&left_item.item_id()));
 8769            assert!(item_ids_in_pane.contains(&top_item.item_id()));
 8770
 8771            // Single pane left: no-op
 8772            workspace.join_pane_into_next(workspace.active_pane().clone(), window, cx)
 8773        });
 8774
 8775        workspace.update(cx, |workspace, _cx| {
 8776            let active_pane = workspace.active_pane();
 8777            assert_eq!(top_pane_id, active_pane.entity_id());
 8778        });
 8779    }
 8780
 8781    fn add_an_item_to_active_pane(
 8782        cx: &mut VisualTestContext,
 8783        workspace: &Entity<Workspace>,
 8784        item_id: u64,
 8785    ) -> Entity<TestItem> {
 8786        let item = cx.new(|cx| {
 8787            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
 8788                item_id,
 8789                "item{item_id}.txt",
 8790                cx,
 8791            )])
 8792        });
 8793        workspace.update_in(cx, |workspace, window, cx| {
 8794            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, window, cx);
 8795        });
 8796        return item;
 8797    }
 8798
 8799    fn split_pane(cx: &mut VisualTestContext, workspace: &Entity<Workspace>) -> Entity<Pane> {
 8800        return workspace.update_in(cx, |workspace, window, cx| {
 8801            let new_pane = workspace.split_pane(
 8802                workspace.active_pane().clone(),
 8803                SplitDirection::Right,
 8804                window,
 8805                cx,
 8806            );
 8807            new_pane
 8808        });
 8809    }
 8810
 8811    #[gpui::test]
 8812    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
 8813        init_test(cx);
 8814        let fs = FakeFs::new(cx.executor());
 8815        let project = Project::test(fs, None, cx).await;
 8816        let (workspace, cx) =
 8817            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8818
 8819        add_an_item_to_active_pane(cx, &workspace, 1);
 8820        split_pane(cx, &workspace);
 8821        add_an_item_to_active_pane(cx, &workspace, 2);
 8822        split_pane(cx, &workspace); // empty pane
 8823        split_pane(cx, &workspace);
 8824        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
 8825
 8826        cx.executor().run_until_parked();
 8827
 8828        workspace.update(cx, |workspace, cx| {
 8829            let num_panes = workspace.panes().len();
 8830            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
 8831            let active_item = workspace
 8832                .active_pane()
 8833                .read(cx)
 8834                .active_item()
 8835                .expect("item is in focus");
 8836
 8837            assert_eq!(num_panes, 4);
 8838            assert_eq!(num_items_in_current_pane, 1);
 8839            assert_eq!(active_item.item_id(), last_item.item_id());
 8840        });
 8841
 8842        workspace.update_in(cx, |workspace, window, cx| {
 8843            workspace.join_all_panes(window, cx);
 8844        });
 8845
 8846        workspace.update(cx, |workspace, cx| {
 8847            let num_panes = workspace.panes().len();
 8848            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
 8849            let active_item = workspace
 8850                .active_pane()
 8851                .read(cx)
 8852                .active_item()
 8853                .expect("item is in focus");
 8854
 8855            assert_eq!(num_panes, 1);
 8856            assert_eq!(num_items_in_current_pane, 3);
 8857            assert_eq!(active_item.item_id(), last_item.item_id());
 8858        });
 8859    }
 8860    struct TestModal(FocusHandle);
 8861
 8862    impl TestModal {
 8863        fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
 8864            Self(cx.focus_handle())
 8865        }
 8866    }
 8867
 8868    impl EventEmitter<DismissEvent> for TestModal {}
 8869
 8870    impl Focusable for TestModal {
 8871        fn focus_handle(&self, _cx: &App) -> FocusHandle {
 8872            self.0.clone()
 8873        }
 8874    }
 8875
 8876    impl ModalView for TestModal {}
 8877
 8878    impl Render for TestModal {
 8879        fn render(
 8880            &mut self,
 8881            _window: &mut Window,
 8882            _cx: &mut Context<TestModal>,
 8883        ) -> impl IntoElement {
 8884            div().track_focus(&self.0)
 8885        }
 8886    }
 8887
 8888    #[gpui::test]
 8889    async fn test_panels(cx: &mut gpui::TestAppContext) {
 8890        init_test(cx);
 8891        let fs = FakeFs::new(cx.executor());
 8892
 8893        let project = Project::test(fs, [], cx).await;
 8894        let (workspace, cx) =
 8895            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 8896
 8897        let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| {
 8898            let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, cx));
 8899            workspace.add_panel(panel_1.clone(), window, cx);
 8900            workspace.toggle_dock(DockPosition::Left, window, cx);
 8901            let panel_2 = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
 8902            workspace.add_panel(panel_2.clone(), window, cx);
 8903            workspace.toggle_dock(DockPosition::Right, window, cx);
 8904
 8905            let left_dock = workspace.left_dock();
 8906            assert_eq!(
 8907                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 8908                panel_1.panel_id()
 8909            );
 8910            assert_eq!(
 8911                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
 8912                panel_1.size(window, cx)
 8913            );
 8914
 8915            left_dock.update(cx, |left_dock, cx| {
 8916                left_dock.resize_active_panel(Some(px(1337.)), window, cx)
 8917            });
 8918            assert_eq!(
 8919                workspace
 8920                    .right_dock()
 8921                    .read(cx)
 8922                    .visible_panel()
 8923                    .unwrap()
 8924                    .panel_id(),
 8925                panel_2.panel_id(),
 8926            );
 8927
 8928            (panel_1, panel_2)
 8929        });
 8930
 8931        // Move panel_1 to the right
 8932        panel_1.update_in(cx, |panel_1, window, cx| {
 8933            panel_1.set_position(DockPosition::Right, window, cx)
 8934        });
 8935
 8936        workspace.update_in(cx, |workspace, window, cx| {
 8937            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
 8938            // Since it was the only panel on the left, the left dock should now be closed.
 8939            assert!(!workspace.left_dock().read(cx).is_open());
 8940            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
 8941            let right_dock = workspace.right_dock();
 8942            assert_eq!(
 8943                right_dock.read(cx).visible_panel().unwrap().panel_id(),
 8944                panel_1.panel_id()
 8945            );
 8946            assert_eq!(
 8947                right_dock.read(cx).active_panel_size(window, cx).unwrap(),
 8948                px(1337.)
 8949            );
 8950
 8951            // Now we move panel_2 to the left
 8952            panel_2.set_position(DockPosition::Left, window, cx);
 8953        });
 8954
 8955        workspace.update(cx, |workspace, cx| {
 8956            // Since panel_2 was not visible on the right, we don't open the left dock.
 8957            assert!(!workspace.left_dock().read(cx).is_open());
 8958            // And the right dock is unaffected in its displaying of panel_1
 8959            assert!(workspace.right_dock().read(cx).is_open());
 8960            assert_eq!(
 8961                workspace
 8962                    .right_dock()
 8963                    .read(cx)
 8964                    .visible_panel()
 8965                    .unwrap()
 8966                    .panel_id(),
 8967                panel_1.panel_id(),
 8968            );
 8969        });
 8970
 8971        // Move panel_1 back to the left
 8972        panel_1.update_in(cx, |panel_1, window, cx| {
 8973            panel_1.set_position(DockPosition::Left, window, cx)
 8974        });
 8975
 8976        workspace.update_in(cx, |workspace, window, cx| {
 8977            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
 8978            let left_dock = workspace.left_dock();
 8979            assert!(left_dock.read(cx).is_open());
 8980            assert_eq!(
 8981                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 8982                panel_1.panel_id()
 8983            );
 8984            assert_eq!(
 8985                left_dock.read(cx).active_panel_size(window, cx).unwrap(),
 8986                px(1337.)
 8987            );
 8988            // And the right dock should be closed as it no longer has any panels.
 8989            assert!(!workspace.right_dock().read(cx).is_open());
 8990
 8991            // Now we move panel_1 to the bottom
 8992            panel_1.set_position(DockPosition::Bottom, window, cx);
 8993        });
 8994
 8995        workspace.update_in(cx, |workspace, window, cx| {
 8996            // Since panel_1 was visible on the left, we close the left dock.
 8997            assert!(!workspace.left_dock().read(cx).is_open());
 8998            // The bottom dock is sized based on the panel's default size,
 8999            // since the panel orientation changed from vertical to horizontal.
 9000            let bottom_dock = workspace.bottom_dock();
 9001            assert_eq!(
 9002                bottom_dock.read(cx).active_panel_size(window, cx).unwrap(),
 9003                panel_1.size(window, cx),
 9004            );
 9005            // Close bottom dock and move panel_1 back to the left.
 9006            bottom_dock.update(cx, |bottom_dock, cx| {
 9007                bottom_dock.set_open(false, window, cx)
 9008            });
 9009            panel_1.set_position(DockPosition::Left, window, cx);
 9010        });
 9011
 9012        // Emit activated event on panel 1
 9013        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
 9014
 9015        // Now the left dock is open and panel_1 is active and focused.
 9016        workspace.update_in(cx, |workspace, window, cx| {
 9017            let left_dock = workspace.left_dock();
 9018            assert!(left_dock.read(cx).is_open());
 9019            assert_eq!(
 9020                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 9021                panel_1.panel_id(),
 9022            );
 9023            assert!(panel_1.focus_handle(cx).is_focused(window));
 9024        });
 9025
 9026        // Emit closed event on panel 2, which is not active
 9027        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
 9028
 9029        // Wo don't close the left dock, because panel_2 wasn't the active panel
 9030        workspace.update(cx, |workspace, cx| {
 9031            let left_dock = workspace.left_dock();
 9032            assert!(left_dock.read(cx).is_open());
 9033            assert_eq!(
 9034                left_dock.read(cx).visible_panel().unwrap().panel_id(),
 9035                panel_1.panel_id(),
 9036            );
 9037        });
 9038
 9039        // Emitting a ZoomIn event shows the panel as zoomed.
 9040        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
 9041        workspace.read_with(cx, |workspace, _| {
 9042            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9043            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
 9044        });
 9045
 9046        // Move panel to another dock while it is zoomed
 9047        panel_1.update_in(cx, |panel, window, cx| {
 9048            panel.set_position(DockPosition::Right, window, cx)
 9049        });
 9050        workspace.read_with(cx, |workspace, _| {
 9051            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9052
 9053            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
 9054        });
 9055
 9056        // This is a helper for getting a:
 9057        // - valid focus on an element,
 9058        // - that isn't a part of the panes and panels system of the Workspace,
 9059        // - and doesn't trigger the 'on_focus_lost' API.
 9060        let focus_other_view = {
 9061            let workspace = workspace.clone();
 9062            move |cx: &mut VisualTestContext| {
 9063                workspace.update_in(cx, |workspace, window, cx| {
 9064                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
 9065                        workspace.toggle_modal(window, cx, TestModal::new);
 9066                        workspace.toggle_modal(window, cx, TestModal::new);
 9067                    } else {
 9068                        workspace.toggle_modal(window, cx, TestModal::new);
 9069                    }
 9070                })
 9071            }
 9072        };
 9073
 9074        // If focus is transferred to another view that's not a panel or another pane, we still show
 9075        // the panel as zoomed.
 9076        focus_other_view(cx);
 9077        workspace.read_with(cx, |workspace, _| {
 9078            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9079            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
 9080        });
 9081
 9082        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
 9083        workspace.update_in(cx, |_workspace, window, cx| {
 9084            cx.focus_self(window);
 9085        });
 9086        workspace.read_with(cx, |workspace, _| {
 9087            assert_eq!(workspace.zoomed, None);
 9088            assert_eq!(workspace.zoomed_position, None);
 9089        });
 9090
 9091        // If focus is transferred again to another view that's not a panel or a pane, we won't
 9092        // show the panel as zoomed because it wasn't zoomed before.
 9093        focus_other_view(cx);
 9094        workspace.read_with(cx, |workspace, _| {
 9095            assert_eq!(workspace.zoomed, None);
 9096            assert_eq!(workspace.zoomed_position, None);
 9097        });
 9098
 9099        // When the panel is activated, it is zoomed again.
 9100        cx.dispatch_action(ToggleRightDock);
 9101        workspace.read_with(cx, |workspace, _| {
 9102            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
 9103            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
 9104        });
 9105
 9106        // Emitting a ZoomOut event unzooms the panel.
 9107        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
 9108        workspace.read_with(cx, |workspace, _| {
 9109            assert_eq!(workspace.zoomed, None);
 9110            assert_eq!(workspace.zoomed_position, None);
 9111        });
 9112
 9113        // Emit closed event on panel 1, which is active
 9114        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
 9115
 9116        // Now the left dock is closed, because panel_1 was the active panel
 9117        workspace.update(cx, |workspace, cx| {
 9118            let right_dock = workspace.right_dock();
 9119            assert!(!right_dock.read(cx).is_open());
 9120        });
 9121    }
 9122
 9123    #[gpui::test]
 9124    async fn test_no_save_prompt_when_multi_buffer_dirty_items_closed(cx: &mut TestAppContext) {
 9125        init_test(cx);
 9126
 9127        let fs = FakeFs::new(cx.background_executor.clone());
 9128        let project = Project::test(fs, [], cx).await;
 9129        let (workspace, cx) =
 9130            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9131        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9132
 9133        let dirty_regular_buffer = cx.new(|cx| {
 9134            TestItem::new(cx)
 9135                .with_dirty(true)
 9136                .with_label("1.txt")
 9137                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 9138        });
 9139        let dirty_regular_buffer_2 = cx.new(|cx| {
 9140            TestItem::new(cx)
 9141                .with_dirty(true)
 9142                .with_label("2.txt")
 9143                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 9144        });
 9145        let dirty_multi_buffer_with_both = cx.new(|cx| {
 9146            TestItem::new(cx)
 9147                .with_dirty(true)
 9148                .with_singleton(false)
 9149                .with_label("Fake Project Search")
 9150                .with_project_items(&[
 9151                    dirty_regular_buffer.read(cx).project_items[0].clone(),
 9152                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
 9153                ])
 9154        });
 9155        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
 9156        workspace.update_in(cx, |workspace, window, cx| {
 9157            workspace.add_item(
 9158                pane.clone(),
 9159                Box::new(dirty_regular_buffer.clone()),
 9160                None,
 9161                false,
 9162                false,
 9163                window,
 9164                cx,
 9165            );
 9166            workspace.add_item(
 9167                pane.clone(),
 9168                Box::new(dirty_regular_buffer_2.clone()),
 9169                None,
 9170                false,
 9171                false,
 9172                window,
 9173                cx,
 9174            );
 9175            workspace.add_item(
 9176                pane.clone(),
 9177                Box::new(dirty_multi_buffer_with_both.clone()),
 9178                None,
 9179                false,
 9180                false,
 9181                window,
 9182                cx,
 9183            );
 9184        });
 9185
 9186        pane.update_in(cx, |pane, window, cx| {
 9187            pane.activate_item(2, true, true, window, cx);
 9188            assert_eq!(
 9189                pane.active_item().unwrap().item_id(),
 9190                multi_buffer_with_both_files_id,
 9191                "Should select the multi buffer in the pane"
 9192            );
 9193        });
 9194        let close_all_but_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9195            pane.close_inactive_items(
 9196                &CloseInactiveItems {
 9197                    save_intent: Some(SaveIntent::Save),
 9198                    close_pinned: true,
 9199                },
 9200                window,
 9201                cx,
 9202            )
 9203        });
 9204        cx.background_executor.run_until_parked();
 9205        assert!(!cx.has_pending_prompt());
 9206        close_all_but_multi_buffer_task
 9207            .await
 9208            .expect("Closing all buffers but the multi buffer failed");
 9209        pane.update(cx, |pane, cx| {
 9210            assert_eq!(dirty_regular_buffer.read(cx).save_count, 1);
 9211            assert_eq!(dirty_multi_buffer_with_both.read(cx).save_count, 0);
 9212            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 1);
 9213            assert_eq!(pane.items_len(), 1);
 9214            assert_eq!(
 9215                pane.active_item().unwrap().item_id(),
 9216                multi_buffer_with_both_files_id,
 9217                "Should have only the multi buffer left in the pane"
 9218            );
 9219            assert!(
 9220                dirty_multi_buffer_with_both.read(cx).is_dirty,
 9221                "The multi buffer containing the unsaved buffer should still be dirty"
 9222            );
 9223        });
 9224
 9225        dirty_regular_buffer.update(cx, |buffer, cx| {
 9226            buffer.project_items[0].update(cx, |pi, _| pi.is_dirty = true)
 9227        });
 9228
 9229        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9230            pane.close_active_item(
 9231                &CloseActiveItem {
 9232                    save_intent: Some(SaveIntent::Close),
 9233                    close_pinned: false,
 9234                },
 9235                window,
 9236                cx,
 9237            )
 9238        });
 9239        cx.background_executor.run_until_parked();
 9240        assert!(
 9241            cx.has_pending_prompt(),
 9242            "Dirty multi buffer should prompt a save dialog"
 9243        );
 9244        cx.simulate_prompt_answer("Save");
 9245        cx.background_executor.run_until_parked();
 9246        close_multi_buffer_task
 9247            .await
 9248            .expect("Closing the multi buffer failed");
 9249        pane.update(cx, |pane, cx| {
 9250            assert_eq!(
 9251                dirty_multi_buffer_with_both.read(cx).save_count,
 9252                1,
 9253                "Multi buffer item should get be saved"
 9254            );
 9255            // Test impl does not save inner items, so we do not assert them
 9256            assert_eq!(
 9257                pane.items_len(),
 9258                0,
 9259                "No more items should be left in the pane"
 9260            );
 9261            assert!(pane.active_item().is_none());
 9262        });
 9263    }
 9264
 9265    #[gpui::test]
 9266    async fn test_save_prompt_when_dirty_multi_buffer_closed_with_some_of_its_dirty_items_not_present_in_the_pane(
 9267        cx: &mut TestAppContext,
 9268    ) {
 9269        init_test(cx);
 9270
 9271        let fs = FakeFs::new(cx.background_executor.clone());
 9272        let project = Project::test(fs, [], cx).await;
 9273        let (workspace, cx) =
 9274            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9275        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9276
 9277        let dirty_regular_buffer = cx.new(|cx| {
 9278            TestItem::new(cx)
 9279                .with_dirty(true)
 9280                .with_label("1.txt")
 9281                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 9282        });
 9283        let dirty_regular_buffer_2 = cx.new(|cx| {
 9284            TestItem::new(cx)
 9285                .with_dirty(true)
 9286                .with_label("2.txt")
 9287                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 9288        });
 9289        let clear_regular_buffer = cx.new(|cx| {
 9290            TestItem::new(cx)
 9291                .with_label("3.txt")
 9292                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
 9293        });
 9294
 9295        let dirty_multi_buffer_with_both = cx.new(|cx| {
 9296            TestItem::new(cx)
 9297                .with_dirty(true)
 9298                .with_singleton(false)
 9299                .with_label("Fake Project Search")
 9300                .with_project_items(&[
 9301                    dirty_regular_buffer.read(cx).project_items[0].clone(),
 9302                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
 9303                    clear_regular_buffer.read(cx).project_items[0].clone(),
 9304                ])
 9305        });
 9306        let multi_buffer_with_both_files_id = dirty_multi_buffer_with_both.item_id();
 9307        workspace.update_in(cx, |workspace, window, cx| {
 9308            workspace.add_item(
 9309                pane.clone(),
 9310                Box::new(dirty_regular_buffer.clone()),
 9311                None,
 9312                false,
 9313                false,
 9314                window,
 9315                cx,
 9316            );
 9317            workspace.add_item(
 9318                pane.clone(),
 9319                Box::new(dirty_multi_buffer_with_both.clone()),
 9320                None,
 9321                false,
 9322                false,
 9323                window,
 9324                cx,
 9325            );
 9326        });
 9327
 9328        pane.update_in(cx, |pane, window, cx| {
 9329            pane.activate_item(1, true, true, window, cx);
 9330            assert_eq!(
 9331                pane.active_item().unwrap().item_id(),
 9332                multi_buffer_with_both_files_id,
 9333                "Should select the multi buffer in the pane"
 9334            );
 9335        });
 9336        let _close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9337            pane.close_active_item(
 9338                &CloseActiveItem {
 9339                    save_intent: None,
 9340                    close_pinned: false,
 9341                },
 9342                window,
 9343                cx,
 9344            )
 9345        });
 9346        cx.background_executor.run_until_parked();
 9347        assert!(
 9348            cx.has_pending_prompt(),
 9349            "With one dirty item from the multi buffer not being in the pane, a save prompt should be shown"
 9350        );
 9351    }
 9352
 9353    /// Tests that when `close_on_file_delete` is enabled, files are automatically
 9354    /// closed when they are deleted from disk.
 9355    #[gpui::test]
 9356    async fn test_close_on_disk_deletion_enabled(cx: &mut TestAppContext) {
 9357        init_test(cx);
 9358
 9359        // Enable the close_on_disk_deletion setting
 9360        cx.update_global(|store: &mut SettingsStore, cx| {
 9361            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9362                settings.close_on_file_delete = Some(true);
 9363            });
 9364        });
 9365
 9366        let fs = FakeFs::new(cx.background_executor.clone());
 9367        let project = Project::test(fs, [], cx).await;
 9368        let (workspace, cx) =
 9369            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9370        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9371
 9372        // Create a test item that simulates a file
 9373        let item = cx.new(|cx| {
 9374            TestItem::new(cx)
 9375                .with_label("test.txt")
 9376                .with_project_items(&[TestProjectItem::new(1, "test.txt", cx)])
 9377        });
 9378
 9379        // Add item to workspace
 9380        workspace.update_in(cx, |workspace, window, cx| {
 9381            workspace.add_item(
 9382                pane.clone(),
 9383                Box::new(item.clone()),
 9384                None,
 9385                false,
 9386                false,
 9387                window,
 9388                cx,
 9389            );
 9390        });
 9391
 9392        // Verify the item is in the pane
 9393        pane.read_with(cx, |pane, _| {
 9394            assert_eq!(pane.items().count(), 1);
 9395        });
 9396
 9397        // Simulate file deletion by setting the item's deleted state
 9398        item.update(cx, |item, _| {
 9399            item.set_has_deleted_file(true);
 9400        });
 9401
 9402        // Emit UpdateTab event to trigger the close behavior
 9403        cx.run_until_parked();
 9404        item.update(cx, |_, cx| {
 9405            cx.emit(ItemEvent::UpdateTab);
 9406        });
 9407
 9408        // Allow the close operation to complete
 9409        cx.run_until_parked();
 9410
 9411        // Verify the item was automatically closed
 9412        pane.read_with(cx, |pane, _| {
 9413            assert_eq!(
 9414                pane.items().count(),
 9415                0,
 9416                "Item should be automatically closed when file is deleted"
 9417            );
 9418        });
 9419    }
 9420
 9421    /// Tests that when `close_on_file_delete` is disabled (default), files remain
 9422    /// open with a strikethrough when they are deleted from disk.
 9423    #[gpui::test]
 9424    async fn test_close_on_disk_deletion_disabled(cx: &mut TestAppContext) {
 9425        init_test(cx);
 9426
 9427        // Ensure close_on_disk_deletion is disabled (default)
 9428        cx.update_global(|store: &mut SettingsStore, cx| {
 9429            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9430                settings.close_on_file_delete = Some(false);
 9431            });
 9432        });
 9433
 9434        let fs = FakeFs::new(cx.background_executor.clone());
 9435        let project = Project::test(fs, [], cx).await;
 9436        let (workspace, cx) =
 9437            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9438        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9439
 9440        // Create a test item that simulates a file
 9441        let item = cx.new(|cx| {
 9442            TestItem::new(cx)
 9443                .with_label("test.txt")
 9444                .with_project_items(&[TestProjectItem::new(1, "test.txt", cx)])
 9445        });
 9446
 9447        // Add item to workspace
 9448        workspace.update_in(cx, |workspace, window, cx| {
 9449            workspace.add_item(
 9450                pane.clone(),
 9451                Box::new(item.clone()),
 9452                None,
 9453                false,
 9454                false,
 9455                window,
 9456                cx,
 9457            );
 9458        });
 9459
 9460        // Verify the item is in the pane
 9461        pane.read_with(cx, |pane, _| {
 9462            assert_eq!(pane.items().count(), 1);
 9463        });
 9464
 9465        // Simulate file deletion
 9466        item.update(cx, |item, _| {
 9467            item.set_has_deleted_file(true);
 9468        });
 9469
 9470        // Emit UpdateTab event
 9471        cx.run_until_parked();
 9472        item.update(cx, |_, cx| {
 9473            cx.emit(ItemEvent::UpdateTab);
 9474        });
 9475
 9476        // Allow any potential close operation to complete
 9477        cx.run_until_parked();
 9478
 9479        // Verify the item remains open (with strikethrough)
 9480        pane.read_with(cx, |pane, _| {
 9481            assert_eq!(
 9482                pane.items().count(),
 9483                1,
 9484                "Item should remain open when close_on_disk_deletion is disabled"
 9485            );
 9486        });
 9487
 9488        // Verify the item shows as deleted
 9489        item.read_with(cx, |item, _| {
 9490            assert!(
 9491                item.has_deleted_file,
 9492                "Item should be marked as having deleted file"
 9493            );
 9494        });
 9495    }
 9496
 9497    /// Tests that dirty files are not automatically closed when deleted from disk,
 9498    /// even when `close_on_file_delete` is enabled. This ensures users don't lose
 9499    /// unsaved changes without being prompted.
 9500    #[gpui::test]
 9501    async fn test_close_on_disk_deletion_with_dirty_file(cx: &mut TestAppContext) {
 9502        init_test(cx);
 9503
 9504        // Enable the close_on_file_delete setting
 9505        cx.update_global(|store: &mut SettingsStore, cx| {
 9506            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9507                settings.close_on_file_delete = Some(true);
 9508            });
 9509        });
 9510
 9511        let fs = FakeFs::new(cx.background_executor.clone());
 9512        let project = Project::test(fs, [], cx).await;
 9513        let (workspace, cx) =
 9514            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9515        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9516
 9517        // Create a dirty test item
 9518        let item = cx.new(|cx| {
 9519            TestItem::new(cx)
 9520                .with_dirty(true)
 9521                .with_label("test.txt")
 9522                .with_project_items(&[TestProjectItem::new(1, "test.txt", cx)])
 9523        });
 9524
 9525        // Add item to workspace
 9526        workspace.update_in(cx, |workspace, window, cx| {
 9527            workspace.add_item(
 9528                pane.clone(),
 9529                Box::new(item.clone()),
 9530                None,
 9531                false,
 9532                false,
 9533                window,
 9534                cx,
 9535            );
 9536        });
 9537
 9538        // Simulate file deletion
 9539        item.update(cx, |item, _| {
 9540            item.set_has_deleted_file(true);
 9541        });
 9542
 9543        // Emit UpdateTab event to trigger the close behavior
 9544        cx.run_until_parked();
 9545        item.update(cx, |_, cx| {
 9546            cx.emit(ItemEvent::UpdateTab);
 9547        });
 9548
 9549        // Allow any potential close operation to complete
 9550        cx.run_until_parked();
 9551
 9552        // Verify the item remains open (dirty files are not auto-closed)
 9553        pane.read_with(cx, |pane, _| {
 9554            assert_eq!(
 9555                pane.items().count(),
 9556                1,
 9557                "Dirty items should not be automatically closed even when file is deleted"
 9558            );
 9559        });
 9560
 9561        // Verify the item is marked as deleted and still dirty
 9562        item.read_with(cx, |item, _| {
 9563            assert!(
 9564                item.has_deleted_file,
 9565                "Item should be marked as having deleted file"
 9566            );
 9567            assert!(item.is_dirty, "Item should still be dirty");
 9568        });
 9569    }
 9570
 9571    /// Tests that navigation history is cleaned up when files are auto-closed
 9572    /// due to deletion from disk.
 9573    #[gpui::test]
 9574    async fn test_close_on_disk_deletion_cleans_navigation_history(cx: &mut TestAppContext) {
 9575        init_test(cx);
 9576
 9577        // Enable the close_on_file_delete setting
 9578        cx.update_global(|store: &mut SettingsStore, cx| {
 9579            store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
 9580                settings.close_on_file_delete = Some(true);
 9581            });
 9582        });
 9583
 9584        let fs = FakeFs::new(cx.background_executor.clone());
 9585        let project = Project::test(fs, [], cx).await;
 9586        let (workspace, cx) =
 9587            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9588        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9589
 9590        // Create test items
 9591        let item1 = cx.new(|cx| {
 9592            TestItem::new(cx)
 9593                .with_label("test1.txt")
 9594                .with_project_items(&[TestProjectItem::new(1, "test1.txt", cx)])
 9595        });
 9596        let item1_id = item1.item_id();
 9597
 9598        let item2 = cx.new(|cx| {
 9599            TestItem::new(cx)
 9600                .with_label("test2.txt")
 9601                .with_project_items(&[TestProjectItem::new(2, "test2.txt", cx)])
 9602        });
 9603
 9604        // Add items to workspace
 9605        workspace.update_in(cx, |workspace, window, cx| {
 9606            workspace.add_item(
 9607                pane.clone(),
 9608                Box::new(item1.clone()),
 9609                None,
 9610                false,
 9611                false,
 9612                window,
 9613                cx,
 9614            );
 9615            workspace.add_item(
 9616                pane.clone(),
 9617                Box::new(item2.clone()),
 9618                None,
 9619                false,
 9620                false,
 9621                window,
 9622                cx,
 9623            );
 9624        });
 9625
 9626        // Activate item1 to ensure it gets navigation entries
 9627        pane.update_in(cx, |pane, window, cx| {
 9628            pane.activate_item(0, true, true, window, cx);
 9629        });
 9630
 9631        // Switch to item2 and back to create navigation history
 9632        pane.update_in(cx, |pane, window, cx| {
 9633            pane.activate_item(1, true, true, window, cx);
 9634        });
 9635        cx.run_until_parked();
 9636
 9637        pane.update_in(cx, |pane, window, cx| {
 9638            pane.activate_item(0, true, true, window, cx);
 9639        });
 9640        cx.run_until_parked();
 9641
 9642        // Simulate file deletion for item1
 9643        item1.update(cx, |item, _| {
 9644            item.set_has_deleted_file(true);
 9645        });
 9646
 9647        // Emit UpdateTab event to trigger the close behavior
 9648        item1.update(cx, |_, cx| {
 9649            cx.emit(ItemEvent::UpdateTab);
 9650        });
 9651        cx.run_until_parked();
 9652
 9653        // Verify item1 was closed
 9654        pane.read_with(cx, |pane, _| {
 9655            assert_eq!(
 9656                pane.items().count(),
 9657                1,
 9658                "Should have 1 item remaining after auto-close"
 9659            );
 9660        });
 9661
 9662        // Check navigation history after close
 9663        let has_item = pane.read_with(cx, |pane, cx| {
 9664            let mut has_item = false;
 9665            pane.nav_history().for_each_entry(cx, |entry, _| {
 9666                if entry.item.id() == item1_id {
 9667                    has_item = true;
 9668                }
 9669            });
 9670            has_item
 9671        });
 9672
 9673        assert!(
 9674            !has_item,
 9675            "Navigation history should not contain closed item entries"
 9676        );
 9677    }
 9678
 9679    #[gpui::test]
 9680    async fn test_no_save_prompt_when_dirty_multi_buffer_closed_with_all_of_its_dirty_items_present_in_the_pane(
 9681        cx: &mut TestAppContext,
 9682    ) {
 9683        init_test(cx);
 9684
 9685        let fs = FakeFs::new(cx.background_executor.clone());
 9686        let project = Project::test(fs, [], cx).await;
 9687        let (workspace, cx) =
 9688            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9689        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 9690
 9691        let dirty_regular_buffer = cx.new(|cx| {
 9692            TestItem::new(cx)
 9693                .with_dirty(true)
 9694                .with_label("1.txt")
 9695                .with_project_items(&[dirty_project_item(1, "1.txt", cx)])
 9696        });
 9697        let dirty_regular_buffer_2 = cx.new(|cx| {
 9698            TestItem::new(cx)
 9699                .with_dirty(true)
 9700                .with_label("2.txt")
 9701                .with_project_items(&[dirty_project_item(2, "2.txt", cx)])
 9702        });
 9703        let clear_regular_buffer = cx.new(|cx| {
 9704            TestItem::new(cx)
 9705                .with_label("3.txt")
 9706                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
 9707        });
 9708
 9709        let dirty_multi_buffer = cx.new(|cx| {
 9710            TestItem::new(cx)
 9711                .with_dirty(true)
 9712                .with_singleton(false)
 9713                .with_label("Fake Project Search")
 9714                .with_project_items(&[
 9715                    dirty_regular_buffer.read(cx).project_items[0].clone(),
 9716                    dirty_regular_buffer_2.read(cx).project_items[0].clone(),
 9717                    clear_regular_buffer.read(cx).project_items[0].clone(),
 9718                ])
 9719        });
 9720        workspace.update_in(cx, |workspace, window, cx| {
 9721            workspace.add_item(
 9722                pane.clone(),
 9723                Box::new(dirty_regular_buffer.clone()),
 9724                None,
 9725                false,
 9726                false,
 9727                window,
 9728                cx,
 9729            );
 9730            workspace.add_item(
 9731                pane.clone(),
 9732                Box::new(dirty_regular_buffer_2.clone()),
 9733                None,
 9734                false,
 9735                false,
 9736                window,
 9737                cx,
 9738            );
 9739            workspace.add_item(
 9740                pane.clone(),
 9741                Box::new(dirty_multi_buffer.clone()),
 9742                None,
 9743                false,
 9744                false,
 9745                window,
 9746                cx,
 9747            );
 9748        });
 9749
 9750        pane.update_in(cx, |pane, window, cx| {
 9751            pane.activate_item(2, true, true, window, cx);
 9752            assert_eq!(
 9753                pane.active_item().unwrap().item_id(),
 9754                dirty_multi_buffer.item_id(),
 9755                "Should select the multi buffer in the pane"
 9756            );
 9757        });
 9758        let close_multi_buffer_task = pane.update_in(cx, |pane, window, cx| {
 9759            pane.close_active_item(
 9760                &CloseActiveItem {
 9761                    save_intent: None,
 9762                    close_pinned: false,
 9763                },
 9764                window,
 9765                cx,
 9766            )
 9767        });
 9768        cx.background_executor.run_until_parked();
 9769        assert!(
 9770            !cx.has_pending_prompt(),
 9771            "All dirty items from the multi buffer are in the pane still, no save prompts should be shown"
 9772        );
 9773        close_multi_buffer_task
 9774            .await
 9775            .expect("Closing multi buffer failed");
 9776        pane.update(cx, |pane, cx| {
 9777            assert_eq!(dirty_regular_buffer.read(cx).save_count, 0);
 9778            assert_eq!(dirty_multi_buffer.read(cx).save_count, 0);
 9779            assert_eq!(dirty_regular_buffer_2.read(cx).save_count, 0);
 9780            assert_eq!(
 9781                pane.items()
 9782                    .map(|item| item.item_id())
 9783                    .sorted()
 9784                    .collect::<Vec<_>>(),
 9785                vec![
 9786                    dirty_regular_buffer.item_id(),
 9787                    dirty_regular_buffer_2.item_id(),
 9788                ],
 9789                "Should have no multi buffer left in the pane"
 9790            );
 9791            assert!(dirty_regular_buffer.read(cx).is_dirty);
 9792            assert!(dirty_regular_buffer_2.read(cx).is_dirty);
 9793        });
 9794    }
 9795
 9796    #[gpui::test]
 9797    async fn test_move_focused_panel_to_next_position(cx: &mut gpui::TestAppContext) {
 9798        init_test(cx);
 9799        let fs = FakeFs::new(cx.executor());
 9800        let project = Project::test(fs, [], cx).await;
 9801        let (workspace, cx) =
 9802            cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
 9803
 9804        // Add a new panel to the right dock, opening the dock and setting the
 9805        // focus to the new panel.
 9806        let panel = workspace.update_in(cx, |workspace, window, cx| {
 9807            let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, cx));
 9808            workspace.add_panel(panel.clone(), window, cx);
 9809
 9810            workspace
 9811                .right_dock()
 9812                .update(cx, |right_dock, cx| right_dock.set_open(true, window, cx));
 9813
 9814            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 9815
 9816            panel
 9817        });
 9818
 9819        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
 9820        // panel to the next valid position which, in this case, is the left
 9821        // dock.
 9822        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9823        workspace.update(cx, |workspace, cx| {
 9824            assert!(workspace.left_dock().read(cx).is_open());
 9825            assert_eq!(panel.read(cx).position, DockPosition::Left);
 9826        });
 9827
 9828        // Dispatch the `MoveFocusedPanelToNextPosition` action, moving the
 9829        // panel to the next valid position which, in this case, is the bottom
 9830        // dock.
 9831        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9832        workspace.update(cx, |workspace, cx| {
 9833            assert!(workspace.bottom_dock().read(cx).is_open());
 9834            assert_eq!(panel.read(cx).position, DockPosition::Bottom);
 9835        });
 9836
 9837        // Dispatch the `MoveFocusedPanelToNextPosition` action again, this time
 9838        // around moving the panel to its initial position, the right dock.
 9839        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9840        workspace.update(cx, |workspace, cx| {
 9841            assert!(workspace.right_dock().read(cx).is_open());
 9842            assert_eq!(panel.read(cx).position, DockPosition::Right);
 9843        });
 9844
 9845        // Remove focus from the panel, ensuring that, if the panel is not
 9846        // focused, the `MoveFocusedPanelToNextPosition` action does not update
 9847        // the panel's position, so the panel is still in the right dock.
 9848        workspace.update_in(cx, |workspace, window, cx| {
 9849            workspace.toggle_panel_focus::<TestPanel>(window, cx);
 9850        });
 9851
 9852        cx.dispatch_action(MoveFocusedPanelToNextPosition);
 9853        workspace.update(cx, |workspace, cx| {
 9854            assert!(workspace.right_dock().read(cx).is_open());
 9855            assert_eq!(panel.read(cx).position, DockPosition::Right);
 9856        });
 9857    }
 9858
 9859    #[gpui::test]
 9860    async fn test_moving_items_create_panes(cx: &mut TestAppContext) {
 9861        init_test(cx);
 9862
 9863        let fs = FakeFs::new(cx.executor());
 9864        let project = Project::test(fs, [], cx).await;
 9865        let (workspace, cx) =
 9866            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9867
 9868        let item_1 = cx.new(|cx| {
 9869            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "first.txt", cx)])
 9870        });
 9871        workspace.update_in(cx, |workspace, window, cx| {
 9872            workspace.add_item_to_active_pane(Box::new(item_1), None, true, window, cx);
 9873            workspace.move_item_to_pane_in_direction(
 9874                &MoveItemToPaneInDirection {
 9875                    direction: SplitDirection::Right,
 9876                    focus: true,
 9877                    clone: false,
 9878                },
 9879                window,
 9880                cx,
 9881            );
 9882            workspace.move_item_to_pane_at_index(
 9883                &MoveItemToPane {
 9884                    destination: 3,
 9885                    focus: true,
 9886                    clone: false,
 9887                },
 9888                window,
 9889                cx,
 9890            );
 9891
 9892            assert_eq!(workspace.panes.len(), 1, "No new panes were created");
 9893            assert_eq!(
 9894                pane_items_paths(&workspace.active_pane, cx),
 9895                vec!["first.txt".to_string()],
 9896                "Single item was not moved anywhere"
 9897            );
 9898        });
 9899
 9900        let item_2 = cx.new(|cx| {
 9901            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "second.txt", cx)])
 9902        });
 9903        workspace.update_in(cx, |workspace, window, cx| {
 9904            workspace.add_item_to_active_pane(Box::new(item_2), None, true, window, cx);
 9905            assert_eq!(
 9906                pane_items_paths(&workspace.panes[0], cx),
 9907                vec!["first.txt".to_string(), "second.txt".to_string()],
 9908            );
 9909            workspace.move_item_to_pane_in_direction(
 9910                &MoveItemToPaneInDirection {
 9911                    direction: SplitDirection::Right,
 9912                    focus: true,
 9913                    clone: false,
 9914                },
 9915                window,
 9916                cx,
 9917            );
 9918
 9919            assert_eq!(workspace.panes.len(), 2, "A new pane should be created");
 9920            assert_eq!(
 9921                pane_items_paths(&workspace.panes[0], cx),
 9922                vec!["first.txt".to_string()],
 9923                "After moving, one item should be left in the original pane"
 9924            );
 9925            assert_eq!(
 9926                pane_items_paths(&workspace.panes[1], cx),
 9927                vec!["second.txt".to_string()],
 9928                "New item should have been moved to the new pane"
 9929            );
 9930        });
 9931
 9932        let item_3 = cx.new(|cx| {
 9933            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "third.txt", cx)])
 9934        });
 9935        workspace.update_in(cx, |workspace, window, cx| {
 9936            let original_pane = workspace.panes[0].clone();
 9937            workspace.set_active_pane(&original_pane, window, cx);
 9938            workspace.add_item_to_active_pane(Box::new(item_3), None, true, window, cx);
 9939            assert_eq!(workspace.panes.len(), 2, "No new panes were created");
 9940            assert_eq!(
 9941                pane_items_paths(&workspace.active_pane, cx),
 9942                vec!["first.txt".to_string(), "third.txt".to_string()],
 9943                "New pane should be ready to move one item out"
 9944            );
 9945
 9946            workspace.move_item_to_pane_at_index(
 9947                &MoveItemToPane {
 9948                    destination: 3,
 9949                    focus: true,
 9950                    clone: false,
 9951                },
 9952                window,
 9953                cx,
 9954            );
 9955            assert_eq!(workspace.panes.len(), 3, "A new pane should be created");
 9956            assert_eq!(
 9957                pane_items_paths(&workspace.active_pane, cx),
 9958                vec!["first.txt".to_string()],
 9959                "After moving, one item should be left in the original pane"
 9960            );
 9961            assert_eq!(
 9962                pane_items_paths(&workspace.panes[1], cx),
 9963                vec!["second.txt".to_string()],
 9964                "Previously created pane should be unchanged"
 9965            );
 9966            assert_eq!(
 9967                pane_items_paths(&workspace.panes[2], cx),
 9968                vec!["third.txt".to_string()],
 9969                "New item should have been moved to the new pane"
 9970            );
 9971        });
 9972    }
 9973
 9974    #[gpui::test]
 9975    async fn test_moving_items_can_clone_panes(cx: &mut TestAppContext) {
 9976        init_test(cx);
 9977
 9978        let fs = FakeFs::new(cx.executor());
 9979        let project = Project::test(fs, [], cx).await;
 9980        let (workspace, cx) =
 9981            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9982
 9983        let item_1 = cx.new(|cx| {
 9984            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "first.txt", cx)])
 9985        });
 9986        workspace.update_in(cx, |workspace, window, cx| {
 9987            workspace.add_item_to_active_pane(Box::new(item_1), None, true, window, cx);
 9988            workspace.move_item_to_pane_in_direction(
 9989                &MoveItemToPaneInDirection {
 9990                    direction: SplitDirection::Right,
 9991                    focus: true,
 9992                    clone: true,
 9993                },
 9994                window,
 9995                cx,
 9996            );
 9997            workspace.move_item_to_pane_at_index(
 9998                &MoveItemToPane {
 9999                    destination: 3,
10000                    focus: true,
10001                    clone: true,
10002                },
10003                window,
10004                cx,
10005            );
10006
10007            assert_eq!(workspace.panes.len(), 3, "Two new panes were created");
10008            for pane in workspace.panes() {
10009                assert_eq!(
10010                    pane_items_paths(pane, cx),
10011                    vec!["first.txt".to_string()],
10012                    "Single item exists in all panes"
10013                );
10014            }
10015        });
10016
10017        // verify that the active pane has been updated after waiting for the
10018        // pane focus event to fire and resolve
10019        workspace.read_with(cx, |workspace, _app| {
10020            assert_eq!(
10021                workspace.active_pane(),
10022                &workspace.panes[2],
10023                "The third pane should be the active one: {:?}",
10024                workspace.panes
10025            );
10026        })
10027    }
10028
10029    mod register_project_item_tests {
10030
10031        use super::*;
10032
10033        // View
10034        struct TestPngItemView {
10035            focus_handle: FocusHandle,
10036        }
10037        // Model
10038        struct TestPngItem {}
10039
10040        impl project::ProjectItem for TestPngItem {
10041            fn try_open(
10042                _project: &Entity<Project>,
10043                path: &ProjectPath,
10044                cx: &mut App,
10045            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
10046                if path.path.extension().unwrap() == "png" {
10047                    Some(cx.spawn(async move |cx| cx.new(|_| TestPngItem {})))
10048                } else {
10049                    None
10050                }
10051            }
10052
10053            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
10054                None
10055            }
10056
10057            fn project_path(&self, _: &App) -> Option<ProjectPath> {
10058                None
10059            }
10060
10061            fn is_dirty(&self) -> bool {
10062                false
10063            }
10064        }
10065
10066        impl Item for TestPngItemView {
10067            type Event = ();
10068            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
10069                "".into()
10070            }
10071        }
10072        impl EventEmitter<()> for TestPngItemView {}
10073        impl Focusable for TestPngItemView {
10074            fn focus_handle(&self, _cx: &App) -> FocusHandle {
10075                self.focus_handle.clone()
10076            }
10077        }
10078
10079        impl Render for TestPngItemView {
10080            fn render(
10081                &mut self,
10082                _window: &mut Window,
10083                _cx: &mut Context<Self>,
10084            ) -> impl IntoElement {
10085                Empty
10086            }
10087        }
10088
10089        impl ProjectItem for TestPngItemView {
10090            type Item = TestPngItem;
10091
10092            fn for_project_item(
10093                _project: Entity<Project>,
10094                _pane: Option<&Pane>,
10095                _item: Entity<Self::Item>,
10096                _: &mut Window,
10097                cx: &mut Context<Self>,
10098            ) -> Self
10099            where
10100                Self: Sized,
10101            {
10102                Self {
10103                    focus_handle: cx.focus_handle(),
10104                }
10105            }
10106        }
10107
10108        // View
10109        struct TestIpynbItemView {
10110            focus_handle: FocusHandle,
10111        }
10112        // Model
10113        struct TestIpynbItem {}
10114
10115        impl project::ProjectItem for TestIpynbItem {
10116            fn try_open(
10117                _project: &Entity<Project>,
10118                path: &ProjectPath,
10119                cx: &mut App,
10120            ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
10121                if path.path.extension().unwrap() == "ipynb" {
10122                    Some(cx.spawn(async move |cx| cx.new(|_| TestIpynbItem {})))
10123                } else {
10124                    None
10125                }
10126            }
10127
10128            fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
10129                None
10130            }
10131
10132            fn project_path(&self, _: &App) -> Option<ProjectPath> {
10133                None
10134            }
10135
10136            fn is_dirty(&self) -> bool {
10137                false
10138            }
10139        }
10140
10141        impl Item for TestIpynbItemView {
10142            type Event = ();
10143            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
10144                "".into()
10145            }
10146        }
10147        impl EventEmitter<()> for TestIpynbItemView {}
10148        impl Focusable for TestIpynbItemView {
10149            fn focus_handle(&self, _cx: &App) -> FocusHandle {
10150                self.focus_handle.clone()
10151            }
10152        }
10153
10154        impl Render for TestIpynbItemView {
10155            fn render(
10156                &mut self,
10157                _window: &mut Window,
10158                _cx: &mut Context<Self>,
10159            ) -> impl IntoElement {
10160                Empty
10161            }
10162        }
10163
10164        impl ProjectItem for TestIpynbItemView {
10165            type Item = TestIpynbItem;
10166
10167            fn for_project_item(
10168                _project: Entity<Project>,
10169                _pane: Option<&Pane>,
10170                _item: Entity<Self::Item>,
10171                _: &mut Window,
10172                cx: &mut Context<Self>,
10173            ) -> Self
10174            where
10175                Self: Sized,
10176            {
10177                Self {
10178                    focus_handle: cx.focus_handle(),
10179                }
10180            }
10181        }
10182
10183        struct TestAlternatePngItemView {
10184            focus_handle: FocusHandle,
10185        }
10186
10187        impl Item for TestAlternatePngItemView {
10188            type Event = ();
10189            fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
10190                "".into()
10191            }
10192        }
10193
10194        impl EventEmitter<()> for TestAlternatePngItemView {}
10195        impl Focusable for TestAlternatePngItemView {
10196            fn focus_handle(&self, _cx: &App) -> FocusHandle {
10197                self.focus_handle.clone()
10198            }
10199        }
10200
10201        impl Render for TestAlternatePngItemView {
10202            fn render(
10203                &mut self,
10204                _window: &mut Window,
10205                _cx: &mut Context<Self>,
10206            ) -> impl IntoElement {
10207                Empty
10208            }
10209        }
10210
10211        impl ProjectItem for TestAlternatePngItemView {
10212            type Item = TestPngItem;
10213
10214            fn for_project_item(
10215                _project: Entity<Project>,
10216                _pane: Option<&Pane>,
10217                _item: Entity<Self::Item>,
10218                _: &mut Window,
10219                cx: &mut Context<Self>,
10220            ) -> Self
10221            where
10222                Self: Sized,
10223            {
10224                Self {
10225                    focus_handle: cx.focus_handle(),
10226                }
10227            }
10228        }
10229
10230        #[gpui::test]
10231        async fn test_register_project_item(cx: &mut TestAppContext) {
10232            init_test(cx);
10233
10234            cx.update(|cx| {
10235                register_project_item::<TestPngItemView>(cx);
10236                register_project_item::<TestIpynbItemView>(cx);
10237            });
10238
10239            let fs = FakeFs::new(cx.executor());
10240            fs.insert_tree(
10241                "/root1",
10242                json!({
10243                    "one.png": "BINARYDATAHERE",
10244                    "two.ipynb": "{ totally a notebook }",
10245                    "three.txt": "editing text, sure why not?"
10246                }),
10247            )
10248            .await;
10249
10250            let project = Project::test(fs, ["root1".as_ref()], cx).await;
10251            let (workspace, cx) =
10252                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
10253
10254            let worktree_id = project.update(cx, |project, cx| {
10255                project.worktrees(cx).next().unwrap().read(cx).id()
10256            });
10257
10258            let handle = workspace
10259                .update_in(cx, |workspace, window, cx| {
10260                    let project_path = (worktree_id, "one.png");
10261                    workspace.open_path(project_path, None, true, window, cx)
10262                })
10263                .await
10264                .unwrap();
10265
10266            // Now we can check if the handle we got back errored or not
10267            assert_eq!(
10268                handle.to_any().entity_type(),
10269                TypeId::of::<TestPngItemView>()
10270            );
10271
10272            let handle = workspace
10273                .update_in(cx, |workspace, window, cx| {
10274                    let project_path = (worktree_id, "two.ipynb");
10275                    workspace.open_path(project_path, None, true, window, cx)
10276                })
10277                .await
10278                .unwrap();
10279
10280            assert_eq!(
10281                handle.to_any().entity_type(),
10282                TypeId::of::<TestIpynbItemView>()
10283            );
10284
10285            let handle = workspace
10286                .update_in(cx, |workspace, window, cx| {
10287                    let project_path = (worktree_id, "three.txt");
10288                    workspace.open_path(project_path, None, true, window, cx)
10289                })
10290                .await;
10291            assert!(handle.is_err());
10292        }
10293
10294        #[gpui::test]
10295        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
10296            init_test(cx);
10297
10298            cx.update(|cx| {
10299                register_project_item::<TestPngItemView>(cx);
10300                register_project_item::<TestAlternatePngItemView>(cx);
10301            });
10302
10303            let fs = FakeFs::new(cx.executor());
10304            fs.insert_tree(
10305                "/root1",
10306                json!({
10307                    "one.png": "BINARYDATAHERE",
10308                    "two.ipynb": "{ totally a notebook }",
10309                    "three.txt": "editing text, sure why not?"
10310                }),
10311            )
10312            .await;
10313            let project = Project::test(fs, ["root1".as_ref()], cx).await;
10314            let (workspace, cx) =
10315                cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
10316            let worktree_id = project.update(cx, |project, cx| {
10317                project.worktrees(cx).next().unwrap().read(cx).id()
10318            });
10319
10320            let handle = workspace
10321                .update_in(cx, |workspace, window, cx| {
10322                    let project_path = (worktree_id, "one.png");
10323                    workspace.open_path(project_path, None, true, window, cx)
10324                })
10325                .await
10326                .unwrap();
10327
10328            // This _must_ be the second item registered
10329            assert_eq!(
10330                handle.to_any().entity_type(),
10331                TypeId::of::<TestAlternatePngItemView>()
10332            );
10333
10334            let handle = workspace
10335                .update_in(cx, |workspace, window, cx| {
10336                    let project_path = (worktree_id, "three.txt");
10337                    workspace.open_path(project_path, None, true, window, cx)
10338                })
10339                .await;
10340            assert!(handle.is_err());
10341        }
10342    }
10343
10344    fn pane_items_paths(pane: &Entity<Pane>, cx: &App) -> Vec<String> {
10345        pane.read(cx)
10346            .items()
10347            .flat_map(|item| {
10348                item.project_paths(cx)
10349                    .into_iter()
10350                    .map(|path| path.path.to_string_lossy().to_string())
10351            })
10352            .collect()
10353    }
10354
10355    pub fn init_test(cx: &mut TestAppContext) {
10356        cx.update(|cx| {
10357            let settings_store = SettingsStore::test(cx);
10358            cx.set_global(settings_store);
10359            theme::init(theme::LoadThemes::JustBase, cx);
10360            language::init(cx);
10361            crate::init_settings(cx);
10362            Project::init_settings(cx);
10363        });
10364    }
10365
10366    fn dirty_project_item(id: u64, path: &str, cx: &mut App) -> Entity<TestProjectItem> {
10367        let item = TestProjectItem::new(id, path, cx);
10368        item.update(cx, |item, _| {
10369            item.is_dirty = true;
10370        });
10371        item
10372    }
10373}