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