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