workspace.rs

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