workspace.rs

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