workspace.rs

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