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