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 bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
2950        self.center.bounding_box_for_pane(pane)
2951    }
2952
2953    pub fn find_pane_in_direction(
2954        &mut self,
2955        direction: SplitDirection,
2956        cx: &WindowContext,
2957    ) -> Option<View<Pane>> {
2958        let bounding_box = self.center.bounding_box_for_pane(&self.active_pane)?;
2959        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2960        let center = match cursor {
2961            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2962            _ => bounding_box.center(),
2963        };
2964
2965        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2966
2967        let target = match direction {
2968            SplitDirection::Left => {
2969                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2970            }
2971            SplitDirection::Right => {
2972                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2973            }
2974            SplitDirection::Up => {
2975                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2976            }
2977            SplitDirection::Down => {
2978                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2979            }
2980        };
2981        self.center.pane_at_pixel_position(target).cloned()
2982    }
2983
2984    pub fn swap_pane_in_direction(
2985        &mut self,
2986        direction: SplitDirection,
2987        cx: &mut ViewContext<Self>,
2988    ) {
2989        if let Some(to) = self.find_pane_in_direction(direction, cx) {
2990            self.center.swap(&self.active_pane.clone(), &to);
2991            cx.notify();
2992        }
2993    }
2994
2995    pub fn resize_pane(&mut self, axis: gpui::Axis, amount: Pixels, cx: &mut ViewContext<Self>) {
2996        self.center
2997            .resize(&self.active_pane.clone(), axis, amount, &self.bounds);
2998        cx.notify();
2999    }
3000
3001    pub fn reset_pane_sizes(&mut self, cx: &mut ViewContext<Self>) {
3002        self.center.reset_pane_sizes();
3003        cx.notify();
3004    }
3005
3006    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
3007        // This is explicitly hoisted out of the following check for pane identity as
3008        // terminal panel panes are not registered as a center panes.
3009        self.status_bar.update(cx, |status_bar, cx| {
3010            status_bar.set_active_pane(&pane, cx);
3011        });
3012        if self.active_pane != pane {
3013            self.set_active_pane(&pane, cx);
3014        }
3015
3016        if self.last_active_center_pane.is_none() {
3017            self.last_active_center_pane = Some(pane.downgrade());
3018        }
3019
3020        self.dismiss_zoomed_items_to_reveal(None, cx);
3021        if pane.read(cx).is_zoomed() {
3022            self.zoomed = Some(pane.downgrade().into());
3023        } else {
3024            self.zoomed = None;
3025        }
3026        self.zoomed_position = None;
3027        cx.emit(Event::ZoomChanged);
3028        self.update_active_view_for_followers(cx);
3029        pane.model.update(cx, |pane, _| {
3030            pane.track_alternate_file_items();
3031        });
3032
3033        cx.notify();
3034    }
3035
3036    fn set_active_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) {
3037        self.active_pane = pane.clone();
3038        self.active_item_path_changed(cx);
3039        self.last_active_center_pane = Some(pane.downgrade());
3040    }
3041
3042    fn handle_panel_focused(&mut self, cx: &mut ViewContext<Self>) {
3043        self.update_active_view_for_followers(cx);
3044    }
3045
3046    fn handle_pane_event(
3047        &mut self,
3048        pane: View<Pane>,
3049        event: &pane::Event,
3050        cx: &mut ViewContext<Self>,
3051    ) {
3052        match event {
3053            pane::Event::AddItem { item } => {
3054                item.added_to_pane(self, pane, cx);
3055                cx.emit(Event::ItemAdded {
3056                    item: item.boxed_clone(),
3057                });
3058            }
3059            pane::Event::Split(direction) => {
3060                self.split_and_clone(pane, *direction, cx);
3061            }
3062            pane::Event::JoinIntoNext => self.join_pane_into_next(pane, cx),
3063            pane::Event::JoinAll => self.join_all_panes(cx),
3064            pane::Event::Remove { focus_on_pane } => {
3065                self.remove_pane(pane, focus_on_pane.clone(), cx)
3066            }
3067            pane::Event::ActivateItem { local } => {
3068                cx.on_next_frame(|_, cx| {
3069                    cx.invalidate_character_coordinates();
3070                });
3071
3072                pane.model.update(cx, |pane, _| {
3073                    pane.track_alternate_file_items();
3074                });
3075                if *local {
3076                    self.unfollow_in_pane(&pane, cx);
3077                }
3078                if &pane == self.active_pane() {
3079                    self.active_item_path_changed(cx);
3080                    self.update_active_view_for_followers(cx);
3081                }
3082            }
3083            pane::Event::UserSavedItem { item, save_intent } => cx.emit(Event::UserSavedItem {
3084                pane: pane.downgrade(),
3085                item: item.boxed_clone(),
3086                save_intent: *save_intent,
3087            }),
3088            pane::Event::ChangeItemTitle => {
3089                if pane == self.active_pane {
3090                    self.active_item_path_changed(cx);
3091                }
3092                self.update_window_edited(cx);
3093            }
3094            pane::Event::RemoveItem { .. } => {}
3095            pane::Event::RemovedItem { item_id } => {
3096                cx.emit(Event::ActiveItemChanged);
3097                self.update_window_edited(cx);
3098                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
3099                    if entry.get().entity_id() == pane.entity_id() {
3100                        entry.remove();
3101                    }
3102                }
3103            }
3104            pane::Event::Focus => {
3105                cx.on_next_frame(|_, cx| {
3106                    cx.invalidate_character_coordinates();
3107                });
3108                self.handle_pane_focused(pane.clone(), cx);
3109            }
3110            pane::Event::ZoomIn => {
3111                if pane == self.active_pane {
3112                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
3113                    if pane.read(cx).has_focus(cx) {
3114                        self.zoomed = Some(pane.downgrade().into());
3115                        self.zoomed_position = None;
3116                        cx.emit(Event::ZoomChanged);
3117                    }
3118                    cx.notify();
3119                }
3120            }
3121            pane::Event::ZoomOut => {
3122                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
3123                if self.zoomed_position.is_none() {
3124                    self.zoomed = None;
3125                    cx.emit(Event::ZoomChanged);
3126                }
3127                cx.notify();
3128            }
3129        }
3130
3131        self.serialize_workspace(cx);
3132    }
3133
3134    pub fn unfollow_in_pane(
3135        &mut self,
3136        pane: &View<Pane>,
3137        cx: &mut ViewContext<Workspace>,
3138    ) -> Option<PeerId> {
3139        let leader_id = self.leader_for_pane(pane)?;
3140        self.unfollow(leader_id, cx);
3141        Some(leader_id)
3142    }
3143
3144    pub fn split_pane(
3145        &mut self,
3146        pane_to_split: View<Pane>,
3147        split_direction: SplitDirection,
3148        cx: &mut ViewContext<Self>,
3149    ) -> View<Pane> {
3150        let new_pane = self.add_pane(cx);
3151        self.center
3152            .split(&pane_to_split, &new_pane, split_direction)
3153            .unwrap();
3154        cx.notify();
3155        new_pane
3156    }
3157
3158    pub fn split_and_clone(
3159        &mut self,
3160        pane: View<Pane>,
3161        direction: SplitDirection,
3162        cx: &mut ViewContext<Self>,
3163    ) -> Option<View<Pane>> {
3164        let item = pane.read(cx).active_item()?;
3165        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
3166            let new_pane = self.add_pane(cx);
3167            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
3168            self.center.split(&pane, &new_pane, direction).unwrap();
3169            Some(new_pane)
3170        } else {
3171            None
3172        };
3173        cx.notify();
3174        maybe_pane_handle
3175    }
3176
3177    pub fn split_pane_with_item(
3178        &mut self,
3179        pane_to_split: WeakView<Pane>,
3180        split_direction: SplitDirection,
3181        from: WeakView<Pane>,
3182        item_id_to_move: EntityId,
3183        cx: &mut ViewContext<Self>,
3184    ) {
3185        let Some(pane_to_split) = pane_to_split.upgrade() else {
3186            return;
3187        };
3188        let Some(from) = from.upgrade() else {
3189            return;
3190        };
3191
3192        let new_pane = self.add_pane(cx);
3193        move_item(&from, &new_pane, item_id_to_move, 0, cx);
3194        self.center
3195            .split(&pane_to_split, &new_pane, split_direction)
3196            .unwrap();
3197        cx.notify();
3198    }
3199
3200    pub fn split_pane_with_project_entry(
3201        &mut self,
3202        pane_to_split: WeakView<Pane>,
3203        split_direction: SplitDirection,
3204        project_entry: ProjectEntryId,
3205        cx: &mut ViewContext<Self>,
3206    ) -> Option<Task<Result<()>>> {
3207        let pane_to_split = pane_to_split.upgrade()?;
3208        let new_pane = self.add_pane(cx);
3209        self.center
3210            .split(&pane_to_split, &new_pane, split_direction)
3211            .unwrap();
3212
3213        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
3214        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
3215        Some(cx.foreground_executor().spawn(async move {
3216            task.await?;
3217            Ok(())
3218        }))
3219    }
3220
3221    pub fn join_all_panes(&mut self, cx: &mut ViewContext<Self>) {
3222        let active_item = self.active_pane.read(cx).active_item();
3223        for pane in &self.panes {
3224            join_pane_into_active(&self.active_pane, pane, cx);
3225        }
3226        if let Some(active_item) = active_item {
3227            self.activate_item(active_item.as_ref(), true, true, cx);
3228        }
3229        cx.notify();
3230    }
3231
3232    pub fn join_pane_into_next(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
3233        let next_pane = self
3234            .find_pane_in_direction(SplitDirection::Right, cx)
3235            .or_else(|| self.find_pane_in_direction(SplitDirection::Down, cx))
3236            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3237            .or_else(|| self.find_pane_in_direction(SplitDirection::Up, cx));
3238        let Some(next_pane) = next_pane else {
3239            return;
3240        };
3241        move_all_items(&pane, &next_pane, cx);
3242        cx.notify();
3243    }
3244
3245    fn remove_pane(
3246        &mut self,
3247        pane: View<Pane>,
3248        focus_on: Option<View<Pane>>,
3249        cx: &mut ViewContext<Self>,
3250    ) {
3251        if self.center.remove(&pane).unwrap() {
3252            self.force_remove_pane(&pane, &focus_on, cx);
3253            self.unfollow_in_pane(&pane, cx);
3254            self.last_leaders_by_pane.remove(&pane.downgrade());
3255            for removed_item in pane.read(cx).items() {
3256                self.panes_by_item.remove(&removed_item.item_id());
3257            }
3258
3259            cx.notify();
3260        } else {
3261            self.active_item_path_changed(cx);
3262        }
3263        cx.emit(Event::PaneRemoved);
3264    }
3265
3266    pub fn panes(&self) -> &[View<Pane>] {
3267        &self.panes
3268    }
3269
3270    pub fn active_pane(&self) -> &View<Pane> {
3271        &self.active_pane
3272    }
3273
3274    pub fn focused_pane(&self, cx: &WindowContext) -> View<Pane> {
3275        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
3276            if dock.focus_handle(cx).contains_focused(cx) {
3277                if let Some(pane) = dock
3278                    .read(cx)
3279                    .active_panel()
3280                    .and_then(|panel| panel.pane(cx))
3281                {
3282                    return pane;
3283                }
3284            }
3285        }
3286        self.active_pane().clone()
3287    }
3288
3289    pub fn adjacent_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
3290        self.find_pane_in_direction(SplitDirection::Right, cx)
3291            .or_else(|| self.find_pane_in_direction(SplitDirection::Left, cx))
3292            .unwrap_or_else(|| self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx))
3293            .clone()
3294    }
3295
3296    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
3297        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
3298        weak_pane.upgrade()
3299    }
3300
3301    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
3302        self.follower_states.retain(|leader_id, state| {
3303            if *leader_id == peer_id {
3304                for item in state.items_by_leader_view_id.values() {
3305                    item.view.set_leader_peer_id(None, cx);
3306                }
3307                false
3308            } else {
3309                true
3310            }
3311        });
3312        cx.notify();
3313    }
3314
3315    pub fn start_following(
3316        &mut self,
3317        leader_id: PeerId,
3318        cx: &mut ViewContext<Self>,
3319    ) -> Option<Task<Result<()>>> {
3320        let pane = self.active_pane().clone();
3321
3322        self.last_leaders_by_pane
3323            .insert(pane.downgrade(), leader_id);
3324        self.unfollow(leader_id, cx);
3325        self.unfollow_in_pane(&pane, cx);
3326        self.follower_states.insert(
3327            leader_id,
3328            FollowerState {
3329                center_pane: pane.clone(),
3330                dock_pane: None,
3331                active_view_id: None,
3332                items_by_leader_view_id: Default::default(),
3333            },
3334        );
3335        cx.notify();
3336
3337        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3338        let project_id = self.project.read(cx).remote_id();
3339        let request = self.app_state.client.request(proto::Follow {
3340            room_id,
3341            project_id,
3342            leader_id: Some(leader_id),
3343        });
3344
3345        Some(cx.spawn(|this, mut cx| async move {
3346            let response = request.await?;
3347            this.update(&mut cx, |this, _| {
3348                let state = this
3349                    .follower_states
3350                    .get_mut(&leader_id)
3351                    .ok_or_else(|| anyhow!("following interrupted"))?;
3352                state.active_view_id = response
3353                    .active_view
3354                    .as_ref()
3355                    .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3356                Ok::<_, anyhow::Error>(())
3357            })??;
3358            if let Some(view) = response.active_view {
3359                Self::add_view_from_leader(this.clone(), leader_id, &view, &mut cx).await?;
3360            }
3361            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
3362            Ok(())
3363        }))
3364    }
3365
3366    pub fn follow_next_collaborator(
3367        &mut self,
3368        _: &FollowNextCollaborator,
3369        cx: &mut ViewContext<Self>,
3370    ) {
3371        let collaborators = self.project.read(cx).collaborators();
3372        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
3373            let mut collaborators = collaborators.keys().copied();
3374            for peer_id in collaborators.by_ref() {
3375                if peer_id == leader_id {
3376                    break;
3377                }
3378            }
3379            collaborators.next()
3380        } else if let Some(last_leader_id) =
3381            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
3382        {
3383            if collaborators.contains_key(last_leader_id) {
3384                Some(*last_leader_id)
3385            } else {
3386                None
3387            }
3388        } else {
3389            None
3390        };
3391
3392        let pane = self.active_pane.clone();
3393        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
3394        else {
3395            return;
3396        };
3397        if self.unfollow_in_pane(&pane, cx) == Some(leader_id) {
3398            return;
3399        }
3400        if let Some(task) = self.start_following(leader_id, cx) {
3401            task.detach_and_log_err(cx)
3402        }
3403    }
3404
3405    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
3406        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
3407            return;
3408        };
3409        let room = room.read(cx);
3410        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
3411            return;
3412        };
3413
3414        let project = self.project.read(cx);
3415
3416        let other_project_id = match remote_participant.location {
3417            call::ParticipantLocation::External => None,
3418            call::ParticipantLocation::UnsharedProject => None,
3419            call::ParticipantLocation::SharedProject { project_id } => {
3420                if Some(project_id) == project.remote_id() {
3421                    None
3422                } else {
3423                    Some(project_id)
3424                }
3425            }
3426        };
3427
3428        // if they are active in another project, follow there.
3429        if let Some(project_id) = other_project_id {
3430            let app_state = self.app_state.clone();
3431            crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
3432                .detach_and_log_err(cx);
3433        }
3434
3435        // if you're already following, find the right pane and focus it.
3436        if let Some(follower_state) = self.follower_states.get(&leader_id) {
3437            cx.focus_view(follower_state.pane());
3438            return;
3439        }
3440
3441        // Otherwise, follow.
3442        if let Some(task) = self.start_following(leader_id, cx) {
3443            task.detach_and_log_err(cx)
3444        }
3445    }
3446
3447    pub fn unfollow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3448        cx.notify();
3449        let state = self.follower_states.remove(&leader_id)?;
3450        for (_, item) in state.items_by_leader_view_id {
3451            item.view.set_leader_peer_id(None, cx);
3452        }
3453
3454        let project_id = self.project.read(cx).remote_id();
3455        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
3456        self.app_state
3457            .client
3458            .send(proto::Unfollow {
3459                room_id,
3460                project_id,
3461                leader_id: Some(leader_id),
3462            })
3463            .log_err();
3464
3465        Some(())
3466    }
3467
3468    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
3469        self.follower_states.contains_key(&peer_id)
3470    }
3471
3472    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
3473        cx.emit(Event::ActiveItemChanged);
3474        let active_entry = self.active_project_path(cx);
3475        self.project
3476            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
3477
3478        self.update_window_title(cx);
3479    }
3480
3481    fn update_window_title(&mut self, cx: &mut WindowContext) {
3482        let project = self.project().read(cx);
3483        let mut title = String::new();
3484
3485        for (i, name) in project.worktree_root_names(cx).enumerate() {
3486            if i > 0 {
3487                title.push_str(", ");
3488            }
3489            title.push_str(name);
3490        }
3491
3492        if title.is_empty() {
3493            title = "empty project".to_string();
3494        }
3495
3496        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
3497            let filename = path
3498                .path
3499                .file_name()
3500                .map(|s| s.to_string_lossy())
3501                .or_else(|| {
3502                    Some(Cow::Borrowed(
3503                        project
3504                            .worktree_for_id(path.worktree_id, cx)?
3505                            .read(cx)
3506                            .root_name(),
3507                    ))
3508                });
3509
3510            if let Some(filename) = filename {
3511                title.push_str("");
3512                title.push_str(filename.as_ref());
3513            }
3514        }
3515
3516        if project.is_via_collab() {
3517            title.push_str("");
3518        } else if project.is_shared() {
3519            title.push_str("");
3520        }
3521
3522        cx.set_window_title(&title);
3523    }
3524
3525    fn update_window_edited(&mut self, cx: &mut WindowContext) {
3526        let is_edited = !self.project.read(cx).is_disconnected(cx)
3527            && self
3528                .items(cx)
3529                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
3530        if is_edited != self.window_edited {
3531            self.window_edited = is_edited;
3532            cx.set_window_edited(self.window_edited)
3533        }
3534    }
3535
3536    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
3537        if self.notifications.is_empty() {
3538            None
3539        } else {
3540            Some(
3541                div()
3542                    .absolute()
3543                    .right_3()
3544                    .bottom_3()
3545                    .w_112()
3546                    .h_full()
3547                    .flex()
3548                    .flex_col()
3549                    .justify_end()
3550                    .gap_2()
3551                    .children(
3552                        self.notifications
3553                            .iter()
3554                            .map(|(_, notification)| notification.to_any()),
3555                    ),
3556            )
3557        }
3558    }
3559
3560    // RPC handlers
3561
3562    fn active_view_for_follower(
3563        &self,
3564        follower_project_id: Option<u64>,
3565        cx: &mut ViewContext<Self>,
3566    ) -> Option<proto::View> {
3567        let (item, panel_id) = self.active_item_for_followers(cx);
3568        let item = item?;
3569        let leader_id = self
3570            .pane_for(&*item)
3571            .and_then(|pane| self.leader_for_pane(&pane));
3572
3573        let item_handle = item.to_followable_item_handle(cx)?;
3574        let id = item_handle.remote_id(&self.app_state.client, cx)?;
3575        let variant = item_handle.to_state_proto(cx)?;
3576
3577        if item_handle.is_project_item(cx)
3578            && (follower_project_id.is_none()
3579                || follower_project_id != self.project.read(cx).remote_id())
3580        {
3581            return None;
3582        }
3583
3584        Some(proto::View {
3585            id: Some(id.to_proto()),
3586            leader_id,
3587            variant: Some(variant),
3588            panel_id: panel_id.map(|id| id as i32),
3589        })
3590    }
3591
3592    fn handle_follow(
3593        &mut self,
3594        follower_project_id: Option<u64>,
3595        cx: &mut ViewContext<Self>,
3596    ) -> proto::FollowResponse {
3597        let active_view = self.active_view_for_follower(follower_project_id, cx);
3598
3599        cx.notify();
3600        proto::FollowResponse {
3601            // TODO: Remove after version 0.145.x stabilizes.
3602            active_view_id: active_view.as_ref().and_then(|view| view.id.clone()),
3603            views: active_view.iter().cloned().collect(),
3604            active_view,
3605        }
3606    }
3607
3608    fn handle_update_followers(
3609        &mut self,
3610        leader_id: PeerId,
3611        message: proto::UpdateFollowers,
3612        _cx: &mut ViewContext<Self>,
3613    ) {
3614        self.leader_updates_tx
3615            .unbounded_send((leader_id, message))
3616            .ok();
3617    }
3618
3619    async fn process_leader_update(
3620        this: &WeakView<Self>,
3621        leader_id: PeerId,
3622        update: proto::UpdateFollowers,
3623        cx: &mut AsyncWindowContext,
3624    ) -> Result<()> {
3625        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
3626            proto::update_followers::Variant::CreateView(view) => {
3627                let view_id = ViewId::from_proto(view.id.clone().context("invalid view id")?)?;
3628                let should_add_view = this.update(cx, |this, _| {
3629                    if let Some(state) = this.follower_states.get_mut(&leader_id) {
3630                        anyhow::Ok(!state.items_by_leader_view_id.contains_key(&view_id))
3631                    } else {
3632                        anyhow::Ok(false)
3633                    }
3634                })??;
3635
3636                if should_add_view {
3637                    Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
3638                }
3639            }
3640            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
3641                let should_add_view = this.update(cx, |this, _| {
3642                    if let Some(state) = this.follower_states.get_mut(&leader_id) {
3643                        state.active_view_id = update_active_view
3644                            .view
3645                            .as_ref()
3646                            .and_then(|view| ViewId::from_proto(view.id.clone()?).ok());
3647
3648                        if state.active_view_id.is_some_and(|view_id| {
3649                            !state.items_by_leader_view_id.contains_key(&view_id)
3650                        }) {
3651                            anyhow::Ok(true)
3652                        } else {
3653                            anyhow::Ok(false)
3654                        }
3655                    } else {
3656                        anyhow::Ok(false)
3657                    }
3658                })??;
3659
3660                if should_add_view {
3661                    if let Some(view) = update_active_view.view {
3662                        Self::add_view_from_leader(this.clone(), leader_id, &view, cx).await?
3663                    }
3664                }
3665            }
3666            proto::update_followers::Variant::UpdateView(update_view) => {
3667                let variant = update_view
3668                    .variant
3669                    .ok_or_else(|| anyhow!("missing update view variant"))?;
3670                let id = update_view
3671                    .id
3672                    .ok_or_else(|| anyhow!("missing update view id"))?;
3673                let mut tasks = Vec::new();
3674                this.update(cx, |this, cx| {
3675                    let project = this.project.clone();
3676                    if let Some(state) = this.follower_states.get(&leader_id) {
3677                        let view_id = ViewId::from_proto(id.clone())?;
3678                        if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
3679                            tasks.push(item.view.apply_update_proto(&project, variant.clone(), cx));
3680                        }
3681                    }
3682                    anyhow::Ok(())
3683                })??;
3684                try_join_all(tasks).await.log_err();
3685            }
3686        }
3687        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
3688        Ok(())
3689    }
3690
3691    async fn add_view_from_leader(
3692        this: WeakView<Self>,
3693        leader_id: PeerId,
3694        view: &proto::View,
3695        cx: &mut AsyncWindowContext,
3696    ) -> Result<()> {
3697        let this = this.upgrade().context("workspace dropped")?;
3698
3699        let Some(id) = view.id.clone() else {
3700            return Err(anyhow!("no id for view"));
3701        };
3702        let id = ViewId::from_proto(id)?;
3703        let panel_id = view.panel_id.and_then(proto::PanelId::from_i32);
3704
3705        let pane = this.update(cx, |this, _cx| {
3706            let state = this
3707                .follower_states
3708                .get(&leader_id)
3709                .context("stopped following")?;
3710            anyhow::Ok(state.pane().clone())
3711        })??;
3712        let existing_item = pane.update(cx, |pane, cx| {
3713            let client = this.read(cx).client().clone();
3714            pane.items().find_map(|item| {
3715                let item = item.to_followable_item_handle(cx)?;
3716                if item.remote_id(&client, cx) == Some(id) {
3717                    Some(item)
3718                } else {
3719                    None
3720                }
3721            })
3722        })?;
3723        let item = if let Some(existing_item) = existing_item {
3724            existing_item
3725        } else {
3726            let variant = view.variant.clone();
3727            if variant.is_none() {
3728                Err(anyhow!("missing view variant"))?;
3729            }
3730
3731            let task = cx.update(|cx| {
3732                FollowableViewRegistry::from_state_proto(this.clone(), id, variant, cx)
3733            })?;
3734
3735            let Some(task) = task else {
3736                return Err(anyhow!(
3737                    "failed to construct view from leader (maybe from a different version of zed?)"
3738                ));
3739            };
3740
3741            let mut new_item = task.await?;
3742            pane.update(cx, |pane, cx| {
3743                let mut item_ix_to_remove = None;
3744                for (ix, item) in pane.items().enumerate() {
3745                    if let Some(item) = item.to_followable_item_handle(cx) {
3746                        match new_item.dedup(item.as_ref(), cx) {
3747                            Some(item::Dedup::KeepExisting) => {
3748                                new_item =
3749                                    item.boxed_clone().to_followable_item_handle(cx).unwrap();
3750                                break;
3751                            }
3752                            Some(item::Dedup::ReplaceExisting) => {
3753                                item_ix_to_remove = Some(ix);
3754                                break;
3755                            }
3756                            None => {}
3757                        }
3758                    }
3759                }
3760
3761                if let Some(ix) = item_ix_to_remove {
3762                    pane.remove_item(ix, false, false, cx);
3763                    pane.add_item(new_item.boxed_clone(), false, false, Some(ix), cx);
3764                }
3765            })?;
3766
3767            new_item
3768        };
3769
3770        this.update(cx, |this, cx| {
3771            let state = this.follower_states.get_mut(&leader_id)?;
3772            item.set_leader_peer_id(Some(leader_id), cx);
3773            state.items_by_leader_view_id.insert(
3774                id,
3775                FollowerView {
3776                    view: item,
3777                    location: panel_id,
3778                },
3779            );
3780
3781            Some(())
3782        })?;
3783
3784        Ok(())
3785    }
3786
3787    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
3788        let mut is_project_item = true;
3789        let mut update = proto::UpdateActiveView::default();
3790        if cx.is_window_active() {
3791            let (active_item, panel_id) = self.active_item_for_followers(cx);
3792
3793            if let Some(item) = active_item {
3794                if item.focus_handle(cx).contains_focused(cx) {
3795                    let leader_id = self
3796                        .pane_for(&*item)
3797                        .and_then(|pane| self.leader_for_pane(&pane));
3798
3799                    if let Some(item) = item.to_followable_item_handle(cx) {
3800                        let id = item
3801                            .remote_id(&self.app_state.client, cx)
3802                            .map(|id| id.to_proto());
3803
3804                        if let Some(id) = id.clone() {
3805                            if let Some(variant) = item.to_state_proto(cx) {
3806                                let view = Some(proto::View {
3807                                    id: Some(id.clone()),
3808                                    leader_id,
3809                                    variant: Some(variant),
3810                                    panel_id: panel_id.map(|id| id as i32),
3811                                });
3812
3813                                is_project_item = item.is_project_item(cx);
3814                                update = proto::UpdateActiveView {
3815                                    view,
3816                                    // TODO: Remove after version 0.145.x stabilizes.
3817                                    id: Some(id.clone()),
3818                                    leader_id,
3819                                };
3820                            }
3821                        };
3822                    }
3823                }
3824            }
3825        }
3826
3827        let active_view_id = update.view.as_ref().and_then(|view| view.id.as_ref());
3828        if active_view_id != self.last_active_view_id.as_ref() {
3829            self.last_active_view_id = active_view_id.cloned();
3830            self.update_followers(
3831                is_project_item,
3832                proto::update_followers::Variant::UpdateActiveView(update),
3833                cx,
3834            );
3835        }
3836    }
3837
3838    fn active_item_for_followers(
3839        &self,
3840        cx: &mut WindowContext,
3841    ) -> (Option<Box<dyn ItemHandle>>, Option<proto::PanelId>) {
3842        let mut active_item = None;
3843        let mut panel_id = None;
3844        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
3845            if dock.focus_handle(cx).contains_focused(cx) {
3846                if let Some(panel) = dock.read(cx).active_panel() {
3847                    if let Some(pane) = panel.pane(cx) {
3848                        if let Some(item) = pane.read(cx).active_item() {
3849                            active_item = Some(item);
3850                            panel_id = panel.remote_id();
3851                            break;
3852                        }
3853                    }
3854                }
3855            }
3856        }
3857
3858        if active_item.is_none() {
3859            active_item = self.active_pane().read(cx).active_item();
3860        }
3861        (active_item, panel_id)
3862    }
3863
3864    fn update_followers(
3865        &self,
3866        project_only: bool,
3867        update: proto::update_followers::Variant,
3868        cx: &mut WindowContext,
3869    ) -> Option<()> {
3870        // If this update only applies to for followers in the current project,
3871        // then skip it unless this project is shared. If it applies to all
3872        // followers, regardless of project, then set `project_id` to none,
3873        // indicating that it goes to all followers.
3874        let project_id = if project_only {
3875            Some(self.project.read(cx).remote_id()?)
3876        } else {
3877            None
3878        };
3879        self.app_state().workspace_store.update(cx, |store, cx| {
3880            store.update_followers(project_id, update, cx)
3881        })
3882    }
3883
3884    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
3885        self.follower_states.iter().find_map(|(leader_id, state)| {
3886            if state.center_pane == *pane || state.dock_pane.as_ref() == Some(pane) {
3887                Some(*leader_id)
3888            } else {
3889                None
3890            }
3891        })
3892    }
3893
3894    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3895        cx.notify();
3896
3897        let call = self.active_call()?;
3898        let room = call.read(cx).room()?.read(cx);
3899        let participant = room.remote_participant_for_peer_id(leader_id)?;
3900
3901        let leader_in_this_app;
3902        let leader_in_this_project;
3903        match participant.location {
3904            call::ParticipantLocation::SharedProject { project_id } => {
3905                leader_in_this_app = true;
3906                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3907            }
3908            call::ParticipantLocation::UnsharedProject => {
3909                leader_in_this_app = true;
3910                leader_in_this_project = false;
3911            }
3912            call::ParticipantLocation::External => {
3913                leader_in_this_app = false;
3914                leader_in_this_project = false;
3915            }
3916        };
3917
3918        let state = self.follower_states.get(&leader_id)?;
3919        let mut item_to_activate = None;
3920        if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3921            if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3922                if leader_in_this_project || !item.view.is_project_item(cx) {
3923                    item_to_activate = Some((item.location, item.view.boxed_clone()));
3924                }
3925            }
3926        } else if let Some(shared_screen) =
3927            self.shared_screen_for_peer(leader_id, &state.center_pane, cx)
3928        {
3929            item_to_activate = Some((None, Box::new(shared_screen)));
3930        }
3931
3932        let (panel_id, item) = item_to_activate?;
3933
3934        let mut transfer_focus = state.center_pane.read(cx).has_focus(cx);
3935        let pane;
3936        if let Some(panel_id) = panel_id {
3937            pane = self.activate_panel_for_proto_id(panel_id, cx)?.pane(cx)?;
3938            let state = self.follower_states.get_mut(&leader_id)?;
3939            state.dock_pane = Some(pane.clone());
3940        } else {
3941            pane = state.center_pane.clone();
3942            let state = self.follower_states.get_mut(&leader_id)?;
3943            if let Some(dock_pane) = state.dock_pane.take() {
3944                transfer_focus |= dock_pane.focus_handle(cx).contains_focused(cx);
3945            }
3946        }
3947
3948        pane.update(cx, |pane, cx| {
3949            let focus_active_item = pane.has_focus(cx) || transfer_focus;
3950            if let Some(index) = pane.index_for_item(item.as_ref()) {
3951                pane.activate_item(index, false, false, cx);
3952            } else {
3953                pane.add_item(item.boxed_clone(), false, false, None, cx)
3954            }
3955
3956            if focus_active_item {
3957                pane.focus_active_item(cx)
3958            }
3959        });
3960
3961        None
3962    }
3963
3964    fn shared_screen_for_peer(
3965        &self,
3966        peer_id: PeerId,
3967        pane: &View<Pane>,
3968        cx: &mut WindowContext,
3969    ) -> Option<View<SharedScreen>> {
3970        let call = self.active_call()?;
3971        let room = call.read(cx).room()?.read(cx);
3972        let participant = room.remote_participant_for_peer_id(peer_id)?;
3973        let track = participant.video_tracks.values().next()?.clone();
3974        let user = participant.user.clone();
3975
3976        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3977            if item.read(cx).peer_id == peer_id {
3978                return Some(item);
3979            }
3980        }
3981
3982        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3983    }
3984
3985    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
3986        if cx.is_window_active() {
3987            self.update_active_view_for_followers(cx);
3988
3989            if let Some(database_id) = self.database_id {
3990                cx.background_executor()
3991                    .spawn(persistence::DB.update_timestamp(database_id))
3992                    .detach();
3993            }
3994        } else {
3995            for pane in &self.panes {
3996                pane.update(cx, |pane, cx| {
3997                    if let Some(item) = pane.active_item() {
3998                        item.workspace_deactivated(cx);
3999                    }
4000                    for item in pane.items() {
4001                        if matches!(
4002                            item.workspace_settings(cx).autosave,
4003                            AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
4004                        ) {
4005                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
4006                                .detach_and_log_err(cx);
4007                        }
4008                    }
4009                });
4010            }
4011        }
4012    }
4013
4014    fn active_call(&self) -> Option<&Model<ActiveCall>> {
4015        self.active_call.as_ref().map(|(call, _)| call)
4016    }
4017
4018    fn on_active_call_event(
4019        &mut self,
4020        _: Model<ActiveCall>,
4021        event: &call::room::Event,
4022        cx: &mut ViewContext<Self>,
4023    ) {
4024        match event {
4025            call::room::Event::ParticipantLocationChanged { participant_id }
4026            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
4027                self.leader_updated(*participant_id, cx);
4028            }
4029            _ => {}
4030        }
4031    }
4032
4033    pub fn database_id(&self) -> Option<WorkspaceId> {
4034        self.database_id
4035    }
4036
4037    fn local_paths(&self, cx: &AppContext) -> Option<Vec<Arc<Path>>> {
4038        let project = self.project().read(cx);
4039
4040        if project.is_local() {
4041            Some(
4042                project
4043                    .visible_worktrees(cx)
4044                    .map(|worktree| worktree.read(cx).abs_path())
4045                    .collect::<Vec<_>>(),
4046            )
4047        } else {
4048            None
4049        }
4050    }
4051
4052    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
4053        match member {
4054            Member::Axis(PaneAxis { members, .. }) => {
4055                for child in members.iter() {
4056                    self.remove_panes(child.clone(), cx)
4057                }
4058            }
4059            Member::Pane(pane) => {
4060                self.force_remove_pane(&pane, &None, cx);
4061            }
4062        }
4063    }
4064
4065    fn remove_from_session(&mut self, cx: &mut WindowContext) -> Task<()> {
4066        self.session_id.take();
4067        self.serialize_workspace_internal(cx)
4068    }
4069
4070    fn force_remove_pane(
4071        &mut self,
4072        pane: &View<Pane>,
4073        focus_on: &Option<View<Pane>>,
4074        cx: &mut ViewContext<Workspace>,
4075    ) {
4076        self.panes.retain(|p| p != pane);
4077        if let Some(focus_on) = focus_on {
4078            focus_on.update(cx, |pane, cx| pane.focus(cx));
4079        } else {
4080            self.panes
4081                .last()
4082                .unwrap()
4083                .update(cx, |pane, cx| pane.focus(cx));
4084        }
4085        if self.last_active_center_pane == Some(pane.downgrade()) {
4086            self.last_active_center_pane = None;
4087        }
4088        cx.notify();
4089    }
4090
4091    fn serialize_workspace(&mut self, cx: &mut ViewContext<Self>) {
4092        if self._schedule_serialize.is_none() {
4093            self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
4094                cx.background_executor()
4095                    .timer(Duration::from_millis(100))
4096                    .await;
4097                this.update(&mut cx, |this, cx| {
4098                    this.serialize_workspace_internal(cx).detach();
4099                    this._schedule_serialize.take();
4100                })
4101                .log_err();
4102            }));
4103        }
4104    }
4105
4106    fn serialize_workspace_internal(&self, cx: &mut WindowContext) -> Task<()> {
4107        let Some(database_id) = self.database_id() else {
4108            return Task::ready(());
4109        };
4110
4111        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
4112            let (items, active, pinned_count) = {
4113                let pane = pane_handle.read(cx);
4114                let active_item_id = pane.active_item().map(|item| item.item_id());
4115                (
4116                    pane.items()
4117                        .filter_map(|handle| {
4118                            let handle = handle.to_serializable_item_handle(cx)?;
4119
4120                            Some(SerializedItem {
4121                                kind: Arc::from(handle.serialized_item_kind()),
4122                                item_id: handle.item_id().as_u64(),
4123                                active: Some(handle.item_id()) == active_item_id,
4124                                preview: pane.is_active_preview_item(handle.item_id()),
4125                            })
4126                        })
4127                        .collect::<Vec<_>>(),
4128                    pane.has_focus(cx),
4129                    pane.pinned_count(),
4130                )
4131            };
4132
4133            SerializedPane::new(items, active, pinned_count)
4134        }
4135
4136        fn build_serialized_pane_group(
4137            pane_group: &Member,
4138            cx: &WindowContext,
4139        ) -> SerializedPaneGroup {
4140            match pane_group {
4141                Member::Axis(PaneAxis {
4142                    axis,
4143                    members,
4144                    flexes,
4145                    bounding_boxes: _,
4146                }) => SerializedPaneGroup::Group {
4147                    axis: SerializedAxis(*axis),
4148                    children: members
4149                        .iter()
4150                        .map(|member| build_serialized_pane_group(member, cx))
4151                        .collect::<Vec<_>>(),
4152                    flexes: Some(flexes.lock().clone()),
4153                },
4154                Member::Pane(pane_handle) => {
4155                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
4156                }
4157            }
4158        }
4159
4160        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
4161            let left_dock = this.left_dock.read(cx);
4162            let left_visible = left_dock.is_open();
4163            let left_active_panel = left_dock
4164                .visible_panel()
4165                .map(|panel| panel.persistent_name().to_string());
4166            let left_dock_zoom = left_dock
4167                .visible_panel()
4168                .map(|panel| panel.is_zoomed(cx))
4169                .unwrap_or(false);
4170
4171            let right_dock = this.right_dock.read(cx);
4172            let right_visible = right_dock.is_open();
4173            let right_active_panel = right_dock
4174                .visible_panel()
4175                .map(|panel| panel.persistent_name().to_string());
4176            let right_dock_zoom = right_dock
4177                .visible_panel()
4178                .map(|panel| panel.is_zoomed(cx))
4179                .unwrap_or(false);
4180
4181            let bottom_dock = this.bottom_dock.read(cx);
4182            let bottom_visible = bottom_dock.is_open();
4183            let bottom_active_panel = bottom_dock
4184                .visible_panel()
4185                .map(|panel| panel.persistent_name().to_string());
4186            let bottom_dock_zoom = bottom_dock
4187                .visible_panel()
4188                .map(|panel| panel.is_zoomed(cx))
4189                .unwrap_or(false);
4190
4191            DockStructure {
4192                left: DockData {
4193                    visible: left_visible,
4194                    active_panel: left_active_panel,
4195                    zoom: left_dock_zoom,
4196                },
4197                right: DockData {
4198                    visible: right_visible,
4199                    active_panel: right_active_panel,
4200                    zoom: right_dock_zoom,
4201                },
4202                bottom: DockData {
4203                    visible: bottom_visible,
4204                    active_panel: bottom_active_panel,
4205                    zoom: bottom_dock_zoom,
4206                },
4207            }
4208        }
4209
4210        let location = if let Some(ssh_project) = &self.serialized_ssh_project {
4211            Some(SerializedWorkspaceLocation::Ssh(ssh_project.clone()))
4212        } else if let Some(local_paths) = self.local_paths(cx) {
4213            if !local_paths.is_empty() {
4214                Some(SerializedWorkspaceLocation::from_local_paths(local_paths))
4215            } else {
4216                None
4217            }
4218        } else {
4219            None
4220        };
4221
4222        if let Some(location) = location {
4223            let center_group = build_serialized_pane_group(&self.center.root, cx);
4224            let docks = build_serialized_docks(self, cx);
4225            let window_bounds = Some(SerializedWindowBounds(cx.window_bounds()));
4226            let serialized_workspace = SerializedWorkspace {
4227                id: database_id,
4228                location,
4229                center_group,
4230                window_bounds,
4231                display: Default::default(),
4232                docks,
4233                centered_layout: self.centered_layout,
4234                session_id: self.session_id.clone(),
4235                window_id: Some(cx.window_handle().window_id().as_u64()),
4236            };
4237            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
4238        }
4239        Task::ready(())
4240    }
4241
4242    async fn serialize_items(
4243        this: &WeakView<Self>,
4244        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
4245        cx: &mut AsyncWindowContext,
4246    ) -> Result<()> {
4247        const CHUNK_SIZE: usize = 200;
4248        const THROTTLE_TIME: Duration = Duration::from_millis(200);
4249
4250        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
4251
4252        while let Some(items_received) = serializable_items.next().await {
4253            let unique_items =
4254                items_received
4255                    .into_iter()
4256                    .fold(HashMap::default(), |mut acc, item| {
4257                        acc.entry(item.item_id()).or_insert(item);
4258                        acc
4259                    });
4260
4261            // We use into_iter() here so that the references to the items are moved into
4262            // the tasks and not kept alive while we're sleeping.
4263            for (_, item) in unique_items.into_iter() {
4264                if let Ok(Some(task)) =
4265                    this.update(cx, |workspace, cx| item.serialize(workspace, false, cx))
4266                {
4267                    cx.background_executor()
4268                        .spawn(async move { task.await.log_err() })
4269                        .detach();
4270                }
4271            }
4272
4273            cx.background_executor().timer(THROTTLE_TIME).await;
4274        }
4275
4276        Ok(())
4277    }
4278
4279    pub(crate) fn enqueue_item_serialization(
4280        &mut self,
4281        item: Box<dyn SerializableItemHandle>,
4282    ) -> Result<()> {
4283        self.serializable_items_tx
4284            .unbounded_send(item)
4285            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
4286    }
4287
4288    pub(crate) fn load_workspace(
4289        serialized_workspace: SerializedWorkspace,
4290        paths_to_open: Vec<Option<ProjectPath>>,
4291        cx: &mut ViewContext<Workspace>,
4292    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
4293        cx.spawn(|workspace, mut cx| async move {
4294            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
4295
4296            let mut center_group = None;
4297            let mut center_items = None;
4298
4299            // Traverse the splits tree and add to things
4300            if let Some((group, active_pane, items)) = serialized_workspace
4301                .center_group
4302                .deserialize(
4303                    &project,
4304                    serialized_workspace.id,
4305                    workspace.clone(),
4306                    &mut cx,
4307                )
4308                .await
4309            {
4310                center_items = Some(items);
4311                center_group = Some((group, active_pane))
4312            }
4313
4314            let mut items_by_project_path = HashMap::default();
4315            let mut item_ids_by_kind = HashMap::default();
4316            let mut all_deserialized_items = Vec::default();
4317            cx.update(|cx| {
4318                for item in center_items.unwrap_or_default().into_iter().flatten() {
4319                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
4320                        item_ids_by_kind
4321                            .entry(serializable_item_handle.serialized_item_kind())
4322                            .or_insert(Vec::new())
4323                            .push(item.item_id().as_u64() as ItemId);
4324                    }
4325
4326                    if let Some(project_path) = item.project_path(cx) {
4327                        items_by_project_path.insert(project_path, item.clone());
4328                    }
4329                    all_deserialized_items.push(item);
4330                }
4331            })?;
4332
4333            let opened_items = paths_to_open
4334                .into_iter()
4335                .map(|path_to_open| {
4336                    path_to_open
4337                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
4338                })
4339                .collect::<Vec<_>>();
4340
4341            // Remove old panes from workspace panes list
4342            workspace.update(&mut cx, |workspace, cx| {
4343                if let Some((center_group, active_pane)) = center_group {
4344                    workspace.remove_panes(workspace.center.root.clone(), cx);
4345
4346                    // Swap workspace center group
4347                    workspace.center = PaneGroup::with_root(center_group);
4348                    if let Some(active_pane) = active_pane {
4349                        workspace.set_active_pane(&active_pane, cx);
4350                        cx.focus_self();
4351                    } else {
4352                        workspace.set_active_pane(&workspace.center.first_pane(), cx);
4353                    }
4354                }
4355
4356                let docks = serialized_workspace.docks;
4357
4358                for (dock, serialized_dock) in [
4359                    (&mut workspace.right_dock, docks.right),
4360                    (&mut workspace.left_dock, docks.left),
4361                    (&mut workspace.bottom_dock, docks.bottom),
4362                ]
4363                .iter_mut()
4364                {
4365                    dock.update(cx, |dock, cx| {
4366                        dock.serialized_dock = Some(serialized_dock.clone());
4367                        dock.restore_state(cx);
4368                    });
4369                }
4370
4371                cx.notify();
4372            })?;
4373
4374            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
4375            // after loading the items, we might have different items and in order to avoid
4376            // the database filling up, we delete items that haven't been loaded now.
4377            //
4378            // The items that have been loaded, have been saved after they've been added to the workspace.
4379            let clean_up_tasks = workspace.update(&mut cx, |_, cx| {
4380                item_ids_by_kind
4381                    .into_iter()
4382                    .map(|(item_kind, loaded_items)| {
4383                        SerializableItemRegistry::cleanup(
4384                            item_kind,
4385                            serialized_workspace.id,
4386                            loaded_items,
4387                            cx,
4388                        )
4389                        .log_err()
4390                    })
4391                    .collect::<Vec<_>>()
4392            })?;
4393
4394            futures::future::join_all(clean_up_tasks).await;
4395
4396            workspace
4397                .update(&mut cx, |workspace, cx| {
4398                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
4399                    workspace.serialize_workspace_internal(cx).detach();
4400
4401                    // Ensure that we mark the window as edited if we did load dirty items
4402                    workspace.update_window_edited(cx);
4403                })
4404                .ok();
4405
4406            Ok(opened_items)
4407        })
4408    }
4409
4410    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
4411        self.add_workspace_actions_listeners(div, cx)
4412            .on_action(cx.listener(Self::close_inactive_items_and_panes))
4413            .on_action(cx.listener(Self::close_all_items_and_panes))
4414            .on_action(cx.listener(Self::save_all))
4415            .on_action(cx.listener(Self::send_keystrokes))
4416            .on_action(cx.listener(Self::add_folder_to_project))
4417            .on_action(cx.listener(Self::follow_next_collaborator))
4418            .on_action(cx.listener(Self::close_window))
4419            .on_action(cx.listener(Self::activate_pane_at_index))
4420            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
4421                let pane = workspace.active_pane().clone();
4422                workspace.unfollow_in_pane(&pane, cx);
4423            }))
4424            .on_action(cx.listener(|workspace, action: &Save, cx| {
4425                workspace
4426                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
4427                    .detach_and_prompt_err("Failed to save", cx, |_, _| None);
4428            }))
4429            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
4430                workspace
4431                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
4432                    .detach_and_prompt_err("Failed to save", cx, |_, _| None);
4433            }))
4434            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
4435                workspace
4436                    .save_active_item(SaveIntent::SaveAs, cx)
4437                    .detach_and_prompt_err("Failed to save", cx, |_, _| None);
4438            }))
4439            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
4440                workspace.activate_previous_pane(cx)
4441            }))
4442            .on_action(
4443                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
4444            )
4445            .on_action(
4446                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
4447                    workspace.activate_pane_in_direction(action.0, cx)
4448                }),
4449            )
4450            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
4451                workspace.swap_pane_in_direction(action.0, cx)
4452            }))
4453            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
4454                this.toggle_dock(DockPosition::Left, cx);
4455            }))
4456            .on_action(
4457                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
4458                    workspace.toggle_dock(DockPosition::Right, cx);
4459                }),
4460            )
4461            .on_action(
4462                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
4463                    workspace.toggle_dock(DockPosition::Bottom, cx);
4464                }),
4465            )
4466            .on_action(
4467                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
4468                    workspace.close_all_docks(cx);
4469                }),
4470            )
4471            .on_action(
4472                cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| {
4473                    workspace.clear_all_notifications(cx);
4474                }),
4475            )
4476            .on_action(
4477                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
4478                    workspace.reopen_closed_item(cx).detach();
4479                }),
4480            )
4481            .on_action(cx.listener(Workspace::toggle_centered_layout))
4482    }
4483
4484    #[cfg(any(test, feature = "test-support"))]
4485    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
4486        use node_runtime::NodeRuntime;
4487        use session::Session;
4488
4489        let client = project.read(cx).client();
4490        let user_store = project.read(cx).user_store();
4491
4492        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
4493        let session = cx.new_model(|cx| AppSession::new(Session::test(), cx));
4494        cx.activate_window();
4495        let app_state = Arc::new(AppState {
4496            languages: project.read(cx).languages().clone(),
4497            workspace_store,
4498            client,
4499            user_store,
4500            fs: project.read(cx).fs().clone(),
4501            build_window_options: |_, _| Default::default(),
4502            node_runtime: NodeRuntime::unavailable(),
4503            session,
4504        });
4505        let workspace = Self::new(Default::default(), project, app_state, cx);
4506        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
4507        workspace
4508    }
4509
4510    pub fn register_action<A: Action>(
4511        &mut self,
4512        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
4513    ) -> &mut Self {
4514        let callback = Arc::new(callback);
4515
4516        self.workspace_actions.push(Box::new(move |div, cx| {
4517            let callback = callback.clone();
4518            div.on_action(
4519                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
4520            )
4521        }));
4522        self
4523    }
4524
4525    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
4526        for action in self.workspace_actions.iter() {
4527            div = (action)(div, cx)
4528        }
4529        div
4530    }
4531
4532    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
4533        self.modal_layer.read(cx).has_active_modal()
4534    }
4535
4536    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &AppContext) -> Option<View<V>> {
4537        self.modal_layer.read(cx).active_modal()
4538    }
4539
4540    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
4541    where
4542        B: FnOnce(&mut ViewContext<V>) -> V,
4543    {
4544        self.modal_layer
4545            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
4546    }
4547
4548    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
4549        self.centered_layout = !self.centered_layout;
4550        if let Some(database_id) = self.database_id() {
4551            cx.background_executor()
4552                .spawn(DB.set_centered_layout(database_id, self.centered_layout))
4553                .detach_and_log_err(cx);
4554        }
4555        cx.notify();
4556    }
4557
4558    fn adjust_padding(padding: Option<f32>) -> f32 {
4559        padding
4560            .unwrap_or(Self::DEFAULT_PADDING)
4561            .clamp(0.0, Self::MAX_PADDING)
4562    }
4563
4564    fn render_dock(
4565        &self,
4566        position: DockPosition,
4567        dock: &View<Dock>,
4568        cx: &WindowContext,
4569    ) -> Option<Div> {
4570        if self.zoomed_position == Some(position) {
4571            return None;
4572        }
4573
4574        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
4575            let pane = panel.pane(cx)?;
4576            let follower_states = &self.follower_states;
4577            leader_border_for_pane(follower_states, &pane, cx)
4578        });
4579
4580        Some(
4581            div()
4582                .flex()
4583                .flex_none()
4584                .overflow_hidden()
4585                .child(dock.clone())
4586                .children(leader_border),
4587        )
4588    }
4589
4590    pub fn for_window(cx: &mut WindowContext) -> Option<View<Workspace>> {
4591        let window = cx.window_handle().downcast::<Workspace>()?;
4592        cx.read_window(&window, |workspace, _| workspace).ok()
4593    }
4594}
4595
4596fn leader_border_for_pane(
4597    follower_states: &HashMap<PeerId, FollowerState>,
4598    pane: &View<Pane>,
4599    cx: &WindowContext,
4600) -> Option<Div> {
4601    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
4602        if state.pane() == pane {
4603            Some((*leader_id, state))
4604        } else {
4605            None
4606        }
4607    })?;
4608
4609    let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
4610    let leader = room.remote_participant_for_peer_id(leader_id)?;
4611
4612    let mut leader_color = cx
4613        .theme()
4614        .players()
4615        .color_for_participant(leader.participant_index.0)
4616        .cursor;
4617    leader_color.fade_out(0.3);
4618    Some(
4619        div()
4620            .absolute()
4621            .size_full()
4622            .left_0()
4623            .top_0()
4624            .border_2()
4625            .border_color(leader_color),
4626    )
4627}
4628
4629fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
4630    ZED_WINDOW_POSITION
4631        .zip(*ZED_WINDOW_SIZE)
4632        .map(|(position, size)| Bounds {
4633            origin: position,
4634            size,
4635        })
4636}
4637
4638fn open_items(
4639    serialized_workspace: Option<SerializedWorkspace>,
4640    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
4641    cx: &mut ViewContext<Workspace>,
4642) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
4643    let restored_items = serialized_workspace.map(|serialized_workspace| {
4644        Workspace::load_workspace(
4645            serialized_workspace,
4646            project_paths_to_open
4647                .iter()
4648                .map(|(_, project_path)| project_path)
4649                .cloned()
4650                .collect(),
4651            cx,
4652        )
4653    });
4654
4655    cx.spawn(|workspace, mut cx| async move {
4656        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
4657
4658        if let Some(restored_items) = restored_items {
4659            let restored_items = restored_items.await?;
4660
4661            let restored_project_paths = restored_items
4662                .iter()
4663                .filter_map(|item| {
4664                    cx.update(|cx| item.as_ref()?.project_path(cx))
4665                        .ok()
4666                        .flatten()
4667                })
4668                .collect::<HashSet<_>>();
4669
4670            for restored_item in restored_items {
4671                opened_items.push(restored_item.map(Ok));
4672            }
4673
4674            project_paths_to_open
4675                .iter_mut()
4676                .for_each(|(_, project_path)| {
4677                    if let Some(project_path_to_open) = project_path {
4678                        if restored_project_paths.contains(project_path_to_open) {
4679                            *project_path = None;
4680                        }
4681                    }
4682                });
4683        } else {
4684            for _ in 0..project_paths_to_open.len() {
4685                opened_items.push(None);
4686            }
4687        }
4688        assert!(opened_items.len() == project_paths_to_open.len());
4689
4690        let tasks =
4691            project_paths_to_open
4692                .into_iter()
4693                .enumerate()
4694                .map(|(ix, (abs_path, project_path))| {
4695                    let workspace = workspace.clone();
4696                    cx.spawn(|mut cx| async move {
4697                        let file_project_path = project_path?;
4698                        let abs_path_task = workspace.update(&mut cx, |workspace, cx| {
4699                            workspace.project().update(cx, |project, cx| {
4700                                project.resolve_abs_path(abs_path.to_string_lossy().as_ref(), cx)
4701                            })
4702                        });
4703
4704                        // We only want to open file paths here. If one of the items
4705                        // here is a directory, it was already opened further above
4706                        // with a `find_or_create_worktree`.
4707                        if let Ok(task) = abs_path_task {
4708                            if task.await.map_or(true, |p| p.is_file()) {
4709                                return Some((
4710                                    ix,
4711                                    workspace
4712                                        .update(&mut cx, |workspace, cx| {
4713                                            workspace.open_path(file_project_path, None, true, cx)
4714                                        })
4715                                        .log_err()?
4716                                        .await,
4717                                ));
4718                            }
4719                        }
4720                        None
4721                    })
4722                });
4723
4724        let tasks = tasks.collect::<Vec<_>>();
4725
4726        let tasks = futures::future::join_all(tasks);
4727        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4728            opened_items[ix] = Some(path_open_result);
4729        }
4730
4731        Ok(opened_items)
4732    })
4733}
4734
4735enum ActivateInDirectionTarget {
4736    Pane(View<Pane>),
4737    Dock(View<Dock>),
4738}
4739
4740fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4741    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";
4742
4743    workspace
4744        .update(cx, |workspace, cx| {
4745            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4746                struct DatabaseFailedNotification;
4747
4748                workspace.show_notification_once(
4749                    NotificationId::unique::<DatabaseFailedNotification>(),
4750                    cx,
4751                    |cx| {
4752                        cx.new_view(|_| {
4753                            MessageNotification::new("Failed to load the database file.")
4754                                .with_click_message("File an issue")
4755                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4756                        })
4757                    },
4758                );
4759            }
4760        })
4761        .log_err();
4762}
4763
4764impl FocusableView for Workspace {
4765    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4766        self.active_pane.focus_handle(cx)
4767    }
4768}
4769
4770#[derive(Clone, Render)]
4771struct DraggedDock(DockPosition);
4772
4773impl Render for Workspace {
4774    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4775        let mut context = KeyContext::new_with_defaults();
4776        context.add("Workspace");
4777        context.set("keyboard_layout", cx.keyboard_layout().clone());
4778        let centered_layout = self.centered_layout
4779            && self.center.panes().len() == 1
4780            && self.active_item(cx).is_some();
4781        let render_padding = |size| {
4782            (size > 0.0).then(|| {
4783                div()
4784                    .h_full()
4785                    .w(relative(size))
4786                    .bg(cx.theme().colors().editor_background)
4787                    .border_color(cx.theme().colors().pane_group_border)
4788            })
4789        };
4790        let paddings = if centered_layout {
4791            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4792            (
4793                render_padding(Self::adjust_padding(settings.left_padding)),
4794                render_padding(Self::adjust_padding(settings.right_padding)),
4795            )
4796        } else {
4797            (None, None)
4798        };
4799        let ui_font = theme::setup_ui_font(cx);
4800
4801        let theme = cx.theme().clone();
4802        let colors = theme.colors();
4803
4804        client_side_decorations(
4805            self.actions(div(), cx)
4806                .key_context(context)
4807                .relative()
4808                .size_full()
4809                .flex()
4810                .flex_col()
4811                .font(ui_font)
4812                .gap_0()
4813                .justify_start()
4814                .items_start()
4815                .text_color(colors.text)
4816                .overflow_hidden()
4817                .children(self.titlebar_item.clone())
4818                .child(
4819                    div()
4820                        .size_full()
4821                        .relative()
4822                        .flex_1()
4823                        .flex()
4824                        .flex_col()
4825                        .child(
4826                            div()
4827                                .id("workspace")
4828                                .bg(colors.background)
4829                                .relative()
4830                                .flex_1()
4831                                .w_full()
4832                                .flex()
4833                                .flex_col()
4834                                .overflow_hidden()
4835                                .border_t_1()
4836                                .border_b_1()
4837                                .border_color(colors.border)
4838                                .child({
4839                                    let this = cx.view().clone();
4840                                    canvas(
4841                                        move |bounds, cx| {
4842                                            this.update(cx, |this, cx| {
4843                                                let bounds_changed = this.bounds != bounds;
4844                                                this.bounds = bounds;
4845
4846                                                if bounds_changed {
4847                                                    this.left_dock.update(cx, |dock, cx| {
4848                                                        dock.clamp_panel_size(bounds.size.width, cx)
4849                                                    });
4850
4851                                                    this.right_dock.update(cx, |dock, cx| {
4852                                                        dock.clamp_panel_size(bounds.size.width, cx)
4853                                                    });
4854
4855                                                    this.bottom_dock.update(cx, |dock, cx| {
4856                                                        dock.clamp_panel_size(
4857                                                            bounds.size.height,
4858                                                            cx,
4859                                                        )
4860                                                    });
4861                                                }
4862                                            })
4863                                        },
4864                                        |_, _, _| {},
4865                                    )
4866                                    .absolute()
4867                                    .size_full()
4868                                })
4869                                .when(self.zoomed.is_none(), |this| {
4870                                    this.on_drag_move(cx.listener(
4871                                        |workspace, e: &DragMoveEvent<DraggedDock>, cx| {
4872                                            match e.drag(cx).0 {
4873                                                DockPosition::Left => {
4874                                                    resize_left_dock(
4875                                                        e.event.position.x
4876                                                            - workspace.bounds.left(),
4877                                                        workspace,
4878                                                        cx,
4879                                                    );
4880                                                }
4881                                                DockPosition::Right => {
4882                                                    resize_right_dock(
4883                                                        workspace.bounds.right()
4884                                                            - e.event.position.x,
4885                                                        workspace,
4886                                                        cx,
4887                                                    );
4888                                                }
4889                                                DockPosition::Bottom => {
4890                                                    resize_bottom_dock(
4891                                                        workspace.bounds.bottom()
4892                                                            - e.event.position.y,
4893                                                        workspace,
4894                                                        cx,
4895                                                    );
4896                                                }
4897                                            }
4898                                        },
4899                                    ))
4900                                })
4901                                .child(
4902                                    div()
4903                                        .flex()
4904                                        .flex_row()
4905                                        .h_full()
4906                                        // Left Dock
4907                                        .children(self.render_dock(
4908                                            DockPosition::Left,
4909                                            &self.left_dock,
4910                                            cx,
4911                                        ))
4912                                        // Panes
4913                                        .child(
4914                                            div()
4915                                                .flex()
4916                                                .flex_col()
4917                                                .flex_1()
4918                                                .overflow_hidden()
4919                                                .child(
4920                                                    h_flex()
4921                                                        .flex_1()
4922                                                        .when_some(paddings.0, |this, p| {
4923                                                            this.child(p.border_r_1())
4924                                                        })
4925                                                        .child(self.center.render(
4926                                                            &self.project,
4927                                                            &self.follower_states,
4928                                                            self.active_call(),
4929                                                            &self.active_pane,
4930                                                            self.zoomed.as_ref(),
4931                                                            &self.app_state,
4932                                                            cx,
4933                                                        ))
4934                                                        .when_some(paddings.1, |this, p| {
4935                                                            this.child(p.border_l_1())
4936                                                        }),
4937                                                )
4938                                                .children(self.render_dock(
4939                                                    DockPosition::Bottom,
4940                                                    &self.bottom_dock,
4941                                                    cx,
4942                                                )),
4943                                        )
4944                                        // Right Dock
4945                                        .children(self.render_dock(
4946                                            DockPosition::Right,
4947                                            &self.right_dock,
4948                                            cx,
4949                                        )),
4950                                )
4951                                .children(self.zoomed.as_ref().and_then(|view| {
4952                                    let zoomed_view = view.upgrade()?;
4953                                    let div = div()
4954                                        .occlude()
4955                                        .absolute()
4956                                        .overflow_hidden()
4957                                        .border_color(colors.border)
4958                                        .bg(colors.background)
4959                                        .child(zoomed_view)
4960                                        .inset_0()
4961                                        .shadow_lg();
4962
4963                                    Some(match self.zoomed_position {
4964                                        Some(DockPosition::Left) => div.right_2().border_r_1(),
4965                                        Some(DockPosition::Right) => div.left_2().border_l_1(),
4966                                        Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4967                                        None => {
4968                                            div.top_2().bottom_2().left_2().right_2().border_1()
4969                                        }
4970                                    })
4971                                }))
4972                                .children(self.render_notifications(cx)),
4973                        )
4974                        .child(self.status_bar.clone())
4975                        .child(self.modal_layer.clone()),
4976                ),
4977            cx,
4978        )
4979    }
4980}
4981
4982fn resize_bottom_dock(
4983    new_size: Pixels,
4984    workspace: &mut Workspace,
4985    cx: &mut ViewContext<'_, Workspace>,
4986) {
4987    let size = new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE);
4988    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4989        bottom_dock.resize_active_panel(Some(size), cx);
4990    });
4991}
4992
4993fn resize_right_dock(
4994    new_size: Pixels,
4995    workspace: &mut Workspace,
4996    cx: &mut ViewContext<'_, Workspace>,
4997) {
4998    let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE);
4999    workspace.right_dock.update(cx, |right_dock, cx| {
5000        right_dock.resize_active_panel(Some(size), cx);
5001    });
5002}
5003
5004fn resize_left_dock(
5005    new_size: Pixels,
5006    workspace: &mut Workspace,
5007    cx: &mut ViewContext<'_, Workspace>,
5008) {
5009    let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE);
5010
5011    workspace.left_dock.update(cx, |left_dock, cx| {
5012        left_dock.resize_active_panel(Some(size), cx);
5013    });
5014}
5015
5016impl WorkspaceStore {
5017    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
5018        Self {
5019            workspaces: Default::default(),
5020            _subscriptions: vec![
5021                client.add_request_handler(cx.weak_model(), Self::handle_follow),
5022                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
5023            ],
5024            client,
5025        }
5026    }
5027
5028    pub fn update_followers(
5029        &self,
5030        project_id: Option<u64>,
5031        update: proto::update_followers::Variant,
5032        cx: &AppContext,
5033    ) -> Option<()> {
5034        let active_call = ActiveCall::try_global(cx)?;
5035        let room_id = active_call.read(cx).room()?.read(cx).id();
5036        self.client
5037            .send(proto::UpdateFollowers {
5038                room_id,
5039                project_id,
5040                variant: Some(update),
5041            })
5042            .log_err()
5043    }
5044
5045    pub async fn handle_follow(
5046        this: Model<Self>,
5047        envelope: TypedEnvelope<proto::Follow>,
5048        mut cx: AsyncAppContext,
5049    ) -> Result<proto::FollowResponse> {
5050        this.update(&mut cx, |this, cx| {
5051            let follower = Follower {
5052                project_id: envelope.payload.project_id,
5053                peer_id: envelope.original_sender_id()?,
5054            };
5055
5056            let mut response = proto::FollowResponse::default();
5057            this.workspaces.retain(|workspace| {
5058                workspace
5059                    .update(cx, |workspace, cx| {
5060                        let handler_response = workspace.handle_follow(follower.project_id, cx);
5061                        if let Some(active_view) = handler_response.active_view.clone() {
5062                            if workspace.project.read(cx).remote_id() == follower.project_id {
5063                                response.active_view = Some(active_view)
5064                            }
5065                        }
5066                    })
5067                    .is_ok()
5068            });
5069
5070            Ok(response)
5071        })?
5072    }
5073
5074    async fn handle_update_followers(
5075        this: Model<Self>,
5076        envelope: TypedEnvelope<proto::UpdateFollowers>,
5077        mut cx: AsyncAppContext,
5078    ) -> Result<()> {
5079        let leader_id = envelope.original_sender_id()?;
5080        let update = envelope.payload;
5081
5082        this.update(&mut cx, |this, cx| {
5083            this.workspaces.retain(|workspace| {
5084                workspace
5085                    .update(cx, |workspace, cx| {
5086                        let project_id = workspace.project.read(cx).remote_id();
5087                        if update.project_id != project_id && update.project_id.is_some() {
5088                            return;
5089                        }
5090                        workspace.handle_update_followers(leader_id, update.clone(), cx);
5091                    })
5092                    .is_ok()
5093            });
5094            Ok(())
5095        })?
5096    }
5097}
5098
5099impl ViewId {
5100    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
5101        Ok(Self {
5102            creator: message
5103                .creator
5104                .ok_or_else(|| anyhow!("creator is missing"))?,
5105            id: message.id,
5106        })
5107    }
5108
5109    pub(crate) fn to_proto(self) -> proto::ViewId {
5110        proto::ViewId {
5111            creator: Some(self.creator),
5112            id: self.id,
5113        }
5114    }
5115}
5116
5117impl FollowerState {
5118    fn pane(&self) -> &View<Pane> {
5119        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
5120    }
5121}
5122
5123pub trait WorkspaceHandle {
5124    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
5125}
5126
5127impl WorkspaceHandle for View<Workspace> {
5128    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
5129        self.read(cx)
5130            .worktrees(cx)
5131            .flat_map(|worktree| {
5132                let worktree_id = worktree.read(cx).id();
5133                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
5134                    worktree_id,
5135                    path: f.path.clone(),
5136                })
5137            })
5138            .collect::<Vec<_>>()
5139    }
5140}
5141
5142impl std::fmt::Debug for OpenPaths {
5143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5144        f.debug_struct("OpenPaths")
5145            .field("paths", &self.paths)
5146            .finish()
5147    }
5148}
5149
5150pub fn activate_workspace_for_project(
5151    cx: &mut AppContext,
5152    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
5153) -> Option<WindowHandle<Workspace>> {
5154    for window in cx.windows() {
5155        let Some(workspace) = window.downcast::<Workspace>() else {
5156            continue;
5157        };
5158
5159        let predicate = workspace
5160            .update(cx, |workspace, cx| {
5161                let project = workspace.project.read(cx);
5162                if predicate(project, cx) {
5163                    cx.activate_window();
5164                    true
5165                } else {
5166                    false
5167                }
5168            })
5169            .log_err()
5170            .unwrap_or(false);
5171
5172        if predicate {
5173            return Some(workspace);
5174        }
5175    }
5176
5177    None
5178}
5179
5180pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
5181    DB.last_workspace().await.log_err().flatten()
5182}
5183
5184pub fn last_session_workspace_locations(
5185    last_session_id: &str,
5186    last_session_window_stack: Option<Vec<WindowId>>,
5187) -> Option<Vec<SerializedWorkspaceLocation>> {
5188    DB.last_session_workspace_locations(last_session_id, last_session_window_stack)
5189        .log_err()
5190}
5191
5192actions!(collab, [OpenChannelNotes]);
5193actions!(zed, [OpenLog]);
5194
5195async fn join_channel_internal(
5196    channel_id: ChannelId,
5197    app_state: &Arc<AppState>,
5198    requesting_window: Option<WindowHandle<Workspace>>,
5199    active_call: &Model<ActiveCall>,
5200    cx: &mut AsyncAppContext,
5201) -> Result<bool> {
5202    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
5203        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
5204            return (false, None);
5205        };
5206
5207        let already_in_channel = room.channel_id() == Some(channel_id);
5208        let should_prompt = room.is_sharing_project()
5209            && !room.remote_participants().is_empty()
5210            && !already_in_channel;
5211        let open_room = if already_in_channel {
5212            active_call.room().cloned()
5213        } else {
5214            None
5215        };
5216        (should_prompt, open_room)
5217    })?;
5218
5219    if let Some(room) = open_room {
5220        let task = room.update(cx, |room, cx| {
5221            if let Some((project, host)) = room.most_active_project(cx) {
5222                return Some(join_in_room_project(project, host, app_state.clone(), cx));
5223            }
5224
5225            None
5226        })?;
5227        if let Some(task) = task {
5228            task.await?;
5229        }
5230        return anyhow::Ok(true);
5231    }
5232
5233    if should_prompt {
5234        if let Some(workspace) = requesting_window {
5235            let answer = workspace
5236                .update(cx, |_, cx| {
5237                    cx.prompt(
5238                        PromptLevel::Warning,
5239                        "Do you want to switch channels?",
5240                        Some("Leaving this call will unshare your current project."),
5241                        &["Yes, Join Channel", "Cancel"],
5242                    )
5243                })?
5244                .await;
5245
5246            if answer == Ok(1) {
5247                return Ok(false);
5248            }
5249        } else {
5250            return Ok(false); // unreachable!() hopefully
5251        }
5252    }
5253
5254    let client = cx.update(|cx| active_call.read(cx).client())?;
5255
5256    let mut client_status = client.status();
5257
5258    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
5259    'outer: loop {
5260        let Some(status) = client_status.recv().await else {
5261            return Err(anyhow!("error connecting"));
5262        };
5263
5264        match status {
5265            Status::Connecting
5266            | Status::Authenticating
5267            | Status::Reconnecting
5268            | Status::Reauthenticating => continue,
5269            Status::Connected { .. } => break 'outer,
5270            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
5271            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
5272            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
5273                return Err(ErrorCode::Disconnected.into());
5274            }
5275        }
5276    }
5277
5278    let room = active_call
5279        .update(cx, |active_call, cx| {
5280            active_call.join_channel(channel_id, cx)
5281        })?
5282        .await?;
5283
5284    let Some(room) = room else {
5285        return anyhow::Ok(true);
5286    };
5287
5288    room.update(cx, |room, _| room.room_update_completed())?
5289        .await;
5290
5291    let task = room.update(cx, |room, cx| {
5292        if let Some((project, host)) = room.most_active_project(cx) {
5293            return Some(join_in_room_project(project, host, app_state.clone(), cx));
5294        }
5295
5296        // If you are the first to join a channel, see if you should share your project.
5297        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
5298            if let Some(workspace) = requesting_window {
5299                let project = workspace.update(cx, |workspace, cx| {
5300                    let project = workspace.project.read(cx);
5301
5302                    if !CallSettings::get_global(cx).share_on_join {
5303                        return None;
5304                    }
5305
5306                    if (project.is_local() || project.is_via_ssh())
5307                        && project.visible_worktrees(cx).any(|tree| {
5308                            tree.read(cx)
5309                                .root_entry()
5310                                .map_or(false, |entry| entry.is_dir())
5311                        })
5312                    {
5313                        Some(workspace.project.clone())
5314                    } else {
5315                        None
5316                    }
5317                });
5318                if let Ok(Some(project)) = project {
5319                    return Some(cx.spawn(|room, mut cx| async move {
5320                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
5321                            .await?;
5322                        Ok(())
5323                    }));
5324                }
5325            }
5326        }
5327
5328        None
5329    })?;
5330    if let Some(task) = task {
5331        task.await?;
5332        return anyhow::Ok(true);
5333    }
5334    anyhow::Ok(false)
5335}
5336
5337pub fn join_channel(
5338    channel_id: ChannelId,
5339    app_state: Arc<AppState>,
5340    requesting_window: Option<WindowHandle<Workspace>>,
5341    cx: &mut AppContext,
5342) -> Task<Result<()>> {
5343    let active_call = ActiveCall::global(cx);
5344    cx.spawn(|mut cx| async move {
5345        let result = join_channel_internal(
5346            channel_id,
5347            &app_state,
5348            requesting_window,
5349            &active_call,
5350            &mut cx,
5351        )
5352            .await;
5353
5354        // join channel succeeded, and opened a window
5355        if matches!(result, Ok(true)) {
5356            return anyhow::Ok(());
5357        }
5358
5359        // find an existing workspace to focus and show call controls
5360        let mut active_window =
5361            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
5362        if active_window.is_none() {
5363            // no open workspaces, make one to show the error in (blergh)
5364            let (window_handle, _) = cx
5365                .update(|cx| {
5366                    Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
5367                })?
5368                .await?;
5369
5370            if result.is_ok() {
5371                cx.update(|cx| {
5372                    cx.dispatch_action(&OpenChannelNotes);
5373                }).log_err();
5374            }
5375
5376            active_window = Some(window_handle);
5377        }
5378
5379        if let Err(err) = result {
5380            log::error!("failed to join channel: {}", err);
5381            if let Some(active_window) = active_window {
5382                active_window
5383                    .update(&mut cx, |_, cx| {
5384                        let detail: SharedString = match err.error_code() {
5385                            ErrorCode::SignedOut => {
5386                                "Please sign in to continue.".into()
5387                            }
5388                            ErrorCode::UpgradeRequired => {
5389                                "Your are running an unsupported version of Zed. Please update to continue.".into()
5390                            }
5391                            ErrorCode::NoSuchChannel => {
5392                                "No matching channel was found. Please check the link and try again.".into()
5393                            }
5394                            ErrorCode::Forbidden => {
5395                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
5396                            }
5397                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
5398                            _ => format!("{}\n\nPlease try again.", err).into(),
5399                        };
5400                        cx.prompt(
5401                            PromptLevel::Critical,
5402                            "Failed to join channel",
5403                            Some(&detail),
5404                            &["Ok"],
5405                        )
5406                    })?
5407                    .await
5408                    .ok();
5409            }
5410        }
5411
5412        // return ok, we showed the error to the user.
5413        anyhow::Ok(())
5414    })
5415}
5416
5417pub async fn get_any_active_workspace(
5418    app_state: Arc<AppState>,
5419    mut cx: AsyncAppContext,
5420) -> anyhow::Result<WindowHandle<Workspace>> {
5421    // find an existing workspace to focus and show call controls
5422    let active_window = activate_any_workspace_window(&mut cx);
5423    if active_window.is_none() {
5424        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
5425            .await?;
5426    }
5427    activate_any_workspace_window(&mut cx).context("could not open zed")
5428}
5429
5430fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
5431    cx.update(|cx| {
5432        if let Some(workspace_window) = cx
5433            .active_window()
5434            .and_then(|window| window.downcast::<Workspace>())
5435        {
5436            return Some(workspace_window);
5437        }
5438
5439        for window in cx.windows() {
5440            if let Some(workspace_window) = window.downcast::<Workspace>() {
5441                workspace_window
5442                    .update(cx, |_, cx| cx.activate_window())
5443                    .ok();
5444                return Some(workspace_window);
5445            }
5446        }
5447        None
5448    })
5449    .ok()
5450    .flatten()
5451}
5452
5453pub fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
5454    cx.windows()
5455        .into_iter()
5456        .filter_map(|window| window.downcast::<Workspace>())
5457        .filter(|workspace| {
5458            workspace
5459                .read(cx)
5460                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
5461        })
5462        .collect()
5463}
5464
5465#[derive(Default)]
5466pub struct OpenOptions {
5467    pub open_new_workspace: Option<bool>,
5468    pub replace_window: Option<WindowHandle<Workspace>>,
5469    pub env: Option<HashMap<String, String>>,
5470}
5471
5472#[allow(clippy::type_complexity)]
5473pub fn open_paths(
5474    abs_paths: &[PathBuf],
5475    app_state: Arc<AppState>,
5476    open_options: OpenOptions,
5477    cx: &mut AppContext,
5478) -> Task<
5479    anyhow::Result<(
5480        WindowHandle<Workspace>,
5481        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
5482    )>,
5483> {
5484    let abs_paths = abs_paths.to_vec();
5485    let mut existing = None;
5486    let mut best_match = None;
5487    let mut open_visible = OpenVisible::All;
5488
5489    if open_options.open_new_workspace != Some(true) {
5490        for window in local_workspace_windows(cx) {
5491            if let Ok(workspace) = window.read(cx) {
5492                let m = workspace
5493                    .project
5494                    .read(cx)
5495                    .visibility_for_paths(&abs_paths, cx);
5496                if m > best_match {
5497                    existing = Some(window);
5498                    best_match = m;
5499                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
5500                    existing = Some(window)
5501                }
5502            }
5503        }
5504    }
5505
5506    cx.spawn(move |mut cx| async move {
5507        if open_options.open_new_workspace.is_none() && existing.is_none() {
5508            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
5509            if futures::future::join_all(all_files)
5510                .await
5511                .into_iter()
5512                .filter_map(|result| result.ok().flatten())
5513                .all(|file| !file.is_dir)
5514            {
5515                cx.update(|cx| {
5516                    for window in local_workspace_windows(cx) {
5517                        if let Ok(workspace) = window.read(cx) {
5518                            let project = workspace.project().read(cx);
5519                            if project.is_via_collab() {
5520                                continue;
5521                            }
5522                            existing = Some(window);
5523                            open_visible = OpenVisible::None;
5524                            break;
5525                        }
5526                    }
5527                })?;
5528            }
5529        }
5530
5531        if let Some(existing) = existing {
5532            Ok((
5533                existing,
5534                existing
5535                    .update(&mut cx, |workspace, cx| {
5536                        cx.activate_window();
5537                        workspace.open_paths(abs_paths, open_visible, None, cx)
5538                    })?
5539                    .await,
5540            ))
5541        } else {
5542            cx.update(move |cx| {
5543                Workspace::new_local(
5544                    abs_paths,
5545                    app_state.clone(),
5546                    open_options.replace_window,
5547                    open_options.env,
5548                    cx,
5549                )
5550            })?
5551            .await
5552        }
5553    })
5554}
5555
5556pub fn open_new(
5557    open_options: OpenOptions,
5558    app_state: Arc<AppState>,
5559    cx: &mut AppContext,
5560    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
5561) -> Task<anyhow::Result<()>> {
5562    let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
5563    cx.spawn(|mut cx| async move {
5564        let (workspace, opened_paths) = task.await?;
5565        workspace.update(&mut cx, |workspace, cx| {
5566            if opened_paths.is_empty() {
5567                init(workspace, cx)
5568            }
5569        })?;
5570        Ok(())
5571    })
5572}
5573
5574pub fn create_and_open_local_file(
5575    path: &'static Path,
5576    cx: &mut ViewContext<Workspace>,
5577    default_content: impl 'static + Send + FnOnce() -> Rope,
5578) -> Task<Result<Box<dyn ItemHandle>>> {
5579    cx.spawn(|workspace, mut cx| async move {
5580        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
5581        if !fs.is_file(path).await {
5582            fs.create_file(path, Default::default()).await?;
5583            fs.save(path, &default_content(), Default::default())
5584                .await?;
5585        }
5586
5587        let mut items = workspace
5588            .update(&mut cx, |workspace, cx| {
5589                workspace.with_local_workspace(cx, |workspace, cx| {
5590                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
5591                })
5592            })?
5593            .await?
5594            .await;
5595
5596        let item = items.pop().flatten();
5597        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
5598    })
5599}
5600
5601pub fn open_ssh_project(
5602    window: WindowHandle<Workspace>,
5603    connection_options: SshConnectionOptions,
5604    cancel_rx: oneshot::Receiver<()>,
5605    delegate: Arc<dyn SshClientDelegate>,
5606    app_state: Arc<AppState>,
5607    paths: Vec<PathBuf>,
5608    cx: &mut AppContext,
5609) -> Task<Result<()>> {
5610    cx.spawn(|mut cx| async move {
5611        let (serialized_ssh_project, workspace_id, serialized_workspace) =
5612            serialize_ssh_project(connection_options.clone(), paths.clone(), &cx).await?;
5613
5614        let session = match cx
5615            .update(|cx| {
5616                remote::SshRemoteClient::new(
5617                    ConnectionIdentifier::Workspace(workspace_id.0),
5618                    connection_options,
5619                    cancel_rx,
5620                    delegate,
5621                    cx,
5622                )
5623            })?
5624            .await?
5625        {
5626            Some(result) => result,
5627            None => return Ok(()),
5628        };
5629
5630        let project = cx.update(|cx| {
5631            project::Project::ssh(
5632                session,
5633                app_state.client.clone(),
5634                app_state.node_runtime.clone(),
5635                app_state.user_store.clone(),
5636                app_state.languages.clone(),
5637                app_state.fs.clone(),
5638                cx,
5639            )
5640        })?;
5641
5642        let toolchains = DB.toolchains(workspace_id).await?;
5643        for (toolchain, worktree_id) in toolchains {
5644            project
5645                .update(&mut cx, |this, cx| {
5646                    this.activate_toolchain(worktree_id, toolchain, cx)
5647                })?
5648                .await;
5649        }
5650        let mut project_paths_to_open = vec![];
5651        let mut project_path_errors = vec![];
5652
5653        for path in paths {
5654            let result = cx
5655                .update(|cx| Workspace::project_path_for_path(project.clone(), &path, true, cx))?
5656                .await;
5657            match result {
5658                Ok((_, project_path)) => {
5659                    project_paths_to_open.push((path.clone(), Some(project_path)));
5660                }
5661                Err(error) => {
5662                    project_path_errors.push(error);
5663                }
5664            };
5665        }
5666
5667        if project_paths_to_open.is_empty() {
5668            return Err(project_path_errors
5669                .pop()
5670                .unwrap_or_else(|| anyhow!("no paths given")));
5671        }
5672
5673        cx.update_window(window.into(), |_, cx| {
5674            cx.replace_root_view(|cx| {
5675                let mut workspace =
5676                    Workspace::new(Some(workspace_id), project, app_state.clone(), cx);
5677
5678                workspace
5679                    .client()
5680                    .telemetry()
5681                    .report_app_event("open ssh project".to_string());
5682
5683                workspace.set_serialized_ssh_project(serialized_ssh_project);
5684                workspace
5685            });
5686        })?;
5687
5688        window
5689            .update(&mut cx, |_, cx| {
5690                cx.activate_window();
5691
5692                open_items(serialized_workspace, project_paths_to_open, cx)
5693            })?
5694            .await?;
5695
5696        window.update(&mut cx, |workspace, cx| {
5697            for error in project_path_errors {
5698                if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
5699                    if let Some(path) = error.error_tag("path") {
5700                        workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
5701                    }
5702                } else {
5703                    workspace.show_error(&error, cx)
5704                }
5705            }
5706        })
5707    })
5708}
5709
5710fn serialize_ssh_project(
5711    connection_options: SshConnectionOptions,
5712    paths: Vec<PathBuf>,
5713    cx: &AsyncAppContext,
5714) -> Task<
5715    Result<(
5716        SerializedSshProject,
5717        WorkspaceId,
5718        Option<SerializedWorkspace>,
5719    )>,
5720> {
5721    cx.background_executor().spawn(async move {
5722        let serialized_ssh_project = persistence::DB
5723            .get_or_create_ssh_project(
5724                connection_options.host.clone(),
5725                connection_options.port,
5726                paths
5727                    .iter()
5728                    .map(|path| path.to_string_lossy().to_string())
5729                    .collect::<Vec<_>>(),
5730                connection_options.username.clone(),
5731            )
5732            .await?;
5733
5734        let serialized_workspace =
5735            persistence::DB.workspace_for_ssh_project(&serialized_ssh_project);
5736
5737        let workspace_id = if let Some(workspace_id) =
5738            serialized_workspace.as_ref().map(|workspace| workspace.id)
5739        {
5740            workspace_id
5741        } else {
5742            persistence::DB.next_id().await?
5743        };
5744
5745        Ok((serialized_ssh_project, workspace_id, serialized_workspace))
5746    })
5747}
5748
5749pub fn join_in_room_project(
5750    project_id: u64,
5751    follow_user_id: u64,
5752    app_state: Arc<AppState>,
5753    cx: &mut AppContext,
5754) -> Task<Result<()>> {
5755    let windows = cx.windows();
5756    cx.spawn(|mut cx| async move {
5757        let existing_workspace = windows.into_iter().find_map(|window| {
5758            window.downcast::<Workspace>().and_then(|window| {
5759                window
5760                    .update(&mut cx, |workspace, cx| {
5761                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5762                            Some(window)
5763                        } else {
5764                            None
5765                        }
5766                    })
5767                    .unwrap_or(None)
5768            })
5769        });
5770
5771        let workspace = if let Some(existing_workspace) = existing_workspace {
5772            existing_workspace
5773        } else {
5774            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5775            let room = active_call
5776                .read_with(&cx, |call, _| call.room().cloned())?
5777                .ok_or_else(|| anyhow!("not in a call"))?;
5778            let project = room
5779                .update(&mut cx, |room, cx| {
5780                    room.join_project(
5781                        project_id,
5782                        app_state.languages.clone(),
5783                        app_state.fs.clone(),
5784                        cx,
5785                    )
5786                })?
5787                .await?;
5788
5789            let window_bounds_override = window_bounds_env_override();
5790            cx.update(|cx| {
5791                let mut options = (app_state.build_window_options)(None, cx);
5792                options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
5793                cx.open_window(options, |cx| {
5794                    cx.new_view(|cx| {
5795                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5796                    })
5797                })
5798            })??
5799        };
5800
5801        workspace.update(&mut cx, |workspace, cx| {
5802            cx.activate(true);
5803            cx.activate_window();
5804
5805            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5806                let follow_peer_id = room
5807                    .read(cx)
5808                    .remote_participants()
5809                    .iter()
5810                    .find(|(_, participant)| participant.user.id == follow_user_id)
5811                    .map(|(_, p)| p.peer_id)
5812                    .or_else(|| {
5813                        // If we couldn't follow the given user, follow the host instead.
5814                        let collaborator = workspace
5815                            .project()
5816                            .read(cx)
5817                            .collaborators()
5818                            .values()
5819                            .find(|collaborator| collaborator.is_host)?;
5820                        Some(collaborator.peer_id)
5821                    });
5822
5823                if let Some(follow_peer_id) = follow_peer_id {
5824                    workspace.follow(follow_peer_id, cx);
5825                }
5826            }
5827        })?;
5828
5829        anyhow::Ok(())
5830    })
5831}
5832
5833pub fn reload(reload: &Reload, cx: &mut AppContext) {
5834    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5835    let mut workspace_windows = cx
5836        .windows()
5837        .into_iter()
5838        .filter_map(|window| window.downcast::<Workspace>())
5839        .collect::<Vec<_>>();
5840
5841    // If multiple windows have unsaved changes, and need a save prompt,
5842    // prompt in the active window before switching to a different window.
5843    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5844
5845    let mut prompt = None;
5846    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5847        prompt = window
5848            .update(cx, |_, cx| {
5849                cx.prompt(
5850                    PromptLevel::Info,
5851                    "Are you sure you want to restart?",
5852                    None,
5853                    &["Restart", "Cancel"],
5854                )
5855            })
5856            .ok();
5857    }
5858
5859    let binary_path = reload.binary_path.clone();
5860    cx.spawn(|mut cx| async move {
5861        if let Some(prompt) = prompt {
5862            let answer = prompt.await?;
5863            if answer != 0 {
5864                return Ok(());
5865            }
5866        }
5867
5868        // If the user cancels any save prompt, then keep the app open.
5869        for window in workspace_windows {
5870            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5871                workspace.prepare_to_close(CloseIntent::Quit, cx)
5872            }) {
5873                if !should_close.await? {
5874                    return Ok(());
5875                }
5876            }
5877        }
5878
5879        cx.update(|cx| cx.restart(binary_path))
5880    })
5881    .detach_and_log_err(cx);
5882}
5883
5884fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5885    let mut parts = value.split(',');
5886    let x: usize = parts.next()?.parse().ok()?;
5887    let y: usize = parts.next()?.parse().ok()?;
5888    Some(point(px(x as f32), px(y as f32)))
5889}
5890
5891fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5892    let mut parts = value.split(',');
5893    let width: usize = parts.next()?.parse().ok()?;
5894    let height: usize = parts.next()?.parse().ok()?;
5895    Some(size(px(width as f32), px(height as f32)))
5896}
5897
5898pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
5899    const BORDER_SIZE: Pixels = px(1.0);
5900    let decorations = cx.window_decorations();
5901
5902    if matches!(decorations, Decorations::Client { .. }) {
5903        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
5904    }
5905
5906    struct GlobalResizeEdge(ResizeEdge);
5907    impl Global for GlobalResizeEdge {}
5908
5909    div()
5910        .id("window-backdrop")
5911        .bg(transparent_black())
5912        .map(|div| match decorations {
5913            Decorations::Server => div,
5914            Decorations::Client { tiling, .. } => div
5915                .when(!(tiling.top || tiling.right), |div| {
5916                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5917                })
5918                .when(!(tiling.top || tiling.left), |div| {
5919                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5920                })
5921                .when(!(tiling.bottom || tiling.right), |div| {
5922                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5923                })
5924                .when(!(tiling.bottom || tiling.left), |div| {
5925                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5926                })
5927                .when(!tiling.top, |div| {
5928                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
5929                })
5930                .when(!tiling.bottom, |div| {
5931                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
5932                })
5933                .when(!tiling.left, |div| {
5934                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
5935                })
5936                .when(!tiling.right, |div| {
5937                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
5938                })
5939                .on_mouse_move(move |e, cx| {
5940                    let size = cx.window_bounds().get_bounds().size;
5941                    let pos = e.position;
5942
5943                    let new_edge =
5944                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
5945
5946                    let edge = cx.try_global::<GlobalResizeEdge>();
5947                    if new_edge != edge.map(|edge| edge.0) {
5948                        cx.window_handle()
5949                            .update(cx, |workspace, cx| cx.notify(Some(workspace.entity_id())))
5950                            .ok();
5951                    }
5952                })
5953                .on_mouse_down(MouseButton::Left, move |e, cx| {
5954                    let size = cx.window_bounds().get_bounds().size;
5955                    let pos = e.position;
5956
5957                    let edge = match resize_edge(
5958                        pos,
5959                        theme::CLIENT_SIDE_DECORATION_SHADOW,
5960                        size,
5961                        tiling,
5962                    ) {
5963                        Some(value) => value,
5964                        None => return,
5965                    };
5966
5967                    cx.start_window_resize(edge);
5968                }),
5969        })
5970        .size_full()
5971        .child(
5972            div()
5973                .cursor(CursorStyle::Arrow)
5974                .map(|div| match decorations {
5975                    Decorations::Server => div,
5976                    Decorations::Client { tiling } => div
5977                        .border_color(cx.theme().colors().border)
5978                        .when(!(tiling.top || tiling.right), |div| {
5979                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5980                        })
5981                        .when(!(tiling.top || tiling.left), |div| {
5982                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5983                        })
5984                        .when(!(tiling.bottom || tiling.right), |div| {
5985                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5986                        })
5987                        .when(!(tiling.bottom || tiling.left), |div| {
5988                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
5989                        })
5990                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
5991                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
5992                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
5993                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
5994                        .when(!tiling.is_tiled(), |div| {
5995                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
5996                                color: Hsla {
5997                                    h: 0.,
5998                                    s: 0.,
5999                                    l: 0.,
6000                                    a: 0.4,
6001                                },
6002                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
6003                                spread_radius: px(0.),
6004                                offset: point(px(0.0), px(0.0)),
6005                            }])
6006                        }),
6007                })
6008                .on_mouse_move(|_e, cx| {
6009                    cx.stop_propagation();
6010                })
6011                .size_full()
6012                .child(element),
6013        )
6014        .map(|div| match decorations {
6015            Decorations::Server => div,
6016            Decorations::Client { tiling, .. } => div.child(
6017                canvas(
6018                    |_bounds, cx| {
6019                        cx.insert_hitbox(
6020                            Bounds::new(
6021                                point(px(0.0), px(0.0)),
6022                                cx.window_bounds().get_bounds().size,
6023                            ),
6024                            false,
6025                        )
6026                    },
6027                    move |_bounds, hitbox, cx| {
6028                        let mouse = cx.mouse_position();
6029                        let size = cx.window_bounds().get_bounds().size;
6030                        let Some(edge) =
6031                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
6032                        else {
6033                            return;
6034                        };
6035                        cx.set_global(GlobalResizeEdge(edge));
6036                        cx.set_cursor_style(
6037                            match edge {
6038                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
6039                                ResizeEdge::Left | ResizeEdge::Right => {
6040                                    CursorStyle::ResizeLeftRight
6041                                }
6042                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
6043                                    CursorStyle::ResizeUpLeftDownRight
6044                                }
6045                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
6046                                    CursorStyle::ResizeUpRightDownLeft
6047                                }
6048                            },
6049                            &hitbox,
6050                        );
6051                    },
6052                )
6053                .size_full()
6054                .absolute(),
6055            ),
6056        })
6057}
6058
6059fn resize_edge(
6060    pos: Point<Pixels>,
6061    shadow_size: Pixels,
6062    window_size: Size<Pixels>,
6063    tiling: Tiling,
6064) -> Option<ResizeEdge> {
6065    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
6066    if bounds.contains(&pos) {
6067        return None;
6068    }
6069
6070    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
6071    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
6072    if !tiling.top && top_left_bounds.contains(&pos) {
6073        return Some(ResizeEdge::TopLeft);
6074    }
6075
6076    let top_right_bounds = Bounds::new(
6077        Point::new(window_size.width - corner_size.width, px(0.)),
6078        corner_size,
6079    );
6080    if !tiling.top && top_right_bounds.contains(&pos) {
6081        return Some(ResizeEdge::TopRight);
6082    }
6083
6084    let bottom_left_bounds = Bounds::new(
6085        Point::new(px(0.), window_size.height - corner_size.height),
6086        corner_size,
6087    );
6088    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
6089        return Some(ResizeEdge::BottomLeft);
6090    }
6091
6092    let bottom_right_bounds = Bounds::new(
6093        Point::new(
6094            window_size.width - corner_size.width,
6095            window_size.height - corner_size.height,
6096        ),
6097        corner_size,
6098    );
6099    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
6100        return Some(ResizeEdge::BottomRight);
6101    }
6102
6103    if !tiling.top && pos.y < shadow_size {
6104        Some(ResizeEdge::Top)
6105    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
6106        Some(ResizeEdge::Bottom)
6107    } else if !tiling.left && pos.x < shadow_size {
6108        Some(ResizeEdge::Left)
6109    } else if !tiling.right && pos.x > window_size.width - shadow_size {
6110        Some(ResizeEdge::Right)
6111    } else {
6112        None
6113    }
6114}
6115
6116fn join_pane_into_active(active_pane: &View<Pane>, pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6117    if pane == active_pane {
6118        return;
6119    } else if pane.read(cx).items_len() == 0 {
6120        pane.update(cx, |_, cx| {
6121            cx.emit(pane::Event::Remove {
6122                focus_on_pane: None,
6123            });
6124        })
6125    } else {
6126        move_all_items(pane, active_pane, cx);
6127    }
6128}
6129
6130fn move_all_items(from_pane: &View<Pane>, to_pane: &View<Pane>, cx: &mut WindowContext<'_>) {
6131    let destination_is_different = from_pane != to_pane;
6132    let mut moved_items = 0;
6133    for (item_ix, item_handle) in from_pane
6134        .read(cx)
6135        .items()
6136        .enumerate()
6137        .map(|(ix, item)| (ix, item.clone()))
6138        .collect::<Vec<_>>()
6139    {
6140        let ix = item_ix - moved_items;
6141        if destination_is_different {
6142            // Close item from previous pane
6143            from_pane.update(cx, |source, cx| {
6144                source.remove_item_and_focus_on_pane(ix, false, to_pane.clone(), cx);
6145            });
6146            moved_items += 1;
6147        }
6148
6149        // This automatically removes duplicate items in the pane
6150        to_pane.update(cx, |destination, cx| {
6151            destination.add_item(item_handle, true, true, None, cx);
6152            destination.focus(cx)
6153        });
6154    }
6155}
6156
6157pub fn move_item(
6158    source: &View<Pane>,
6159    destination: &View<Pane>,
6160    item_id_to_move: EntityId,
6161    destination_index: usize,
6162    cx: &mut WindowContext<'_>,
6163) {
6164    let Some((item_ix, item_handle)) = source
6165        .read(cx)
6166        .items()
6167        .enumerate()
6168        .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
6169        .map(|(ix, item)| (ix, item.clone()))
6170    else {
6171        // Tab was closed during drag
6172        return;
6173    };
6174
6175    if source != destination {
6176        // Close item from previous pane
6177        source.update(cx, |source, cx| {
6178            source.remove_item_and_focus_on_pane(item_ix, false, destination.clone(), cx);
6179        });
6180    }
6181
6182    // This automatically removes duplicate items in the pane
6183    destination.update(cx, |destination, cx| {
6184        destination.add_item(item_handle, true, true, Some(destination_index), cx);
6185        destination.focus(cx)
6186    });
6187}
6188
6189#[cfg(test)]
6190mod tests {
6191    use std::{cell::RefCell, rc::Rc};
6192
6193    use super::*;
6194    use crate::{
6195        dock::{test::TestPanel, PanelEvent},
6196        item::{
6197            test::{TestItem, TestProjectItem},
6198            ItemEvent,
6199        },
6200    };
6201    use fs::FakeFs;
6202    use gpui::{
6203        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
6204        UpdateGlobal, VisualTestContext,
6205    };
6206    use project::{Project, ProjectEntryId};
6207    use serde_json::json;
6208    use settings::SettingsStore;
6209
6210    #[gpui::test]
6211    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
6212        init_test(cx);
6213
6214        let fs = FakeFs::new(cx.executor());
6215        let project = Project::test(fs, [], cx).await;
6216        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6217
6218        // Adding an item with no ambiguity renders the tab without detail.
6219        let item1 = cx.new_view(|cx| {
6220            let mut item = TestItem::new(cx);
6221            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
6222            item
6223        });
6224        workspace.update(cx, |workspace, cx| {
6225            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6226        });
6227        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
6228
6229        // Adding an item that creates ambiguity increases the level of detail on
6230        // both tabs.
6231        let item2 = cx.new_view(|cx| {
6232            let mut item = TestItem::new(cx);
6233            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6234            item
6235        });
6236        workspace.update(cx, |workspace, cx| {
6237            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6238        });
6239        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6240        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6241
6242        // Adding an item that creates ambiguity increases the level of detail only
6243        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
6244        // we stop at the highest detail available.
6245        let item3 = cx.new_view(|cx| {
6246            let mut item = TestItem::new(cx);
6247            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
6248            item
6249        });
6250        workspace.update(cx, |workspace, cx| {
6251            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6252        });
6253        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
6254        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6255        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
6256    }
6257
6258    #[gpui::test]
6259    async fn test_tracking_active_path(cx: &mut TestAppContext) {
6260        init_test(cx);
6261
6262        let fs = FakeFs::new(cx.executor());
6263        fs.insert_tree(
6264            "/root1",
6265            json!({
6266                "one.txt": "",
6267                "two.txt": "",
6268            }),
6269        )
6270        .await;
6271        fs.insert_tree(
6272            "/root2",
6273            json!({
6274                "three.txt": "",
6275            }),
6276        )
6277        .await;
6278
6279        let project = Project::test(fs, ["root1".as_ref()], cx).await;
6280        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6281        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6282        let worktree_id = project.update(cx, |project, cx| {
6283            project.worktrees(cx).next().unwrap().read(cx).id()
6284        });
6285
6286        let item1 = cx.new_view(|cx| {
6287            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
6288        });
6289        let item2 = cx.new_view(|cx| {
6290            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
6291        });
6292
6293        // Add an item to an empty pane
6294        workspace.update(cx, |workspace, cx| {
6295            workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
6296        });
6297        project.update(cx, |project, cx| {
6298            assert_eq!(
6299                project.active_entry(),
6300                project
6301                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6302                    .map(|e| e.id)
6303            );
6304        });
6305        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
6306
6307        // Add a second item to a non-empty pane
6308        workspace.update(cx, |workspace, cx| {
6309            workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
6310        });
6311        assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
6312        project.update(cx, |project, cx| {
6313            assert_eq!(
6314                project.active_entry(),
6315                project
6316                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
6317                    .map(|e| e.id)
6318            );
6319        });
6320
6321        // Close the active item
6322        pane.update(cx, |pane, cx| {
6323            pane.close_active_item(&Default::default(), cx).unwrap()
6324        })
6325        .await
6326        .unwrap();
6327        assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
6328        project.update(cx, |project, cx| {
6329            assert_eq!(
6330                project.active_entry(),
6331                project
6332                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
6333                    .map(|e| e.id)
6334            );
6335        });
6336
6337        // Add a project folder
6338        project
6339            .update(cx, |project, cx| {
6340                project.find_or_create_worktree("root2", true, cx)
6341            })
6342            .await
6343            .unwrap();
6344        assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
6345
6346        // Remove a project folder
6347        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
6348        assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
6349    }
6350
6351    #[gpui::test]
6352    async fn test_close_window(cx: &mut TestAppContext) {
6353        init_test(cx);
6354
6355        let fs = FakeFs::new(cx.executor());
6356        fs.insert_tree("/root", json!({ "one": "" })).await;
6357
6358        let project = Project::test(fs, ["root".as_ref()], cx).await;
6359        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6360
6361        // When there are no dirty items, there's nothing to do.
6362        let item1 = cx.new_view(TestItem::new);
6363        workspace.update(cx, |w, cx| {
6364            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
6365        });
6366        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6367        assert!(task.await.unwrap());
6368
6369        // When there are dirty untitled items, prompt to save each one. If the user
6370        // cancels any prompt, then abort.
6371        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
6372        let item3 = cx.new_view(|cx| {
6373            TestItem::new(cx)
6374                .with_dirty(true)
6375                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6376        });
6377        workspace.update(cx, |w, cx| {
6378            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6379            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6380        });
6381        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6382        cx.executor().run_until_parked();
6383        cx.simulate_prompt_answer(2); // cancel save all
6384        cx.executor().run_until_parked();
6385        cx.simulate_prompt_answer(2); // cancel save all
6386        cx.executor().run_until_parked();
6387        assert!(!cx.has_pending_prompt());
6388        assert!(!task.await.unwrap());
6389    }
6390
6391    #[gpui::test]
6392    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
6393        init_test(cx);
6394
6395        // Register TestItem as a serializable item
6396        cx.update(|cx| {
6397            register_serializable_item::<TestItem>(cx);
6398        });
6399
6400        let fs = FakeFs::new(cx.executor());
6401        fs.insert_tree("/root", json!({ "one": "" })).await;
6402
6403        let project = Project::test(fs, ["root".as_ref()], cx).await;
6404        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6405
6406        // When there are dirty untitled items, but they can serialize, then there is no prompt.
6407        let item1 = cx.new_view(|cx| {
6408            TestItem::new(cx)
6409                .with_dirty(true)
6410                .with_serialize(|| Some(Task::ready(Ok(()))))
6411        });
6412        let item2 = cx.new_view(|cx| {
6413            TestItem::new(cx)
6414                .with_dirty(true)
6415                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6416                .with_serialize(|| Some(Task::ready(Ok(()))))
6417        });
6418        workspace.update(cx, |w, cx| {
6419            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6420            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6421        });
6422        let task = workspace.update(cx, |w, cx| w.prepare_to_close(CloseIntent::CloseWindow, cx));
6423        assert!(task.await.unwrap());
6424    }
6425
6426    #[gpui::test]
6427    async fn test_close_pane_items(cx: &mut TestAppContext) {
6428        init_test(cx);
6429
6430        let fs = FakeFs::new(cx.executor());
6431
6432        let project = Project::test(fs, None, cx).await;
6433        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6434
6435        let item1 = cx.new_view(|cx| {
6436            TestItem::new(cx)
6437                .with_dirty(true)
6438                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6439        });
6440        let item2 = cx.new_view(|cx| {
6441            TestItem::new(cx)
6442                .with_dirty(true)
6443                .with_conflict(true)
6444                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
6445        });
6446        let item3 = cx.new_view(|cx| {
6447            TestItem::new(cx)
6448                .with_dirty(true)
6449                .with_conflict(true)
6450                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
6451        });
6452        let item4 = cx.new_view(|cx| {
6453            TestItem::new(cx)
6454                .with_dirty(true)
6455                .with_project_items(&[TestProjectItem::new_untitled(cx)])
6456        });
6457        let pane = workspace.update(cx, |workspace, cx| {
6458            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
6459            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
6460            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
6461            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
6462            workspace.active_pane().clone()
6463        });
6464
6465        let close_items = pane.update(cx, |pane, cx| {
6466            pane.activate_item(1, true, true, cx);
6467            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6468            let item1_id = item1.item_id();
6469            let item3_id = item3.item_id();
6470            let item4_id = item4.item_id();
6471            pane.close_items(cx, SaveIntent::Close, move |id| {
6472                [item1_id, item3_id, item4_id].contains(&id)
6473            })
6474        });
6475        cx.executor().run_until_parked();
6476
6477        assert!(cx.has_pending_prompt());
6478        // Ignore "Save all" prompt
6479        cx.simulate_prompt_answer(2);
6480        cx.executor().run_until_parked();
6481        // There's a prompt to save item 1.
6482        pane.update(cx, |pane, _| {
6483            assert_eq!(pane.items_len(), 4);
6484            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
6485        });
6486        // Confirm saving item 1.
6487        cx.simulate_prompt_answer(0);
6488        cx.executor().run_until_parked();
6489
6490        // Item 1 is saved. There's a prompt to save item 3.
6491        pane.update(cx, |pane, cx| {
6492            assert_eq!(item1.read(cx).save_count, 1);
6493            assert_eq!(item1.read(cx).save_as_count, 0);
6494            assert_eq!(item1.read(cx).reload_count, 0);
6495            assert_eq!(pane.items_len(), 3);
6496            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
6497        });
6498        assert!(cx.has_pending_prompt());
6499
6500        // Cancel saving item 3.
6501        cx.simulate_prompt_answer(1);
6502        cx.executor().run_until_parked();
6503
6504        // Item 3 is reloaded. There's a prompt to save item 4.
6505        pane.update(cx, |pane, cx| {
6506            assert_eq!(item3.read(cx).save_count, 0);
6507            assert_eq!(item3.read(cx).save_as_count, 0);
6508            assert_eq!(item3.read(cx).reload_count, 1);
6509            assert_eq!(pane.items_len(), 2);
6510            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
6511        });
6512        assert!(cx.has_pending_prompt());
6513
6514        // Confirm saving item 4.
6515        cx.simulate_prompt_answer(0);
6516        cx.executor().run_until_parked();
6517
6518        // There's a prompt for a path for item 4.
6519        cx.simulate_new_path_selection(|_| Some(Default::default()));
6520        close_items.await.unwrap();
6521
6522        // The requested items are closed.
6523        pane.update(cx, |pane, cx| {
6524            assert_eq!(item4.read(cx).save_count, 0);
6525            assert_eq!(item4.read(cx).save_as_count, 1);
6526            assert_eq!(item4.read(cx).reload_count, 0);
6527            assert_eq!(pane.items_len(), 1);
6528            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
6529        });
6530    }
6531
6532    #[gpui::test]
6533    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
6534        init_test(cx);
6535
6536        let fs = FakeFs::new(cx.executor());
6537        let project = Project::test(fs, [], cx).await;
6538        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6539
6540        // Create several workspace items with single project entries, and two
6541        // workspace items with multiple project entries.
6542        let single_entry_items = (0..=4)
6543            .map(|project_entry_id| {
6544                cx.new_view(|cx| {
6545                    TestItem::new(cx)
6546                        .with_dirty(true)
6547                        .with_project_items(&[TestProjectItem::new(
6548                            project_entry_id,
6549                            &format!("{project_entry_id}.txt"),
6550                            cx,
6551                        )])
6552                })
6553            })
6554            .collect::<Vec<_>>();
6555        let item_2_3 = cx.new_view(|cx| {
6556            TestItem::new(cx)
6557                .with_dirty(true)
6558                .with_singleton(false)
6559                .with_project_items(&[
6560                    single_entry_items[2].read(cx).project_items[0].clone(),
6561                    single_entry_items[3].read(cx).project_items[0].clone(),
6562                ])
6563        });
6564        let item_3_4 = cx.new_view(|cx| {
6565            TestItem::new(cx)
6566                .with_dirty(true)
6567                .with_singleton(false)
6568                .with_project_items(&[
6569                    single_entry_items[3].read(cx).project_items[0].clone(),
6570                    single_entry_items[4].read(cx).project_items[0].clone(),
6571                ])
6572        });
6573
6574        // Create two panes that contain the following project entries:
6575        //   left pane:
6576        //     multi-entry items:   (2, 3)
6577        //     single-entry items:  0, 1, 2, 3, 4
6578        //   right pane:
6579        //     single-entry items:  1
6580        //     multi-entry items:   (3, 4)
6581        let left_pane = workspace.update(cx, |workspace, cx| {
6582            let left_pane = workspace.active_pane().clone();
6583            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
6584            for item in single_entry_items {
6585                workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
6586            }
6587            left_pane.update(cx, |pane, cx| {
6588                pane.activate_item(2, true, true, cx);
6589            });
6590
6591            let right_pane = workspace
6592                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
6593                .unwrap();
6594
6595            right_pane.update(cx, |pane, cx| {
6596                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
6597            });
6598
6599            left_pane
6600        });
6601
6602        cx.focus_view(&left_pane);
6603
6604        // When closing all of the items in the left pane, we should be prompted twice:
6605        // once for project entry 0, and once for project entry 2. Project entries 1,
6606        // 3, and 4 are all still open in the other paten. After those two
6607        // prompts, the task should complete.
6608
6609        let close = left_pane.update(cx, |pane, cx| {
6610            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
6611        });
6612        cx.executor().run_until_parked();
6613
6614        // Discard "Save all" prompt
6615        cx.simulate_prompt_answer(2);
6616
6617        cx.executor().run_until_parked();
6618        left_pane.update(cx, |pane, cx| {
6619            assert_eq!(
6620                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6621                &[ProjectEntryId::from_proto(0)]
6622            );
6623        });
6624        cx.simulate_prompt_answer(0);
6625
6626        cx.executor().run_until_parked();
6627        left_pane.update(cx, |pane, cx| {
6628            assert_eq!(
6629                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6630                &[ProjectEntryId::from_proto(2)]
6631            );
6632        });
6633        cx.simulate_prompt_answer(0);
6634
6635        cx.executor().run_until_parked();
6636        close.await.unwrap();
6637        left_pane.update(cx, |pane, _| {
6638            assert_eq!(pane.items_len(), 0);
6639        });
6640    }
6641
6642    #[gpui::test]
6643    async fn test_autosave(cx: &mut gpui::TestAppContext) {
6644        init_test(cx);
6645
6646        let fs = FakeFs::new(cx.executor());
6647        let project = Project::test(fs, [], cx).await;
6648        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6649        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6650
6651        let item = cx.new_view(|cx| {
6652            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6653        });
6654        let item_id = item.entity_id();
6655        workspace.update(cx, |workspace, cx| {
6656            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6657        });
6658
6659        // Autosave on window change.
6660        item.update(cx, |item, cx| {
6661            SettingsStore::update_global(cx, |settings, cx| {
6662                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6663                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
6664                })
6665            });
6666            item.is_dirty = true;
6667        });
6668
6669        // Deactivating the window saves the file.
6670        cx.deactivate_window();
6671        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6672
6673        // Re-activating the window doesn't save the file.
6674        cx.update(|cx| cx.activate_window());
6675        cx.executor().run_until_parked();
6676        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6677
6678        // Autosave on focus change.
6679        item.update(cx, |item, cx| {
6680            cx.focus_self();
6681            SettingsStore::update_global(cx, |settings, cx| {
6682                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6683                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6684                })
6685            });
6686            item.is_dirty = true;
6687        });
6688
6689        // Blurring the item saves the file.
6690        item.update(cx, |_, cx| cx.blur());
6691        cx.executor().run_until_parked();
6692        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6693
6694        // Deactivating the window still saves the file.
6695        item.update(cx, |item, cx| {
6696            cx.focus_self();
6697            item.is_dirty = true;
6698        });
6699        cx.deactivate_window();
6700        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6701
6702        // Autosave after delay.
6703        item.update(cx, |item, cx| {
6704            SettingsStore::update_global(cx, |settings, cx| {
6705                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6706                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
6707                })
6708            });
6709            item.is_dirty = true;
6710            cx.emit(ItemEvent::Edit);
6711        });
6712
6713        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6714        cx.executor().advance_clock(Duration::from_millis(250));
6715        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6716
6717        // After delay expires, the file is saved.
6718        cx.executor().advance_clock(Duration::from_millis(250));
6719        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6720
6721        // Autosave on focus change, ensuring closing the tab counts as such.
6722        item.update(cx, |item, cx| {
6723            SettingsStore::update_global(cx, |settings, cx| {
6724                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6725                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6726                })
6727            });
6728            item.is_dirty = true;
6729        });
6730
6731        pane.update(cx, |pane, cx| {
6732            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6733        })
6734        .await
6735        .unwrap();
6736        assert!(!cx.has_pending_prompt());
6737        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6738
6739        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6740        workspace.update(cx, |workspace, cx| {
6741            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6742        });
6743        item.update(cx, |item, cx| {
6744            item.project_items[0].update(cx, |item, _| {
6745                item.entry_id = None;
6746            });
6747            item.is_dirty = true;
6748            cx.blur();
6749        });
6750        cx.run_until_parked();
6751        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6752
6753        // Ensure autosave is prevented for deleted files also when closing the buffer.
6754        let _close_items = pane.update(cx, |pane, cx| {
6755            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6756        });
6757        cx.run_until_parked();
6758        assert!(cx.has_pending_prompt());
6759        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6760    }
6761
6762    #[gpui::test]
6763    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6764        init_test(cx);
6765
6766        let fs = FakeFs::new(cx.executor());
6767
6768        let project = Project::test(fs, [], cx).await;
6769        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6770
6771        let item = cx.new_view(|cx| {
6772            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6773        });
6774        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6775        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6776        let toolbar_notify_count = Rc::new(RefCell::new(0));
6777
6778        workspace.update(cx, |workspace, cx| {
6779            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6780            let toolbar_notification_count = toolbar_notify_count.clone();
6781            cx.observe(&toolbar, move |_, _, _| {
6782                *toolbar_notification_count.borrow_mut() += 1
6783            })
6784            .detach();
6785        });
6786
6787        pane.update(cx, |pane, _| {
6788            assert!(!pane.can_navigate_backward());
6789            assert!(!pane.can_navigate_forward());
6790        });
6791
6792        item.update(cx, |item, cx| {
6793            item.set_state("one".to_string(), cx);
6794        });
6795
6796        // Toolbar must be notified to re-render the navigation buttons
6797        assert_eq!(*toolbar_notify_count.borrow(), 1);
6798
6799        pane.update(cx, |pane, _| {
6800            assert!(pane.can_navigate_backward());
6801            assert!(!pane.can_navigate_forward());
6802        });
6803
6804        workspace
6805            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6806            .await
6807            .unwrap();
6808
6809        assert_eq!(*toolbar_notify_count.borrow(), 2);
6810        pane.update(cx, |pane, _| {
6811            assert!(!pane.can_navigate_backward());
6812            assert!(pane.can_navigate_forward());
6813        });
6814    }
6815
6816    #[gpui::test]
6817    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6818        init_test(cx);
6819        let fs = FakeFs::new(cx.executor());
6820
6821        let project = Project::test(fs, [], cx).await;
6822        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6823
6824        let panel = workspace.update(cx, |workspace, cx| {
6825            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6826            workspace.add_panel(panel.clone(), cx);
6827
6828            workspace
6829                .right_dock()
6830                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6831
6832            panel
6833        });
6834
6835        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6836        pane.update(cx, |pane, cx| {
6837            let item = cx.new_view(TestItem::new);
6838            pane.add_item(Box::new(item), true, true, None, cx);
6839        });
6840
6841        // Transfer focus from center to panel
6842        workspace.update(cx, |workspace, cx| {
6843            workspace.toggle_panel_focus::<TestPanel>(cx);
6844        });
6845
6846        workspace.update(cx, |workspace, cx| {
6847            assert!(workspace.right_dock().read(cx).is_open());
6848            assert!(!panel.is_zoomed(cx));
6849            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6850        });
6851
6852        // Transfer focus from panel to center
6853        workspace.update(cx, |workspace, cx| {
6854            workspace.toggle_panel_focus::<TestPanel>(cx);
6855        });
6856
6857        workspace.update(cx, |workspace, cx| {
6858            assert!(workspace.right_dock().read(cx).is_open());
6859            assert!(!panel.is_zoomed(cx));
6860            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6861        });
6862
6863        // Close the dock
6864        workspace.update(cx, |workspace, cx| {
6865            workspace.toggle_dock(DockPosition::Right, cx);
6866        });
6867
6868        workspace.update(cx, |workspace, cx| {
6869            assert!(!workspace.right_dock().read(cx).is_open());
6870            assert!(!panel.is_zoomed(cx));
6871            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6872        });
6873
6874        // Open the dock
6875        workspace.update(cx, |workspace, cx| {
6876            workspace.toggle_dock(DockPosition::Right, cx);
6877        });
6878
6879        workspace.update(cx, |workspace, cx| {
6880            assert!(workspace.right_dock().read(cx).is_open());
6881            assert!(!panel.is_zoomed(cx));
6882            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6883        });
6884
6885        // Focus and zoom panel
6886        panel.update(cx, |panel, cx| {
6887            cx.focus_self();
6888            panel.set_zoomed(true, cx)
6889        });
6890
6891        workspace.update(cx, |workspace, cx| {
6892            assert!(workspace.right_dock().read(cx).is_open());
6893            assert!(panel.is_zoomed(cx));
6894            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6895        });
6896
6897        // Transfer focus to the center closes the dock
6898        workspace.update(cx, |workspace, cx| {
6899            workspace.toggle_panel_focus::<TestPanel>(cx);
6900        });
6901
6902        workspace.update(cx, |workspace, cx| {
6903            assert!(!workspace.right_dock().read(cx).is_open());
6904            assert!(panel.is_zoomed(cx));
6905            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6906        });
6907
6908        // Transferring focus back to the panel keeps it zoomed
6909        workspace.update(cx, |workspace, cx| {
6910            workspace.toggle_panel_focus::<TestPanel>(cx);
6911        });
6912
6913        workspace.update(cx, |workspace, cx| {
6914            assert!(workspace.right_dock().read(cx).is_open());
6915            assert!(panel.is_zoomed(cx));
6916            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6917        });
6918
6919        // Close the dock while it is zoomed
6920        workspace.update(cx, |workspace, cx| {
6921            workspace.toggle_dock(DockPosition::Right, cx)
6922        });
6923
6924        workspace.update(cx, |workspace, cx| {
6925            assert!(!workspace.right_dock().read(cx).is_open());
6926            assert!(panel.is_zoomed(cx));
6927            assert!(workspace.zoomed.is_none());
6928            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6929        });
6930
6931        // Opening the dock, when it's zoomed, retains focus
6932        workspace.update(cx, |workspace, cx| {
6933            workspace.toggle_dock(DockPosition::Right, cx)
6934        });
6935
6936        workspace.update(cx, |workspace, cx| {
6937            assert!(workspace.right_dock().read(cx).is_open());
6938            assert!(panel.is_zoomed(cx));
6939            assert!(workspace.zoomed.is_some());
6940            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6941        });
6942
6943        // Unzoom and close the panel, zoom the active pane.
6944        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6945        workspace.update(cx, |workspace, cx| {
6946            workspace.toggle_dock(DockPosition::Right, cx)
6947        });
6948        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6949
6950        // Opening a dock unzooms the pane.
6951        workspace.update(cx, |workspace, cx| {
6952            workspace.toggle_dock(DockPosition::Right, cx)
6953        });
6954        workspace.update(cx, |workspace, cx| {
6955            let pane = pane.read(cx);
6956            assert!(!pane.is_zoomed());
6957            assert!(!pane.focus_handle(cx).is_focused(cx));
6958            assert!(workspace.right_dock().read(cx).is_open());
6959            assert!(workspace.zoomed.is_none());
6960        });
6961    }
6962
6963    #[gpui::test]
6964    async fn test_join_pane_into_next(cx: &mut gpui::TestAppContext) {
6965        init_test(cx);
6966
6967        let fs = FakeFs::new(cx.executor());
6968
6969        let project = Project::test(fs, None, cx).await;
6970        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6971
6972        // Let's arrange the panes like this:
6973        //
6974        // +-----------------------+
6975        // |         top           |
6976        // +------+--------+-------+
6977        // | left | center | right |
6978        // +------+--------+-------+
6979        // |        bottom         |
6980        // +-----------------------+
6981
6982        let top_item = cx.new_view(|cx| {
6983            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "top.txt", cx)])
6984        });
6985        let bottom_item = cx.new_view(|cx| {
6986            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "bottom.txt", cx)])
6987        });
6988        let left_item = cx.new_view(|cx| {
6989            TestItem::new(cx).with_project_items(&[TestProjectItem::new(3, "left.txt", cx)])
6990        });
6991        let right_item = cx.new_view(|cx| {
6992            TestItem::new(cx).with_project_items(&[TestProjectItem::new(4, "right.txt", cx)])
6993        });
6994        let center_item = cx.new_view(|cx| {
6995            TestItem::new(cx).with_project_items(&[TestProjectItem::new(5, "center.txt", cx)])
6996        });
6997
6998        let top_pane_id = workspace.update(cx, |workspace, cx| {
6999            let top_pane_id = workspace.active_pane().entity_id();
7000            workspace.add_item_to_active_pane(Box::new(top_item.clone()), None, false, cx);
7001            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Down, cx);
7002            top_pane_id
7003        });
7004        let bottom_pane_id = workspace.update(cx, |workspace, cx| {
7005            let bottom_pane_id = workspace.active_pane().entity_id();
7006            workspace.add_item_to_active_pane(Box::new(bottom_item.clone()), None, false, cx);
7007            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Up, cx);
7008            bottom_pane_id
7009        });
7010        let left_pane_id = workspace.update(cx, |workspace, cx| {
7011            let left_pane_id = workspace.active_pane().entity_id();
7012            workspace.add_item_to_active_pane(Box::new(left_item.clone()), None, false, cx);
7013            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
7014            left_pane_id
7015        });
7016        let right_pane_id = workspace.update(cx, |workspace, cx| {
7017            let right_pane_id = workspace.active_pane().entity_id();
7018            workspace.add_item_to_active_pane(Box::new(right_item.clone()), None, false, cx);
7019            workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Left, cx);
7020            right_pane_id
7021        });
7022        let center_pane_id = workspace.update(cx, |workspace, cx| {
7023            let center_pane_id = workspace.active_pane().entity_id();
7024            workspace.add_item_to_active_pane(Box::new(center_item.clone()), None, false, cx);
7025            center_pane_id
7026        });
7027        cx.executor().run_until_parked();
7028
7029        workspace.update(cx, |workspace, cx| {
7030            assert_eq!(center_pane_id, workspace.active_pane().entity_id());
7031
7032            // Join into next from center pane into right
7033            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7034        });
7035
7036        workspace.update(cx, |workspace, cx| {
7037            let active_pane = workspace.active_pane();
7038            assert_eq!(right_pane_id, active_pane.entity_id());
7039            assert_eq!(2, active_pane.read(cx).items_len());
7040            let item_ids_in_pane =
7041                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7042            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7043            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7044
7045            // Join into next from right pane into bottom
7046            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7047        });
7048
7049        workspace.update(cx, |workspace, cx| {
7050            let active_pane = workspace.active_pane();
7051            assert_eq!(bottom_pane_id, active_pane.entity_id());
7052            assert_eq!(3, active_pane.read(cx).items_len());
7053            let item_ids_in_pane =
7054                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7055            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7056            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7057            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
7058
7059            // Join into next from bottom pane into left
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!(left_pane_id, active_pane.entity_id());
7066            assert_eq!(4, 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
7074            // Join into next from left pane into top
7075            workspace.join_pane_into_next(workspace.active_pane().clone(), cx);
7076        });
7077
7078        workspace.update(cx, |workspace, cx| {
7079            let active_pane = workspace.active_pane();
7080            assert_eq!(top_pane_id, active_pane.entity_id());
7081            assert_eq!(5, active_pane.read(cx).items_len());
7082            let item_ids_in_pane =
7083                HashSet::from_iter(active_pane.read(cx).items().map(|item| item.item_id()));
7084            assert!(item_ids_in_pane.contains(&center_item.item_id()));
7085            assert!(item_ids_in_pane.contains(&right_item.item_id()));
7086            assert!(item_ids_in_pane.contains(&bottom_item.item_id()));
7087            assert!(item_ids_in_pane.contains(&left_item.item_id()));
7088            assert!(item_ids_in_pane.contains(&top_item.item_id()));
7089
7090            // Single pane left: no-op
7091            workspace.join_pane_into_next(workspace.active_pane().clone(), cx)
7092        });
7093
7094        workspace.update(cx, |workspace, _cx| {
7095            let active_pane = workspace.active_pane();
7096            assert_eq!(top_pane_id, active_pane.entity_id());
7097        });
7098    }
7099
7100    fn add_an_item_to_active_pane(
7101        cx: &mut VisualTestContext,
7102        workspace: &View<Workspace>,
7103        item_id: u64,
7104    ) -> View<TestItem> {
7105        let item = cx.new_view(|cx| {
7106            TestItem::new(cx).with_project_items(&[TestProjectItem::new(
7107                item_id,
7108                "item{item_id}.txt",
7109                cx,
7110            )])
7111        });
7112        workspace.update(cx, |workspace, cx| {
7113            workspace.add_item_to_active_pane(Box::new(item.clone()), None, false, cx);
7114        });
7115        return item;
7116    }
7117
7118    fn split_pane(cx: &mut VisualTestContext, workspace: &View<Workspace>) -> View<Pane> {
7119        return workspace.update(cx, |workspace, cx| {
7120            let new_pane =
7121                workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
7122            new_pane
7123        });
7124    }
7125
7126    #[gpui::test]
7127    async fn test_join_all_panes(cx: &mut gpui::TestAppContext) {
7128        init_test(cx);
7129        let fs = FakeFs::new(cx.executor());
7130        let project = Project::test(fs, None, cx).await;
7131        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7132
7133        add_an_item_to_active_pane(cx, &workspace, 1);
7134        split_pane(cx, &workspace);
7135        add_an_item_to_active_pane(cx, &workspace, 2);
7136        split_pane(cx, &workspace); // empty pane
7137        split_pane(cx, &workspace);
7138        let last_item = add_an_item_to_active_pane(cx, &workspace, 3);
7139
7140        cx.executor().run_until_parked();
7141
7142        workspace.update(cx, |workspace, cx| {
7143            let num_panes = workspace.panes().len();
7144            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7145            let active_item = workspace
7146                .active_pane()
7147                .read(cx)
7148                .active_item()
7149                .expect("item is in focus");
7150
7151            assert_eq!(num_panes, 4);
7152            assert_eq!(num_items_in_current_pane, 1);
7153            assert_eq!(active_item.item_id(), last_item.item_id());
7154        });
7155
7156        workspace.update(cx, |workspace, cx| {
7157            workspace.join_all_panes(cx);
7158        });
7159
7160        workspace.update(cx, |workspace, cx| {
7161            let num_panes = workspace.panes().len();
7162            let num_items_in_current_pane = workspace.active_pane().read(cx).items().count();
7163            let active_item = workspace
7164                .active_pane()
7165                .read(cx)
7166                .active_item()
7167                .expect("item is in focus");
7168
7169            assert_eq!(num_panes, 1);
7170            assert_eq!(num_items_in_current_pane, 3);
7171            assert_eq!(active_item.item_id(), last_item.item_id());
7172        });
7173    }
7174    struct TestModal(FocusHandle);
7175
7176    impl TestModal {
7177        fn new(cx: &mut ViewContext<Self>) -> Self {
7178            Self(cx.focus_handle())
7179        }
7180    }
7181
7182    impl EventEmitter<DismissEvent> for TestModal {}
7183
7184    impl FocusableView for TestModal {
7185        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7186            self.0.clone()
7187        }
7188    }
7189
7190    impl ModalView for TestModal {}
7191
7192    impl Render for TestModal {
7193        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
7194            div().track_focus(&self.0)
7195        }
7196    }
7197
7198    #[gpui::test]
7199    async fn test_panels(cx: &mut gpui::TestAppContext) {
7200        init_test(cx);
7201        let fs = FakeFs::new(cx.executor());
7202
7203        let project = Project::test(fs, [], cx).await;
7204        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
7205
7206        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
7207            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
7208            workspace.add_panel(panel_1.clone(), cx);
7209            workspace
7210                .left_dock()
7211                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
7212            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
7213            workspace.add_panel(panel_2.clone(), cx);
7214            workspace
7215                .right_dock()
7216                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
7217
7218            let left_dock = workspace.left_dock();
7219            assert_eq!(
7220                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7221                panel_1.panel_id()
7222            );
7223            assert_eq!(
7224                left_dock.read(cx).active_panel_size(cx).unwrap(),
7225                panel_1.size(cx)
7226            );
7227
7228            left_dock.update(cx, |left_dock, cx| {
7229                left_dock.resize_active_panel(Some(px(1337.)), cx)
7230            });
7231            assert_eq!(
7232                workspace
7233                    .right_dock()
7234                    .read(cx)
7235                    .visible_panel()
7236                    .unwrap()
7237                    .panel_id(),
7238                panel_2.panel_id(),
7239            );
7240
7241            (panel_1, panel_2)
7242        });
7243
7244        // Move panel_1 to the right
7245        panel_1.update(cx, |panel_1, cx| {
7246            panel_1.set_position(DockPosition::Right, cx)
7247        });
7248
7249        workspace.update(cx, |workspace, cx| {
7250            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
7251            // Since it was the only panel on the left, the left dock should now be closed.
7252            assert!(!workspace.left_dock().read(cx).is_open());
7253            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
7254            let right_dock = workspace.right_dock();
7255            assert_eq!(
7256                right_dock.read(cx).visible_panel().unwrap().panel_id(),
7257                panel_1.panel_id()
7258            );
7259            assert_eq!(
7260                right_dock.read(cx).active_panel_size(cx).unwrap(),
7261                px(1337.)
7262            );
7263
7264            // Now we move panel_2 to the left
7265            panel_2.set_position(DockPosition::Left, cx);
7266        });
7267
7268        workspace.update(cx, |workspace, cx| {
7269            // Since panel_2 was not visible on the right, we don't open the left dock.
7270            assert!(!workspace.left_dock().read(cx).is_open());
7271            // And the right dock is unaffected in its displaying of panel_1
7272            assert!(workspace.right_dock().read(cx).is_open());
7273            assert_eq!(
7274                workspace
7275                    .right_dock()
7276                    .read(cx)
7277                    .visible_panel()
7278                    .unwrap()
7279                    .panel_id(),
7280                panel_1.panel_id(),
7281            );
7282        });
7283
7284        // Move panel_1 back to the left
7285        panel_1.update(cx, |panel_1, cx| {
7286            panel_1.set_position(DockPosition::Left, cx)
7287        });
7288
7289        workspace.update(cx, |workspace, cx| {
7290            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
7291            let left_dock = workspace.left_dock();
7292            assert!(left_dock.read(cx).is_open());
7293            assert_eq!(
7294                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7295                panel_1.panel_id()
7296            );
7297            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
7298            // And the right dock should be closed as it no longer has any panels.
7299            assert!(!workspace.right_dock().read(cx).is_open());
7300
7301            // Now we move panel_1 to the bottom
7302            panel_1.set_position(DockPosition::Bottom, cx);
7303        });
7304
7305        workspace.update(cx, |workspace, cx| {
7306            // Since panel_1 was visible on the left, we close the left dock.
7307            assert!(!workspace.left_dock().read(cx).is_open());
7308            // The bottom dock is sized based on the panel's default size,
7309            // since the panel orientation changed from vertical to horizontal.
7310            let bottom_dock = workspace.bottom_dock();
7311            assert_eq!(
7312                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
7313                panel_1.size(cx),
7314            );
7315            // Close bottom dock and move panel_1 back to the left.
7316            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
7317            panel_1.set_position(DockPosition::Left, cx);
7318        });
7319
7320        // Emit activated event on panel 1
7321        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
7322
7323        // Now the left dock is open and panel_1 is active and focused.
7324        workspace.update(cx, |workspace, cx| {
7325            let left_dock = workspace.left_dock();
7326            assert!(left_dock.read(cx).is_open());
7327            assert_eq!(
7328                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7329                panel_1.panel_id(),
7330            );
7331            assert!(panel_1.focus_handle(cx).is_focused(cx));
7332        });
7333
7334        // Emit closed event on panel 2, which is not active
7335        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7336
7337        // Wo don't close the left dock, because panel_2 wasn't the active panel
7338        workspace.update(cx, |workspace, cx| {
7339            let left_dock = workspace.left_dock();
7340            assert!(left_dock.read(cx).is_open());
7341            assert_eq!(
7342                left_dock.read(cx).visible_panel().unwrap().panel_id(),
7343                panel_1.panel_id(),
7344            );
7345        });
7346
7347        // Emitting a ZoomIn event shows the panel as zoomed.
7348        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
7349        workspace.update(cx, |workspace, _| {
7350            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7351            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
7352        });
7353
7354        // Move panel to another dock while it is zoomed
7355        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
7356        workspace.update(cx, |workspace, _| {
7357            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7358
7359            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7360        });
7361
7362        // This is a helper for getting a:
7363        // - valid focus on an element,
7364        // - that isn't a part of the panes and panels system of the Workspace,
7365        // - and doesn't trigger the 'on_focus_lost' API.
7366        let focus_other_view = {
7367            let workspace = workspace.clone();
7368            move |cx: &mut VisualTestContext| {
7369                workspace.update(cx, |workspace, cx| {
7370                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
7371                        workspace.toggle_modal(cx, TestModal::new);
7372                        workspace.toggle_modal(cx, TestModal::new);
7373                    } else {
7374                        workspace.toggle_modal(cx, TestModal::new);
7375                    }
7376                })
7377            }
7378        };
7379
7380        // If focus is transferred to another view that's not a panel or another pane, we still show
7381        // the panel as zoomed.
7382        focus_other_view(cx);
7383        workspace.update(cx, |workspace, _| {
7384            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7385            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7386        });
7387
7388        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
7389        workspace.update(cx, |_, cx| cx.focus_self());
7390        workspace.update(cx, |workspace, _| {
7391            assert_eq!(workspace.zoomed, None);
7392            assert_eq!(workspace.zoomed_position, None);
7393        });
7394
7395        // If focus is transferred again to another view that's not a panel or a pane, we won't
7396        // show the panel as zoomed because it wasn't zoomed before.
7397        focus_other_view(cx);
7398        workspace.update(cx, |workspace, _| {
7399            assert_eq!(workspace.zoomed, None);
7400            assert_eq!(workspace.zoomed_position, None);
7401        });
7402
7403        // When the panel is activated, it is zoomed again.
7404        cx.dispatch_action(ToggleRightDock);
7405        workspace.update(cx, |workspace, _| {
7406            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
7407            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
7408        });
7409
7410        // Emitting a ZoomOut event unzooms the panel.
7411        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
7412        workspace.update(cx, |workspace, _| {
7413            assert_eq!(workspace.zoomed, None);
7414            assert_eq!(workspace.zoomed_position, None);
7415        });
7416
7417        // Emit closed event on panel 1, which is active
7418        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
7419
7420        // Now the left dock is closed, because panel_1 was the active panel
7421        workspace.update(cx, |workspace, cx| {
7422            let right_dock = workspace.right_dock();
7423            assert!(!right_dock.read(cx).is_open());
7424        });
7425    }
7426
7427    mod register_project_item_tests {
7428        use ui::Context as _;
7429
7430        use super::*;
7431
7432        // View
7433        struct TestPngItemView {
7434            focus_handle: FocusHandle,
7435        }
7436        // Model
7437        struct TestPngItem {}
7438
7439        impl project::Item for TestPngItem {
7440            fn try_open(
7441                _project: &Model<Project>,
7442                path: &ProjectPath,
7443                cx: &mut AppContext,
7444            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7445                if path.path.extension().unwrap() == "png" {
7446                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
7447                } else {
7448                    None
7449                }
7450            }
7451
7452            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7453                None
7454            }
7455
7456            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7457                None
7458            }
7459        }
7460
7461        impl Item for TestPngItemView {
7462            type Event = ();
7463        }
7464        impl EventEmitter<()> for TestPngItemView {}
7465        impl FocusableView for TestPngItemView {
7466            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7467                self.focus_handle.clone()
7468            }
7469        }
7470
7471        impl Render for TestPngItemView {
7472            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7473                Empty
7474            }
7475        }
7476
7477        impl ProjectItem for TestPngItemView {
7478            type Item = TestPngItem;
7479
7480            fn for_project_item(
7481                _project: Model<Project>,
7482                _item: Model<Self::Item>,
7483                cx: &mut ViewContext<Self>,
7484            ) -> Self
7485            where
7486                Self: Sized,
7487            {
7488                Self {
7489                    focus_handle: cx.focus_handle(),
7490                }
7491            }
7492        }
7493
7494        // View
7495        struct TestIpynbItemView {
7496            focus_handle: FocusHandle,
7497        }
7498        // Model
7499        struct TestIpynbItem {}
7500
7501        impl project::Item for TestIpynbItem {
7502            fn try_open(
7503                _project: &Model<Project>,
7504                path: &ProjectPath,
7505                cx: &mut AppContext,
7506            ) -> Option<Task<gpui::Result<Model<Self>>>> {
7507                if path.path.extension().unwrap() == "ipynb" {
7508                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
7509                } else {
7510                    None
7511                }
7512            }
7513
7514            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
7515                None
7516            }
7517
7518            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
7519                None
7520            }
7521        }
7522
7523        impl Item for TestIpynbItemView {
7524            type Event = ();
7525        }
7526        impl EventEmitter<()> for TestIpynbItemView {}
7527        impl FocusableView for TestIpynbItemView {
7528            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7529                self.focus_handle.clone()
7530            }
7531        }
7532
7533        impl Render for TestIpynbItemView {
7534            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7535                Empty
7536            }
7537        }
7538
7539        impl ProjectItem for TestIpynbItemView {
7540            type Item = TestIpynbItem;
7541
7542            fn for_project_item(
7543                _project: Model<Project>,
7544                _item: Model<Self::Item>,
7545                cx: &mut ViewContext<Self>,
7546            ) -> Self
7547            where
7548                Self: Sized,
7549            {
7550                Self {
7551                    focus_handle: cx.focus_handle(),
7552                }
7553            }
7554        }
7555
7556        struct TestAlternatePngItemView {
7557            focus_handle: FocusHandle,
7558        }
7559
7560        impl Item for TestAlternatePngItemView {
7561            type Event = ();
7562        }
7563
7564        impl EventEmitter<()> for TestAlternatePngItemView {}
7565        impl FocusableView for TestAlternatePngItemView {
7566            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
7567                self.focus_handle.clone()
7568            }
7569        }
7570
7571        impl Render for TestAlternatePngItemView {
7572            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
7573                Empty
7574            }
7575        }
7576
7577        impl ProjectItem for TestAlternatePngItemView {
7578            type Item = TestPngItem;
7579
7580            fn for_project_item(
7581                _project: Model<Project>,
7582                _item: Model<Self::Item>,
7583                cx: &mut ViewContext<Self>,
7584            ) -> Self
7585            where
7586                Self: Sized,
7587            {
7588                Self {
7589                    focus_handle: cx.focus_handle(),
7590                }
7591            }
7592        }
7593
7594        #[gpui::test]
7595        async fn test_register_project_item(cx: &mut TestAppContext) {
7596            init_test(cx);
7597
7598            cx.update(|cx| {
7599                register_project_item::<TestPngItemView>(cx);
7600                register_project_item::<TestIpynbItemView>(cx);
7601            });
7602
7603            let fs = FakeFs::new(cx.executor());
7604            fs.insert_tree(
7605                "/root1",
7606                json!({
7607                    "one.png": "BINARYDATAHERE",
7608                    "two.ipynb": "{ totally a notebook }",
7609                    "three.txt": "editing text, sure why not?"
7610                }),
7611            )
7612            .await;
7613
7614            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7615            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7616
7617            let worktree_id = project.update(cx, |project, cx| {
7618                project.worktrees(cx).next().unwrap().read(cx).id()
7619            });
7620
7621            let handle = workspace
7622                .update(cx, |workspace, cx| {
7623                    let project_path = (worktree_id, "one.png");
7624                    workspace.open_path(project_path, None, true, cx)
7625                })
7626                .await
7627                .unwrap();
7628
7629            // Now we can check if the handle we got back errored or not
7630            assert_eq!(
7631                handle.to_any().entity_type(),
7632                TypeId::of::<TestPngItemView>()
7633            );
7634
7635            let handle = workspace
7636                .update(cx, |workspace, cx| {
7637                    let project_path = (worktree_id, "two.ipynb");
7638                    workspace.open_path(project_path, None, true, cx)
7639                })
7640                .await
7641                .unwrap();
7642
7643            assert_eq!(
7644                handle.to_any().entity_type(),
7645                TypeId::of::<TestIpynbItemView>()
7646            );
7647
7648            let handle = workspace
7649                .update(cx, |workspace, cx| {
7650                    let project_path = (worktree_id, "three.txt");
7651                    workspace.open_path(project_path, None, true, cx)
7652                })
7653                .await;
7654            assert!(handle.is_err());
7655        }
7656
7657        #[gpui::test]
7658        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
7659            init_test(cx);
7660
7661            cx.update(|cx| {
7662                register_project_item::<TestPngItemView>(cx);
7663                register_project_item::<TestAlternatePngItemView>(cx);
7664            });
7665
7666            let fs = FakeFs::new(cx.executor());
7667            fs.insert_tree(
7668                "/root1",
7669                json!({
7670                    "one.png": "BINARYDATAHERE",
7671                    "two.ipynb": "{ totally a notebook }",
7672                    "three.txt": "editing text, sure why not?"
7673                }),
7674            )
7675            .await;
7676
7677            let project = Project::test(fs, ["root1".as_ref()], cx).await;
7678            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
7679
7680            let worktree_id = project.update(cx, |project, cx| {
7681                project.worktrees(cx).next().unwrap().read(cx).id()
7682            });
7683
7684            let handle = workspace
7685                .update(cx, |workspace, cx| {
7686                    let project_path = (worktree_id, "one.png");
7687                    workspace.open_path(project_path, None, true, cx)
7688                })
7689                .await
7690                .unwrap();
7691
7692            // This _must_ be the second item registered
7693            assert_eq!(
7694                handle.to_any().entity_type(),
7695                TypeId::of::<TestAlternatePngItemView>()
7696            );
7697
7698            let handle = workspace
7699                .update(cx, |workspace, cx| {
7700                    let project_path = (worktree_id, "three.txt");
7701                    workspace.open_path(project_path, None, true, cx)
7702                })
7703                .await;
7704            assert!(handle.is_err());
7705        }
7706    }
7707
7708    pub fn init_test(cx: &mut TestAppContext) {
7709        cx.update(|cx| {
7710            let settings_store = SettingsStore::test(cx);
7711            cx.set_global(settings_store);
7712            theme::init(theme::LoadThemes::JustBase, cx);
7713            language::init(cx);
7714            crate::init_settings(cx);
7715            Project::init_settings(cx);
7716        });
7717    }
7718}