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