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