workspace.rs

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