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