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