workspace.rs

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