workspace.rs

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