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