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, 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::Session;
  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: Session,
 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 = Session::test();
 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.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            };
4036            return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
4037        }
4038        Task::ready(())
4039    }
4040
4041    async fn serialize_items(
4042        this: &WeakView<Self>,
4043        items_rx: UnboundedReceiver<Box<dyn SerializableItemHandle>>,
4044        cx: &mut AsyncWindowContext,
4045    ) -> Result<()> {
4046        const CHUNK_SIZE: usize = 200;
4047        const THROTTLE_TIME: Duration = Duration::from_millis(200);
4048
4049        let mut serializable_items = items_rx.ready_chunks(CHUNK_SIZE);
4050
4051        while let Some(items_received) = serializable_items.next().await {
4052            let unique_items =
4053                items_received
4054                    .into_iter()
4055                    .fold(HashMap::default(), |mut acc, item| {
4056                        acc.entry(item.item_id()).or_insert(item);
4057                        acc
4058                    });
4059
4060            // We use into_iter() here so that the references to the items are moved into
4061            // the tasks and not kept alive while we're sleeping.
4062            for (_, item) in unique_items.into_iter() {
4063                if let Ok(Some(task)) =
4064                    this.update(cx, |workspace, cx| item.serialize(workspace, false, cx))
4065                {
4066                    cx.background_executor()
4067                        .spawn(async move { task.await.log_err() })
4068                        .detach();
4069                }
4070            }
4071
4072            cx.background_executor().timer(THROTTLE_TIME).await;
4073        }
4074
4075        Ok(())
4076    }
4077
4078    pub(crate) fn enqueue_item_serialization(
4079        &mut self,
4080        item: Box<dyn SerializableItemHandle>,
4081    ) -> Result<()> {
4082        self.serializable_items_tx
4083            .unbounded_send(item)
4084            .map_err(|err| anyhow!("failed to send serializable item over channel: {}", err))
4085    }
4086
4087    pub(crate) fn load_workspace(
4088        serialized_workspace: SerializedWorkspace,
4089        paths_to_open: Vec<Option<ProjectPath>>,
4090        cx: &mut ViewContext<Workspace>,
4091    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
4092        cx.spawn(|workspace, mut cx| async move {
4093            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
4094
4095            let mut center_group = None;
4096            let mut center_items = None;
4097
4098            // Traverse the splits tree and add to things
4099            if let Some((group, active_pane, items)) = serialized_workspace
4100                .center_group
4101                .deserialize(
4102                    &project,
4103                    serialized_workspace.id,
4104                    workspace.clone(),
4105                    &mut cx,
4106                )
4107                .await
4108            {
4109                center_items = Some(items);
4110                center_group = Some((group, active_pane))
4111            }
4112
4113            let mut items_by_project_path = HashMap::default();
4114            let mut item_ids_by_kind = HashMap::default();
4115            let mut all_deserialized_items = Vec::default();
4116            cx.update(|cx| {
4117                for item in center_items.unwrap_or_default().into_iter().flatten() {
4118                    if let Some(serializable_item_handle) = item.to_serializable_item_handle(cx) {
4119                        item_ids_by_kind
4120                            .entry(serializable_item_handle.serialized_item_kind())
4121                            .or_insert(Vec::new())
4122                            .push(item.item_id().as_u64() as ItemId);
4123                    }
4124
4125                    if let Some(project_path) = item.project_path(cx) {
4126                        items_by_project_path.insert(project_path, item.clone());
4127                    }
4128                    all_deserialized_items.push(item);
4129                }
4130            })?;
4131
4132            let opened_items = paths_to_open
4133                .into_iter()
4134                .map(|path_to_open| {
4135                    path_to_open
4136                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
4137                })
4138                .collect::<Vec<_>>();
4139
4140            // Remove old panes from workspace panes list
4141            workspace.update(&mut cx, |workspace, cx| {
4142                if let Some((center_group, active_pane)) = center_group {
4143                    workspace.remove_panes(workspace.center.root.clone(), cx);
4144
4145                    // Swap workspace center group
4146                    workspace.center = PaneGroup::with_root(center_group);
4147                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
4148                    if let Some(active_pane) = active_pane {
4149                        workspace.active_pane = active_pane;
4150                        cx.focus_self();
4151                    } else {
4152                        workspace.active_pane = workspace.center.first_pane().clone();
4153                    }
4154                }
4155
4156                let docks = serialized_workspace.docks;
4157
4158                for (dock, serialized_dock) in [
4159                    (&mut workspace.right_dock, docks.right),
4160                    (&mut workspace.left_dock, docks.left),
4161                    (&mut workspace.bottom_dock, docks.bottom),
4162                ]
4163                .iter_mut()
4164                {
4165                    dock.update(cx, |dock, cx| {
4166                        dock.serialized_dock = Some(serialized_dock.clone());
4167                        dock.restore_state(cx);
4168                    });
4169                }
4170
4171                cx.notify();
4172            })?;
4173
4174            // Clean up all the items that have _not_ been loaded. Our ItemIds aren't stable. That means
4175            // after loading the items, we might have different items and in order to avoid
4176            // the database filling up, we delete items that haven't been loaded now.
4177            //
4178            // The items that have been loaded, have been saved after they've been added to the workspace.
4179            let clean_up_tasks = workspace.update(&mut cx, |_, cx| {
4180                item_ids_by_kind
4181                    .into_iter()
4182                    .map(|(item_kind, loaded_items)| {
4183                        SerializableItemRegistry::cleanup(
4184                            item_kind,
4185                            serialized_workspace.id,
4186                            loaded_items,
4187                            cx,
4188                        )
4189                        .log_err()
4190                    })
4191                    .collect::<Vec<_>>()
4192            })?;
4193
4194            futures::future::join_all(clean_up_tasks).await;
4195
4196            workspace
4197                .update(&mut cx, |workspace, cx| {
4198                    // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
4199                    workspace.serialize_workspace_internal(cx).detach();
4200
4201                    // Ensure that we mark the window as edited if we did load dirty items
4202                    workspace.update_window_edited(cx);
4203                })
4204                .ok();
4205
4206            Ok(opened_items)
4207        })
4208    }
4209
4210    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
4211        self.add_workspace_actions_listeners(div, cx)
4212            .on_action(cx.listener(Self::close_inactive_items_and_panes))
4213            .on_action(cx.listener(Self::close_all_items_and_panes))
4214            .on_action(cx.listener(Self::save_all))
4215            .on_action(cx.listener(Self::send_keystrokes))
4216            .on_action(cx.listener(Self::add_folder_to_project))
4217            .on_action(cx.listener(Self::follow_next_collaborator))
4218            .on_action(cx.listener(Self::open))
4219            .on_action(cx.listener(Self::close_window))
4220            .on_action(cx.listener(Self::activate_pane_at_index))
4221            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
4222                let pane = workspace.active_pane().clone();
4223                workspace.unfollow_in_pane(&pane, cx);
4224            }))
4225            .on_action(cx.listener(|workspace, action: &Save, cx| {
4226                workspace
4227                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
4228                    .detach_and_log_err(cx);
4229            }))
4230            .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| {
4231                workspace
4232                    .save_active_item(SaveIntent::SaveWithoutFormat, cx)
4233                    .detach_and_log_err(cx);
4234            }))
4235            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
4236                workspace
4237                    .save_active_item(SaveIntent::SaveAs, cx)
4238                    .detach_and_log_err(cx);
4239            }))
4240            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
4241                workspace.activate_previous_pane(cx)
4242            }))
4243            .on_action(
4244                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
4245            )
4246            .on_action(
4247                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
4248                    workspace.activate_pane_in_direction(action.0, cx)
4249                }),
4250            )
4251            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
4252                workspace.swap_pane_in_direction(action.0, cx)
4253            }))
4254            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
4255                this.toggle_dock(DockPosition::Left, cx);
4256            }))
4257            .on_action(
4258                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
4259                    workspace.toggle_dock(DockPosition::Right, cx);
4260                }),
4261            )
4262            .on_action(
4263                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
4264                    workspace.toggle_dock(DockPosition::Bottom, cx);
4265                }),
4266            )
4267            .on_action(
4268                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
4269                    workspace.close_all_docks(cx);
4270                }),
4271            )
4272            .on_action(
4273                cx.listener(|workspace: &mut Workspace, _: &ClearAllNotifications, cx| {
4274                    workspace.clear_all_notifications(cx);
4275                }),
4276            )
4277            .on_action(
4278                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
4279                    workspace.reopen_closed_item(cx).detach();
4280                }),
4281            )
4282            .on_action(cx.listener(Workspace::toggle_centered_layout))
4283    }
4284
4285    #[cfg(any(test, feature = "test-support"))]
4286    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
4287        use node_runtime::FakeNodeRuntime;
4288        use session::Session;
4289
4290        let client = project.read(cx).client();
4291        let user_store = project.read(cx).user_store();
4292
4293        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
4294        cx.activate_window();
4295        let app_state = Arc::new(AppState {
4296            languages: project.read(cx).languages().clone(),
4297            workspace_store,
4298            client,
4299            user_store,
4300            fs: project.read(cx).fs().clone(),
4301            build_window_options: |_, _| Default::default(),
4302            node_runtime: FakeNodeRuntime::new(),
4303            session: Session::test(),
4304        });
4305        let workspace = Self::new(Default::default(), project, app_state, cx);
4306        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
4307        workspace
4308    }
4309
4310    pub fn register_action<A: Action>(
4311        &mut self,
4312        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
4313    ) -> &mut Self {
4314        let callback = Arc::new(callback);
4315
4316        self.workspace_actions.push(Box::new(move |div, cx| {
4317            let callback = callback.clone();
4318            div.on_action(
4319                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
4320            )
4321        }));
4322        self
4323    }
4324
4325    fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
4326        for action in self.workspace_actions.iter() {
4327            div = (action)(div, cx)
4328        }
4329        div
4330    }
4331
4332    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
4333        self.modal_layer.read(cx).has_active_modal()
4334    }
4335
4336    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
4337        self.modal_layer.read(cx).active_modal()
4338    }
4339
4340    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
4341    where
4342        B: FnOnce(&mut ViewContext<V>) -> V,
4343    {
4344        self.modal_layer
4345            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
4346    }
4347
4348    pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
4349        self.centered_layout = !self.centered_layout;
4350        if let Some(database_id) = self.database_id() {
4351            cx.background_executor()
4352                .spawn(DB.set_centered_layout(database_id, self.centered_layout))
4353                .detach_and_log_err(cx);
4354        }
4355        cx.notify();
4356    }
4357
4358    fn adjust_padding(padding: Option<f32>) -> f32 {
4359        padding
4360            .unwrap_or(Self::DEFAULT_PADDING)
4361            .clamp(0.0, Self::MAX_PADDING)
4362    }
4363
4364    fn render_dock(
4365        &self,
4366        position: DockPosition,
4367        dock: &View<Dock>,
4368        cx: &WindowContext,
4369    ) -> Option<Div> {
4370        if self.zoomed_position == Some(position) {
4371            return None;
4372        }
4373
4374        let leader_border = dock.read(cx).active_panel().and_then(|panel| {
4375            let pane = panel.pane(cx)?;
4376            let follower_states = &self.follower_states;
4377            leader_border_for_pane(follower_states, &pane, cx)
4378        });
4379
4380        Some(
4381            div()
4382                .flex()
4383                .flex_none()
4384                .overflow_hidden()
4385                .child(dock.clone())
4386                .children(leader_border),
4387        )
4388    }
4389}
4390
4391fn leader_border_for_pane(
4392    follower_states: &HashMap<PeerId, FollowerState>,
4393    pane: &View<Pane>,
4394    cx: &WindowContext,
4395) -> Option<Div> {
4396    let (leader_id, _follower_state) = follower_states.iter().find_map(|(leader_id, state)| {
4397        if state.pane() == pane {
4398            Some((*leader_id, state))
4399        } else {
4400            None
4401        }
4402    })?;
4403
4404    let room = ActiveCall::try_global(cx)?.read(cx).room()?.read(cx);
4405    let leader = room.remote_participant_for_peer_id(leader_id)?;
4406
4407    let mut leader_color = cx
4408        .theme()
4409        .players()
4410        .color_for_participant(leader.participant_index.0)
4411        .cursor;
4412    leader_color.fade_out(0.3);
4413    Some(
4414        div()
4415            .absolute()
4416            .size_full()
4417            .left_0()
4418            .top_0()
4419            .border_2()
4420            .border_color(leader_color),
4421    )
4422}
4423
4424fn window_bounds_env_override() -> Option<Bounds<Pixels>> {
4425    ZED_WINDOW_POSITION
4426        .zip(*ZED_WINDOW_SIZE)
4427        .map(|(position, size)| Bounds {
4428            origin: position,
4429            size,
4430        })
4431}
4432
4433fn open_items(
4434    serialized_workspace: Option<SerializedWorkspace>,
4435    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
4436    app_state: Arc<AppState>,
4437    cx: &mut ViewContext<Workspace>,
4438) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
4439    let restored_items = serialized_workspace.map(|serialized_workspace| {
4440        Workspace::load_workspace(
4441            serialized_workspace,
4442            project_paths_to_open
4443                .iter()
4444                .map(|(_, project_path)| project_path)
4445                .cloned()
4446                .collect(),
4447            cx,
4448        )
4449    });
4450
4451    cx.spawn(|workspace, mut cx| async move {
4452        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
4453
4454        if let Some(restored_items) = restored_items {
4455            let restored_items = restored_items.await?;
4456
4457            let restored_project_paths = restored_items
4458                .iter()
4459                .filter_map(|item| {
4460                    cx.update(|cx| item.as_ref()?.project_path(cx))
4461                        .ok()
4462                        .flatten()
4463                })
4464                .collect::<HashSet<_>>();
4465
4466            for restored_item in restored_items {
4467                opened_items.push(restored_item.map(Ok));
4468            }
4469
4470            project_paths_to_open
4471                .iter_mut()
4472                .for_each(|(_, project_path)| {
4473                    if let Some(project_path_to_open) = project_path {
4474                        if restored_project_paths.contains(project_path_to_open) {
4475                            *project_path = None;
4476                        }
4477                    }
4478                });
4479        } else {
4480            for _ in 0..project_paths_to_open.len() {
4481                opened_items.push(None);
4482            }
4483        }
4484        assert!(opened_items.len() == project_paths_to_open.len());
4485
4486        let tasks =
4487            project_paths_to_open
4488                .into_iter()
4489                .enumerate()
4490                .map(|(ix, (abs_path, project_path))| {
4491                    let workspace = workspace.clone();
4492                    cx.spawn(|mut cx| {
4493                        let fs = app_state.fs.clone();
4494                        async move {
4495                            let file_project_path = project_path?;
4496                            if fs.is_dir(&abs_path).await {
4497                                None
4498                            } else {
4499                                Some((
4500                                    ix,
4501                                    workspace
4502                                        .update(&mut cx, |workspace, cx| {
4503                                            workspace.open_path(file_project_path, None, true, cx)
4504                                        })
4505                                        .log_err()?
4506                                        .await,
4507                                ))
4508                            }
4509                        }
4510                    })
4511                });
4512
4513        let tasks = tasks.collect::<Vec<_>>();
4514
4515        let tasks = futures::future::join_all(tasks);
4516        for (ix, path_open_result) in tasks.await.into_iter().flatten() {
4517            opened_items[ix] = Some(path_open_result);
4518        }
4519
4520        Ok(opened_items)
4521    })
4522}
4523
4524enum ActivateInDirectionTarget {
4525    Pane(View<Pane>),
4526    Dock(View<Dock>),
4527}
4528
4529fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
4530    const REPORT_ISSUE_URL: &str = "https://github.com/zed-industries/zed/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
4531
4532    workspace
4533        .update(cx, |workspace, cx| {
4534            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
4535                struct DatabaseFailedNotification;
4536
4537                workspace.show_notification_once(
4538                    NotificationId::unique::<DatabaseFailedNotification>(),
4539                    cx,
4540                    |cx| {
4541                        cx.new_view(|_| {
4542                            MessageNotification::new("Failed to load the database file.")
4543                                .with_click_message("Click to let us know about this error")
4544                                .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
4545                        })
4546                    },
4547                );
4548            }
4549        })
4550        .log_err();
4551}
4552
4553impl FocusableView for Workspace {
4554    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4555        self.active_pane.focus_handle(cx)
4556    }
4557}
4558
4559#[derive(Clone, Render)]
4560struct DraggedDock(DockPosition);
4561
4562impl Render for Workspace {
4563    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4564        let mut context = KeyContext::new_with_defaults();
4565        context.add("Workspace");
4566        let centered_layout = self.centered_layout
4567            && self.center.panes().len() == 1
4568            && self.active_item(cx).is_some();
4569        let render_padding = |size| {
4570            (size > 0.0).then(|| {
4571                div()
4572                    .h_full()
4573                    .w(relative(size))
4574                    .bg(cx.theme().colors().editor_background)
4575                    .border_color(cx.theme().colors().pane_group_border)
4576            })
4577        };
4578        let paddings = if centered_layout {
4579            let settings = WorkspaceSettings::get_global(cx).centered_layout;
4580            (
4581                render_padding(Self::adjust_padding(settings.left_padding)),
4582                render_padding(Self::adjust_padding(settings.right_padding)),
4583            )
4584        } else {
4585            (None, None)
4586        };
4587        let ui_font = theme::setup_ui_font(cx);
4588
4589        let theme = cx.theme().clone();
4590        let colors = theme.colors();
4591
4592        client_side_decorations(
4593            self.actions(div(), cx)
4594                .key_context(context)
4595                .relative()
4596                .size_full()
4597                .flex()
4598                .flex_col()
4599                .font(ui_font)
4600                .gap_0()
4601                .justify_start()
4602                .items_start()
4603                .text_color(colors.text)
4604                .overflow_hidden()
4605                .children(self.titlebar_item.clone())
4606                .child(
4607                    div()
4608                        .id("workspace")
4609                        .bg(colors.background)
4610                        .relative()
4611                        .flex_1()
4612                        .w_full()
4613                        .flex()
4614                        .flex_col()
4615                        .overflow_hidden()
4616                        .border_t_1()
4617                        .border_b_1()
4618                        .border_color(colors.border)
4619                        .child({
4620                            let this = cx.view().clone();
4621                            canvas(
4622                                move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
4623                                |_, _, _| {},
4624                            )
4625                            .absolute()
4626                            .size_full()
4627                        })
4628                        .when(self.zoomed.is_none(), |this| {
4629                            this.on_drag_move(cx.listener(
4630                                |workspace, e: &DragMoveEvent<DraggedDock>, cx| match e.drag(cx).0 {
4631                                    DockPosition::Left => {
4632                                        let size = e.event.position.x - workspace.bounds.left();
4633                                        workspace.left_dock.update(cx, |left_dock, cx| {
4634                                            left_dock.resize_active_panel(Some(size), cx);
4635                                        });
4636                                    }
4637                                    DockPosition::Right => {
4638                                        let size = workspace.bounds.right() - e.event.position.x;
4639                                        workspace.right_dock.update(cx, |right_dock, cx| {
4640                                            right_dock.resize_active_panel(Some(size), cx);
4641                                        });
4642                                    }
4643                                    DockPosition::Bottom => {
4644                                        let size = workspace.bounds.bottom() - e.event.position.y;
4645                                        workspace.bottom_dock.update(cx, |bottom_dock, cx| {
4646                                            bottom_dock.resize_active_panel(Some(size), cx);
4647                                        });
4648                                    }
4649                                },
4650                            ))
4651                        })
4652                        .child(
4653                            div()
4654                                .flex()
4655                                .flex_row()
4656                                .h_full()
4657                                // Left Dock
4658                                .children(self.render_dock(DockPosition::Left, &self.left_dock, cx))
4659                                // Panes
4660                                .child(
4661                                    div()
4662                                        .flex()
4663                                        .flex_col()
4664                                        .flex_1()
4665                                        .overflow_hidden()
4666                                        .child(
4667                                            h_flex()
4668                                                .flex_1()
4669                                                .when_some(paddings.0, |this, p| {
4670                                                    this.child(p.border_r_1())
4671                                                })
4672                                                .child(self.center.render(
4673                                                    &self.project,
4674                                                    &self.follower_states,
4675                                                    self.active_call(),
4676                                                    &self.active_pane,
4677                                                    self.zoomed.as_ref(),
4678                                                    &self.app_state,
4679                                                    cx,
4680                                                ))
4681                                                .when_some(paddings.1, |this, p| {
4682                                                    this.child(p.border_l_1())
4683                                                }),
4684                                        )
4685                                        .children(self.render_dock(
4686                                            DockPosition::Bottom,
4687                                            &self.bottom_dock,
4688                                            cx,
4689                                        )),
4690                                )
4691                                // Right Dock
4692                                .children(self.render_dock(
4693                                    DockPosition::Right,
4694                                    &self.right_dock,
4695                                    cx,
4696                                )),
4697                        )
4698                        .children(self.zoomed.as_ref().and_then(|view| {
4699                            let zoomed_view = view.upgrade()?;
4700                            let div = div()
4701                                .occlude()
4702                                .absolute()
4703                                .overflow_hidden()
4704                                .border_color(colors.border)
4705                                .bg(colors.background)
4706                                .child(zoomed_view)
4707                                .inset_0()
4708                                .shadow_lg();
4709
4710                            Some(match self.zoomed_position {
4711                                Some(DockPosition::Left) => div.right_2().border_r_1(),
4712                                Some(DockPosition::Right) => div.left_2().border_l_1(),
4713                                Some(DockPosition::Bottom) => div.top_2().border_t_1(),
4714                                None => div.top_2().bottom_2().left_2().right_2().border_1(),
4715                            })
4716                        }))
4717                        .child(self.modal_layer.clone())
4718                        .children(self.render_notifications(cx)),
4719                )
4720                .child(self.status_bar.clone())
4721                .children(if self.project.read(cx).is_disconnected() {
4722                    if let Some(render) = self.render_disconnected_overlay.take() {
4723                        let result = render(self, cx);
4724                        self.render_disconnected_overlay = Some(render);
4725                        Some(result)
4726                    } else {
4727                        None
4728                    }
4729                } else {
4730                    None
4731                }),
4732            cx,
4733        )
4734    }
4735}
4736
4737impl WorkspaceStore {
4738    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
4739        Self {
4740            workspaces: Default::default(),
4741            _subscriptions: vec![
4742                client.add_request_handler(cx.weak_model(), Self::handle_follow),
4743                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
4744            ],
4745            client,
4746        }
4747    }
4748
4749    pub fn update_followers(
4750        &self,
4751        project_id: Option<u64>,
4752        update: proto::update_followers::Variant,
4753        cx: &AppContext,
4754    ) -> Option<()> {
4755        let active_call = ActiveCall::try_global(cx)?;
4756        let room_id = active_call.read(cx).room()?.read(cx).id();
4757        self.client
4758            .send(proto::UpdateFollowers {
4759                room_id,
4760                project_id,
4761                variant: Some(update),
4762            })
4763            .log_err()
4764    }
4765
4766    pub async fn handle_follow(
4767        this: Model<Self>,
4768        envelope: TypedEnvelope<proto::Follow>,
4769        mut cx: AsyncAppContext,
4770    ) -> Result<proto::FollowResponse> {
4771        this.update(&mut cx, |this, cx| {
4772            let follower = Follower {
4773                project_id: envelope.payload.project_id,
4774                peer_id: envelope.original_sender_id()?,
4775            };
4776
4777            let mut response = proto::FollowResponse::default();
4778            this.workspaces.retain(|workspace| {
4779                workspace
4780                    .update(cx, |workspace, cx| {
4781                        let handler_response = workspace.handle_follow(follower.project_id, cx);
4782                        if let Some(active_view) = handler_response.active_view.clone() {
4783                            if workspace.project.read(cx).remote_id() == follower.project_id {
4784                                response.active_view = Some(active_view)
4785                            }
4786                        }
4787                    })
4788                    .is_ok()
4789            });
4790
4791            Ok(response)
4792        })?
4793    }
4794
4795    async fn handle_update_followers(
4796        this: Model<Self>,
4797        envelope: TypedEnvelope<proto::UpdateFollowers>,
4798        mut cx: AsyncAppContext,
4799    ) -> Result<()> {
4800        let leader_id = envelope.original_sender_id()?;
4801        let update = envelope.payload;
4802
4803        this.update(&mut cx, |this, cx| {
4804            this.workspaces.retain(|workspace| {
4805                workspace
4806                    .update(cx, |workspace, cx| {
4807                        let project_id = workspace.project.read(cx).remote_id();
4808                        if update.project_id != project_id && update.project_id.is_some() {
4809                            return;
4810                        }
4811                        workspace.handle_update_followers(leader_id, update.clone(), cx);
4812                    })
4813                    .is_ok()
4814            });
4815            Ok(())
4816        })?
4817    }
4818}
4819
4820impl ViewId {
4821    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4822        Ok(Self {
4823            creator: message
4824                .creator
4825                .ok_or_else(|| anyhow!("creator is missing"))?,
4826            id: message.id,
4827        })
4828    }
4829
4830    pub(crate) fn to_proto(&self) -> proto::ViewId {
4831        proto::ViewId {
4832            creator: Some(self.creator),
4833            id: self.id,
4834        }
4835    }
4836}
4837
4838impl FollowerState {
4839    fn pane(&self) -> &View<Pane> {
4840        self.dock_pane.as_ref().unwrap_or(&self.center_pane)
4841    }
4842}
4843
4844pub trait WorkspaceHandle {
4845    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4846}
4847
4848impl WorkspaceHandle for View<Workspace> {
4849    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4850        self.read(cx)
4851            .worktrees(cx)
4852            .flat_map(|worktree| {
4853                let worktree_id = worktree.read(cx).id();
4854                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4855                    worktree_id,
4856                    path: f.path.clone(),
4857                })
4858            })
4859            .collect::<Vec<_>>()
4860    }
4861}
4862
4863impl std::fmt::Debug for OpenPaths {
4864    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4865        f.debug_struct("OpenPaths")
4866            .field("paths", &self.paths)
4867            .finish()
4868    }
4869}
4870
4871pub fn activate_workspace_for_project(
4872    cx: &mut AppContext,
4873    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4874) -> Option<WindowHandle<Workspace>> {
4875    for window in cx.windows() {
4876        let Some(workspace) = window.downcast::<Workspace>() else {
4877            continue;
4878        };
4879
4880        let predicate = workspace
4881            .update(cx, |workspace, cx| {
4882                let project = workspace.project.read(cx);
4883                if predicate(project, cx) {
4884                    cx.activate_window();
4885                    true
4886                } else {
4887                    false
4888                }
4889            })
4890            .log_err()
4891            .unwrap_or(false);
4892
4893        if predicate {
4894            return Some(workspace);
4895        }
4896    }
4897
4898    None
4899}
4900
4901pub async fn last_opened_workspace_paths() -> Option<LocalPaths> {
4902    DB.last_workspace().await.log_err().flatten()
4903}
4904
4905pub fn last_session_workspace_locations(last_session_id: &str) -> Option<Vec<LocalPaths>> {
4906    DB.last_session_workspace_locations(last_session_id)
4907        .log_err()
4908}
4909
4910actions!(collab, [OpenChannelNotes]);
4911actions!(zed, [OpenLog]);
4912
4913async fn join_channel_internal(
4914    channel_id: ChannelId,
4915    app_state: &Arc<AppState>,
4916    requesting_window: Option<WindowHandle<Workspace>>,
4917    active_call: &Model<ActiveCall>,
4918    cx: &mut AsyncAppContext,
4919) -> Result<bool> {
4920    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
4921        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4922            return (false, None);
4923        };
4924
4925        let already_in_channel = room.channel_id() == Some(channel_id);
4926        let should_prompt = room.is_sharing_project()
4927            && room.remote_participants().len() > 0
4928            && !already_in_channel;
4929        let open_room = if already_in_channel {
4930            active_call.room().cloned()
4931        } else {
4932            None
4933        };
4934        (should_prompt, open_room)
4935    })?;
4936
4937    if let Some(room) = open_room {
4938        let task = room.update(cx, |room, cx| {
4939            if let Some((project, host)) = room.most_active_project(cx) {
4940                return Some(join_in_room_project(project, host, app_state.clone(), cx));
4941            }
4942
4943            None
4944        })?;
4945        if let Some(task) = task {
4946            task.await?;
4947        }
4948        return anyhow::Ok(true);
4949    }
4950
4951    if should_prompt {
4952        if let Some(workspace) = requesting_window {
4953            let answer = workspace
4954                .update(cx, |_, cx| {
4955                    cx.prompt(
4956                        PromptLevel::Warning,
4957                        "Do you want to switch channels?",
4958                        Some("Leaving this call will unshare your current project."),
4959                        &["Yes, Join Channel", "Cancel"],
4960                    )
4961                })?
4962                .await;
4963
4964            if answer == Ok(1) {
4965                return Ok(false);
4966            }
4967        } else {
4968            return Ok(false); // unreachable!() hopefully
4969        }
4970    }
4971
4972    let client = cx.update(|cx| active_call.read(cx).client())?;
4973
4974    let mut client_status = client.status();
4975
4976    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4977    'outer: loop {
4978        let Some(status) = client_status.recv().await else {
4979            return Err(anyhow!("error connecting"));
4980        };
4981
4982        match status {
4983            Status::Connecting
4984            | Status::Authenticating
4985            | Status::Reconnecting
4986            | Status::Reauthenticating => continue,
4987            Status::Connected { .. } => break 'outer,
4988            Status::SignedOut => return Err(ErrorCode::SignedOut.into()),
4989            Status::UpgradeRequired => return Err(ErrorCode::UpgradeRequired.into()),
4990            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4991                return Err(ErrorCode::Disconnected.into());
4992            }
4993        }
4994    }
4995
4996    let room = active_call
4997        .update(cx, |active_call, cx| {
4998            active_call.join_channel(channel_id, cx)
4999        })?
5000        .await?;
5001
5002    let Some(room) = room else {
5003        return anyhow::Ok(true);
5004    };
5005
5006    room.update(cx, |room, _| room.room_update_completed())?
5007        .await;
5008
5009    let task = room.update(cx, |room, cx| {
5010        if let Some((project, host)) = room.most_active_project(cx) {
5011            return Some(join_in_room_project(project, host, app_state.clone(), cx));
5012        }
5013
5014        // If you are the first to join a channel, see if you should share your project.
5015        if room.remote_participants().is_empty() && !room.local_participant_is_guest() {
5016            if let Some(workspace) = requesting_window {
5017                let project = workspace.update(cx, |workspace, cx| {
5018                    let project = workspace.project.read(cx);
5019                    let is_dev_server = project.dev_server_project_id().is_some();
5020
5021                    if !is_dev_server && !CallSettings::get_global(cx).share_on_join {
5022                        return None;
5023                    }
5024
5025                    if (project.is_local() || is_dev_server)
5026                        && project.visible_worktrees(cx).any(|tree| {
5027                            tree.read(cx)
5028                                .root_entry()
5029                                .map_or(false, |entry| entry.is_dir())
5030                        })
5031                    {
5032                        Some(workspace.project.clone())
5033                    } else {
5034                        None
5035                    }
5036                });
5037                if let Ok(Some(project)) = project {
5038                    return Some(cx.spawn(|room, mut cx| async move {
5039                        room.update(&mut cx, |room, cx| room.share_project(project, cx))?
5040                            .await?;
5041                        Ok(())
5042                    }));
5043                }
5044            }
5045        }
5046
5047        None
5048    })?;
5049    if let Some(task) = task {
5050        task.await?;
5051        return anyhow::Ok(true);
5052    }
5053    anyhow::Ok(false)
5054}
5055
5056pub fn join_channel(
5057    channel_id: ChannelId,
5058    app_state: Arc<AppState>,
5059    requesting_window: Option<WindowHandle<Workspace>>,
5060    cx: &mut AppContext,
5061) -> Task<Result<()>> {
5062    let active_call = ActiveCall::global(cx);
5063    cx.spawn(|mut cx| async move {
5064        let result = join_channel_internal(
5065            channel_id,
5066            &app_state,
5067            requesting_window,
5068            &active_call,
5069            &mut cx,
5070        )
5071            .await;
5072
5073        // join channel succeeded, and opened a window
5074        if matches!(result, Ok(true)) {
5075            return anyhow::Ok(());
5076        }
5077
5078        // find an existing workspace to focus and show call controls
5079        let mut active_window =
5080            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
5081        if active_window.is_none() {
5082            // no open workspaces, make one to show the error in (blergh)
5083            let (window_handle, _) = cx
5084                .update(|cx| {
5085                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
5086                })?
5087                .await?;
5088
5089            if result.is_ok() {
5090                cx.update(|cx| {
5091                    cx.dispatch_action(&OpenChannelNotes);
5092                }).log_err();
5093            }
5094
5095            active_window = Some(window_handle);
5096        }
5097
5098        if let Err(err) = result {
5099            log::error!("failed to join channel: {}", err);
5100            if let Some(active_window) = active_window {
5101                active_window
5102                    .update(&mut cx, |_, cx| {
5103                        let detail: SharedString = match err.error_code() {
5104                            ErrorCode::SignedOut => {
5105                                "Please sign in to continue.".into()
5106                            }
5107                            ErrorCode::UpgradeRequired => {
5108                                "Your are running an unsupported version of Zed. Please update to continue.".into()
5109                            }
5110                            ErrorCode::NoSuchChannel => {
5111                                "No matching channel was found. Please check the link and try again.".into()
5112                            }
5113                            ErrorCode::Forbidden => {
5114                                "This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
5115                            }
5116                            ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
5117                            _ => format!("{}\n\nPlease try again.", err).into(),
5118                        };
5119                        cx.prompt(
5120                            PromptLevel::Critical,
5121                            "Failed to join channel",
5122                            Some(&detail),
5123                            &["Ok"],
5124                        )
5125                    })?
5126                    .await
5127                    .ok();
5128            }
5129        }
5130
5131        // return ok, we showed the error to the user.
5132        return anyhow::Ok(());
5133    })
5134}
5135
5136pub async fn get_any_active_workspace(
5137    app_state: Arc<AppState>,
5138    mut cx: AsyncAppContext,
5139) -> anyhow::Result<WindowHandle<Workspace>> {
5140    // find an existing workspace to focus and show call controls
5141    let active_window = activate_any_workspace_window(&mut cx);
5142    if active_window.is_none() {
5143        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
5144            .await?;
5145    }
5146    activate_any_workspace_window(&mut cx).context("could not open zed")
5147}
5148
5149fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
5150    cx.update(|cx| {
5151        if let Some(workspace_window) = cx
5152            .active_window()
5153            .and_then(|window| window.downcast::<Workspace>())
5154        {
5155            return Some(workspace_window);
5156        }
5157
5158        for window in cx.windows() {
5159            if let Some(workspace_window) = window.downcast::<Workspace>() {
5160                workspace_window
5161                    .update(cx, |_, cx| cx.activate_window())
5162                    .ok();
5163                return Some(workspace_window);
5164            }
5165        }
5166        None
5167    })
5168    .ok()
5169    .flatten()
5170}
5171
5172fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>> {
5173    cx.windows()
5174        .into_iter()
5175        .filter_map(|window| window.downcast::<Workspace>())
5176        .filter(|workspace| {
5177            workspace
5178                .read(cx)
5179                .is_ok_and(|workspace| workspace.project.read(cx).is_local())
5180        })
5181        .collect()
5182}
5183
5184#[derive(Default)]
5185pub struct OpenOptions {
5186    pub open_new_workspace: Option<bool>,
5187    pub replace_window: Option<WindowHandle<Workspace>>,
5188}
5189
5190#[allow(clippy::type_complexity)]
5191pub fn open_paths(
5192    abs_paths: &[PathBuf],
5193    app_state: Arc<AppState>,
5194    open_options: OpenOptions,
5195    cx: &mut AppContext,
5196) -> Task<
5197    anyhow::Result<(
5198        WindowHandle<Workspace>,
5199        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
5200    )>,
5201> {
5202    let abs_paths = abs_paths.to_vec();
5203    let mut existing = None;
5204    let mut best_match = None;
5205    let mut open_visible = OpenVisible::All;
5206
5207    if open_options.open_new_workspace != Some(true) {
5208        for window in local_workspace_windows(cx) {
5209            if let Ok(workspace) = window.read(cx) {
5210                let m = workspace
5211                    .project
5212                    .read(cx)
5213                    .visibility_for_paths(&abs_paths, cx);
5214                if m > best_match {
5215                    existing = Some(window);
5216                    best_match = m;
5217                } else if best_match.is_none() && open_options.open_new_workspace == Some(false) {
5218                    existing = Some(window)
5219                }
5220            }
5221        }
5222    }
5223
5224    cx.spawn(move |mut cx| async move {
5225        if open_options.open_new_workspace.is_none() && existing.is_none() {
5226            let all_files = abs_paths.iter().map(|path| app_state.fs.metadata(path));
5227            if futures::future::join_all(all_files)
5228                .await
5229                .into_iter()
5230                .filter_map(|result| result.ok().flatten())
5231                .all(|file| !file.is_dir)
5232            {
5233                cx.update(|cx| {
5234                    for window in local_workspace_windows(cx) {
5235                        if let Ok(workspace) = window.read(cx) {
5236                            let project = workspace.project().read(cx);
5237                            if project.is_remote() {
5238                                continue;
5239                            }
5240                            existing = Some(window);
5241                            open_visible = OpenVisible::None;
5242                            break;
5243                        }
5244                    }
5245                })?;
5246            }
5247        }
5248
5249        if let Some(existing) = existing {
5250            Ok((
5251                existing,
5252                existing
5253                    .update(&mut cx, |workspace, cx| {
5254                        cx.activate_window();
5255                        workspace.open_paths(abs_paths, open_visible, None, cx)
5256                    })?
5257                    .await,
5258            ))
5259        } else {
5260            cx.update(move |cx| {
5261                Workspace::new_local(
5262                    abs_paths,
5263                    app_state.clone(),
5264                    open_options.replace_window,
5265                    cx,
5266                )
5267            })?
5268            .await
5269        }
5270    })
5271}
5272
5273pub fn open_new(
5274    app_state: Arc<AppState>,
5275    cx: &mut AppContext,
5276    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
5277) -> Task<anyhow::Result<()>> {
5278    let task = Workspace::new_local(Vec::new(), app_state, None, cx);
5279    cx.spawn(|mut cx| async move {
5280        let (workspace, opened_paths) = task.await?;
5281        workspace.update(&mut cx, |workspace, cx| {
5282            if opened_paths.is_empty() {
5283                init(workspace, cx)
5284            }
5285        })?;
5286        Ok(())
5287    })
5288}
5289
5290pub fn create_and_open_local_file(
5291    path: &'static Path,
5292    cx: &mut ViewContext<Workspace>,
5293    default_content: impl 'static + Send + FnOnce() -> Rope,
5294) -> Task<Result<Box<dyn ItemHandle>>> {
5295    cx.spawn(|workspace, mut cx| async move {
5296        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
5297        if !fs.is_file(path).await {
5298            fs.create_file(path, Default::default()).await?;
5299            fs.save(path, &default_content(), Default::default())
5300                .await?;
5301        }
5302
5303        let mut items = workspace
5304            .update(&mut cx, |workspace, cx| {
5305                workspace.with_local_workspace(cx, |workspace, cx| {
5306                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
5307                })
5308            })?
5309            .await?
5310            .await;
5311
5312        let item = items.pop().flatten();
5313        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
5314    })
5315}
5316
5317pub fn join_hosted_project(
5318    hosted_project_id: ProjectId,
5319    app_state: Arc<AppState>,
5320    cx: &mut AppContext,
5321) -> Task<Result<()>> {
5322    cx.spawn(|mut cx| async move {
5323        let existing_window = cx.update(|cx| {
5324            cx.windows().into_iter().find_map(|window| {
5325                let workspace = window.downcast::<Workspace>()?;
5326                workspace
5327                    .read(cx)
5328                    .is_ok_and(|workspace| {
5329                        workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
5330                    })
5331                    .then(|| workspace)
5332            })
5333        })?;
5334
5335        let workspace = if let Some(existing_window) = existing_window {
5336            existing_window
5337        } else {
5338            let project = Project::hosted(
5339                hosted_project_id,
5340                app_state.user_store.clone(),
5341                app_state.client.clone(),
5342                app_state.languages.clone(),
5343                app_state.fs.clone(),
5344                cx.clone(),
5345            )
5346            .await?;
5347
5348            let window_bounds_override = window_bounds_env_override();
5349            cx.update(|cx| {
5350                let mut options = (app_state.build_window_options)(None, cx);
5351                options.window_bounds =
5352                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5353                cx.open_window(options, |cx| {
5354                    cx.new_view(|cx| {
5355                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5356                    })
5357                })
5358            })??
5359        };
5360
5361        workspace.update(&mut cx, |_, cx| {
5362            cx.activate(true);
5363            cx.activate_window();
5364        })?;
5365
5366        Ok(())
5367    })
5368}
5369
5370pub fn join_dev_server_project(
5371    dev_server_project_id: DevServerProjectId,
5372    project_id: ProjectId,
5373    app_state: Arc<AppState>,
5374    window_to_replace: Option<WindowHandle<Workspace>>,
5375    cx: &mut AppContext,
5376) -> Task<Result<WindowHandle<Workspace>>> {
5377    let windows = cx.windows();
5378    cx.spawn(|mut cx| async move {
5379        let existing_workspace = windows.into_iter().find_map(|window| {
5380            window.downcast::<Workspace>().and_then(|window| {
5381                window
5382                    .update(&mut cx, |workspace, cx| {
5383                        if workspace.project().read(cx).remote_id() == Some(project_id.0) {
5384                            Some(window)
5385                        } else {
5386                            None
5387                        }
5388                    })
5389                    .unwrap_or(None)
5390            })
5391        });
5392
5393        let workspace = if let Some(existing_workspace) = existing_workspace {
5394            existing_workspace
5395        } else {
5396            let project = Project::remote(
5397                project_id.0,
5398                app_state.client.clone(),
5399                app_state.user_store.clone(),
5400                app_state.languages.clone(),
5401                app_state.fs.clone(),
5402                cx.clone(),
5403            )
5404            .await?;
5405
5406            let serialized_workspace: Option<SerializedWorkspace> =
5407                persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
5408
5409            let workspace_id = if let Some(serialized_workspace) = serialized_workspace {
5410                serialized_workspace.id
5411            } else {
5412                persistence::DB.next_id().await?
5413            };
5414
5415            if let Some(window_to_replace) = window_to_replace {
5416                cx.update_window(window_to_replace.into(), |_, cx| {
5417                    cx.replace_root_view(|cx| {
5418                        Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5419                    });
5420                })?;
5421                window_to_replace
5422            } else {
5423                let window_bounds_override = window_bounds_env_override();
5424                cx.update(|cx| {
5425                    let mut options = (app_state.build_window_options)(None, cx);
5426                    options.window_bounds =
5427                        window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5428                    cx.open_window(options, |cx| {
5429                        cx.new_view(|cx| {
5430                            Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
5431                        })
5432                    })
5433                })??
5434            }
5435        };
5436
5437        workspace.update(&mut cx, |_, cx| {
5438            cx.activate(true);
5439            cx.activate_window();
5440        })?;
5441
5442        anyhow::Ok(workspace)
5443    })
5444}
5445
5446pub fn join_in_room_project(
5447    project_id: u64,
5448    follow_user_id: u64,
5449    app_state: Arc<AppState>,
5450    cx: &mut AppContext,
5451) -> Task<Result<()>> {
5452    let windows = cx.windows();
5453    cx.spawn(|mut cx| async move {
5454        let existing_workspace = windows.into_iter().find_map(|window| {
5455            window.downcast::<Workspace>().and_then(|window| {
5456                window
5457                    .update(&mut cx, |workspace, cx| {
5458                        if workspace.project().read(cx).remote_id() == Some(project_id) {
5459                            Some(window)
5460                        } else {
5461                            None
5462                        }
5463                    })
5464                    .unwrap_or(None)
5465            })
5466        });
5467
5468        let workspace = if let Some(existing_workspace) = existing_workspace {
5469            existing_workspace
5470        } else {
5471            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
5472            let room = active_call
5473                .read_with(&cx, |call, _| call.room().cloned())?
5474                .ok_or_else(|| anyhow!("not in a call"))?;
5475            let project = room
5476                .update(&mut cx, |room, cx| {
5477                    room.join_project(
5478                        project_id,
5479                        app_state.languages.clone(),
5480                        app_state.fs.clone(),
5481                        cx,
5482                    )
5483                })?
5484                .await?;
5485
5486            let window_bounds_override = window_bounds_env_override();
5487            cx.update(|cx| {
5488                let mut options = (app_state.build_window_options)(None, cx);
5489                options.window_bounds =
5490                    window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds));
5491                cx.open_window(options, |cx| {
5492                    cx.new_view(|cx| {
5493                        Workspace::new(Default::default(), project, app_state.clone(), cx)
5494                    })
5495                })
5496            })??
5497        };
5498
5499        workspace.update(&mut cx, |workspace, cx| {
5500            cx.activate(true);
5501            cx.activate_window();
5502
5503            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
5504                let follow_peer_id = room
5505                    .read(cx)
5506                    .remote_participants()
5507                    .iter()
5508                    .find(|(_, participant)| participant.user.id == follow_user_id)
5509                    .map(|(_, p)| p.peer_id)
5510                    .or_else(|| {
5511                        // If we couldn't follow the given user, follow the host instead.
5512                        let collaborator = workspace
5513                            .project()
5514                            .read(cx)
5515                            .collaborators()
5516                            .values()
5517                            .find(|collaborator| collaborator.replica_id == 0)?;
5518                        Some(collaborator.peer_id)
5519                    });
5520
5521                if let Some(follow_peer_id) = follow_peer_id {
5522                    workspace.follow(follow_peer_id, cx);
5523                }
5524            }
5525        })?;
5526
5527        anyhow::Ok(())
5528    })
5529}
5530
5531pub fn reload(reload: &Reload, cx: &mut AppContext) {
5532    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
5533    let mut workspace_windows = cx
5534        .windows()
5535        .into_iter()
5536        .filter_map(|window| window.downcast::<Workspace>())
5537        .collect::<Vec<_>>();
5538
5539    // If multiple windows have unsaved changes, and need a save prompt,
5540    // prompt in the active window before switching to a different window.
5541    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
5542
5543    let mut prompt = None;
5544    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
5545        prompt = window
5546            .update(cx, |_, cx| {
5547                cx.prompt(
5548                    PromptLevel::Info,
5549                    "Are you sure you want to restart?",
5550                    None,
5551                    &["Restart", "Cancel"],
5552                )
5553            })
5554            .ok();
5555    }
5556
5557    let binary_path = reload.binary_path.clone();
5558    cx.spawn(|mut cx| async move {
5559        if let Some(prompt) = prompt {
5560            let answer = prompt.await?;
5561            if answer != 0 {
5562                return Ok(());
5563            }
5564        }
5565
5566        // If the user cancels any save prompt, then keep the app open.
5567        for window in workspace_windows {
5568            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
5569                workspace.prepare_to_close(true, cx)
5570            }) {
5571                if !should_close.await? {
5572                    return Ok(());
5573                }
5574            }
5575        }
5576
5577        cx.update(|cx| cx.restart(binary_path))
5578    })
5579    .detach_and_log_err(cx);
5580}
5581
5582fn parse_pixel_position_env_var(value: &str) -> Option<Point<Pixels>> {
5583    let mut parts = value.split(',');
5584    let x: usize = parts.next()?.parse().ok()?;
5585    let y: usize = parts.next()?.parse().ok()?;
5586    Some(point(px(x as f32), px(y as f32)))
5587}
5588
5589fn parse_pixel_size_env_var(value: &str) -> Option<Size<Pixels>> {
5590    let mut parts = value.split(',');
5591    let width: usize = parts.next()?.parse().ok()?;
5592    let height: usize = parts.next()?.parse().ok()?;
5593    Some(size(px(width as f32), px(height as f32)))
5594}
5595
5596#[cfg(test)]
5597mod tests {
5598    use std::{cell::RefCell, rc::Rc};
5599
5600    use super::*;
5601    use crate::{
5602        dock::{test::TestPanel, PanelEvent},
5603        item::{
5604            test::{TestItem, TestProjectItem},
5605            ItemEvent,
5606        },
5607    };
5608    use fs::FakeFs;
5609    use gpui::{
5610        px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
5611        UpdateGlobal, VisualTestContext,
5612    };
5613    use project::{Project, ProjectEntryId};
5614    use serde_json::json;
5615    use settings::SettingsStore;
5616
5617    #[gpui::test]
5618    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
5619        init_test(cx);
5620
5621        let fs = FakeFs::new(cx.executor());
5622        let project = Project::test(fs, [], cx).await;
5623        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5624
5625        // Adding an item with no ambiguity renders the tab without detail.
5626        let item1 = cx.new_view(|cx| {
5627            let mut item = TestItem::new(cx);
5628            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
5629            item
5630        });
5631        workspace.update(cx, |workspace, cx| {
5632            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
5633        });
5634        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
5635
5636        // Adding an item that creates ambiguity increases the level of detail on
5637        // both tabs.
5638        let item2 = cx.new_view(|cx| {
5639            let mut item = TestItem::new(cx);
5640            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5641            item
5642        });
5643        workspace.update(cx, |workspace, cx| {
5644            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
5645        });
5646        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5647        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5648
5649        // Adding an item that creates ambiguity increases the level of detail only
5650        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
5651        // we stop at the highest detail available.
5652        let item3 = cx.new_view(|cx| {
5653            let mut item = TestItem::new(cx);
5654            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
5655            item
5656        });
5657        workspace.update(cx, |workspace, cx| {
5658            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
5659        });
5660        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
5661        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5662        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
5663    }
5664
5665    #[gpui::test]
5666    async fn test_tracking_active_path(cx: &mut TestAppContext) {
5667        init_test(cx);
5668
5669        let fs = FakeFs::new(cx.executor());
5670        fs.insert_tree(
5671            "/root1",
5672            json!({
5673                "one.txt": "",
5674                "two.txt": "",
5675            }),
5676        )
5677        .await;
5678        fs.insert_tree(
5679            "/root2",
5680            json!({
5681                "three.txt": "",
5682            }),
5683        )
5684        .await;
5685
5686        let project = Project::test(fs, ["root1".as_ref()], cx).await;
5687        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5688        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
5689        let worktree_id = project.update(cx, |project, cx| {
5690            project.worktrees(cx).next().unwrap().read(cx).id()
5691        });
5692
5693        let item1 = cx.new_view(|cx| {
5694            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
5695        });
5696        let item2 = cx.new_view(|cx| {
5697            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
5698        });
5699
5700        // Add an item to an empty pane
5701        workspace.update(cx, |workspace, cx| {
5702            workspace.add_item_to_active_pane(Box::new(item1), None, true, cx)
5703        });
5704        project.update(cx, |project, cx| {
5705            assert_eq!(
5706                project.active_entry(),
5707                project
5708                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5709                    .map(|e| e.id)
5710            );
5711        });
5712        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5713
5714        // Add a second item to a non-empty pane
5715        workspace.update(cx, |workspace, cx| {
5716            workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
5717        });
5718        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
5719        project.update(cx, |project, cx| {
5720            assert_eq!(
5721                project.active_entry(),
5722                project
5723                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
5724                    .map(|e| e.id)
5725            );
5726        });
5727
5728        // Close the active item
5729        pane.update(cx, |pane, cx| {
5730            pane.close_active_item(&Default::default(), cx).unwrap()
5731        })
5732        .await
5733        .unwrap();
5734        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
5735        project.update(cx, |project, cx| {
5736            assert_eq!(
5737                project.active_entry(),
5738                project
5739                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
5740                    .map(|e| e.id)
5741            );
5742        });
5743
5744        // Add a project folder
5745        project
5746            .update(cx, |project, cx| {
5747                project.find_or_create_worktree("root2", true, cx)
5748            })
5749            .await
5750            .unwrap();
5751        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
5752
5753        // Remove a project folder
5754        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
5755        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
5756    }
5757
5758    #[gpui::test]
5759    async fn test_close_window(cx: &mut TestAppContext) {
5760        init_test(cx);
5761
5762        let fs = FakeFs::new(cx.executor());
5763        fs.insert_tree("/root", json!({ "one": "" })).await;
5764
5765        let project = Project::test(fs, ["root".as_ref()], cx).await;
5766        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5767
5768        // When there are no dirty items, there's nothing to do.
5769        let item1 = cx.new_view(|cx| TestItem::new(cx));
5770        workspace.update(cx, |w, cx| {
5771            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx)
5772        });
5773        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5774        assert!(task.await.unwrap());
5775
5776        // When there are dirty untitled items, prompt to save each one. If the user
5777        // cancels any prompt, then abort.
5778        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
5779        let item3 = cx.new_view(|cx| {
5780            TestItem::new(cx)
5781                .with_dirty(true)
5782                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5783        });
5784        workspace.update(cx, |w, cx| {
5785            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
5786            w.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
5787        });
5788        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5789        cx.executor().run_until_parked();
5790        cx.simulate_prompt_answer(2); // cancel save all
5791        cx.executor().run_until_parked();
5792        cx.simulate_prompt_answer(2); // cancel save all
5793        cx.executor().run_until_parked();
5794        assert!(!cx.has_pending_prompt());
5795        assert!(!task.await.unwrap());
5796    }
5797
5798    #[gpui::test]
5799    async fn test_close_window_with_serializable_items(cx: &mut TestAppContext) {
5800        init_test(cx);
5801
5802        // Register TestItem as a serializable item
5803        cx.update(|cx| {
5804            register_serializable_item::<TestItem>(cx);
5805        });
5806
5807        let fs = FakeFs::new(cx.executor());
5808        fs.insert_tree("/root", json!({ "one": "" })).await;
5809
5810        let project = Project::test(fs, ["root".as_ref()], cx).await;
5811        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
5812
5813        // When there are dirty untitled items, but they can serialize, then there is no prompt.
5814        let item1 = cx.new_view(|cx| {
5815            TestItem::new(cx)
5816                .with_dirty(true)
5817                .with_serialize(|| Some(Task::ready(Ok(()))))
5818        });
5819        let item2 = cx.new_view(|cx| {
5820            TestItem::new(cx)
5821                .with_dirty(true)
5822                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5823                .with_serialize(|| Some(Task::ready(Ok(()))))
5824        });
5825        workspace.update(cx, |w, cx| {
5826            w.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
5827            w.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
5828        });
5829        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
5830        assert!(task.await.unwrap());
5831    }
5832
5833    #[gpui::test]
5834    async fn test_close_pane_items(cx: &mut TestAppContext) {
5835        init_test(cx);
5836
5837        let fs = FakeFs::new(cx.executor());
5838
5839        let project = Project::test(fs, None, cx).await;
5840        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5841
5842        let item1 = cx.new_view(|cx| {
5843            TestItem::new(cx)
5844                .with_dirty(true)
5845                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5846        });
5847        let item2 = cx.new_view(|cx| {
5848            TestItem::new(cx)
5849                .with_dirty(true)
5850                .with_conflict(true)
5851                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
5852        });
5853        let item3 = cx.new_view(|cx| {
5854            TestItem::new(cx)
5855                .with_dirty(true)
5856                .with_conflict(true)
5857                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
5858        });
5859        let item4 = cx.new_view(|cx| {
5860            TestItem::new(cx)
5861                .with_dirty(true)
5862                .with_project_items(&[TestProjectItem::new_untitled(cx)])
5863        });
5864        let pane = workspace.update(cx, |workspace, cx| {
5865            workspace.add_item_to_active_pane(Box::new(item1.clone()), None, true, cx);
5866            workspace.add_item_to_active_pane(Box::new(item2.clone()), None, true, cx);
5867            workspace.add_item_to_active_pane(Box::new(item3.clone()), None, true, cx);
5868            workspace.add_item_to_active_pane(Box::new(item4.clone()), None, true, cx);
5869            workspace.active_pane().clone()
5870        });
5871
5872        let close_items = pane.update(cx, |pane, cx| {
5873            pane.activate_item(1, true, true, cx);
5874            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5875            let item1_id = item1.item_id();
5876            let item3_id = item3.item_id();
5877            let item4_id = item4.item_id();
5878            pane.close_items(cx, SaveIntent::Close, move |id| {
5879                [item1_id, item3_id, item4_id].contains(&id)
5880            })
5881        });
5882        cx.executor().run_until_parked();
5883
5884        assert!(cx.has_pending_prompt());
5885        // Ignore "Save all" prompt
5886        cx.simulate_prompt_answer(2);
5887        cx.executor().run_until_parked();
5888        // There's a prompt to save item 1.
5889        pane.update(cx, |pane, _| {
5890            assert_eq!(pane.items_len(), 4);
5891            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
5892        });
5893        // Confirm saving item 1.
5894        cx.simulate_prompt_answer(0);
5895        cx.executor().run_until_parked();
5896
5897        // Item 1 is saved. There's a prompt to save item 3.
5898        pane.update(cx, |pane, cx| {
5899            assert_eq!(item1.read(cx).save_count, 1);
5900            assert_eq!(item1.read(cx).save_as_count, 0);
5901            assert_eq!(item1.read(cx).reload_count, 0);
5902            assert_eq!(pane.items_len(), 3);
5903            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
5904        });
5905        assert!(cx.has_pending_prompt());
5906
5907        // Cancel saving item 3.
5908        cx.simulate_prompt_answer(1);
5909        cx.executor().run_until_parked();
5910
5911        // Item 3 is reloaded. There's a prompt to save item 4.
5912        pane.update(cx, |pane, cx| {
5913            assert_eq!(item3.read(cx).save_count, 0);
5914            assert_eq!(item3.read(cx).save_as_count, 0);
5915            assert_eq!(item3.read(cx).reload_count, 1);
5916            assert_eq!(pane.items_len(), 2);
5917            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
5918        });
5919        assert!(cx.has_pending_prompt());
5920
5921        // Confirm saving item 4.
5922        cx.simulate_prompt_answer(0);
5923        cx.executor().run_until_parked();
5924
5925        // There's a prompt for a path for item 4.
5926        cx.simulate_new_path_selection(|_| Some(Default::default()));
5927        close_items.await.unwrap();
5928
5929        // The requested items are closed.
5930        pane.update(cx, |pane, cx| {
5931            assert_eq!(item4.read(cx).save_count, 0);
5932            assert_eq!(item4.read(cx).save_as_count, 1);
5933            assert_eq!(item4.read(cx).reload_count, 0);
5934            assert_eq!(pane.items_len(), 1);
5935            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
5936        });
5937    }
5938
5939    #[gpui::test]
5940    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
5941        init_test(cx);
5942
5943        let fs = FakeFs::new(cx.executor());
5944        let project = Project::test(fs, [], cx).await;
5945        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5946
5947        // Create several workspace items with single project entries, and two
5948        // workspace items with multiple project entries.
5949        let single_entry_items = (0..=4)
5950            .map(|project_entry_id| {
5951                cx.new_view(|cx| {
5952                    TestItem::new(cx)
5953                        .with_dirty(true)
5954                        .with_project_items(&[TestProjectItem::new(
5955                            project_entry_id,
5956                            &format!("{project_entry_id}.txt"),
5957                            cx,
5958                        )])
5959                })
5960            })
5961            .collect::<Vec<_>>();
5962        let item_2_3 = cx.new_view(|cx| {
5963            TestItem::new(cx)
5964                .with_dirty(true)
5965                .with_singleton(false)
5966                .with_project_items(&[
5967                    single_entry_items[2].read(cx).project_items[0].clone(),
5968                    single_entry_items[3].read(cx).project_items[0].clone(),
5969                ])
5970        });
5971        let item_3_4 = cx.new_view(|cx| {
5972            TestItem::new(cx)
5973                .with_dirty(true)
5974                .with_singleton(false)
5975                .with_project_items(&[
5976                    single_entry_items[3].read(cx).project_items[0].clone(),
5977                    single_entry_items[4].read(cx).project_items[0].clone(),
5978                ])
5979        });
5980
5981        // Create two panes that contain the following project entries:
5982        //   left pane:
5983        //     multi-entry items:   (2, 3)
5984        //     single-entry items:  0, 1, 2, 3, 4
5985        //   right pane:
5986        //     single-entry items:  1
5987        //     multi-entry items:   (3, 4)
5988        let left_pane = workspace.update(cx, |workspace, cx| {
5989            let left_pane = workspace.active_pane().clone();
5990            workspace.add_item_to_active_pane(Box::new(item_2_3.clone()), None, true, cx);
5991            for item in single_entry_items {
5992                workspace.add_item_to_active_pane(Box::new(item), None, true, cx);
5993            }
5994            left_pane.update(cx, |pane, cx| {
5995                pane.activate_item(2, true, true, cx);
5996            });
5997
5998            let right_pane = workspace
5999                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
6000                .unwrap();
6001
6002            right_pane.update(cx, |pane, cx| {
6003                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
6004            });
6005
6006            left_pane
6007        });
6008
6009        cx.focus_view(&left_pane);
6010
6011        // When closing all of the items in the left pane, we should be prompted twice:
6012        // once for project entry 0, and once for project entry 2. Project entries 1,
6013        // 3, and 4 are all still open in the other paten. After those two
6014        // prompts, the task should complete.
6015
6016        let close = left_pane.update(cx, |pane, cx| {
6017            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
6018        });
6019        cx.executor().run_until_parked();
6020
6021        // Discard "Save all" prompt
6022        cx.simulate_prompt_answer(2);
6023
6024        cx.executor().run_until_parked();
6025        left_pane.update(cx, |pane, cx| {
6026            assert_eq!(
6027                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6028                &[ProjectEntryId::from_proto(0)]
6029            );
6030        });
6031        cx.simulate_prompt_answer(0);
6032
6033        cx.executor().run_until_parked();
6034        left_pane.update(cx, |pane, cx| {
6035            assert_eq!(
6036                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
6037                &[ProjectEntryId::from_proto(2)]
6038            );
6039        });
6040        cx.simulate_prompt_answer(0);
6041
6042        cx.executor().run_until_parked();
6043        close.await.unwrap();
6044        left_pane.update(cx, |pane, _| {
6045            assert_eq!(pane.items_len(), 0);
6046        });
6047    }
6048
6049    #[gpui::test]
6050    async fn test_autosave(cx: &mut gpui::TestAppContext) {
6051        init_test(cx);
6052
6053        let fs = FakeFs::new(cx.executor());
6054        let project = Project::test(fs, [], cx).await;
6055        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6056        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6057
6058        let item = cx.new_view(|cx| {
6059            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6060        });
6061        let item_id = item.entity_id();
6062        workspace.update(cx, |workspace, cx| {
6063            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6064        });
6065
6066        // Autosave on window change.
6067        item.update(cx, |item, cx| {
6068            SettingsStore::update_global(cx, |settings, cx| {
6069                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6070                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
6071                })
6072            });
6073            item.is_dirty = true;
6074        });
6075
6076        // Deactivating the window saves the file.
6077        cx.deactivate_window();
6078        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6079
6080        // Re-activating the window doesn't save the file.
6081        cx.update(|cx| cx.activate_window());
6082        cx.executor().run_until_parked();
6083        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
6084
6085        // Autosave on focus change.
6086        item.update(cx, |item, cx| {
6087            cx.focus_self();
6088            SettingsStore::update_global(cx, |settings, cx| {
6089                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6090                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6091                })
6092            });
6093            item.is_dirty = true;
6094        });
6095
6096        // Blurring the item saves the file.
6097        item.update(cx, |_, cx| cx.blur());
6098        cx.executor().run_until_parked();
6099        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
6100
6101        // Deactivating the window still saves the file.
6102        item.update(cx, |item, cx| {
6103            cx.focus_self();
6104            item.is_dirty = true;
6105        });
6106        cx.deactivate_window();
6107        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6108
6109        // Autosave after delay.
6110        item.update(cx, |item, cx| {
6111            SettingsStore::update_global(cx, |settings, cx| {
6112                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6113                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
6114                })
6115            });
6116            item.is_dirty = true;
6117            cx.emit(ItemEvent::Edit);
6118        });
6119
6120        // Delay hasn't fully expired, so the file is still dirty and unsaved.
6121        cx.executor().advance_clock(Duration::from_millis(250));
6122        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
6123
6124        // After delay expires, the file is saved.
6125        cx.executor().advance_clock(Duration::from_millis(250));
6126        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
6127
6128        // Autosave on focus change, ensuring closing the tab counts as such.
6129        item.update(cx, |item, cx| {
6130            SettingsStore::update_global(cx, |settings, cx| {
6131                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
6132                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
6133                })
6134            });
6135            item.is_dirty = true;
6136        });
6137
6138        pane.update(cx, |pane, cx| {
6139            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6140        })
6141        .await
6142        .unwrap();
6143        assert!(!cx.has_pending_prompt());
6144        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6145
6146        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
6147        workspace.update(cx, |workspace, cx| {
6148            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6149        });
6150        item.update(cx, |item, cx| {
6151            item.project_items[0].update(cx, |item, _| {
6152                item.entry_id = None;
6153            });
6154            item.is_dirty = true;
6155            cx.blur();
6156        });
6157        cx.run_until_parked();
6158        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6159
6160        // Ensure autosave is prevented for deleted files also when closing the buffer.
6161        let _close_items = pane.update(cx, |pane, cx| {
6162            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
6163        });
6164        cx.run_until_parked();
6165        assert!(cx.has_pending_prompt());
6166        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
6167    }
6168
6169    #[gpui::test]
6170    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
6171        init_test(cx);
6172
6173        let fs = FakeFs::new(cx.executor());
6174
6175        let project = Project::test(fs, [], cx).await;
6176        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6177
6178        let item = cx.new_view(|cx| {
6179            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
6180        });
6181        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6182        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
6183        let toolbar_notify_count = Rc::new(RefCell::new(0));
6184
6185        workspace.update(cx, |workspace, cx| {
6186            workspace.add_item_to_active_pane(Box::new(item.clone()), None, true, cx);
6187            let toolbar_notification_count = toolbar_notify_count.clone();
6188            cx.observe(&toolbar, move |_, _, _| {
6189                *toolbar_notification_count.borrow_mut() += 1
6190            })
6191            .detach();
6192        });
6193
6194        pane.update(cx, |pane, _| {
6195            assert!(!pane.can_navigate_backward());
6196            assert!(!pane.can_navigate_forward());
6197        });
6198
6199        item.update(cx, |item, cx| {
6200            item.set_state("one".to_string(), cx);
6201        });
6202
6203        // Toolbar must be notified to re-render the navigation buttons
6204        assert_eq!(*toolbar_notify_count.borrow(), 1);
6205
6206        pane.update(cx, |pane, _| {
6207            assert!(pane.can_navigate_backward());
6208            assert!(!pane.can_navigate_forward());
6209        });
6210
6211        workspace
6212            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
6213            .await
6214            .unwrap();
6215
6216        assert_eq!(*toolbar_notify_count.borrow(), 2);
6217        pane.update(cx, |pane, _| {
6218            assert!(!pane.can_navigate_backward());
6219            assert!(pane.can_navigate_forward());
6220        });
6221    }
6222
6223    #[gpui::test]
6224    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
6225        init_test(cx);
6226        let fs = FakeFs::new(cx.executor());
6227
6228        let project = Project::test(fs, [], cx).await;
6229        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6230
6231        let panel = workspace.update(cx, |workspace, cx| {
6232            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6233            workspace.add_panel(panel.clone(), cx);
6234
6235            workspace
6236                .right_dock()
6237                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6238
6239            panel
6240        });
6241
6242        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
6243        pane.update(cx, |pane, cx| {
6244            let item = cx.new_view(|cx| TestItem::new(cx));
6245            pane.add_item(Box::new(item), true, true, None, cx);
6246        });
6247
6248        // Transfer focus from center to panel
6249        workspace.update(cx, |workspace, cx| {
6250            workspace.toggle_panel_focus::<TestPanel>(cx);
6251        });
6252
6253        workspace.update(cx, |workspace, cx| {
6254            assert!(workspace.right_dock().read(cx).is_open());
6255            assert!(!panel.is_zoomed(cx));
6256            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6257        });
6258
6259        // Transfer focus from panel to center
6260        workspace.update(cx, |workspace, cx| {
6261            workspace.toggle_panel_focus::<TestPanel>(cx);
6262        });
6263
6264        workspace.update(cx, |workspace, cx| {
6265            assert!(workspace.right_dock().read(cx).is_open());
6266            assert!(!panel.is_zoomed(cx));
6267            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6268        });
6269
6270        // Close the dock
6271        workspace.update(cx, |workspace, cx| {
6272            workspace.toggle_dock(DockPosition::Right, cx);
6273        });
6274
6275        workspace.update(cx, |workspace, cx| {
6276            assert!(!workspace.right_dock().read(cx).is_open());
6277            assert!(!panel.is_zoomed(cx));
6278            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6279        });
6280
6281        // Open the dock
6282        workspace.update(cx, |workspace, cx| {
6283            workspace.toggle_dock(DockPosition::Right, cx);
6284        });
6285
6286        workspace.update(cx, |workspace, cx| {
6287            assert!(workspace.right_dock().read(cx).is_open());
6288            assert!(!panel.is_zoomed(cx));
6289            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6290        });
6291
6292        // Focus and zoom panel
6293        panel.update(cx, |panel, cx| {
6294            cx.focus_self();
6295            panel.set_zoomed(true, cx)
6296        });
6297
6298        workspace.update(cx, |workspace, cx| {
6299            assert!(workspace.right_dock().read(cx).is_open());
6300            assert!(panel.is_zoomed(cx));
6301            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6302        });
6303
6304        // Transfer focus to the center closes the dock
6305        workspace.update(cx, |workspace, cx| {
6306            workspace.toggle_panel_focus::<TestPanel>(cx);
6307        });
6308
6309        workspace.update(cx, |workspace, cx| {
6310            assert!(!workspace.right_dock().read(cx).is_open());
6311            assert!(panel.is_zoomed(cx));
6312            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6313        });
6314
6315        // Transferring focus back to the panel keeps it zoomed
6316        workspace.update(cx, |workspace, cx| {
6317            workspace.toggle_panel_focus::<TestPanel>(cx);
6318        });
6319
6320        workspace.update(cx, |workspace, cx| {
6321            assert!(workspace.right_dock().read(cx).is_open());
6322            assert!(panel.is_zoomed(cx));
6323            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6324        });
6325
6326        // Close the dock while it is zoomed
6327        workspace.update(cx, |workspace, cx| {
6328            workspace.toggle_dock(DockPosition::Right, cx)
6329        });
6330
6331        workspace.update(cx, |workspace, cx| {
6332            assert!(!workspace.right_dock().read(cx).is_open());
6333            assert!(panel.is_zoomed(cx));
6334            assert!(workspace.zoomed.is_none());
6335            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
6336        });
6337
6338        // Opening the dock, when it's zoomed, retains focus
6339        workspace.update(cx, |workspace, cx| {
6340            workspace.toggle_dock(DockPosition::Right, cx)
6341        });
6342
6343        workspace.update(cx, |workspace, cx| {
6344            assert!(workspace.right_dock().read(cx).is_open());
6345            assert!(panel.is_zoomed(cx));
6346            assert!(workspace.zoomed.is_some());
6347            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
6348        });
6349
6350        // Unzoom and close the panel, zoom the active pane.
6351        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
6352        workspace.update(cx, |workspace, cx| {
6353            workspace.toggle_dock(DockPosition::Right, cx)
6354        });
6355        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
6356
6357        // Opening a dock unzooms the pane.
6358        workspace.update(cx, |workspace, cx| {
6359            workspace.toggle_dock(DockPosition::Right, cx)
6360        });
6361        workspace.update(cx, |workspace, cx| {
6362            let pane = pane.read(cx);
6363            assert!(!pane.is_zoomed());
6364            assert!(!pane.focus_handle(cx).is_focused(cx));
6365            assert!(workspace.right_dock().read(cx).is_open());
6366            assert!(workspace.zoomed.is_none());
6367        });
6368    }
6369
6370    struct TestModal(FocusHandle);
6371
6372    impl TestModal {
6373        fn new(cx: &mut ViewContext<Self>) -> Self {
6374            Self(cx.focus_handle())
6375        }
6376    }
6377
6378    impl EventEmitter<DismissEvent> for TestModal {}
6379
6380    impl FocusableView for TestModal {
6381        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6382            self.0.clone()
6383        }
6384    }
6385
6386    impl ModalView for TestModal {}
6387
6388    impl Render for TestModal {
6389        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
6390            div().track_focus(&self.0)
6391        }
6392    }
6393
6394    #[gpui::test]
6395    async fn test_panels(cx: &mut gpui::TestAppContext) {
6396        init_test(cx);
6397        let fs = FakeFs::new(cx.executor());
6398
6399        let project = Project::test(fs, [], cx).await;
6400        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
6401
6402        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
6403            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
6404            workspace.add_panel(panel_1.clone(), cx);
6405            workspace
6406                .left_dock()
6407                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
6408            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
6409            workspace.add_panel(panel_2.clone(), cx);
6410            workspace
6411                .right_dock()
6412                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
6413
6414            let left_dock = workspace.left_dock();
6415            assert_eq!(
6416                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6417                panel_1.panel_id()
6418            );
6419            assert_eq!(
6420                left_dock.read(cx).active_panel_size(cx).unwrap(),
6421                panel_1.size(cx)
6422            );
6423
6424            left_dock.update(cx, |left_dock, cx| {
6425                left_dock.resize_active_panel(Some(px(1337.)), cx)
6426            });
6427            assert_eq!(
6428                workspace
6429                    .right_dock()
6430                    .read(cx)
6431                    .visible_panel()
6432                    .unwrap()
6433                    .panel_id(),
6434                panel_2.panel_id(),
6435            );
6436
6437            (panel_1, panel_2)
6438        });
6439
6440        // Move panel_1 to the right
6441        panel_1.update(cx, |panel_1, cx| {
6442            panel_1.set_position(DockPosition::Right, cx)
6443        });
6444
6445        workspace.update(cx, |workspace, cx| {
6446            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
6447            // Since it was the only panel on the left, the left dock should now be closed.
6448            assert!(!workspace.left_dock().read(cx).is_open());
6449            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
6450            let right_dock = workspace.right_dock();
6451            assert_eq!(
6452                right_dock.read(cx).visible_panel().unwrap().panel_id(),
6453                panel_1.panel_id()
6454            );
6455            assert_eq!(
6456                right_dock.read(cx).active_panel_size(cx).unwrap(),
6457                px(1337.)
6458            );
6459
6460            // Now we move panel_2 to the left
6461            panel_2.set_position(DockPosition::Left, cx);
6462        });
6463
6464        workspace.update(cx, |workspace, cx| {
6465            // Since panel_2 was not visible on the right, we don't open the left dock.
6466            assert!(!workspace.left_dock().read(cx).is_open());
6467            // And the right dock is unaffected in its displaying of panel_1
6468            assert!(workspace.right_dock().read(cx).is_open());
6469            assert_eq!(
6470                workspace
6471                    .right_dock()
6472                    .read(cx)
6473                    .visible_panel()
6474                    .unwrap()
6475                    .panel_id(),
6476                panel_1.panel_id(),
6477            );
6478        });
6479
6480        // Move panel_1 back to the left
6481        panel_1.update(cx, |panel_1, cx| {
6482            panel_1.set_position(DockPosition::Left, cx)
6483        });
6484
6485        workspace.update(cx, |workspace, cx| {
6486            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
6487            let left_dock = workspace.left_dock();
6488            assert!(left_dock.read(cx).is_open());
6489            assert_eq!(
6490                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6491                panel_1.panel_id()
6492            );
6493            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
6494            // And the right dock should be closed as it no longer has any panels.
6495            assert!(!workspace.right_dock().read(cx).is_open());
6496
6497            // Now we move panel_1 to the bottom
6498            panel_1.set_position(DockPosition::Bottom, cx);
6499        });
6500
6501        workspace.update(cx, |workspace, cx| {
6502            // Since panel_1 was visible on the left, we close the left dock.
6503            assert!(!workspace.left_dock().read(cx).is_open());
6504            // The bottom dock is sized based on the panel's default size,
6505            // since the panel orientation changed from vertical to horizontal.
6506            let bottom_dock = workspace.bottom_dock();
6507            assert_eq!(
6508                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
6509                panel_1.size(cx),
6510            );
6511            // Close bottom dock and move panel_1 back to the left.
6512            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
6513            panel_1.set_position(DockPosition::Left, cx);
6514        });
6515
6516        // Emit activated event on panel 1
6517        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
6518
6519        // Now the left dock is open and panel_1 is active and focused.
6520        workspace.update(cx, |workspace, cx| {
6521            let left_dock = workspace.left_dock();
6522            assert!(left_dock.read(cx).is_open());
6523            assert_eq!(
6524                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6525                panel_1.panel_id(),
6526            );
6527            assert!(panel_1.focus_handle(cx).is_focused(cx));
6528        });
6529
6530        // Emit closed event on panel 2, which is not active
6531        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6532
6533        // Wo don't close the left dock, because panel_2 wasn't the active panel
6534        workspace.update(cx, |workspace, cx| {
6535            let left_dock = workspace.left_dock();
6536            assert!(left_dock.read(cx).is_open());
6537            assert_eq!(
6538                left_dock.read(cx).visible_panel().unwrap().panel_id(),
6539                panel_1.panel_id(),
6540            );
6541        });
6542
6543        // Emitting a ZoomIn event shows the panel as zoomed.
6544        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
6545        workspace.update(cx, |workspace, _| {
6546            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6547            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
6548        });
6549
6550        // Move panel to another dock while it is zoomed
6551        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
6552        workspace.update(cx, |workspace, _| {
6553            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6554
6555            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6556        });
6557
6558        // This is a helper for getting a:
6559        // - valid focus on an element,
6560        // - that isn't a part of the panes and panels system of the Workspace,
6561        // - and doesn't trigger the 'on_focus_lost' API.
6562        let focus_other_view = {
6563            let workspace = workspace.clone();
6564            move |cx: &mut VisualTestContext| {
6565                workspace.update(cx, |workspace, cx| {
6566                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
6567                        workspace.toggle_modal(cx, TestModal::new);
6568                        workspace.toggle_modal(cx, TestModal::new);
6569                    } else {
6570                        workspace.toggle_modal(cx, TestModal::new);
6571                    }
6572                })
6573            }
6574        };
6575
6576        // If focus is transferred to another view that's not a panel or another pane, we still show
6577        // the panel as zoomed.
6578        focus_other_view(cx);
6579        workspace.update(cx, |workspace, _| {
6580            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6581            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6582        });
6583
6584        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
6585        workspace.update(cx, |_, cx| cx.focus_self());
6586        workspace.update(cx, |workspace, _| {
6587            assert_eq!(workspace.zoomed, None);
6588            assert_eq!(workspace.zoomed_position, None);
6589        });
6590
6591        // If focus is transferred again to another view that's not a panel or a pane, we won't
6592        // show the panel as zoomed because it wasn't zoomed before.
6593        focus_other_view(cx);
6594        workspace.update(cx, |workspace, _| {
6595            assert_eq!(workspace.zoomed, None);
6596            assert_eq!(workspace.zoomed_position, None);
6597        });
6598
6599        // When the panel is activated, it is zoomed again.
6600        cx.dispatch_action(ToggleRightDock);
6601        workspace.update(cx, |workspace, _| {
6602            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
6603            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
6604        });
6605
6606        // Emitting a ZoomOut event unzooms the panel.
6607        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
6608        workspace.update(cx, |workspace, _| {
6609            assert_eq!(workspace.zoomed, None);
6610            assert_eq!(workspace.zoomed_position, None);
6611        });
6612
6613        // Emit closed event on panel 1, which is active
6614        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
6615
6616        // Now the left dock is closed, because panel_1 was the active panel
6617        workspace.update(cx, |workspace, cx| {
6618            let right_dock = workspace.right_dock();
6619            assert!(!right_dock.read(cx).is_open());
6620        });
6621    }
6622
6623    mod register_project_item_tests {
6624        use ui::Context as _;
6625
6626        use super::*;
6627
6628        // View
6629        struct TestPngItemView {
6630            focus_handle: FocusHandle,
6631        }
6632        // Model
6633        struct TestPngItem {}
6634
6635        impl project::Item for TestPngItem {
6636            fn try_open(
6637                _project: &Model<Project>,
6638                path: &ProjectPath,
6639                cx: &mut AppContext,
6640            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6641                if path.path.extension().unwrap() == "png" {
6642                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestPngItem {}) }))
6643                } else {
6644                    None
6645                }
6646            }
6647
6648            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6649                None
6650            }
6651
6652            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6653                None
6654            }
6655        }
6656
6657        impl Item for TestPngItemView {
6658            type Event = ();
6659        }
6660        impl EventEmitter<()> for TestPngItemView {}
6661        impl FocusableView for TestPngItemView {
6662            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6663                self.focus_handle.clone()
6664            }
6665        }
6666
6667        impl Render for TestPngItemView {
6668            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6669                Empty
6670            }
6671        }
6672
6673        impl ProjectItem for TestPngItemView {
6674            type Item = TestPngItem;
6675
6676            fn for_project_item(
6677                _project: Model<Project>,
6678                _item: Model<Self::Item>,
6679                cx: &mut ViewContext<Self>,
6680            ) -> Self
6681            where
6682                Self: Sized,
6683            {
6684                Self {
6685                    focus_handle: cx.focus_handle(),
6686                }
6687            }
6688        }
6689
6690        // View
6691        struct TestIpynbItemView {
6692            focus_handle: FocusHandle,
6693        }
6694        // Model
6695        struct TestIpynbItem {}
6696
6697        impl project::Item for TestIpynbItem {
6698            fn try_open(
6699                _project: &Model<Project>,
6700                path: &ProjectPath,
6701                cx: &mut AppContext,
6702            ) -> Option<Task<gpui::Result<Model<Self>>>> {
6703                if path.path.extension().unwrap() == "ipynb" {
6704                    Some(cx.spawn(|mut cx| async move { cx.new_model(|_| TestIpynbItem {}) }))
6705                } else {
6706                    None
6707                }
6708            }
6709
6710            fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
6711                None
6712            }
6713
6714            fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
6715                None
6716            }
6717        }
6718
6719        impl Item for TestIpynbItemView {
6720            type Event = ();
6721        }
6722        impl EventEmitter<()> for TestIpynbItemView {}
6723        impl FocusableView for TestIpynbItemView {
6724            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6725                self.focus_handle.clone()
6726            }
6727        }
6728
6729        impl Render for TestIpynbItemView {
6730            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6731                Empty
6732            }
6733        }
6734
6735        impl ProjectItem for TestIpynbItemView {
6736            type Item = TestIpynbItem;
6737
6738            fn for_project_item(
6739                _project: Model<Project>,
6740                _item: Model<Self::Item>,
6741                cx: &mut ViewContext<Self>,
6742            ) -> Self
6743            where
6744                Self: Sized,
6745            {
6746                Self {
6747                    focus_handle: cx.focus_handle(),
6748                }
6749            }
6750        }
6751
6752        struct TestAlternatePngItemView {
6753            focus_handle: FocusHandle,
6754        }
6755
6756        impl Item for TestAlternatePngItemView {
6757            type Event = ();
6758        }
6759
6760        impl EventEmitter<()> for TestAlternatePngItemView {}
6761        impl FocusableView for TestAlternatePngItemView {
6762            fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
6763                self.focus_handle.clone()
6764            }
6765        }
6766
6767        impl Render for TestAlternatePngItemView {
6768            fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
6769                Empty
6770            }
6771        }
6772
6773        impl ProjectItem for TestAlternatePngItemView {
6774            type Item = TestPngItem;
6775
6776            fn for_project_item(
6777                _project: Model<Project>,
6778                _item: Model<Self::Item>,
6779                cx: &mut ViewContext<Self>,
6780            ) -> Self
6781            where
6782                Self: Sized,
6783            {
6784                Self {
6785                    focus_handle: cx.focus_handle(),
6786                }
6787            }
6788        }
6789
6790        #[gpui::test]
6791        async fn test_register_project_item(cx: &mut TestAppContext) {
6792            init_test(cx);
6793
6794            cx.update(|cx| {
6795                register_project_item::<TestPngItemView>(cx);
6796                register_project_item::<TestIpynbItemView>(cx);
6797            });
6798
6799            let fs = FakeFs::new(cx.executor());
6800            fs.insert_tree(
6801                "/root1",
6802                json!({
6803                    "one.png": "BINARYDATAHERE",
6804                    "two.ipynb": "{ totally a notebook }",
6805                    "three.txt": "editing text, sure why not?"
6806                }),
6807            )
6808            .await;
6809
6810            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6811            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6812
6813            let worktree_id = project.update(cx, |project, cx| {
6814                project.worktrees(cx).next().unwrap().read(cx).id()
6815            });
6816
6817            let handle = workspace
6818                .update(cx, |workspace, cx| {
6819                    let project_path = (worktree_id, "one.png");
6820                    workspace.open_path(project_path, None, true, cx)
6821                })
6822                .await
6823                .unwrap();
6824
6825            // Now we can check if the handle we got back errored or not
6826            assert_eq!(
6827                handle.to_any().entity_type(),
6828                TypeId::of::<TestPngItemView>()
6829            );
6830
6831            let handle = workspace
6832                .update(cx, |workspace, cx| {
6833                    let project_path = (worktree_id, "two.ipynb");
6834                    workspace.open_path(project_path, None, true, cx)
6835                })
6836                .await
6837                .unwrap();
6838
6839            assert_eq!(
6840                handle.to_any().entity_type(),
6841                TypeId::of::<TestIpynbItemView>()
6842            );
6843
6844            let handle = workspace
6845                .update(cx, |workspace, cx| {
6846                    let project_path = (worktree_id, "three.txt");
6847                    workspace.open_path(project_path, None, true, cx)
6848                })
6849                .await;
6850            assert!(handle.is_err());
6851        }
6852
6853        #[gpui::test]
6854        async fn test_register_project_item_two_enter_one_leaves(cx: &mut TestAppContext) {
6855            init_test(cx);
6856
6857            cx.update(|cx| {
6858                register_project_item::<TestPngItemView>(cx);
6859                register_project_item::<TestAlternatePngItemView>(cx);
6860            });
6861
6862            let fs = FakeFs::new(cx.executor());
6863            fs.insert_tree(
6864                "/root1",
6865                json!({
6866                    "one.png": "BINARYDATAHERE",
6867                    "two.ipynb": "{ totally a notebook }",
6868                    "three.txt": "editing text, sure why not?"
6869                }),
6870            )
6871            .await;
6872
6873            let project = Project::test(fs, ["root1".as_ref()], cx).await;
6874            let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
6875
6876            let worktree_id = project.update(cx, |project, cx| {
6877                project.worktrees(cx).next().unwrap().read(cx).id()
6878            });
6879
6880            let handle = workspace
6881                .update(cx, |workspace, cx| {
6882                    let project_path = (worktree_id, "one.png");
6883                    workspace.open_path(project_path, None, true, cx)
6884                })
6885                .await
6886                .unwrap();
6887
6888            // This _must_ be the second item registered
6889            assert_eq!(
6890                handle.to_any().entity_type(),
6891                TypeId::of::<TestAlternatePngItemView>()
6892            );
6893
6894            let handle = workspace
6895                .update(cx, |workspace, cx| {
6896                    let project_path = (worktree_id, "three.txt");
6897                    workspace.open_path(project_path, None, true, cx)
6898                })
6899                .await;
6900            assert!(handle.is_err());
6901        }
6902    }
6903
6904    pub fn init_test(cx: &mut TestAppContext) {
6905        cx.update(|cx| {
6906            let settings_store = SettingsStore::test(cx);
6907            cx.set_global(settings_store);
6908            theme::init(theme::LoadThemes::JustBase, cx);
6909            language::init(cx);
6910            crate::init_settings(cx);
6911            Project::init_settings(cx);
6912        });
6913    }
6914}
6915
6916pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext) -> Stateful<Div> {
6917    const BORDER_SIZE: Pixels = px(1.0);
6918    let decorations = cx.window_decorations();
6919
6920    if matches!(decorations, Decorations::Client { .. }) {
6921        cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
6922    }
6923
6924    struct GlobalResizeEdge(ResizeEdge);
6925    impl Global for GlobalResizeEdge {}
6926
6927    div()
6928        .id("window-backdrop")
6929        .bg(transparent_black())
6930        .map(|div| match decorations {
6931            Decorations::Server => div,
6932            Decorations::Client { tiling, .. } => div
6933                .when(!(tiling.top || tiling.right), |div| {
6934                    div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6935                })
6936                .when(!(tiling.top || tiling.left), |div| {
6937                    div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6938                })
6939                .when(!(tiling.bottom || tiling.right), |div| {
6940                    div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6941                })
6942                .when(!(tiling.bottom || tiling.left), |div| {
6943                    div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6944                })
6945                .when(!tiling.top, |div| {
6946                    div.pt(theme::CLIENT_SIDE_DECORATION_SHADOW)
6947                })
6948                .when(!tiling.bottom, |div| {
6949                    div.pb(theme::CLIENT_SIDE_DECORATION_SHADOW)
6950                })
6951                .when(!tiling.left, |div| {
6952                    div.pl(theme::CLIENT_SIDE_DECORATION_SHADOW)
6953                })
6954                .when(!tiling.right, |div| {
6955                    div.pr(theme::CLIENT_SIDE_DECORATION_SHADOW)
6956                })
6957                .on_mouse_move(move |e, cx| {
6958                    let size = cx.window_bounds().get_bounds().size;
6959                    let pos = e.position;
6960
6961                    let new_edge =
6962                        resize_edge(pos, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling);
6963
6964                    let edge = cx.try_global::<GlobalResizeEdge>();
6965                    if new_edge != edge.map(|edge| edge.0) {
6966                        cx.window_handle()
6967                            .update(cx, |workspace, cx| cx.notify(workspace.entity_id()))
6968                            .ok();
6969                    }
6970                })
6971                .on_mouse_down(MouseButton::Left, move |e, cx| {
6972                    let size = cx.window_bounds().get_bounds().size;
6973                    let pos = e.position;
6974
6975                    let edge = match resize_edge(
6976                        pos,
6977                        theme::CLIENT_SIDE_DECORATION_SHADOW,
6978                        size,
6979                        tiling,
6980                    ) {
6981                        Some(value) => value,
6982                        None => return,
6983                    };
6984
6985                    cx.start_window_resize(edge);
6986                }),
6987        })
6988        .size_full()
6989        .child(
6990            div()
6991                .cursor(CursorStyle::Arrow)
6992                .map(|div| match decorations {
6993                    Decorations::Server => div,
6994                    Decorations::Client { tiling } => div
6995                        .border_color(cx.theme().colors().border)
6996                        .when(!(tiling.top || tiling.right), |div| {
6997                            div.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
6998                        })
6999                        .when(!(tiling.top || tiling.left), |div| {
7000                            div.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7001                        })
7002                        .when(!(tiling.bottom || tiling.right), |div| {
7003                            div.rounded_br(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7004                        })
7005                        .when(!(tiling.bottom || tiling.left), |div| {
7006                            div.rounded_bl(theme::CLIENT_SIDE_DECORATION_ROUNDING)
7007                        })
7008                        .when(!tiling.top, |div| div.border_t(BORDER_SIZE))
7009                        .when(!tiling.bottom, |div| div.border_b(BORDER_SIZE))
7010                        .when(!tiling.left, |div| div.border_l(BORDER_SIZE))
7011                        .when(!tiling.right, |div| div.border_r(BORDER_SIZE))
7012                        .when(!tiling.is_tiled(), |div| {
7013                            div.shadow(smallvec::smallvec![gpui::BoxShadow {
7014                                color: Hsla {
7015                                    h: 0.,
7016                                    s: 0.,
7017                                    l: 0.,
7018                                    a: 0.4,
7019                                },
7020                                blur_radius: theme::CLIENT_SIDE_DECORATION_SHADOW / 2.,
7021                                spread_radius: px(0.),
7022                                offset: point(px(0.0), px(0.0)),
7023                            }])
7024                        }),
7025                })
7026                .on_mouse_move(|_e, cx| {
7027                    cx.stop_propagation();
7028                })
7029                .size_full()
7030                .child(element),
7031        )
7032        .map(|div| match decorations {
7033            Decorations::Server => div,
7034            Decorations::Client { tiling, .. } => div.child(
7035                canvas(
7036                    |_bounds, cx| {
7037                        cx.insert_hitbox(
7038                            Bounds::new(
7039                                point(px(0.0), px(0.0)),
7040                                cx.window_bounds().get_bounds().size,
7041                            ),
7042                            false,
7043                        )
7044                    },
7045                    move |_bounds, hitbox, cx| {
7046                        let mouse = cx.mouse_position();
7047                        let size = cx.window_bounds().get_bounds().size;
7048                        let Some(edge) =
7049                            resize_edge(mouse, theme::CLIENT_SIDE_DECORATION_SHADOW, size, tiling)
7050                        else {
7051                            return;
7052                        };
7053                        cx.set_global(GlobalResizeEdge(edge));
7054                        cx.set_cursor_style(
7055                            match edge {
7056                                ResizeEdge::Top | ResizeEdge::Bottom => CursorStyle::ResizeUpDown,
7057                                ResizeEdge::Left | ResizeEdge::Right => {
7058                                    CursorStyle::ResizeLeftRight
7059                                }
7060                                ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
7061                                    CursorStyle::ResizeUpLeftDownRight
7062                                }
7063                                ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
7064                                    CursorStyle::ResizeUpRightDownLeft
7065                                }
7066                            },
7067                            &hitbox,
7068                        );
7069                    },
7070                )
7071                .size_full()
7072                .absolute(),
7073            ),
7074        })
7075}
7076
7077fn resize_edge(
7078    pos: Point<Pixels>,
7079    shadow_size: Pixels,
7080    window_size: Size<Pixels>,
7081    tiling: Tiling,
7082) -> Option<ResizeEdge> {
7083    let bounds = Bounds::new(Point::default(), window_size).inset(shadow_size * 1.5);
7084    if bounds.contains(&pos) {
7085        return None;
7086    }
7087
7088    let corner_size = size(shadow_size * 1.5, shadow_size * 1.5);
7089    let top_left_bounds = Bounds::new(Point::new(px(0.), px(0.)), corner_size);
7090    if !tiling.top && top_left_bounds.contains(&pos) {
7091        return Some(ResizeEdge::TopLeft);
7092    }
7093
7094    let top_right_bounds = Bounds::new(
7095        Point::new(window_size.width - corner_size.width, px(0.)),
7096        corner_size,
7097    );
7098    if !tiling.top && top_right_bounds.contains(&pos) {
7099        return Some(ResizeEdge::TopRight);
7100    }
7101
7102    let bottom_left_bounds = Bounds::new(
7103        Point::new(px(0.), window_size.height - corner_size.height),
7104        corner_size,
7105    );
7106    if !tiling.bottom && bottom_left_bounds.contains(&pos) {
7107        return Some(ResizeEdge::BottomLeft);
7108    }
7109
7110    let bottom_right_bounds = Bounds::new(
7111        Point::new(
7112            window_size.width - corner_size.width,
7113            window_size.height - corner_size.height,
7114        ),
7115        corner_size,
7116    );
7117    if !tiling.bottom && bottom_right_bounds.contains(&pos) {
7118        return Some(ResizeEdge::BottomRight);
7119    }
7120
7121    if !tiling.top && pos.y < shadow_size {
7122        Some(ResizeEdge::Top)
7123    } else if !tiling.bottom && pos.y > window_size.height - shadow_size {
7124        Some(ResizeEdge::Bottom)
7125    } else if !tiling.left && pos.x < shadow_size {
7126        Some(ResizeEdge::Left)
7127    } else if !tiling.right && pos.x > window_size.width - shadow_size {
7128        Some(ResizeEdge::Right)
7129    } else {
7130        None
7131    }
7132}