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