workspace.rs

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