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                                dbg!(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                dbg!(&project_path);
1372
1373                let this = this.clone();
1374                let abs_path = abs_path.clone();
1375                let fs = fs.clone();
1376                let pane = pane.clone();
1377                let task = cx.spawn(move |mut cx| async move {
1378                    let (worktree, project_path) = project_path?;
1379                    if fs.is_file(&abs_path).await {
1380                        Some(
1381                            this.update(&mut cx, |this, cx| {
1382                                this.open_path(project_path, pane, true, cx)
1383                            })
1384                            .log_err()?
1385                            .await,
1386                        )
1387                    } else {
1388                        this.update(&mut cx, |workspace, cx| {
1389                            let worktree = worktree.read(cx);
1390                            let worktree_abs_path = worktree.abs_path();
1391                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1392                                worktree.root_entry()
1393                            } else {
1394                                abs_path
1395                                    .strip_prefix(worktree_abs_path.as_ref())
1396                                    .ok()
1397                                    .and_then(|relative_path| {
1398                                        worktree.entry_for_path(relative_path)
1399                                    })
1400                            }
1401                            .map(|entry| entry.id);
1402                            if let Some(entry_id) = entry_id {
1403                                workspace.project.update(cx, |_, cx| {
1404                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1405                                })
1406                            }
1407                        })
1408                        .log_err()?;
1409                        None
1410                    }
1411                });
1412                tasks.push(task);
1413            }
1414
1415            futures::future::join_all(tasks).await
1416        })
1417    }
1418
1419    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1420        let paths = cx.prompt_for_paths(PathPromptOptions {
1421            files: false,
1422            directories: true,
1423            multiple: true,
1424        });
1425        cx.spawn(|this, mut cx| async move {
1426            if let Some(paths) = paths.await.log_err().flatten() {
1427                let results = this
1428                    .update(&mut cx, |this, cx| {
1429                        this.open_paths(paths, OpenVisible::All, None, cx)
1430                    })?
1431                    .await;
1432                for result in results.into_iter().flatten() {
1433                    result.log_err();
1434                }
1435            }
1436            anyhow::Ok(())
1437        })
1438        .detach_and_log_err(cx);
1439    }
1440
1441    fn project_path_for_path(
1442        project: Model<Project>,
1443        abs_path: &Path,
1444        visible: bool,
1445        cx: &mut AppContext,
1446    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1447        let entry = project.update(cx, |project, cx| {
1448            project.find_or_create_local_worktree(abs_path, visible, cx)
1449        });
1450        cx.spawn(|mut cx| async move {
1451            let (worktree, path) = entry.await?;
1452            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1453            Ok((
1454                worktree,
1455                ProjectPath {
1456                    worktree_id,
1457                    path: path.into(),
1458                },
1459            ))
1460        })
1461    }
1462
1463    pub fn items<'a>(
1464        &'a self,
1465        cx: &'a AppContext,
1466    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1467        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1468    }
1469
1470    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1471        self.items_of_type(cx).max_by_key(|item| item.item_id())
1472    }
1473
1474    pub fn items_of_type<'a, T: Item>(
1475        &'a self,
1476        cx: &'a AppContext,
1477    ) -> impl 'a + Iterator<Item = View<T>> {
1478        self.panes
1479            .iter()
1480            .flat_map(|pane| pane.read(cx).items_of_type())
1481    }
1482
1483    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1484        self.active_pane().read(cx).active_item()
1485    }
1486
1487    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1488        let item = self.active_item(cx)?;
1489        item.to_any().downcast::<I>().ok()
1490    }
1491
1492    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1493        self.active_item(cx).and_then(|item| item.project_path(cx))
1494    }
1495
1496    pub fn save_active_item(
1497        &mut self,
1498        save_intent: SaveIntent,
1499        cx: &mut WindowContext,
1500    ) -> Task<Result<()>> {
1501        let project = self.project.clone();
1502        let pane = self.active_pane();
1503        let item_ix = pane.read(cx).active_item_index();
1504        let item = pane.read(cx).active_item();
1505        let pane = pane.downgrade();
1506
1507        cx.spawn(|mut cx| async move {
1508            if let Some(item) = item {
1509                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1510                    .await
1511                    .map(|_| ())
1512            } else {
1513                Ok(())
1514            }
1515        })
1516    }
1517
1518    pub fn close_inactive_items_and_panes(
1519        &mut self,
1520        _: &CloseInactiveTabsAndPanes,
1521        cx: &mut ViewContext<Self>,
1522    ) {
1523        self.close_all_internal(true, SaveIntent::Close, cx)
1524            .map(|task| task.detach_and_log_err(cx));
1525    }
1526
1527    pub fn close_all_items_and_panes(
1528        &mut self,
1529        action: &CloseAllItemsAndPanes,
1530        cx: &mut ViewContext<Self>,
1531    ) {
1532        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1533            .map(|task| task.detach_and_log_err(cx));
1534    }
1535
1536    fn close_all_internal(
1537        &mut self,
1538        retain_active_pane: bool,
1539        save_intent: SaveIntent,
1540        cx: &mut ViewContext<Self>,
1541    ) -> Option<Task<Result<()>>> {
1542        let current_pane = self.active_pane();
1543
1544        let mut tasks = Vec::new();
1545
1546        if retain_active_pane {
1547            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1548                pane.close_inactive_items(&CloseInactiveItems, cx)
1549            }) {
1550                tasks.push(current_pane_close);
1551            };
1552        }
1553
1554        for pane in self.panes() {
1555            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1556                continue;
1557            }
1558
1559            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1560                pane.close_all_items(
1561                    &CloseAllItems {
1562                        save_intent: Some(save_intent),
1563                    },
1564                    cx,
1565                )
1566            }) {
1567                tasks.push(close_pane_items)
1568            }
1569        }
1570
1571        if tasks.is_empty() {
1572            None
1573        } else {
1574            Some(cx.spawn(|_, _| async move {
1575                for task in tasks {
1576                    task.await?
1577                }
1578                Ok(())
1579            }))
1580        }
1581    }
1582
1583    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1584        let dock = match dock_side {
1585            DockPosition::Left => &self.left_dock,
1586            DockPosition::Bottom => &self.bottom_dock,
1587            DockPosition::Right => &self.right_dock,
1588        };
1589        let mut focus_center = false;
1590        let mut reveal_dock = false;
1591        dock.update(cx, |dock, cx| {
1592            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1593            let was_visible = dock.is_open() && !other_is_zoomed;
1594            dock.set_open(!was_visible, cx);
1595
1596            if let Some(active_panel) = dock.active_panel() {
1597                if was_visible {
1598                    if active_panel.focus_handle(cx).contains_focused(cx) {
1599                        focus_center = true;
1600                    }
1601                } else {
1602                    let focus_handle = &active_panel.focus_handle(cx);
1603                    cx.focus(focus_handle);
1604                    reveal_dock = true;
1605                }
1606            }
1607        });
1608
1609        if reveal_dock {
1610            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1611        }
1612
1613        if focus_center {
1614            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1615        }
1616
1617        cx.notify();
1618        self.serialize_workspace(cx);
1619    }
1620
1621    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1622        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1623
1624        for dock in docks {
1625            dock.update(cx, |dock, cx| {
1626                dock.set_open(false, cx);
1627            });
1628        }
1629
1630        cx.focus_self();
1631        cx.notify();
1632        self.serialize_workspace(cx);
1633    }
1634
1635    /// Transfer focus to the panel of the given type.
1636    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1637        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1638        panel.to_any().downcast().ok()
1639    }
1640
1641    /// Focus the panel of the given type if it isn't already focused. If it is
1642    /// already focused, then transfer focus back to the workspace center.
1643    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1644        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1645            !panel.focus_handle(cx).contains_focused(cx)
1646        });
1647    }
1648
1649    /// Focus or unfocus the given panel type, depending on the given callback.
1650    fn focus_or_unfocus_panel<T: Panel>(
1651        &mut self,
1652        cx: &mut ViewContext<Self>,
1653        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1654    ) -> Option<Arc<dyn PanelHandle>> {
1655        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1656            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1657                let mut focus_center = false;
1658                let panel = dock.update(cx, |dock, cx| {
1659                    dock.activate_panel(panel_index, cx);
1660
1661                    let panel = dock.active_panel().cloned();
1662                    if let Some(panel) = panel.as_ref() {
1663                        if should_focus(&**panel, cx) {
1664                            dock.set_open(true, cx);
1665                            panel.focus_handle(cx).focus(cx);
1666                        } else {
1667                            focus_center = true;
1668                        }
1669                    }
1670                    panel
1671                });
1672
1673                if focus_center {
1674                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1675                }
1676
1677                self.serialize_workspace(cx);
1678                cx.notify();
1679                return panel;
1680            }
1681        }
1682        None
1683    }
1684
1685    /// Open the panel of the given type
1686    pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1687        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1688            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1689                dock.update(cx, |dock, cx| {
1690                    dock.activate_panel(panel_index, cx);
1691                    dock.set_open(true, cx);
1692                });
1693            }
1694        }
1695    }
1696
1697    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1698        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1699            let dock = dock.read(cx);
1700            if let Some(panel) = dock.panel::<T>() {
1701                return Some(panel);
1702            }
1703        }
1704        None
1705    }
1706
1707    fn dismiss_zoomed_items_to_reveal(
1708        &mut self,
1709        dock_to_reveal: Option<DockPosition>,
1710        cx: &mut ViewContext<Self>,
1711    ) {
1712        // If a center pane is zoomed, unzoom it.
1713        for pane in &self.panes {
1714            if pane != &self.active_pane || dock_to_reveal.is_some() {
1715                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1716            }
1717        }
1718
1719        // If another dock is zoomed, hide it.
1720        let mut focus_center = false;
1721        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1722            dock.update(cx, |dock, cx| {
1723                if Some(dock.position()) != dock_to_reveal {
1724                    if let Some(panel) = dock.active_panel() {
1725                        if panel.is_zoomed(cx) {
1726                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
1727                            dock.set_open(false, cx);
1728                        }
1729                    }
1730                }
1731            });
1732        }
1733
1734        if focus_center {
1735            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1736        }
1737
1738        if self.zoomed_position != dock_to_reveal {
1739            self.zoomed = None;
1740            self.zoomed_position = None;
1741        }
1742
1743        cx.notify();
1744    }
1745
1746    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1747        let pane = cx.new_view(|cx| {
1748            Pane::new(
1749                self.weak_handle(),
1750                self.project.clone(),
1751                self.pane_history_timestamp.clone(),
1752                None,
1753                cx,
1754            )
1755        });
1756        cx.subscribe(&pane, Self::handle_pane_event).detach();
1757        self.panes.push(pane.clone());
1758        cx.focus_view(&pane);
1759        cx.emit(Event::PaneAdded(pane.clone()));
1760        pane
1761    }
1762
1763    pub fn add_item_to_center(
1764        &mut self,
1765        item: Box<dyn ItemHandle>,
1766        cx: &mut ViewContext<Self>,
1767    ) -> bool {
1768        if let Some(center_pane) = self.last_active_center_pane.clone() {
1769            if let Some(center_pane) = center_pane.upgrade() {
1770                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1771                true
1772            } else {
1773                false
1774            }
1775        } else {
1776            false
1777        }
1778    }
1779
1780    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut WindowContext) {
1781        if let Some(text) = item.telemetry_event_text(cx) {
1782            self.client()
1783                .telemetry()
1784                .report_app_event(format!("{}: open", text));
1785        }
1786
1787        self.active_pane
1788            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1789    }
1790
1791    pub fn split_item(
1792        &mut self,
1793        split_direction: SplitDirection,
1794        item: Box<dyn ItemHandle>,
1795        cx: &mut ViewContext<Self>,
1796    ) {
1797        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1798        new_pane.update(cx, move |new_pane, cx| {
1799            new_pane.add_item(item, true, true, None, cx)
1800        })
1801    }
1802
1803    pub fn open_abs_path(
1804        &mut self,
1805        abs_path: PathBuf,
1806        visible: bool,
1807        cx: &mut ViewContext<Self>,
1808    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1809        cx.spawn(|workspace, mut cx| async move {
1810            let open_paths_task_result = workspace
1811                .update(&mut cx, |workspace, cx| {
1812                    workspace.open_paths(
1813                        vec![abs_path.clone()],
1814                        if visible {
1815                            OpenVisible::All
1816                        } else {
1817                            OpenVisible::None
1818                        },
1819                        None,
1820                        cx,
1821                    )
1822                })
1823                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1824                .await;
1825            anyhow::ensure!(
1826                open_paths_task_result.len() == 1,
1827                "open abs path {abs_path:?} task returned incorrect number of results"
1828            );
1829            match open_paths_task_result
1830                .into_iter()
1831                .next()
1832                .expect("ensured single task result")
1833            {
1834                Some(open_result) => {
1835                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1836                }
1837                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1838            }
1839        })
1840    }
1841
1842    pub fn split_abs_path(
1843        &mut self,
1844        abs_path: PathBuf,
1845        visible: bool,
1846        cx: &mut ViewContext<Self>,
1847    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1848        let project_path_task =
1849            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1850        cx.spawn(|this, mut cx| async move {
1851            let (_, path) = project_path_task.await?;
1852            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1853                .await
1854        })
1855    }
1856
1857    pub fn open_path(
1858        &mut self,
1859        path: impl Into<ProjectPath>,
1860        pane: Option<WeakView<Pane>>,
1861        focus_item: bool,
1862        cx: &mut WindowContext,
1863    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1864        let pane = pane.unwrap_or_else(|| {
1865            self.last_active_center_pane.clone().unwrap_or_else(|| {
1866                self.panes
1867                    .first()
1868                    .expect("There must be an active pane")
1869                    .downgrade()
1870            })
1871        });
1872
1873        let task = self.load_path(path.into(), cx);
1874        cx.spawn(move |mut cx| async move {
1875            let (project_entry_id, build_item) = task.await?;
1876            pane.update(&mut cx, |pane, cx| {
1877                pane.open_item(project_entry_id, focus_item, cx, build_item)
1878            })
1879        })
1880    }
1881
1882    pub fn split_path(
1883        &mut self,
1884        path: impl Into<ProjectPath>,
1885        cx: &mut ViewContext<Self>,
1886    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1887        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1888            self.panes
1889                .first()
1890                .expect("There must be an active pane")
1891                .downgrade()
1892        });
1893
1894        if let Member::Pane(center_pane) = &self.center.root {
1895            if center_pane.read(cx).items_len() == 0 {
1896                return self.open_path(path, Some(pane), true, cx);
1897            }
1898        }
1899
1900        let task = self.load_path(path.into(), cx);
1901        cx.spawn(|this, mut cx| async move {
1902            let (project_entry_id, build_item) = task.await?;
1903            this.update(&mut cx, move |this, cx| -> Option<_> {
1904                let pane = pane.upgrade()?;
1905                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
1906                new_pane.update(cx, |new_pane, cx| {
1907                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
1908                })
1909            })
1910            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
1911        })
1912    }
1913
1914    fn load_path(
1915        &mut self,
1916        path: ProjectPath,
1917        cx: &mut WindowContext,
1918    ) -> Task<
1919        Result<(
1920            Option<ProjectEntryId>,
1921            impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1922        )>,
1923    > {
1924        let project = self.project().clone();
1925        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1926        cx.spawn(|mut cx| async move {
1927            let (project_entry_id, project_item) = project_item.await?;
1928            let build_item = cx.update(|cx| {
1929                cx.default_global::<ProjectItemBuilders>()
1930                    .get(&project_item.entity_type())
1931                    .ok_or_else(|| anyhow!("no item builder for project item"))
1932                    .cloned()
1933            })??;
1934            let build_item =
1935                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1936            Ok((project_entry_id, build_item))
1937        })
1938    }
1939
1940    pub fn open_project_item<T>(
1941        &mut self,
1942        project_item: Model<T::Item>,
1943        cx: &mut ViewContext<Self>,
1944    ) -> View<T>
1945    where
1946        T: ProjectItem,
1947    {
1948        use project::Item as _;
1949
1950        let entry_id = project_item.read(cx).entry_id(cx);
1951        if let Some(item) = entry_id
1952            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1953            .and_then(|item| item.downcast())
1954        {
1955            self.activate_item(&item, cx);
1956            return item;
1957        }
1958
1959        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1960        self.add_item(Box::new(item.clone()), cx);
1961        item
1962    }
1963
1964    pub fn split_project_item<T>(
1965        &mut self,
1966        project_item: Model<T::Item>,
1967        cx: &mut ViewContext<Self>,
1968    ) -> View<T>
1969    where
1970        T: ProjectItem,
1971    {
1972        use project::Item as _;
1973
1974        let entry_id = project_item.read(cx).entry_id(cx);
1975        if let Some(item) = entry_id
1976            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1977            .and_then(|item| item.downcast())
1978        {
1979            self.activate_item(&item, cx);
1980            return item;
1981        }
1982
1983        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1984        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
1985        item
1986    }
1987
1988    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1989        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1990            self.active_pane.update(cx, |pane, cx| {
1991                pane.add_item(Box::new(shared_screen), false, true, None, cx)
1992            });
1993        }
1994    }
1995
1996    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
1997        let result = self.panes.iter().find_map(|pane| {
1998            pane.read(cx)
1999                .index_for_item(item)
2000                .map(|ix| (pane.clone(), ix))
2001        });
2002        if let Some((pane, ix)) = result {
2003            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2004            true
2005        } else {
2006            false
2007        }
2008    }
2009
2010    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2011        let panes = self.center.panes();
2012        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2013            cx.focus_view(&pane);
2014        } else {
2015            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2016        }
2017    }
2018
2019    pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
2020        let panes = self.center.panes();
2021        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2022            let next_ix = (ix + 1) % panes.len();
2023            let next_pane = panes[next_ix].clone();
2024            cx.focus_view(&next_pane);
2025        }
2026    }
2027
2028    pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
2029        let panes = self.center.panes();
2030        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2031            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2032            let prev_pane = panes[prev_ix].clone();
2033            cx.focus_view(&prev_pane);
2034        }
2035    }
2036
2037    pub fn activate_pane_in_direction(
2038        &mut self,
2039        direction: SplitDirection,
2040        cx: &mut WindowContext,
2041    ) {
2042        if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2043            cx.focus_view(pane);
2044        }
2045    }
2046
2047    pub fn swap_pane_in_direction(
2048        &mut self,
2049        direction: SplitDirection,
2050        cx: &mut ViewContext<Self>,
2051    ) {
2052        if let Some(to) = self
2053            .find_pane_in_direction(direction, cx)
2054            .map(|pane| pane.clone())
2055        {
2056            self.center.swap(&self.active_pane.clone(), &to);
2057            cx.notify();
2058        }
2059    }
2060
2061    fn find_pane_in_direction(
2062        &mut self,
2063        direction: SplitDirection,
2064        cx: &AppContext,
2065    ) -> Option<&View<Pane>> {
2066        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2067            return None;
2068        };
2069        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2070        let center = match cursor {
2071            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2072            _ => bounding_box.center(),
2073        };
2074
2075        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2076
2077        let target = match direction {
2078            SplitDirection::Left => {
2079                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2080            }
2081            SplitDirection::Right => {
2082                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2083            }
2084            SplitDirection::Up => {
2085                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2086            }
2087            SplitDirection::Down => {
2088                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2089            }
2090        };
2091        self.center.pane_at_pixel_position(target)
2092    }
2093
2094    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2095        if self.active_pane != pane {
2096            self.active_pane = pane.clone();
2097            self.status_bar.update(cx, |status_bar, cx| {
2098                status_bar.set_active_pane(&self.active_pane, cx);
2099            });
2100            self.active_item_path_changed(cx);
2101            self.last_active_center_pane = Some(pane.downgrade());
2102        }
2103
2104        self.dismiss_zoomed_items_to_reveal(None, cx);
2105        if pane.read(cx).is_zoomed() {
2106            self.zoomed = Some(pane.downgrade().into());
2107        } else {
2108            self.zoomed = None;
2109        }
2110        self.zoomed_position = None;
2111        self.update_active_view_for_followers(cx);
2112
2113        cx.notify();
2114    }
2115
2116    fn handle_pane_event(
2117        &mut self,
2118        pane: View<Pane>,
2119        event: &pane::Event,
2120        cx: &mut ViewContext<Self>,
2121    ) {
2122        match event {
2123            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2124            pane::Event::Split(direction) => {
2125                self.split_and_clone(pane, *direction, cx);
2126            }
2127            pane::Event::Remove => self.remove_pane(pane, cx),
2128            pane::Event::ActivateItem { local } => {
2129                if *local {
2130                    self.unfollow(&pane, cx);
2131                }
2132                if &pane == self.active_pane() {
2133                    self.active_item_path_changed(cx);
2134                    self.update_active_view_for_followers(cx);
2135                }
2136            }
2137            pane::Event::ChangeItemTitle => {
2138                if pane == self.active_pane {
2139                    self.active_item_path_changed(cx);
2140                }
2141                self.update_window_edited(cx);
2142            }
2143            pane::Event::RemoveItem { item_id } => {
2144                self.update_window_edited(cx);
2145                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2146                    if entry.get().entity_id() == pane.entity_id() {
2147                        entry.remove();
2148                    }
2149                }
2150            }
2151            pane::Event::Focus => {
2152                self.handle_pane_focused(pane.clone(), cx);
2153            }
2154            pane::Event::ZoomIn => {
2155                if pane == self.active_pane {
2156                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2157                    if pane.read(cx).has_focus(cx) {
2158                        self.zoomed = Some(pane.downgrade().into());
2159                        self.zoomed_position = None;
2160                    }
2161                    cx.notify();
2162                }
2163            }
2164            pane::Event::ZoomOut => {
2165                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2166                if self.zoomed_position.is_none() {
2167                    self.zoomed = None;
2168                }
2169                cx.notify();
2170            }
2171        }
2172
2173        self.serialize_workspace(cx);
2174    }
2175
2176    pub fn split_pane(
2177        &mut self,
2178        pane_to_split: View<Pane>,
2179        split_direction: SplitDirection,
2180        cx: &mut ViewContext<Self>,
2181    ) -> View<Pane> {
2182        let new_pane = self.add_pane(cx);
2183        self.center
2184            .split(&pane_to_split, &new_pane, split_direction)
2185            .unwrap();
2186        cx.notify();
2187        new_pane
2188    }
2189
2190    pub fn split_and_clone(
2191        &mut self,
2192        pane: View<Pane>,
2193        direction: SplitDirection,
2194        cx: &mut ViewContext<Self>,
2195    ) -> Option<View<Pane>> {
2196        let item = pane.read(cx).active_item()?;
2197        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2198            let new_pane = self.add_pane(cx);
2199            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2200            self.center.split(&pane, &new_pane, direction).unwrap();
2201            Some(new_pane)
2202        } else {
2203            None
2204        };
2205        cx.notify();
2206        maybe_pane_handle
2207    }
2208
2209    pub fn split_pane_with_item(
2210        &mut self,
2211        pane_to_split: WeakView<Pane>,
2212        split_direction: SplitDirection,
2213        from: WeakView<Pane>,
2214        item_id_to_move: EntityId,
2215        cx: &mut ViewContext<Self>,
2216    ) {
2217        let Some(pane_to_split) = pane_to_split.upgrade() else {
2218            return;
2219        };
2220        let Some(from) = from.upgrade() else {
2221            return;
2222        };
2223
2224        let new_pane = self.add_pane(cx);
2225        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2226        self.center
2227            .split(&pane_to_split, &new_pane, split_direction)
2228            .unwrap();
2229        cx.notify();
2230    }
2231
2232    pub fn split_pane_with_project_entry(
2233        &mut self,
2234        pane_to_split: WeakView<Pane>,
2235        split_direction: SplitDirection,
2236        project_entry: ProjectEntryId,
2237        cx: &mut ViewContext<Self>,
2238    ) -> Option<Task<Result<()>>> {
2239        let pane_to_split = pane_to_split.upgrade()?;
2240        let new_pane = self.add_pane(cx);
2241        self.center
2242            .split(&pane_to_split, &new_pane, split_direction)
2243            .unwrap();
2244
2245        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2246        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2247        Some(cx.foreground_executor().spawn(async move {
2248            task.await?;
2249            Ok(())
2250        }))
2251    }
2252
2253    pub fn move_item(
2254        &mut self,
2255        source: View<Pane>,
2256        destination: View<Pane>,
2257        item_id_to_move: EntityId,
2258        destination_index: usize,
2259        cx: &mut ViewContext<Self>,
2260    ) {
2261        let Some((item_ix, item_handle)) = source
2262            .read(cx)
2263            .items()
2264            .enumerate()
2265            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move)
2266        else {
2267            // Tab was closed during drag
2268            return;
2269        };
2270
2271        let item_handle = item_handle.clone();
2272
2273        if source != destination {
2274            // Close item from previous pane
2275            source.update(cx, |source, cx| {
2276                source.remove_item(item_ix, false, cx);
2277            });
2278        }
2279
2280        // This automatically removes duplicate items in the pane
2281        destination.update(cx, |destination, cx| {
2282            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2283            destination.focus(cx)
2284        });
2285    }
2286
2287    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2288        if self.center.remove(&pane).unwrap() {
2289            self.force_remove_pane(&pane, cx);
2290            self.unfollow(&pane, cx);
2291            self.last_leaders_by_pane.remove(&pane.downgrade());
2292            for removed_item in pane.read(cx).items() {
2293                self.panes_by_item.remove(&removed_item.item_id());
2294            }
2295
2296            cx.notify();
2297        } else {
2298            self.active_item_path_changed(cx);
2299        }
2300    }
2301
2302    pub fn panes(&self) -> &[View<Pane>] {
2303        &self.panes
2304    }
2305
2306    pub fn active_pane(&self) -> &View<Pane> {
2307        &self.active_pane
2308    }
2309
2310    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2311        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2312        weak_pane.upgrade()
2313    }
2314
2315    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2316        self.follower_states.retain(|_, state| {
2317            if state.leader_id == peer_id {
2318                for item in state.items_by_leader_view_id.values() {
2319                    item.set_leader_peer_id(None, cx);
2320                }
2321                false
2322            } else {
2323                true
2324            }
2325        });
2326        cx.notify();
2327    }
2328
2329    pub fn start_following(
2330        &mut self,
2331        leader_id: PeerId,
2332        cx: &mut ViewContext<Self>,
2333    ) -> Option<Task<Result<()>>> {
2334        let pane = self.active_pane().clone();
2335
2336        self.last_leaders_by_pane
2337            .insert(pane.downgrade(), leader_id);
2338        self.unfollow(&pane, cx);
2339        self.follower_states.insert(
2340            pane.clone(),
2341            FollowerState {
2342                leader_id,
2343                active_view_id: None,
2344                items_by_leader_view_id: Default::default(),
2345            },
2346        );
2347        cx.notify();
2348
2349        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2350        let project_id = self.project.read(cx).remote_id();
2351        let request = self.app_state.client.request(proto::Follow {
2352            room_id,
2353            project_id,
2354            leader_id: Some(leader_id),
2355        });
2356
2357        Some(cx.spawn(|this, mut cx| async move {
2358            let response = request.await?;
2359            this.update(&mut cx, |this, _| {
2360                let state = this
2361                    .follower_states
2362                    .get_mut(&pane)
2363                    .ok_or_else(|| anyhow!("following interrupted"))?;
2364                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2365                    Some(ViewId::from_proto(active_view_id)?)
2366                } else {
2367                    None
2368                };
2369                Ok::<_, anyhow::Error>(())
2370            })??;
2371            Self::add_views_from_leader(
2372                this.clone(),
2373                leader_id,
2374                vec![pane],
2375                response.views,
2376                &mut cx,
2377            )
2378            .await?;
2379            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2380            Ok(())
2381        }))
2382    }
2383
2384    pub fn follow_next_collaborator(
2385        &mut self,
2386        _: &FollowNextCollaborator,
2387        cx: &mut ViewContext<Self>,
2388    ) {
2389        let collaborators = self.project.read(cx).collaborators();
2390        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2391            let mut collaborators = collaborators.keys().copied();
2392            for peer_id in collaborators.by_ref() {
2393                if peer_id == leader_id {
2394                    break;
2395                }
2396            }
2397            collaborators.next()
2398        } else if let Some(last_leader_id) =
2399            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2400        {
2401            if collaborators.contains_key(last_leader_id) {
2402                Some(*last_leader_id)
2403            } else {
2404                None
2405            }
2406        } else {
2407            None
2408        };
2409
2410        let pane = self.active_pane.clone();
2411        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2412        else {
2413            return;
2414        };
2415        if Some(leader_id) == self.unfollow(&pane, cx) {
2416            return;
2417        }
2418        self.start_following(leader_id, cx)
2419            .map(|task| task.detach_and_log_err(cx));
2420    }
2421
2422    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2423        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2424            return;
2425        };
2426        let room = room.read(cx);
2427        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2428            return;
2429        };
2430
2431        let project = self.project.read(cx);
2432
2433        let other_project_id = match remote_participant.location {
2434            call::ParticipantLocation::External => None,
2435            call::ParticipantLocation::UnsharedProject => None,
2436            call::ParticipantLocation::SharedProject { project_id } => {
2437                if Some(project_id) == project.remote_id() {
2438                    None
2439                } else {
2440                    Some(project_id)
2441                }
2442            }
2443        };
2444
2445        // if they are active in another project, follow there.
2446        if let Some(project_id) = other_project_id {
2447            let app_state = self.app_state.clone();
2448            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
2449                .detach_and_log_err(cx);
2450        }
2451
2452        // if you're already following, find the right pane and focus it.
2453        for (pane, state) in &self.follower_states {
2454            if leader_id == state.leader_id {
2455                cx.focus_view(pane);
2456                return;
2457            }
2458        }
2459
2460        // Otherwise, follow.
2461        self.start_following(leader_id, cx)
2462            .map(|task| task.detach_and_log_err(cx));
2463    }
2464
2465    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2466        let state = self.follower_states.remove(pane)?;
2467        let leader_id = state.leader_id;
2468        for (_, item) in state.items_by_leader_view_id {
2469            item.set_leader_peer_id(None, cx);
2470        }
2471
2472        if self
2473            .follower_states
2474            .values()
2475            .all(|state| state.leader_id != state.leader_id)
2476        {
2477            let project_id = self.project.read(cx).remote_id();
2478            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2479            self.app_state
2480                .client
2481                .send(proto::Unfollow {
2482                    room_id,
2483                    project_id,
2484                    leader_id: Some(leader_id),
2485                })
2486                .log_err();
2487        }
2488
2489        cx.notify();
2490        Some(leader_id)
2491    }
2492
2493    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2494        self.follower_states
2495            .values()
2496            .any(|state| state.leader_id == peer_id)
2497    }
2498
2499    fn active_item_path_changed(&mut self, cx: &mut WindowContext) {
2500        let active_entry = self.active_project_path(cx);
2501        self.project
2502            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2503        self.update_window_title(cx);
2504    }
2505
2506    fn update_window_title(&mut self, cx: &mut WindowContext) {
2507        let project = self.project().read(cx);
2508        let mut title = String::new();
2509
2510        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2511            let filename = path
2512                .path
2513                .file_name()
2514                .map(|s| s.to_string_lossy())
2515                .or_else(|| {
2516                    Some(Cow::Borrowed(
2517                        project
2518                            .worktree_for_id(path.worktree_id, cx)?
2519                            .read(cx)
2520                            .root_name(),
2521                    ))
2522                });
2523
2524            if let Some(filename) = filename {
2525                title.push_str(filename.as_ref());
2526                title.push_str("");
2527            }
2528        }
2529
2530        for (i, name) in project.worktree_root_names(cx).enumerate() {
2531            if i > 0 {
2532                title.push_str(", ");
2533            }
2534            title.push_str(name);
2535        }
2536
2537        if title.is_empty() {
2538            title = "empty project".to_string();
2539        }
2540
2541        if project.is_remote() {
2542            title.push_str("");
2543        } else if project.is_shared() {
2544            title.push_str("");
2545        }
2546
2547        cx.set_window_title(&title);
2548    }
2549
2550    fn update_window_edited(&mut self, cx: &mut WindowContext) {
2551        let is_edited = !self.project.read(cx).is_disconnected()
2552            && self
2553                .items(cx)
2554                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2555        if is_edited != self.window_edited {
2556            self.window_edited = is_edited;
2557            cx.set_window_edited(self.window_edited)
2558        }
2559    }
2560
2561    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2562        if self.notifications.is_empty() {
2563            None
2564        } else {
2565            Some(
2566                div()
2567                    .absolute()
2568                    .z_index(100)
2569                    .right_3()
2570                    .bottom_3()
2571                    .w_96()
2572                    .h_full()
2573                    .flex()
2574                    .flex_col()
2575                    .justify_end()
2576                    .gap_2()
2577                    .children(
2578                        self.notifications
2579                            .iter()
2580                            .map(|(_, _, notification)| notification.to_any()),
2581                    ),
2582            )
2583        }
2584    }
2585
2586    // RPC handlers
2587
2588    fn handle_follow(
2589        &mut self,
2590        follower_project_id: Option<u64>,
2591        cx: &mut ViewContext<Self>,
2592    ) -> proto::FollowResponse {
2593        let client = &self.app_state.client;
2594        let project_id = self.project.read(cx).remote_id();
2595
2596        let active_view_id = self.active_item(cx).and_then(|i| {
2597            Some(
2598                i.to_followable_item_handle(cx)?
2599                    .remote_id(client, cx)?
2600                    .to_proto(),
2601            )
2602        });
2603
2604        cx.notify();
2605
2606        self.last_active_view_id = active_view_id.clone();
2607        proto::FollowResponse {
2608            active_view_id,
2609            views: self
2610                .panes()
2611                .iter()
2612                .flat_map(|pane| {
2613                    let leader_id = self.leader_for_pane(pane);
2614                    pane.read(cx).items().filter_map({
2615                        let cx = &cx;
2616                        move |item| {
2617                            let item = item.to_followable_item_handle(cx)?;
2618
2619                            // If the item belongs to a particular project, then it should
2620                            // only be included if this project is shared, and the follower
2621                            // is in the project.
2622                            //
2623                            // Some items, like channel notes, do not belong to a particular
2624                            // project, so they should be included regardless of whether the
2625                            // current project is shared, or what project the follower is in.
2626                            if item.is_project_item(cx)
2627                                && (project_id.is_none() || project_id != follower_project_id)
2628                            {
2629                                return None;
2630                            }
2631
2632                            let id = item.remote_id(client, cx)?.to_proto();
2633                            let variant = item.to_state_proto(cx)?;
2634                            Some(proto::View {
2635                                id: Some(id),
2636                                leader_id,
2637                                variant: Some(variant),
2638                            })
2639                        }
2640                    })
2641                })
2642                .collect(),
2643        }
2644    }
2645
2646    fn handle_update_followers(
2647        &mut self,
2648        leader_id: PeerId,
2649        message: proto::UpdateFollowers,
2650        _cx: &mut ViewContext<Self>,
2651    ) {
2652        self.leader_updates_tx
2653            .unbounded_send((leader_id, message))
2654            .ok();
2655    }
2656
2657    async fn process_leader_update(
2658        this: &WeakView<Self>,
2659        leader_id: PeerId,
2660        update: proto::UpdateFollowers,
2661        cx: &mut AsyncWindowContext,
2662    ) -> Result<()> {
2663        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2664            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2665                this.update(cx, |this, _| {
2666                    for (_, state) in &mut this.follower_states {
2667                        if state.leader_id == leader_id {
2668                            state.active_view_id =
2669                                if let Some(active_view_id) = update_active_view.id.clone() {
2670                                    Some(ViewId::from_proto(active_view_id)?)
2671                                } else {
2672                                    None
2673                                };
2674                        }
2675                    }
2676                    anyhow::Ok(())
2677                })??;
2678            }
2679            proto::update_followers::Variant::UpdateView(update_view) => {
2680                let variant = update_view
2681                    .variant
2682                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2683                let id = update_view
2684                    .id
2685                    .ok_or_else(|| anyhow!("missing update view id"))?;
2686                let mut tasks = Vec::new();
2687                this.update(cx, |this, cx| {
2688                    let project = this.project.clone();
2689                    for (_, state) in &mut this.follower_states {
2690                        if state.leader_id == leader_id {
2691                            let view_id = ViewId::from_proto(id.clone())?;
2692                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2693                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2694                            }
2695                        }
2696                    }
2697                    anyhow::Ok(())
2698                })??;
2699                try_join_all(tasks).await.log_err();
2700            }
2701            proto::update_followers::Variant::CreateView(view) => {
2702                let panes = this.update(cx, |this, _| {
2703                    this.follower_states
2704                        .iter()
2705                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2706                        .cloned()
2707                        .collect()
2708                })?;
2709                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2710            }
2711        }
2712        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2713        Ok(())
2714    }
2715
2716    async fn add_views_from_leader(
2717        this: WeakView<Self>,
2718        leader_id: PeerId,
2719        panes: Vec<View<Pane>>,
2720        views: Vec<proto::View>,
2721        cx: &mut AsyncWindowContext,
2722    ) -> Result<()> {
2723        let this = this.upgrade().context("workspace dropped")?;
2724
2725        let item_builders = cx.update(|cx| {
2726            cx.default_global::<FollowableItemBuilders>()
2727                .values()
2728                .map(|b| b.0)
2729                .collect::<Vec<_>>()
2730        })?;
2731
2732        let mut item_tasks_by_pane = HashMap::default();
2733        for pane in panes {
2734            let mut item_tasks = Vec::new();
2735            let mut leader_view_ids = Vec::new();
2736            for view in &views {
2737                let Some(id) = &view.id else { continue };
2738                let id = ViewId::from_proto(id.clone())?;
2739                let mut variant = view.variant.clone();
2740                if variant.is_none() {
2741                    Err(anyhow!("missing view variant"))?;
2742                }
2743                for build_item in &item_builders {
2744                    let task = cx.update(|cx| {
2745                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2746                    })?;
2747                    if let Some(task) = task {
2748                        item_tasks.push(task);
2749                        leader_view_ids.push(id);
2750                        break;
2751                    } else {
2752                        assert!(variant.is_some());
2753                    }
2754                }
2755            }
2756
2757            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2758        }
2759
2760        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2761            let items = futures::future::try_join_all(item_tasks).await?;
2762            this.update(cx, |this, cx| {
2763                let state = this.follower_states.get_mut(&pane)?;
2764                for (id, item) in leader_view_ids.into_iter().zip(items) {
2765                    item.set_leader_peer_id(Some(leader_id), cx);
2766                    state.items_by_leader_view_id.insert(id, item);
2767                }
2768
2769                Some(())
2770            })?;
2771        }
2772        Ok(())
2773    }
2774
2775    fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
2776        let mut is_project_item = true;
2777        let mut update = proto::UpdateActiveView::default();
2778
2779        if let Some(item) = self.active_item(cx) {
2780            if item.focus_handle(cx).contains_focused(cx) {
2781                if let Some(item) = item.to_followable_item_handle(cx) {
2782                    is_project_item = item.is_project_item(cx);
2783                    update = proto::UpdateActiveView {
2784                        id: item
2785                            .remote_id(&self.app_state.client, cx)
2786                            .map(|id| id.to_proto()),
2787                        leader_id: self.leader_for_pane(&self.active_pane),
2788                    };
2789                }
2790            }
2791        }
2792
2793        if update.id != self.last_active_view_id {
2794            self.last_active_view_id = update.id.clone();
2795            self.update_followers(
2796                is_project_item,
2797                proto::update_followers::Variant::UpdateActiveView(update),
2798                cx,
2799            );
2800        }
2801    }
2802
2803    fn update_followers(
2804        &self,
2805        project_only: bool,
2806        update: proto::update_followers::Variant,
2807        cx: &mut WindowContext,
2808    ) -> Option<()> {
2809        // If this update only applies to for followers in the current project,
2810        // then skip it unless this project is shared. If it applies to all
2811        // followers, regardless of project, then set `project_id` to none,
2812        // indicating that it goes to all followers.
2813        let project_id = if project_only {
2814            Some(self.project.read(cx).remote_id()?)
2815        } else {
2816            None
2817        };
2818        self.app_state().workspace_store.update(cx, |store, cx| {
2819            store.update_followers(project_id, update, cx)
2820        })
2821    }
2822
2823    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2824        self.follower_states.get(pane).map(|state| state.leader_id)
2825    }
2826
2827    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2828        cx.notify();
2829
2830        let call = self.active_call()?;
2831        let room = call.read(cx).room()?.read(cx);
2832        let participant = room.remote_participant_for_peer_id(leader_id)?;
2833        let mut items_to_activate = Vec::new();
2834
2835        let leader_in_this_app;
2836        let leader_in_this_project;
2837        match participant.location {
2838            call::ParticipantLocation::SharedProject { project_id } => {
2839                leader_in_this_app = true;
2840                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2841            }
2842            call::ParticipantLocation::UnsharedProject => {
2843                leader_in_this_app = true;
2844                leader_in_this_project = false;
2845            }
2846            call::ParticipantLocation::External => {
2847                leader_in_this_app = false;
2848                leader_in_this_project = false;
2849            }
2850        };
2851
2852        for (pane, state) in &self.follower_states {
2853            if state.leader_id != leader_id {
2854                continue;
2855            }
2856            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2857                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2858                    if leader_in_this_project || !item.is_project_item(cx) {
2859                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2860                    }
2861                }
2862                continue;
2863            }
2864
2865            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2866                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2867            }
2868        }
2869
2870        for (pane, item) in items_to_activate {
2871            let pane_was_focused = pane.read(cx).has_focus(cx);
2872            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2873                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2874            } else {
2875                pane.update(cx, |pane, cx| {
2876                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2877                });
2878            }
2879
2880            if pane_was_focused {
2881                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2882            }
2883        }
2884
2885        None
2886    }
2887
2888    fn shared_screen_for_peer(
2889        &self,
2890        peer_id: PeerId,
2891        pane: &View<Pane>,
2892        cx: &mut WindowContext,
2893    ) -> Option<View<SharedScreen>> {
2894        let call = self.active_call()?;
2895        let room = call.read(cx).room()?.read(cx);
2896        let participant = room.remote_participant_for_peer_id(peer_id)?;
2897        let track = participant.video_tracks.values().next()?.clone();
2898        let user = participant.user.clone();
2899
2900        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2901            if item.read(cx).peer_id == peer_id {
2902                return Some(item);
2903            }
2904        }
2905
2906        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2907    }
2908
2909    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2910        if cx.is_window_active() {
2911            self.update_active_view_for_followers(cx);
2912            cx.background_executor()
2913                .spawn(persistence::DB.update_timestamp(self.database_id()))
2914                .detach();
2915        } else {
2916            for pane in &self.panes {
2917                pane.update(cx, |pane, cx| {
2918                    if let Some(item) = pane.active_item() {
2919                        item.workspace_deactivated(cx);
2920                    }
2921                    if matches!(
2922                        WorkspaceSettings::get_global(cx).autosave,
2923                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2924                    ) {
2925                        for item in pane.items() {
2926                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2927                                .detach_and_log_err(cx);
2928                        }
2929                    }
2930                });
2931            }
2932        }
2933    }
2934
2935    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2936        self.active_call.as_ref().map(|(call, _)| call)
2937    }
2938
2939    fn on_active_call_event(
2940        &mut self,
2941        _: Model<ActiveCall>,
2942        event: &call::room::Event,
2943        cx: &mut ViewContext<Self>,
2944    ) {
2945        match event {
2946            call::room::Event::ParticipantLocationChanged { participant_id }
2947            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2948                self.leader_updated(*participant_id, cx);
2949            }
2950            _ => {}
2951        }
2952    }
2953
2954    pub fn database_id(&self) -> WorkspaceId {
2955        self.database_id
2956    }
2957
2958    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2959        let project = self.project().read(cx);
2960
2961        if project.is_local() {
2962            Some(
2963                project
2964                    .visible_worktrees(cx)
2965                    .map(|worktree| worktree.read(cx).abs_path())
2966                    .collect::<Vec<_>>()
2967                    .into(),
2968            )
2969        } else {
2970            None
2971        }
2972    }
2973
2974    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2975        match member {
2976            Member::Axis(PaneAxis { members, .. }) => {
2977                for child in members.iter() {
2978                    self.remove_panes(child.clone(), cx)
2979                }
2980            }
2981            Member::Pane(pane) => {
2982                self.force_remove_pane(&pane, cx);
2983            }
2984        }
2985    }
2986
2987    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2988        self.panes.retain(|p| p != pane);
2989        self.panes
2990            .last()
2991            .unwrap()
2992            .update(cx, |pane, cx| pane.focus(cx));
2993        if self.last_active_center_pane == Some(pane.downgrade()) {
2994            self.last_active_center_pane = None;
2995        }
2996        cx.notify();
2997    }
2998
2999    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3000        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3001            cx.background_executor()
3002                .timer(Duration::from_millis(100))
3003                .await;
3004            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3005                .log_err();
3006        }));
3007    }
3008
3009    fn serialize_workspace(&self, cx: &mut WindowContext) {
3010        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3011            let (items, active) = {
3012                let pane = pane_handle.read(cx);
3013                let active_item_id = pane.active_item().map(|item| item.item_id());
3014                (
3015                    pane.items()
3016                        .filter_map(|item_handle| {
3017                            Some(SerializedItem {
3018                                kind: Arc::from(item_handle.serialized_item_kind()?),
3019                                item_id: item_handle.item_id().as_u64(),
3020                                active: Some(item_handle.item_id()) == active_item_id,
3021                            })
3022                        })
3023                        .collect::<Vec<_>>(),
3024                    pane.has_focus(cx),
3025                )
3026            };
3027
3028            SerializedPane::new(items, active)
3029        }
3030
3031        fn build_serialized_pane_group(
3032            pane_group: &Member,
3033            cx: &WindowContext,
3034        ) -> SerializedPaneGroup {
3035            match pane_group {
3036                Member::Axis(PaneAxis {
3037                    axis,
3038                    members,
3039                    flexes,
3040                    bounding_boxes: _,
3041                }) => SerializedPaneGroup::Group {
3042                    axis: SerializedAxis(*axis),
3043                    children: members
3044                        .iter()
3045                        .map(|member| build_serialized_pane_group(member, cx))
3046                        .collect::<Vec<_>>(),
3047                    flexes: Some(flexes.lock().clone()),
3048                },
3049                Member::Pane(pane_handle) => {
3050                    SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
3051                }
3052            }
3053        }
3054
3055        fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3056            let left_dock = this.left_dock.read(cx);
3057            let left_visible = left_dock.is_open();
3058            let left_active_panel = left_dock
3059                .visible_panel()
3060                .and_then(|panel| Some(panel.persistent_name().to_string()));
3061            let left_dock_zoom = left_dock
3062                .visible_panel()
3063                .map(|panel| panel.is_zoomed(cx))
3064                .unwrap_or(false);
3065
3066            let right_dock = this.right_dock.read(cx);
3067            let right_visible = right_dock.is_open();
3068            let right_active_panel = right_dock
3069                .visible_panel()
3070                .and_then(|panel| Some(panel.persistent_name().to_string()));
3071            let right_dock_zoom = right_dock
3072                .visible_panel()
3073                .map(|panel| panel.is_zoomed(cx))
3074                .unwrap_or(false);
3075
3076            let bottom_dock = this.bottom_dock.read(cx);
3077            let bottom_visible = bottom_dock.is_open();
3078            let bottom_active_panel = bottom_dock
3079                .visible_panel()
3080                .and_then(|panel| Some(panel.persistent_name().to_string()));
3081            let bottom_dock_zoom = bottom_dock
3082                .visible_panel()
3083                .map(|panel| panel.is_zoomed(cx))
3084                .unwrap_or(false);
3085
3086            DockStructure {
3087                left: DockData {
3088                    visible: left_visible,
3089                    active_panel: left_active_panel,
3090                    zoom: left_dock_zoom,
3091                },
3092                right: DockData {
3093                    visible: right_visible,
3094                    active_panel: right_active_panel,
3095                    zoom: right_dock_zoom,
3096                },
3097                bottom: DockData {
3098                    visible: bottom_visible,
3099                    active_panel: bottom_active_panel,
3100                    zoom: bottom_dock_zoom,
3101                },
3102            }
3103        }
3104
3105        if let Some(location) = self.location(cx) {
3106            // Load bearing special case:
3107            //  - with_local_workspace() relies on this to not have other stuff open
3108            //    when you open your log
3109            if !location.paths().is_empty() {
3110                let center_group = build_serialized_pane_group(&self.center.root, cx);
3111                let docks = build_serialized_docks(self, cx);
3112
3113                let serialized_workspace = SerializedWorkspace {
3114                    id: self.database_id,
3115                    location,
3116                    center_group,
3117                    bounds: Default::default(),
3118                    display: Default::default(),
3119                    docks,
3120                };
3121
3122                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3123                    .detach();
3124            }
3125        }
3126    }
3127
3128    pub(crate) fn load_workspace(
3129        serialized_workspace: SerializedWorkspace,
3130        paths_to_open: Vec<Option<ProjectPath>>,
3131        cx: &mut ViewContext<Workspace>,
3132    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3133        cx.spawn(|workspace, mut cx| async move {
3134            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3135
3136            let mut center_group = None;
3137            let mut center_items = None;
3138
3139            // Traverse the splits tree and add to things
3140            if let Some((group, active_pane, items)) = serialized_workspace
3141                .center_group
3142                .deserialize(
3143                    &project,
3144                    serialized_workspace.id,
3145                    workspace.clone(),
3146                    &mut cx,
3147                )
3148                .await
3149            {
3150                center_items = Some(items);
3151                center_group = Some((group, active_pane))
3152            }
3153
3154            let mut items_by_project_path = cx.update(|cx| {
3155                center_items
3156                    .unwrap_or_default()
3157                    .into_iter()
3158                    .filter_map(|item| {
3159                        let item = item?;
3160                        let project_path = item.project_path(cx)?;
3161                        Some((project_path, item))
3162                    })
3163                    .collect::<HashMap<_, _>>()
3164            })?;
3165
3166            let opened_items = paths_to_open
3167                .into_iter()
3168                .map(|path_to_open| {
3169                    path_to_open
3170                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3171                })
3172                .collect::<Vec<_>>();
3173
3174            // Remove old panes from workspace panes list
3175            workspace.update(&mut cx, |workspace, cx| {
3176                if let Some((center_group, active_pane)) = center_group {
3177                    workspace.remove_panes(workspace.center.root.clone(), cx);
3178
3179                    // Swap workspace center group
3180                    workspace.center = PaneGroup::with_root(center_group);
3181                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3182                    if let Some(active_pane) = active_pane {
3183                        workspace.active_pane = active_pane;
3184                        cx.focus_self();
3185                    } else {
3186                        workspace.active_pane = workspace.center.first_pane().clone();
3187                    }
3188                }
3189
3190                let docks = serialized_workspace.docks;
3191
3192                let right = docks.right.clone();
3193                workspace
3194                    .right_dock
3195                    .update(cx, |dock, _| dock.serialized_dock = Some(right));
3196                let left = docks.left.clone();
3197                workspace
3198                    .left_dock
3199                    .update(cx, |dock, _| dock.serialized_dock = Some(left));
3200                let bottom = docks.bottom.clone();
3201                workspace
3202                    .bottom_dock
3203                    .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
3204
3205                cx.notify();
3206            })?;
3207
3208            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3209            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3210
3211            Ok(opened_items)
3212        })
3213    }
3214
3215    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3216        self.add_workspace_actions_listeners(div, cx)
3217            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3218            .on_action(cx.listener(Self::close_all_items_and_panes))
3219            .on_action(cx.listener(Self::save_all))
3220            .on_action(cx.listener(Self::add_folder_to_project))
3221            .on_action(cx.listener(Self::follow_next_collaborator))
3222            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3223                let pane = workspace.active_pane().clone();
3224                workspace.unfollow(&pane, cx);
3225            }))
3226            .on_action(cx.listener(|workspace, action: &Save, cx| {
3227                workspace
3228                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3229                    .detach_and_log_err(cx);
3230            }))
3231            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3232                workspace
3233                    .save_active_item(SaveIntent::SaveAs, cx)
3234                    .detach_and_log_err(cx);
3235            }))
3236            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3237                workspace.activate_previous_pane(cx)
3238            }))
3239            .on_action(
3240                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3241            )
3242            .on_action(
3243                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3244                    workspace.activate_pane_in_direction(action.0, cx)
3245                }),
3246            )
3247            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3248                workspace.swap_pane_in_direction(action.0, cx)
3249            }))
3250            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3251                this.toggle_dock(DockPosition::Left, cx);
3252            }))
3253            .on_action(
3254                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3255                    workspace.toggle_dock(DockPosition::Right, cx);
3256                }),
3257            )
3258            .on_action(
3259                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3260                    workspace.toggle_dock(DockPosition::Bottom, cx);
3261                }),
3262            )
3263            .on_action(
3264                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3265                    workspace.close_all_docks(cx);
3266                }),
3267            )
3268            .on_action(cx.listener(Workspace::open))
3269            .on_action(cx.listener(Workspace::close_window))
3270            .on_action(cx.listener(Workspace::activate_pane_at_index))
3271            .on_action(
3272                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3273                    workspace.reopen_closed_item(cx).detach();
3274                }),
3275            )
3276    }
3277
3278    #[cfg(any(test, feature = "test-support"))]
3279    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3280        use node_runtime::FakeNodeRuntime;
3281
3282        let client = project.read(cx).client();
3283        let user_store = project.read(cx).user_store();
3284
3285        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3286        cx.activate_window();
3287        let app_state = Arc::new(AppState {
3288            languages: project.read(cx).languages().clone(),
3289            workspace_store,
3290            client,
3291            user_store,
3292            fs: project.read(cx).fs().clone(),
3293            build_window_options: |_, _, _| Default::default(),
3294            node_runtime: FakeNodeRuntime::new(),
3295        });
3296        let workspace = Self::new(0, project, app_state, cx);
3297        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3298        workspace
3299    }
3300
3301    pub fn register_action<A: Action>(
3302        &mut self,
3303        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3304    ) -> &mut Self {
3305        let callback = Arc::new(callback);
3306
3307        self.workspace_actions.push(Box::new(move |div, cx| {
3308            let callback = callback.clone();
3309            div.on_action(
3310                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3311            )
3312        }));
3313        self
3314    }
3315
3316    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3317        let mut div = div
3318            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3319            .on_action(cx.listener(Self::close_all_items_and_panes))
3320            .on_action(cx.listener(Self::add_folder_to_project))
3321            .on_action(cx.listener(Self::save_all))
3322            .on_action(cx.listener(Self::open));
3323        for action in self.workspace_actions.iter() {
3324            div = (action)(div, cx)
3325        }
3326        div
3327    }
3328
3329    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3330        self.modal_layer.read(cx).has_active_modal()
3331    }
3332
3333    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3334        self.modal_layer.read(cx).active_modal()
3335    }
3336
3337    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3338    where
3339        B: FnOnce(&mut ViewContext<V>) -> V,
3340    {
3341        self.modal_layer
3342            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3343    }
3344}
3345
3346fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3347    let display_origin = cx
3348        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3349        .ok()??;
3350    ZED_WINDOW_POSITION
3351        .zip(*ZED_WINDOW_SIZE)
3352        .map(|(position, size)| {
3353            WindowBounds::Fixed(Bounds {
3354                origin: display_origin + position,
3355                size,
3356            })
3357        })
3358}
3359
3360fn open_items(
3361    serialized_workspace: Option<SerializedWorkspace>,
3362    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3363    app_state: Arc<AppState>,
3364    cx: &mut ViewContext<Workspace>,
3365) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3366    let restored_items = serialized_workspace.map(|serialized_workspace| {
3367        Workspace::load_workspace(
3368            serialized_workspace,
3369            project_paths_to_open
3370                .iter()
3371                .map(|(_, project_path)| project_path)
3372                .cloned()
3373                .collect(),
3374            cx,
3375        )
3376    });
3377
3378    cx.spawn(|workspace, mut cx| async move {
3379        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3380
3381        if let Some(restored_items) = restored_items {
3382            let restored_items = restored_items.await?;
3383
3384            let restored_project_paths = restored_items
3385                .iter()
3386                .filter_map(|item| {
3387                    cx.update(|cx| item.as_ref()?.project_path(cx))
3388                        .ok()
3389                        .flatten()
3390                })
3391                .collect::<HashSet<_>>();
3392
3393            for restored_item in restored_items {
3394                opened_items.push(restored_item.map(Ok));
3395            }
3396
3397            project_paths_to_open
3398                .iter_mut()
3399                .for_each(|(_, project_path)| {
3400                    if let Some(project_path_to_open) = project_path {
3401                        if restored_project_paths.contains(project_path_to_open) {
3402                            *project_path = None;
3403                        }
3404                    }
3405                });
3406        } else {
3407            for _ in 0..project_paths_to_open.len() {
3408                opened_items.push(None);
3409            }
3410        }
3411        assert!(opened_items.len() == project_paths_to_open.len());
3412
3413        let tasks =
3414            project_paths_to_open
3415                .into_iter()
3416                .enumerate()
3417                .map(|(i, (abs_path, project_path))| {
3418                    let workspace = workspace.clone();
3419                    cx.spawn(|mut cx| {
3420                        let fs = app_state.fs.clone();
3421                        async move {
3422                            let file_project_path = project_path?;
3423                            if fs.is_file(&abs_path).await {
3424                                Some((
3425                                    i,
3426                                    workspace
3427                                        .update(&mut cx, |workspace, cx| {
3428                                            workspace.open_path(file_project_path, None, true, cx)
3429                                        })
3430                                        .log_err()?
3431                                        .await,
3432                                ))
3433                            } else {
3434                                None
3435                            }
3436                        }
3437                    })
3438                });
3439
3440        let tasks = tasks.collect::<Vec<_>>();
3441
3442        let tasks = futures::future::join_all(tasks.into_iter());
3443        for maybe_opened_path in tasks.await.into_iter() {
3444            if let Some((i, path_open_result)) = maybe_opened_path {
3445                opened_items[i] = Some(path_open_result);
3446            }
3447        }
3448
3449        Ok(opened_items)
3450    })
3451}
3452
3453fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3454    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3455
3456    workspace
3457        .update(cx, |workspace, cx| {
3458            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3459                workspace.show_notification_once(0, cx, |cx| {
3460                    cx.new_view(|_| {
3461                        MessageNotification::new("Failed to load the database file.")
3462                            .with_click_message("Click to let us know about this error")
3463                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3464                    })
3465                });
3466            }
3467        })
3468        .log_err();
3469}
3470
3471impl FocusableView for Workspace {
3472    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3473        self.active_pane.focus_handle(cx)
3474    }
3475}
3476
3477#[derive(Clone, Render)]
3478struct DraggedDock(DockPosition);
3479
3480impl Render for Workspace {
3481    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3482        let mut context = KeyContext::default();
3483        context.add("Workspace");
3484
3485        let (ui_font, ui_font_size) = {
3486            let theme_settings = ThemeSettings::get_global(cx);
3487            (
3488                theme_settings.ui_font.family.clone(),
3489                theme_settings.ui_font_size.clone(),
3490            )
3491        };
3492
3493        let theme = cx.theme().clone();
3494        let colors = theme.colors();
3495        cx.set_rem_size(ui_font_size);
3496
3497        self.actions(div(), cx)
3498            .key_context(context)
3499            .relative()
3500            .size_full()
3501            .flex()
3502            .flex_col()
3503            .font(ui_font)
3504            .gap_0()
3505            .justify_start()
3506            .items_start()
3507            .text_color(colors.text)
3508            .bg(colors.background)
3509            .border()
3510            .border_color(colors.border)
3511            .children(self.titlebar_item.clone())
3512            .child(
3513                div()
3514                    .id("workspace")
3515                    .relative()
3516                    .flex_1()
3517                    .w_full()
3518                    .flex()
3519                    .flex_col()
3520                    .overflow_hidden()
3521                    .border_t()
3522                    .border_b()
3523                    .border_color(colors.border)
3524                    .child(
3525                        canvas({
3526                            let this = cx.view().clone();
3527                            move |bounds, cx| {
3528                                this.update(cx, |this, _cx| {
3529                                    this.bounds = *bounds;
3530                                })
3531                            }
3532                        })
3533                        .absolute()
3534                        .size_full(),
3535                    )
3536                    .on_drag_move(
3537                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3538                            match e.drag(cx).0 {
3539                                DockPosition::Left => {
3540                                    let size = workspace.bounds.left() + e.event.position.x;
3541                                    workspace.left_dock.update(cx, |left_dock, cx| {
3542                                        left_dock.resize_active_panel(Some(size), cx);
3543                                    });
3544                                }
3545                                DockPosition::Right => {
3546                                    let size = workspace.bounds.right() - e.event.position.x;
3547                                    workspace.right_dock.update(cx, |right_dock, cx| {
3548                                        right_dock.resize_active_panel(Some(size), cx);
3549                                    });
3550                                }
3551                                DockPosition::Bottom => {
3552                                    let size = workspace.bounds.bottom() - e.event.position.y;
3553                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3554                                        bottom_dock.resize_active_panel(Some(size), cx);
3555                                    });
3556                                }
3557                            }
3558                        }),
3559                    )
3560                    .child(self.modal_layer.clone())
3561                    .child(
3562                        div()
3563                            .flex()
3564                            .flex_row()
3565                            .h_full()
3566                            // Left Dock
3567                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3568                                || {
3569                                    div()
3570                                        .flex()
3571                                        .flex_none()
3572                                        .overflow_hidden()
3573                                        .child(self.left_dock.clone())
3574                                },
3575                            ))
3576                            // Panes
3577                            .child(
3578                                div()
3579                                    .flex()
3580                                    .flex_col()
3581                                    .flex_1()
3582                                    .overflow_hidden()
3583                                    .child(self.center.render(
3584                                        &self.project,
3585                                        &self.follower_states,
3586                                        self.active_call(),
3587                                        &self.active_pane,
3588                                        self.zoomed.as_ref(),
3589                                        &self.app_state,
3590                                        cx,
3591                                    ))
3592                                    .children(
3593                                        self.zoomed_position
3594                                            .ne(&Some(DockPosition::Bottom))
3595                                            .then(|| self.bottom_dock.clone()),
3596                                    ),
3597                            )
3598                            // Right Dock
3599                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3600                                || {
3601                                    div()
3602                                        .flex()
3603                                        .flex_none()
3604                                        .overflow_hidden()
3605                                        .child(self.right_dock.clone())
3606                                },
3607                            )),
3608                    )
3609                    .children(self.render_notifications(cx))
3610                    .children(self.zoomed.as_ref().and_then(|view| {
3611                        let zoomed_view = view.upgrade()?;
3612                        let div = div()
3613                            .z_index(1)
3614                            .absolute()
3615                            .overflow_hidden()
3616                            .border_color(colors.border)
3617                            .bg(colors.background)
3618                            .child(zoomed_view)
3619                            .inset_0()
3620                            .shadow_lg();
3621
3622                        Some(match self.zoomed_position {
3623                            Some(DockPosition::Left) => div.right_2().border_r(),
3624                            Some(DockPosition::Right) => div.left_2().border_l(),
3625                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3626                            None => div.top_2().bottom_2().left_2().right_2().border(),
3627                        })
3628                    })),
3629            )
3630            .child(self.status_bar.clone())
3631            .children(if self.project.read(cx).is_disconnected() {
3632                Some(DisconnectedOverlay)
3633            } else {
3634                None
3635            })
3636    }
3637}
3638
3639impl WorkspaceStore {
3640    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3641        Self {
3642            workspaces: Default::default(),
3643            followers: Default::default(),
3644            _subscriptions: vec![
3645                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3646                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3647                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3648            ],
3649            client,
3650        }
3651    }
3652
3653    pub fn update_followers(
3654        &self,
3655        project_id: Option<u64>,
3656        update: proto::update_followers::Variant,
3657        cx: &AppContext,
3658    ) -> Option<()> {
3659        let active_call = cx.try_global::<Model<ActiveCall>>()?;
3660        let room_id = active_call.read(cx).room()?.read(cx).id();
3661        let follower_ids: Vec<_> = self
3662            .followers
3663            .iter()
3664            .filter_map(|follower| {
3665                if follower.project_id == project_id || project_id.is_none() {
3666                    Some(follower.peer_id.into())
3667                } else {
3668                    None
3669                }
3670            })
3671            .collect();
3672        if follower_ids.is_empty() {
3673            return None;
3674        }
3675        self.client
3676            .send(proto::UpdateFollowers {
3677                room_id,
3678                project_id,
3679                follower_ids,
3680                variant: Some(update),
3681            })
3682            .log_err()
3683    }
3684
3685    pub async fn handle_follow(
3686        this: Model<Self>,
3687        envelope: TypedEnvelope<proto::Follow>,
3688        _: Arc<Client>,
3689        mut cx: AsyncAppContext,
3690    ) -> Result<proto::FollowResponse> {
3691        this.update(&mut cx, |this, cx| {
3692            let follower = Follower {
3693                project_id: envelope.payload.project_id,
3694                peer_id: envelope.original_sender_id()?,
3695            };
3696            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3697
3698            let mut response = proto::FollowResponse::default();
3699            this.workspaces.retain(|workspace| {
3700                workspace
3701                    .update(cx, |workspace, cx| {
3702                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3703                        if response.views.is_empty() {
3704                            response.views = handler_response.views;
3705                        } else {
3706                            response.views.extend_from_slice(&handler_response.views);
3707                        }
3708
3709                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3710                            if response.active_view_id.is_none()
3711                                || Some(workspace.project.downgrade()) == active_project
3712                            {
3713                                response.active_view_id = Some(active_view_id);
3714                            }
3715                        }
3716                    })
3717                    .is_ok()
3718            });
3719
3720            if let Err(ix) = this.followers.binary_search(&follower) {
3721                this.followers.insert(ix, follower);
3722            }
3723
3724            Ok(response)
3725        })?
3726    }
3727
3728    async fn handle_unfollow(
3729        model: Model<Self>,
3730        envelope: TypedEnvelope<proto::Unfollow>,
3731        _: Arc<Client>,
3732        mut cx: AsyncAppContext,
3733    ) -> Result<()> {
3734        model.update(&mut cx, |this, _| {
3735            let follower = Follower {
3736                project_id: envelope.payload.project_id,
3737                peer_id: envelope.original_sender_id()?,
3738            };
3739            if let Ok(ix) = this.followers.binary_search(&follower) {
3740                this.followers.remove(ix);
3741            }
3742            Ok(())
3743        })?
3744    }
3745
3746    async fn handle_update_followers(
3747        this: Model<Self>,
3748        envelope: TypedEnvelope<proto::UpdateFollowers>,
3749        _: Arc<Client>,
3750        mut cx: AsyncAppContext,
3751    ) -> Result<()> {
3752        let leader_id = envelope.original_sender_id()?;
3753        let update = envelope.payload;
3754
3755        this.update(&mut cx, |this, cx| {
3756            this.workspaces.retain(|workspace| {
3757                workspace
3758                    .update(cx, |workspace, cx| {
3759                        let project_id = workspace.project.read(cx).remote_id();
3760                        if update.project_id != project_id && update.project_id.is_some() {
3761                            return;
3762                        }
3763                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3764                    })
3765                    .is_ok()
3766            });
3767            Ok(())
3768        })?
3769    }
3770}
3771
3772impl ViewId {
3773    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3774        Ok(Self {
3775            creator: message
3776                .creator
3777                .ok_or_else(|| anyhow!("creator is missing"))?,
3778            id: message.id,
3779        })
3780    }
3781
3782    pub(crate) fn to_proto(&self) -> proto::ViewId {
3783        proto::ViewId {
3784            creator: Some(self.creator),
3785            id: self.id,
3786        }
3787    }
3788}
3789
3790pub trait WorkspaceHandle {
3791    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3792}
3793
3794impl WorkspaceHandle for View<Workspace> {
3795    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3796        self.read(cx)
3797            .worktrees(cx)
3798            .flat_map(|worktree| {
3799                let worktree_id = worktree.read(cx).id();
3800                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3801                    worktree_id,
3802                    path: f.path.clone(),
3803                })
3804            })
3805            .collect::<Vec<_>>()
3806    }
3807}
3808
3809impl std::fmt::Debug for OpenPaths {
3810    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3811        f.debug_struct("OpenPaths")
3812            .field("paths", &self.paths)
3813            .finish()
3814    }
3815}
3816
3817pub fn activate_workspace_for_project(
3818    cx: &mut AppContext,
3819    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3820) -> Option<WindowHandle<Workspace>> {
3821    for window in cx.windows() {
3822        let Some(workspace) = window.downcast::<Workspace>() else {
3823            continue;
3824        };
3825
3826        let predicate = workspace
3827            .update(cx, |workspace, cx| {
3828                let project = workspace.project.read(cx);
3829                if predicate(project, cx) {
3830                    cx.activate_window();
3831                    true
3832                } else {
3833                    false
3834                }
3835            })
3836            .log_err()
3837            .unwrap_or(false);
3838
3839        if predicate {
3840            return Some(workspace);
3841        }
3842    }
3843
3844    None
3845}
3846
3847pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3848    DB.last_workspace().await.log_err().flatten()
3849}
3850
3851async fn join_channel_internal(
3852    channel_id: u64,
3853    app_state: &Arc<AppState>,
3854    requesting_window: Option<WindowHandle<Workspace>>,
3855    active_call: &Model<ActiveCall>,
3856    cx: &mut AsyncAppContext,
3857) -> Result<bool> {
3858    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3859        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3860            return (false, None);
3861        };
3862
3863        let already_in_channel = room.channel_id() == Some(channel_id);
3864        let should_prompt = room.is_sharing_project()
3865            && room.remote_participants().len() > 0
3866            && !already_in_channel;
3867        let open_room = if already_in_channel {
3868            active_call.room().cloned()
3869        } else {
3870            None
3871        };
3872        (should_prompt, open_room)
3873    })?;
3874
3875    if let Some(room) = open_room {
3876        let task = room.update(cx, |room, cx| {
3877            if let Some((project, host)) = room.most_active_project(cx) {
3878                return Some(join_remote_project(project, host, app_state.clone(), cx));
3879            }
3880
3881            None
3882        })?;
3883        if let Some(task) = task {
3884            task.await?;
3885        }
3886        return anyhow::Ok(true);
3887    }
3888
3889    if should_prompt {
3890        if let Some(workspace) = requesting_window {
3891            let answer  = workspace.update(cx, |_, cx| {
3892                cx.prompt(
3893                    PromptLevel::Warning,
3894                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3895                    &["Yes, Join Channel", "Cancel"],
3896                )
3897            })?.await;
3898
3899            if answer == Ok(1) {
3900                return Ok(false);
3901            }
3902        } else {
3903            return Ok(false); // unreachable!() hopefully
3904        }
3905    }
3906
3907    let client = cx.update(|cx| active_call.read(cx).client())?;
3908
3909    let mut client_status = client.status();
3910
3911    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3912    'outer: loop {
3913        let Some(status) = client_status.recv().await else {
3914            return Err(anyhow!("error connecting"));
3915        };
3916
3917        match status {
3918            Status::Connecting
3919            | Status::Authenticating
3920            | Status::Reconnecting
3921            | Status::Reauthenticating => continue,
3922            Status::Connected { .. } => break 'outer,
3923            Status::SignedOut => return Err(anyhow!("not signed in")),
3924            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3925            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3926                return Err(anyhow!("zed is offline"))
3927            }
3928        }
3929    }
3930
3931    let room = active_call
3932        .update(cx, |active_call, cx| {
3933            active_call.join_channel(channel_id, cx)
3934        })?
3935        .await?;
3936
3937    let Some(room) = room else {
3938        return anyhow::Ok(true);
3939    };
3940
3941    room.update(cx, |room, _| room.room_update_completed())?
3942        .await;
3943
3944    let task = room.update(cx, |room, cx| {
3945        if let Some((project, host)) = room.most_active_project(cx) {
3946            return Some(join_remote_project(project, host, app_state.clone(), cx));
3947        }
3948
3949        None
3950    })?;
3951    if let Some(task) = task {
3952        task.await?;
3953        return anyhow::Ok(true);
3954    }
3955    anyhow::Ok(false)
3956}
3957
3958pub fn join_channel(
3959    channel_id: u64,
3960    app_state: Arc<AppState>,
3961    requesting_window: Option<WindowHandle<Workspace>>,
3962    cx: &mut AppContext,
3963) -> Task<Result<()>> {
3964    let active_call = ActiveCall::global(cx);
3965    cx.spawn(|mut cx| async move {
3966        let result = join_channel_internal(
3967            channel_id,
3968            &app_state,
3969            requesting_window,
3970            &active_call,
3971            &mut cx,
3972        )
3973        .await;
3974
3975        // join channel succeeded, and opened a window
3976        if matches!(result, Ok(true)) {
3977            return anyhow::Ok(());
3978        }
3979
3980        // find an existing workspace to focus and show call controls
3981        let mut active_window =
3982            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
3983        if active_window.is_none() {
3984            // no open workspaces, make one to show the error in (blergh)
3985            let (window_handle, _) = cx
3986                .update(|cx| {
3987                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
3988                })?
3989                .await?;
3990
3991            active_window = Some(window_handle);
3992        }
3993
3994        if let Err(err) = result {
3995            log::error!("failed to join channel: {}", err);
3996            if let Some(active_window) = active_window {
3997                active_window
3998                    .update(&mut cx, |_, cx| {
3999                        cx.prompt(
4000                            PromptLevel::Critical,
4001                            &format!("Failed to join channel: {}", err),
4002                            &["Ok"],
4003                        )
4004                    })?
4005                    .await
4006                    .ok();
4007            }
4008        }
4009
4010        // return ok, we showed the error to the user.
4011        return anyhow::Ok(());
4012    })
4013}
4014
4015pub async fn get_any_active_workspace(
4016    app_state: Arc<AppState>,
4017    mut cx: AsyncAppContext,
4018) -> anyhow::Result<WindowHandle<Workspace>> {
4019    // find an existing workspace to focus and show call controls
4020    let active_window = activate_any_workspace_window(&mut cx);
4021    if active_window.is_none() {
4022        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4023            .await?;
4024    }
4025    activate_any_workspace_window(&mut cx).context("could not open zed")
4026}
4027
4028fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4029    cx.update(|cx| {
4030        for window in cx.windows() {
4031            if let Some(workspace_window) = window.downcast::<Workspace>() {
4032                workspace_window
4033                    .update(cx, |_, cx| cx.activate_window())
4034                    .ok();
4035                return Some(workspace_window);
4036            }
4037        }
4038        None
4039    })
4040    .ok()
4041    .flatten()
4042}
4043
4044#[allow(clippy::type_complexity)]
4045pub fn open_paths(
4046    abs_paths: &[PathBuf],
4047    app_state: &Arc<AppState>,
4048    requesting_window: Option<WindowHandle<Workspace>>,
4049    cx: &mut AppContext,
4050) -> Task<
4051    anyhow::Result<(
4052        WindowHandle<Workspace>,
4053        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4054    )>,
4055> {
4056    let app_state = app_state.clone();
4057    let abs_paths = abs_paths.to_vec();
4058    // Open paths in existing workspace if possible
4059    let existing = activate_workspace_for_project(cx, {
4060        let abs_paths = abs_paths.clone();
4061        move |project, cx| project.contains_paths(&abs_paths, cx)
4062    });
4063    cx.spawn(move |mut cx| async move {
4064        if let Some(existing) = existing {
4065            Ok((
4066                existing.clone(),
4067                existing
4068                    .update(&mut cx, |workspace, cx| {
4069                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4070                    })?
4071                    .await,
4072            ))
4073        } else {
4074            cx.update(move |cx| {
4075                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4076            })?
4077            .await
4078        }
4079    })
4080}
4081
4082pub fn open_new(
4083    app_state: &Arc<AppState>,
4084    cx: &mut AppContext,
4085    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4086) -> Task<()> {
4087    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4088    cx.spawn(|mut cx| async move {
4089        if let Some((workspace, opened_paths)) = task.await.log_err() {
4090            workspace
4091                .update(&mut cx, |workspace, cx| {
4092                    if opened_paths.is_empty() {
4093                        init(workspace, cx)
4094                    }
4095                })
4096                .log_err();
4097        }
4098    })
4099}
4100
4101pub fn create_and_open_local_file(
4102    path: &'static Path,
4103    cx: &mut ViewContext<Workspace>,
4104    default_content: impl 'static + Send + FnOnce() -> Rope,
4105) -> Task<Result<Box<dyn ItemHandle>>> {
4106    cx.spawn(|workspace, mut cx| async move {
4107        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4108        if !fs.is_file(path).await {
4109            fs.create_file(path, Default::default()).await?;
4110            fs.save(path, &default_content(), Default::default())
4111                .await?;
4112        }
4113
4114        let mut items = workspace
4115            .update(&mut cx, |workspace, cx| {
4116                workspace.with_local_workspace(cx, |workspace, cx| {
4117                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4118                })
4119            })?
4120            .await?
4121            .await;
4122
4123        let item = items.pop().flatten();
4124        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4125    })
4126}
4127
4128pub fn join_remote_project(
4129    project_id: u64,
4130    follow_user_id: u64,
4131    app_state: Arc<AppState>,
4132    cx: &mut AppContext,
4133) -> Task<Result<()>> {
4134    let windows = cx.windows();
4135    cx.spawn(|mut cx| async move {
4136        let existing_workspace = windows.into_iter().find_map(|window| {
4137            window.downcast::<Workspace>().and_then(|window| {
4138                window
4139                    .update(&mut cx, |workspace, cx| {
4140                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4141                            Some(window)
4142                        } else {
4143                            None
4144                        }
4145                    })
4146                    .unwrap_or(None)
4147            })
4148        });
4149
4150        let workspace = if let Some(existing_workspace) = existing_workspace {
4151            existing_workspace
4152        } else {
4153            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4154            let room = active_call
4155                .read_with(&cx, |call, _| call.room().cloned())?
4156                .ok_or_else(|| anyhow!("not in a call"))?;
4157            let project = room
4158                .update(&mut cx, |room, cx| {
4159                    room.join_project(
4160                        project_id,
4161                        app_state.languages.clone(),
4162                        app_state.fs.clone(),
4163                        cx,
4164                    )
4165                })?
4166                .await?;
4167
4168            let window_bounds_override = window_bounds_env_override(&cx);
4169            cx.update(|cx| {
4170                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4171                cx.open_window(options, |cx| {
4172                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4173                })
4174            })?
4175        };
4176
4177        workspace.update(&mut cx, |workspace, cx| {
4178            cx.activate(true);
4179            cx.activate_window();
4180
4181            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4182                let follow_peer_id = room
4183                    .read(cx)
4184                    .remote_participants()
4185                    .iter()
4186                    .find(|(_, participant)| participant.user.id == follow_user_id)
4187                    .map(|(_, p)| p.peer_id)
4188                    .or_else(|| {
4189                        // If we couldn't follow the given user, follow the host instead.
4190                        let collaborator = workspace
4191                            .project()
4192                            .read(cx)
4193                            .collaborators()
4194                            .values()
4195                            .find(|collaborator| collaborator.replica_id == 0)?;
4196                        Some(collaborator.peer_id)
4197                    });
4198
4199                if let Some(follow_peer_id) = follow_peer_id {
4200                    workspace.follow(follow_peer_id, cx);
4201                }
4202            }
4203        })?;
4204
4205        anyhow::Ok(())
4206    })
4207}
4208
4209pub fn restart(_: &Restart, cx: &mut AppContext) {
4210    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4211    let mut workspace_windows = cx
4212        .windows()
4213        .into_iter()
4214        .filter_map(|window| window.downcast::<Workspace>())
4215        .collect::<Vec<_>>();
4216
4217    // If multiple windows have unsaved changes, and need a save prompt,
4218    // prompt in the active window before switching to a different window.
4219    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4220
4221    let mut prompt = None;
4222    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4223        prompt = window
4224            .update(cx, |_, cx| {
4225                cx.prompt(
4226                    PromptLevel::Info,
4227                    "Are you sure you want to restart?",
4228                    &["Restart", "Cancel"],
4229                )
4230            })
4231            .ok();
4232    }
4233
4234    cx.spawn(|mut cx| async move {
4235        if let Some(prompt) = prompt {
4236            let answer = prompt.await?;
4237            if answer != 0 {
4238                return Ok(());
4239            }
4240        }
4241
4242        // If the user cancels any save prompt, then keep the app open.
4243        for window in workspace_windows {
4244            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4245                workspace.prepare_to_close(true, cx)
4246            }) {
4247                if !should_close.await? {
4248                    return Ok(());
4249                }
4250            }
4251        }
4252
4253        cx.update(|cx| cx.restart())
4254    })
4255    .detach_and_log_err(cx);
4256}
4257
4258fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4259    let mut parts = value.split(',');
4260    let x: usize = parts.next()?.parse().ok()?;
4261    let y: usize = parts.next()?.parse().ok()?;
4262    Some(point((x as f64).into(), (y as f64).into()))
4263}
4264
4265fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4266    let mut parts = value.split(',');
4267    let width: usize = parts.next()?.parse().ok()?;
4268    let height: usize = parts.next()?.parse().ok()?;
4269    Some(size((width as f64).into(), (height as f64).into()))
4270}
4271
4272pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4273    (1.75 * cx.rem_size()).max(px(32.))
4274}
4275
4276struct DisconnectedOverlay;
4277
4278impl Element for DisconnectedOverlay {
4279    type State = AnyElement;
4280
4281    fn request_layout(
4282        &mut self,
4283        _: Option<Self::State>,
4284        cx: &mut ElementContext,
4285    ) -> (LayoutId, Self::State) {
4286        let mut background = cx.theme().colors().elevated_surface_background;
4287        background.fade_out(0.2);
4288        let mut overlay = div()
4289            .bg(background)
4290            .absolute()
4291            .left_0()
4292            .top(titlebar_height(cx))
4293            .size_full()
4294            .flex()
4295            .items_center()
4296            .justify_center()
4297            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4298            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4299            .child(Label::new(
4300                "Your connection to the remote project has been lost.",
4301            ))
4302            .into_any();
4303        (overlay.request_layout(cx), overlay)
4304    }
4305
4306    fn paint(
4307        &mut self,
4308        bounds: Bounds<Pixels>,
4309        overlay: &mut Self::State,
4310        cx: &mut ElementContext,
4311    ) {
4312        cx.with_z_index(u16::MAX, |cx| {
4313            cx.add_opaque_layer(bounds);
4314            overlay.paint(cx);
4315        })
4316    }
4317}
4318
4319impl IntoElement for DisconnectedOverlay {
4320    type Element = Self;
4321
4322    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4323        None
4324    }
4325
4326    fn into_element(self) -> Self::Element {
4327        self
4328    }
4329}
4330
4331#[cfg(test)]
4332mod tests {
4333    use std::{cell::RefCell, rc::Rc};
4334
4335    use super::*;
4336    use crate::{
4337        dock::{test::TestPanel, PanelEvent},
4338        item::{
4339            test::{TestItem, TestProjectItem},
4340            ItemEvent,
4341        },
4342    };
4343    use fs::FakeFs;
4344    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4345    use project::{Project, ProjectEntryId};
4346    use serde_json::json;
4347    use settings::SettingsStore;
4348
4349    #[gpui::test]
4350    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4351        init_test(cx);
4352
4353        let fs = FakeFs::new(cx.executor());
4354        let project = Project::test(fs, [], cx).await;
4355        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4356
4357        // Adding an item with no ambiguity renders the tab without detail.
4358        let item1 = cx.new_view(|cx| {
4359            let mut item = TestItem::new(cx);
4360            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4361            item
4362        });
4363        workspace.update(cx, |workspace, cx| {
4364            workspace.add_item(Box::new(item1.clone()), cx);
4365        });
4366        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4367
4368        // Adding an item that creates ambiguity increases the level of detail on
4369        // both tabs.
4370        let item2 = cx.new_view(|cx| {
4371            let mut item = TestItem::new(cx);
4372            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4373            item
4374        });
4375        workspace.update(cx, |workspace, cx| {
4376            workspace.add_item(Box::new(item2.clone()), cx);
4377        });
4378        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4379        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4380
4381        // Adding an item that creates ambiguity increases the level of detail only
4382        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4383        // we stop at the highest detail available.
4384        let item3 = cx.new_view(|cx| {
4385            let mut item = TestItem::new(cx);
4386            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4387            item
4388        });
4389        workspace.update(cx, |workspace, cx| {
4390            workspace.add_item(Box::new(item3.clone()), cx);
4391        });
4392        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4393        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4394        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4395    }
4396
4397    #[gpui::test]
4398    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4399        init_test(cx);
4400
4401        let fs = FakeFs::new(cx.executor());
4402        fs.insert_tree(
4403            "/root1",
4404            json!({
4405                "one.txt": "",
4406                "two.txt": "",
4407            }),
4408        )
4409        .await;
4410        fs.insert_tree(
4411            "/root2",
4412            json!({
4413                "three.txt": "",
4414            }),
4415        )
4416        .await;
4417
4418        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4419        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4420        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4421        let worktree_id = project.update(cx, |project, cx| {
4422            project.worktrees().next().unwrap().read(cx).id()
4423        });
4424
4425        let item1 = cx.new_view(|cx| {
4426            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4427        });
4428        let item2 = cx.new_view(|cx| {
4429            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4430        });
4431
4432        // Add an item to an empty pane
4433        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4434        project.update(cx, |project, cx| {
4435            assert_eq!(
4436                project.active_entry(),
4437                project
4438                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4439                    .map(|e| e.id)
4440            );
4441        });
4442        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4443
4444        // Add a second item to a non-empty pane
4445        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4446        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4447        project.update(cx, |project, cx| {
4448            assert_eq!(
4449                project.active_entry(),
4450                project
4451                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4452                    .map(|e| e.id)
4453            );
4454        });
4455
4456        // Close the active item
4457        pane.update(cx, |pane, cx| {
4458            pane.close_active_item(&Default::default(), cx).unwrap()
4459        })
4460        .await
4461        .unwrap();
4462        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4463        project.update(cx, |project, cx| {
4464            assert_eq!(
4465                project.active_entry(),
4466                project
4467                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4468                    .map(|e| e.id)
4469            );
4470        });
4471
4472        // Add a project folder
4473        project
4474            .update(cx, |project, cx| {
4475                project.find_or_create_local_worktree("/root2", true, cx)
4476            })
4477            .await
4478            .unwrap();
4479        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4480
4481        // Remove a project folder
4482        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4483        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4484    }
4485
4486    #[gpui::test]
4487    async fn test_close_window(cx: &mut TestAppContext) {
4488        init_test(cx);
4489
4490        let fs = FakeFs::new(cx.executor());
4491        fs.insert_tree("/root", json!({ "one": "" })).await;
4492
4493        let project = Project::test(fs, ["root".as_ref()], cx).await;
4494        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4495
4496        // When there are no dirty items, there's nothing to do.
4497        let item1 = cx.new_view(|cx| TestItem::new(cx));
4498        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4499        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4500        assert!(task.await.unwrap());
4501
4502        // When there are dirty untitled items, prompt to save each one. If the user
4503        // cancels any prompt, then abort.
4504        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4505        let item3 = cx.new_view(|cx| {
4506            TestItem::new(cx)
4507                .with_dirty(true)
4508                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4509        });
4510        workspace.update(cx, |w, cx| {
4511            w.add_item(Box::new(item2.clone()), cx);
4512            w.add_item(Box::new(item3.clone()), cx);
4513        });
4514        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4515        cx.executor().run_until_parked();
4516        cx.simulate_prompt_answer(2); // cancel save all
4517        cx.executor().run_until_parked();
4518        cx.simulate_prompt_answer(2); // cancel save all
4519        cx.executor().run_until_parked();
4520        assert!(!cx.has_pending_prompt());
4521        assert!(!task.await.unwrap());
4522    }
4523
4524    #[gpui::test]
4525    async fn test_close_pane_items(cx: &mut TestAppContext) {
4526        init_test(cx);
4527
4528        let fs = FakeFs::new(cx.executor());
4529
4530        let project = Project::test(fs, None, cx).await;
4531        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4532
4533        let item1 = cx.new_view(|cx| {
4534            TestItem::new(cx)
4535                .with_dirty(true)
4536                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4537        });
4538        let item2 = cx.new_view(|cx| {
4539            TestItem::new(cx)
4540                .with_dirty(true)
4541                .with_conflict(true)
4542                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4543        });
4544        let item3 = cx.new_view(|cx| {
4545            TestItem::new(cx)
4546                .with_dirty(true)
4547                .with_conflict(true)
4548                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4549        });
4550        let item4 = cx.new_view(|cx| {
4551            TestItem::new(cx)
4552                .with_dirty(true)
4553                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4554        });
4555        let pane = workspace.update(cx, |workspace, cx| {
4556            workspace.add_item(Box::new(item1.clone()), cx);
4557            workspace.add_item(Box::new(item2.clone()), cx);
4558            workspace.add_item(Box::new(item3.clone()), cx);
4559            workspace.add_item(Box::new(item4.clone()), cx);
4560            workspace.active_pane().clone()
4561        });
4562
4563        let close_items = pane.update(cx, |pane, cx| {
4564            pane.activate_item(1, true, true, cx);
4565            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4566            let item1_id = item1.item_id();
4567            let item3_id = item3.item_id();
4568            let item4_id = item4.item_id();
4569            pane.close_items(cx, SaveIntent::Close, move |id| {
4570                [item1_id, item3_id, item4_id].contains(&id)
4571            })
4572        });
4573        cx.executor().run_until_parked();
4574
4575        assert!(cx.has_pending_prompt());
4576        // Ignore "Save all" prompt
4577        cx.simulate_prompt_answer(2);
4578        cx.executor().run_until_parked();
4579        // There's a prompt to save item 1.
4580        pane.update(cx, |pane, _| {
4581            assert_eq!(pane.items_len(), 4);
4582            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4583        });
4584        // Confirm saving item 1.
4585        cx.simulate_prompt_answer(0);
4586        cx.executor().run_until_parked();
4587
4588        // Item 1 is saved. There's a prompt to save item 3.
4589        pane.update(cx, |pane, cx| {
4590            assert_eq!(item1.read(cx).save_count, 1);
4591            assert_eq!(item1.read(cx).save_as_count, 0);
4592            assert_eq!(item1.read(cx).reload_count, 0);
4593            assert_eq!(pane.items_len(), 3);
4594            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4595        });
4596        assert!(cx.has_pending_prompt());
4597
4598        // Cancel saving item 3.
4599        cx.simulate_prompt_answer(1);
4600        cx.executor().run_until_parked();
4601
4602        // Item 3 is reloaded. There's a prompt to save item 4.
4603        pane.update(cx, |pane, cx| {
4604            assert_eq!(item3.read(cx).save_count, 0);
4605            assert_eq!(item3.read(cx).save_as_count, 0);
4606            assert_eq!(item3.read(cx).reload_count, 1);
4607            assert_eq!(pane.items_len(), 2);
4608            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4609        });
4610        assert!(cx.has_pending_prompt());
4611
4612        // Confirm saving item 4.
4613        cx.simulate_prompt_answer(0);
4614        cx.executor().run_until_parked();
4615
4616        // There's a prompt for a path for item 4.
4617        cx.simulate_new_path_selection(|_| Some(Default::default()));
4618        close_items.await.unwrap();
4619
4620        // The requested items are closed.
4621        pane.update(cx, |pane, cx| {
4622            assert_eq!(item4.read(cx).save_count, 0);
4623            assert_eq!(item4.read(cx).save_as_count, 1);
4624            assert_eq!(item4.read(cx).reload_count, 0);
4625            assert_eq!(pane.items_len(), 1);
4626            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4627        });
4628    }
4629
4630    #[gpui::test]
4631    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4632        init_test(cx);
4633
4634        let fs = FakeFs::new(cx.executor());
4635        let project = Project::test(fs, [], cx).await;
4636        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4637
4638        // Create several workspace items with single project entries, and two
4639        // workspace items with multiple project entries.
4640        let single_entry_items = (0..=4)
4641            .map(|project_entry_id| {
4642                cx.new_view(|cx| {
4643                    TestItem::new(cx)
4644                        .with_dirty(true)
4645                        .with_project_items(&[TestProjectItem::new(
4646                            project_entry_id,
4647                            &format!("{project_entry_id}.txt"),
4648                            cx,
4649                        )])
4650                })
4651            })
4652            .collect::<Vec<_>>();
4653        let item_2_3 = cx.new_view(|cx| {
4654            TestItem::new(cx)
4655                .with_dirty(true)
4656                .with_singleton(false)
4657                .with_project_items(&[
4658                    single_entry_items[2].read(cx).project_items[0].clone(),
4659                    single_entry_items[3].read(cx).project_items[0].clone(),
4660                ])
4661        });
4662        let item_3_4 = cx.new_view(|cx| {
4663            TestItem::new(cx)
4664                .with_dirty(true)
4665                .with_singleton(false)
4666                .with_project_items(&[
4667                    single_entry_items[3].read(cx).project_items[0].clone(),
4668                    single_entry_items[4].read(cx).project_items[0].clone(),
4669                ])
4670        });
4671
4672        // Create two panes that contain the following project entries:
4673        //   left pane:
4674        //     multi-entry items:   (2, 3)
4675        //     single-entry items:  0, 1, 2, 3, 4
4676        //   right pane:
4677        //     single-entry items:  1
4678        //     multi-entry items:   (3, 4)
4679        let left_pane = workspace.update(cx, |workspace, cx| {
4680            let left_pane = workspace.active_pane().clone();
4681            workspace.add_item(Box::new(item_2_3.clone()), cx);
4682            for item in single_entry_items {
4683                workspace.add_item(Box::new(item), cx);
4684            }
4685            left_pane.update(cx, |pane, cx| {
4686                pane.activate_item(2, true, true, cx);
4687            });
4688
4689            let right_pane = workspace
4690                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4691                .unwrap();
4692
4693            right_pane.update(cx, |pane, cx| {
4694                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4695            });
4696
4697            left_pane
4698        });
4699
4700        cx.focus_view(&left_pane);
4701
4702        // When closing all of the items in the left pane, we should be prompted twice:
4703        // once for project entry 0, and once for project entry 2. Project entries 1,
4704        // 3, and 4 are all still open in the other paten. After those two
4705        // prompts, the task should complete.
4706
4707        let close = left_pane.update(cx, |pane, cx| {
4708            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4709        });
4710        cx.executor().run_until_parked();
4711
4712        // Discard "Save all" prompt
4713        cx.simulate_prompt_answer(2);
4714
4715        cx.executor().run_until_parked();
4716        left_pane.update(cx, |pane, cx| {
4717            assert_eq!(
4718                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4719                &[ProjectEntryId::from_proto(0)]
4720            );
4721        });
4722        cx.simulate_prompt_answer(0);
4723
4724        cx.executor().run_until_parked();
4725        left_pane.update(cx, |pane, cx| {
4726            assert_eq!(
4727                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4728                &[ProjectEntryId::from_proto(2)]
4729            );
4730        });
4731        cx.simulate_prompt_answer(0);
4732
4733        cx.executor().run_until_parked();
4734        close.await.unwrap();
4735        left_pane.update(cx, |pane, _| {
4736            assert_eq!(pane.items_len(), 0);
4737        });
4738    }
4739
4740    #[gpui::test]
4741    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4742        init_test(cx);
4743
4744        let fs = FakeFs::new(cx.executor());
4745        let project = Project::test(fs, [], cx).await;
4746        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4747        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4748
4749        let item = cx.new_view(|cx| {
4750            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4751        });
4752        let item_id = item.entity_id();
4753        workspace.update(cx, |workspace, cx| {
4754            workspace.add_item(Box::new(item.clone()), cx);
4755        });
4756
4757        // Autosave on window change.
4758        item.update(cx, |item, cx| {
4759            cx.update_global(|settings: &mut SettingsStore, cx| {
4760                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4761                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4762                })
4763            });
4764            item.is_dirty = true;
4765        });
4766
4767        // Deactivating the window saves the file.
4768        cx.deactivate_window();
4769        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4770
4771        // Autosave on focus change.
4772        item.update(cx, |item, cx| {
4773            cx.focus_self();
4774            cx.update_global(|settings: &mut SettingsStore, cx| {
4775                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4776                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4777                })
4778            });
4779            item.is_dirty = true;
4780        });
4781
4782        // Blurring the item saves the file.
4783        item.update(cx, |_, cx| cx.blur());
4784        cx.executor().run_until_parked();
4785        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4786
4787        // Deactivating the window still saves the file.
4788        cx.update(|cx| cx.activate_window());
4789        item.update(cx, |item, cx| {
4790            cx.focus_self();
4791            item.is_dirty = true;
4792        });
4793        cx.deactivate_window();
4794
4795        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4796
4797        // Autosave after delay.
4798        item.update(cx, |item, cx| {
4799            cx.update_global(|settings: &mut SettingsStore, cx| {
4800                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4801                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4802                })
4803            });
4804            item.is_dirty = true;
4805            cx.emit(ItemEvent::Edit);
4806        });
4807
4808        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4809        cx.executor().advance_clock(Duration::from_millis(250));
4810        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4811
4812        // After delay expires, the file is saved.
4813        cx.executor().advance_clock(Duration::from_millis(250));
4814        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4815
4816        // Autosave on focus change, ensuring closing the tab counts as such.
4817        item.update(cx, |item, cx| {
4818            cx.update_global(|settings: &mut SettingsStore, cx| {
4819                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4820                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4821                })
4822            });
4823            item.is_dirty = true;
4824        });
4825
4826        pane.update(cx, |pane, cx| {
4827            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4828        })
4829        .await
4830        .unwrap();
4831        assert!(!cx.has_pending_prompt());
4832        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4833
4834        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4835        workspace.update(cx, |workspace, cx| {
4836            workspace.add_item(Box::new(item.clone()), cx);
4837        });
4838        item.update(cx, |item, cx| {
4839            item.project_items[0].update(cx, |item, _| {
4840                item.entry_id = None;
4841            });
4842            item.is_dirty = true;
4843            cx.blur();
4844        });
4845        cx.run_until_parked();
4846        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4847
4848        // Ensure autosave is prevented for deleted files also when closing the buffer.
4849        let _close_items = pane.update(cx, |pane, cx| {
4850            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4851        });
4852        cx.run_until_parked();
4853        assert!(cx.has_pending_prompt());
4854        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4855    }
4856
4857    #[gpui::test]
4858    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4859        init_test(cx);
4860
4861        let fs = FakeFs::new(cx.executor());
4862
4863        let project = Project::test(fs, [], cx).await;
4864        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4865
4866        let item = cx.new_view(|cx| {
4867            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4868        });
4869        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4870        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4871        let toolbar_notify_count = Rc::new(RefCell::new(0));
4872
4873        workspace.update(cx, |workspace, cx| {
4874            workspace.add_item(Box::new(item.clone()), cx);
4875            let toolbar_notification_count = toolbar_notify_count.clone();
4876            cx.observe(&toolbar, move |_, _, _| {
4877                *toolbar_notification_count.borrow_mut() += 1
4878            })
4879            .detach();
4880        });
4881
4882        pane.update(cx, |pane, _| {
4883            assert!(!pane.can_navigate_backward());
4884            assert!(!pane.can_navigate_forward());
4885        });
4886
4887        item.update(cx, |item, cx| {
4888            item.set_state("one".to_string(), cx);
4889        });
4890
4891        // Toolbar must be notified to re-render the navigation buttons
4892        assert_eq!(*toolbar_notify_count.borrow(), 1);
4893
4894        pane.update(cx, |pane, _| {
4895            assert!(pane.can_navigate_backward());
4896            assert!(!pane.can_navigate_forward());
4897        });
4898
4899        workspace
4900            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4901            .await
4902            .unwrap();
4903
4904        assert_eq!(*toolbar_notify_count.borrow(), 2);
4905        pane.update(cx, |pane, _| {
4906            assert!(!pane.can_navigate_backward());
4907            assert!(pane.can_navigate_forward());
4908        });
4909    }
4910
4911    #[gpui::test]
4912    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4913        init_test(cx);
4914        let fs = FakeFs::new(cx.executor());
4915
4916        let project = Project::test(fs, [], cx).await;
4917        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4918
4919        let panel = workspace.update(cx, |workspace, cx| {
4920            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4921            workspace.add_panel(panel.clone(), cx);
4922
4923            workspace
4924                .right_dock()
4925                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4926
4927            panel
4928        });
4929
4930        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4931        pane.update(cx, |pane, cx| {
4932            let item = cx.new_view(|cx| TestItem::new(cx));
4933            pane.add_item(Box::new(item), true, true, None, cx);
4934        });
4935
4936        // Transfer focus from center to panel
4937        workspace.update(cx, |workspace, cx| {
4938            workspace.toggle_panel_focus::<TestPanel>(cx);
4939        });
4940
4941        workspace.update(cx, |workspace, cx| {
4942            assert!(workspace.right_dock().read(cx).is_open());
4943            assert!(!panel.is_zoomed(cx));
4944            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4945        });
4946
4947        // Transfer focus from panel to center
4948        workspace.update(cx, |workspace, cx| {
4949            workspace.toggle_panel_focus::<TestPanel>(cx);
4950        });
4951
4952        workspace.update(cx, |workspace, cx| {
4953            assert!(workspace.right_dock().read(cx).is_open());
4954            assert!(!panel.is_zoomed(cx));
4955            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4956        });
4957
4958        // Close the dock
4959        workspace.update(cx, |workspace, cx| {
4960            workspace.toggle_dock(DockPosition::Right, cx);
4961        });
4962
4963        workspace.update(cx, |workspace, cx| {
4964            assert!(!workspace.right_dock().read(cx).is_open());
4965            assert!(!panel.is_zoomed(cx));
4966            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4967        });
4968
4969        // Open the dock
4970        workspace.update(cx, |workspace, cx| {
4971            workspace.toggle_dock(DockPosition::Right, cx);
4972        });
4973
4974        workspace.update(cx, |workspace, cx| {
4975            assert!(workspace.right_dock().read(cx).is_open());
4976            assert!(!panel.is_zoomed(cx));
4977            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4978        });
4979
4980        // Focus and zoom panel
4981        panel.update(cx, |panel, cx| {
4982            cx.focus_self();
4983            panel.set_zoomed(true, cx)
4984        });
4985
4986        workspace.update(cx, |workspace, cx| {
4987            assert!(workspace.right_dock().read(cx).is_open());
4988            assert!(panel.is_zoomed(cx));
4989            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4990        });
4991
4992        // Transfer focus to the center closes the dock
4993        workspace.update(cx, |workspace, cx| {
4994            workspace.toggle_panel_focus::<TestPanel>(cx);
4995        });
4996
4997        workspace.update(cx, |workspace, cx| {
4998            assert!(!workspace.right_dock().read(cx).is_open());
4999            assert!(panel.is_zoomed(cx));
5000            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5001        });
5002
5003        // Transferring focus back to the panel keeps it zoomed
5004        workspace.update(cx, |workspace, cx| {
5005            workspace.toggle_panel_focus::<TestPanel>(cx);
5006        });
5007
5008        workspace.update(cx, |workspace, cx| {
5009            assert!(workspace.right_dock().read(cx).is_open());
5010            assert!(panel.is_zoomed(cx));
5011            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5012        });
5013
5014        // Close the dock while it is zoomed
5015        workspace.update(cx, |workspace, cx| {
5016            workspace.toggle_dock(DockPosition::Right, cx)
5017        });
5018
5019        workspace.update(cx, |workspace, cx| {
5020            assert!(!workspace.right_dock().read(cx).is_open());
5021            assert!(panel.is_zoomed(cx));
5022            assert!(workspace.zoomed.is_none());
5023            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5024        });
5025
5026        // Opening the dock, when it's zoomed, retains focus
5027        workspace.update(cx, |workspace, cx| {
5028            workspace.toggle_dock(DockPosition::Right, cx)
5029        });
5030
5031        workspace.update(cx, |workspace, cx| {
5032            assert!(workspace.right_dock().read(cx).is_open());
5033            assert!(panel.is_zoomed(cx));
5034            assert!(workspace.zoomed.is_some());
5035            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5036        });
5037
5038        // Unzoom and close the panel, zoom the active pane.
5039        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5040        workspace.update(cx, |workspace, cx| {
5041            workspace.toggle_dock(DockPosition::Right, cx)
5042        });
5043        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5044
5045        // Opening a dock unzooms the pane.
5046        workspace.update(cx, |workspace, cx| {
5047            workspace.toggle_dock(DockPosition::Right, cx)
5048        });
5049        workspace.update(cx, |workspace, cx| {
5050            let pane = pane.read(cx);
5051            assert!(!pane.is_zoomed());
5052            assert!(!pane.focus_handle(cx).is_focused(cx));
5053            assert!(workspace.right_dock().read(cx).is_open());
5054            assert!(workspace.zoomed.is_none());
5055        });
5056    }
5057
5058    struct TestModal(FocusHandle);
5059
5060    impl TestModal {
5061        fn new(cx: &mut ViewContext<Self>) -> Self {
5062            Self(cx.focus_handle())
5063        }
5064    }
5065
5066    impl EventEmitter<DismissEvent> for TestModal {}
5067
5068    impl FocusableView for TestModal {
5069        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5070            self.0.clone()
5071        }
5072    }
5073
5074    impl ModalView for TestModal {}
5075
5076    impl Render for TestModal {
5077        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5078            div().track_focus(&self.0)
5079        }
5080    }
5081
5082    #[gpui::test]
5083    async fn test_panels(cx: &mut gpui::TestAppContext) {
5084        init_test(cx);
5085        let fs = FakeFs::new(cx.executor());
5086
5087        let project = Project::test(fs, [], cx).await;
5088        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5089
5090        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5091            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5092            workspace.add_panel(panel_1.clone(), cx);
5093            workspace
5094                .left_dock()
5095                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5096            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5097            workspace.add_panel(panel_2.clone(), cx);
5098            workspace
5099                .right_dock()
5100                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5101
5102            let left_dock = workspace.left_dock();
5103            assert_eq!(
5104                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5105                panel_1.panel_id()
5106            );
5107            assert_eq!(
5108                left_dock.read(cx).active_panel_size(cx).unwrap(),
5109                panel_1.size(cx)
5110            );
5111
5112            left_dock.update(cx, |left_dock, cx| {
5113                left_dock.resize_active_panel(Some(px(1337.)), cx)
5114            });
5115            assert_eq!(
5116                workspace
5117                    .right_dock()
5118                    .read(cx)
5119                    .visible_panel()
5120                    .unwrap()
5121                    .panel_id(),
5122                panel_2.panel_id(),
5123            );
5124
5125            (panel_1, panel_2)
5126        });
5127
5128        // Move panel_1 to the right
5129        panel_1.update(cx, |panel_1, cx| {
5130            panel_1.set_position(DockPosition::Right, cx)
5131        });
5132
5133        workspace.update(cx, |workspace, cx| {
5134            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5135            // Since it was the only panel on the left, the left dock should now be closed.
5136            assert!(!workspace.left_dock().read(cx).is_open());
5137            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5138            let right_dock = workspace.right_dock();
5139            assert_eq!(
5140                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5141                panel_1.panel_id()
5142            );
5143            assert_eq!(
5144                right_dock.read(cx).active_panel_size(cx).unwrap(),
5145                px(1337.)
5146            );
5147
5148            // Now we move panel_2 to the left
5149            panel_2.set_position(DockPosition::Left, cx);
5150        });
5151
5152        workspace.update(cx, |workspace, cx| {
5153            // Since panel_2 was not visible on the right, we don't open the left dock.
5154            assert!(!workspace.left_dock().read(cx).is_open());
5155            // And the right dock is unaffected in it's displaying of panel_1
5156            assert!(workspace.right_dock().read(cx).is_open());
5157            assert_eq!(
5158                workspace
5159                    .right_dock()
5160                    .read(cx)
5161                    .visible_panel()
5162                    .unwrap()
5163                    .panel_id(),
5164                panel_1.panel_id(),
5165            );
5166        });
5167
5168        // Move panel_1 back to the left
5169        panel_1.update(cx, |panel_1, cx| {
5170            panel_1.set_position(DockPosition::Left, cx)
5171        });
5172
5173        workspace.update(cx, |workspace, cx| {
5174            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5175            let left_dock = workspace.left_dock();
5176            assert!(left_dock.read(cx).is_open());
5177            assert_eq!(
5178                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5179                panel_1.panel_id()
5180            );
5181            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5182            // And the right dock should be closed as it no longer has any panels.
5183            assert!(!workspace.right_dock().read(cx).is_open());
5184
5185            // Now we move panel_1 to the bottom
5186            panel_1.set_position(DockPosition::Bottom, cx);
5187        });
5188
5189        workspace.update(cx, |workspace, cx| {
5190            // Since panel_1 was visible on the left, we close the left dock.
5191            assert!(!workspace.left_dock().read(cx).is_open());
5192            // The bottom dock is sized based on the panel's default size,
5193            // since the panel orientation changed from vertical to horizontal.
5194            let bottom_dock = workspace.bottom_dock();
5195            assert_eq!(
5196                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5197                panel_1.size(cx),
5198            );
5199            // Close bottom dock and move panel_1 back to the left.
5200            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5201            panel_1.set_position(DockPosition::Left, cx);
5202        });
5203
5204        // Emit activated event on panel 1
5205        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5206
5207        // Now the left dock is open and panel_1 is active and focused.
5208        workspace.update(cx, |workspace, cx| {
5209            let left_dock = workspace.left_dock();
5210            assert!(left_dock.read(cx).is_open());
5211            assert_eq!(
5212                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5213                panel_1.panel_id(),
5214            );
5215            assert!(panel_1.focus_handle(cx).is_focused(cx));
5216        });
5217
5218        // Emit closed event on panel 2, which is not active
5219        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5220
5221        // Wo don't close the left dock, because panel_2 wasn't the active panel
5222        workspace.update(cx, |workspace, cx| {
5223            let left_dock = workspace.left_dock();
5224            assert!(left_dock.read(cx).is_open());
5225            assert_eq!(
5226                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5227                panel_1.panel_id(),
5228            );
5229        });
5230
5231        // Emitting a ZoomIn event shows the panel as zoomed.
5232        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5233        workspace.update(cx, |workspace, _| {
5234            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5235            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5236        });
5237
5238        // Move panel to another dock while it is zoomed
5239        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5240        workspace.update(cx, |workspace, _| {
5241            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5242
5243            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5244        });
5245
5246        // This is a helper for getting a:
5247        // - valid focus on an element,
5248        // - that isn't a part of the panes and panels system of the Workspace,
5249        // - and doesn't trigger the 'on_focus_lost' API.
5250        let focus_other_view = {
5251            let workspace = workspace.clone();
5252            move |cx: &mut VisualTestContext| {
5253                workspace.update(cx, |workspace, cx| {
5254                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5255                        workspace.toggle_modal(cx, TestModal::new);
5256                        workspace.toggle_modal(cx, TestModal::new);
5257                    } else {
5258                        workspace.toggle_modal(cx, TestModal::new);
5259                    }
5260                })
5261            }
5262        };
5263
5264        // If focus is transferred to another view that's not a panel or another pane, we still show
5265        // the panel as zoomed.
5266        focus_other_view(cx);
5267        workspace.update(cx, |workspace, _| {
5268            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5269            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5270        });
5271
5272        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5273        workspace.update(cx, |_, cx| cx.focus_self());
5274        workspace.update(cx, |workspace, _| {
5275            assert_eq!(workspace.zoomed, None);
5276            assert_eq!(workspace.zoomed_position, None);
5277        });
5278
5279        // If focus is transferred again to another view that's not a panel or a pane, we won't
5280        // show the panel as zoomed because it wasn't zoomed before.
5281        focus_other_view(cx);
5282        workspace.update(cx, |workspace, _| {
5283            assert_eq!(workspace.zoomed, None);
5284            assert_eq!(workspace.zoomed_position, None);
5285        });
5286
5287        // When the panel is activated, it is zoomed again.
5288        cx.dispatch_action(ToggleRightDock);
5289        workspace.update(cx, |workspace, _| {
5290            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5291            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5292        });
5293
5294        // Emitting a ZoomOut event unzooms the panel.
5295        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5296        workspace.update(cx, |workspace, _| {
5297            assert_eq!(workspace.zoomed, None);
5298            assert_eq!(workspace.zoomed_position, None);
5299        });
5300
5301        // Emit closed event on panel 1, which is active
5302        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5303
5304        // Now the left dock is closed, because panel_1 was the active panel
5305        workspace.update(cx, |workspace, cx| {
5306            let right_dock = workspace.right_dock();
5307            assert!(!right_dock.read(cx).is_open());
5308        });
5309    }
5310
5311    pub fn init_test(cx: &mut TestAppContext) {
5312        cx.update(|cx| {
5313            let settings_store = SettingsStore::test(cx);
5314            cx.set_global(settings_store);
5315            theme::init(theme::LoadThemes::JustBase, cx);
5316            language::init(cx);
5317            crate::init_settings(cx);
5318            Project::init_settings(cx);
5319        });
5320    }
5321}