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