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