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