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