workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::ActiveCall;
  16use client::{
  17    proto::{self, PeerId},
  18    Client, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  22use futures::{
  23    channel::{mpsc, oneshot},
  24    future::try_join_all,
  25    Future, FutureExt, StreamExt,
  26};
  27use gpui::{
  28    actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView,
  29    AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
  30    DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
  31    FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
  32    ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
  33    Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
  34    WindowBounds, WindowContext, WindowHandle, WindowOptions,
  35};
  36use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  37use itertools::Itertools;
  38use language::{LanguageRegistry, Rope};
  39use lazy_static::lazy_static;
  40pub use modal_layer::*;
  41use node_runtime::NodeRuntime;
  42use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  43pub use pane::*;
  44pub use pane_group::*;
  45use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  46pub use persistence::{
  47    model::{ItemId, WorkspaceLocation},
  48    WorkspaceDb, DB as WORKSPACE_DB,
  49};
  50use postage::stream::Stream;
  51use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  52use serde::Deserialize;
  53use settings::Settings;
  54use shared_screen::SharedScreen;
  55use status_bar::StatusBar;
  56pub use status_bar::StatusItemView;
  57use std::{
  58    any::TypeId,
  59    borrow::Cow,
  60    cmp, env,
  61    path::{Path, PathBuf},
  62    sync::{atomic::AtomicUsize, Arc},
  63    time::Duration,
  64};
  65use theme::{ActiveTheme, ThemeSettings};
  66pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  67pub use ui;
  68use ui::Label;
  69use util::ResultExt;
  70use uuid::Uuid;
  71pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  72
  73use crate::persistence::{
  74    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  75    SerializedAxis,
  76};
  77
  78lazy_static! {
  79    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  80        .ok()
  81        .as_deref()
  82        .and_then(parse_pixel_size_env_var);
  83    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  84        .ok()
  85        .as_deref()
  86        .and_then(parse_pixel_position_env_var);
  87}
  88
  89#[derive(Clone, PartialEq)]
  90pub struct RemoveWorktreeFromProject(pub WorktreeId);
  91
  92actions!(
  93    workspace,
  94    [
  95        Open,
  96        NewFile,
  97        NewWindow,
  98        CloseWindow,
  99        CloseInactiveTabsAndPanes,
 100        AddFolderToProject,
 101        Unfollow,
 102        SaveAs,
 103        ReloadActiveItem,
 104        ActivatePreviousPane,
 105        ActivateNextPane,
 106        FollowNextCollaborator,
 107        NewTerminal,
 108        NewCenterTerminal,
 109        ToggleTerminalFocus,
 110        NewSearch,
 111        Feedback,
 112        Restart,
 113        Welcome,
 114        ToggleZoom,
 115        ToggleLeftDock,
 116        ToggleRightDock,
 117        ToggleBottomDock,
 118        CloseAllDocks,
 119    ]
 120);
 121
 122#[derive(Clone, PartialEq)]
 123pub struct OpenPaths {
 124    pub paths: Vec<PathBuf>,
 125}
 126
 127#[derive(Clone, Deserialize, PartialEq)]
 128pub struct ActivatePane(pub usize);
 129
 130#[derive(Clone, Deserialize, PartialEq)]
 131pub struct ActivatePaneInDirection(pub SplitDirection);
 132
 133#[derive(Clone, Deserialize, PartialEq)]
 134pub struct SwapPaneInDirection(pub SplitDirection);
 135
 136#[derive(Clone, Deserialize, PartialEq)]
 137pub struct NewFileInDirection(pub SplitDirection);
 138
 139#[derive(Clone, PartialEq, Debug, Deserialize)]
 140#[serde(rename_all = "camelCase")]
 141pub struct SaveAll {
 142    pub save_intent: Option<SaveIntent>,
 143}
 144
 145#[derive(Clone, PartialEq, Debug, Deserialize)]
 146#[serde(rename_all = "camelCase")]
 147pub struct Save {
 148    pub save_intent: Option<SaveIntent>,
 149}
 150
 151#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 152#[serde(rename_all = "camelCase")]
 153pub struct CloseAllItemsAndPanes {
 154    pub save_intent: Option<SaveIntent>,
 155}
 156
 157impl_actions!(
 158    workspace,
 159    [
 160        ActivatePane,
 161        ActivatePaneInDirection,
 162        CloseAllItemsAndPanes,
 163        NewFileInDirection,
 164        OpenTerminal,
 165        Save,
 166        SaveAll,
 167        SwapPaneInDirection,
 168    ]
 169);
 170
 171#[derive(Deserialize)]
 172pub struct Toast {
 173    id: usize,
 174    msg: Cow<'static, str>,
 175    #[serde(skip)]
 176    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 177}
 178
 179impl Toast {
 180    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 181        Toast {
 182            id,
 183            msg: msg.into(),
 184            on_click: None,
 185        }
 186    }
 187
 188    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 189    where
 190        M: Into<Cow<'static, str>>,
 191        F: Fn(&mut WindowContext) + 'static,
 192    {
 193        self.on_click = Some((message.into(), Arc::new(on_click)));
 194        self
 195    }
 196}
 197
 198impl PartialEq for Toast {
 199    fn eq(&self, other: &Self) -> bool {
 200        self.id == other.id
 201            && self.msg == other.msg
 202            && self.on_click.is_some() == other.on_click.is_some()
 203    }
 204}
 205
 206impl Clone for Toast {
 207    fn clone(&self) -> Self {
 208        Toast {
 209            id: self.id,
 210            msg: self.msg.to_owned(),
 211            on_click: self.on_click.clone(),
 212        }
 213    }
 214}
 215
 216#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 217pub struct OpenTerminal {
 218    pub working_directory: PathBuf,
 219}
 220
 221pub type WorkspaceId = i64;
 222
 223pub fn init_settings(cx: &mut AppContext) {
 224    WorkspaceSettings::register(cx);
 225    ItemSettings::register(cx);
 226}
 227
 228pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 229    init_settings(cx);
 230    notifications::init(cx);
 231
 232    cx.on_action(Workspace::close_global);
 233    cx.on_action(restart);
 234
 235    cx.on_action({
 236        let app_state = Arc::downgrade(&app_state);
 237        move |_: &Open, cx: &mut AppContext| {
 238            let paths = cx.prompt_for_paths(PathPromptOptions {
 239                files: true,
 240                directories: true,
 241                multiple: true,
 242            });
 243
 244            if let Some(app_state) = app_state.upgrade() {
 245                cx.spawn(move |cx| async move {
 246                    if let Some(paths) = paths.await.log_err().flatten() {
 247                        cx.update(|cx| {
 248                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 249                        })
 250                        .ok();
 251                    }
 252                })
 253                .detach();
 254            }
 255        }
 256    });
 257}
 258
 259type ProjectItemBuilders =
 260    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 261pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 262    let builders = cx.default_global::<ProjectItemBuilders>();
 263    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 264        let item = model.downcast::<I::Item>().unwrap();
 265        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
 266    });
 267}
 268
 269type FollowableItemBuilder = fn(
 270    View<Pane>,
 271    View<Workspace>,
 272    ViewId,
 273    &mut Option<proto::view::Variant>,
 274    &mut WindowContext,
 275) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 276type FollowableItemBuilders = HashMap<
 277    TypeId,
 278    (
 279        FollowableItemBuilder,
 280        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 281    ),
 282>;
 283pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 284    let builders = cx.default_global::<FollowableItemBuilders>();
 285    builders.insert(
 286        TypeId::of::<I>(),
 287        (
 288            |pane, workspace, id, state, cx| {
 289                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 290                    cx.foreground_executor()
 291                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 292                })
 293            },
 294            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 295        ),
 296    );
 297}
 298
 299type ItemDeserializers = HashMap<
 300    Arc<str>,
 301    fn(
 302        Model<Project>,
 303        WeakView<Workspace>,
 304        WorkspaceId,
 305        ItemId,
 306        &mut ViewContext<Pane>,
 307    ) -> Task<Result<Box<dyn ItemHandle>>>,
 308>;
 309pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 310    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 311        let deserializers = cx.default_global::<ItemDeserializers>();
 312        deserializers.insert(
 313            Arc::from(serialized_item_kind),
 314            |project, workspace, workspace_id, item_id, cx| {
 315                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 316                cx.foreground_executor()
 317                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 318            },
 319        );
 320    }
 321}
 322
 323pub struct AppState {
 324    pub languages: Arc<LanguageRegistry>,
 325    pub client: Arc<Client>,
 326    pub user_store: Model<UserStore>,
 327    pub workspace_store: Model<WorkspaceStore>,
 328    pub fs: Arc<dyn fs::Fs>,
 329    pub build_window_options:
 330        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 331    pub node_runtime: Arc<dyn NodeRuntime>,
 332}
 333
 334pub struct WorkspaceStore {
 335    workspaces: HashSet<WindowHandle<Workspace>>,
 336    followers: Vec<Follower>,
 337    client: Arc<Client>,
 338    _subscriptions: Vec<client::Subscription>,
 339}
 340
 341#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 342struct Follower {
 343    project_id: Option<u64>,
 344    peer_id: PeerId,
 345}
 346
 347impl AppState {
 348    #[cfg(any(test, feature = "test-support"))]
 349    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 350        use node_runtime::FakeNodeRuntime;
 351        use settings::SettingsStore;
 352
 353        if !cx.has_global::<SettingsStore>() {
 354            let settings_store = SettingsStore::test(cx);
 355            cx.set_global(settings_store);
 356        }
 357
 358        let fs = fs::FakeFs::new(cx.background_executor().clone());
 359        let languages = Arc::new(LanguageRegistry::test());
 360        let http_client = util::http::FakeHttpClient::with_404_response();
 361        let client = Client::new(http_client.clone(), cx);
 362        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 363        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 364
 365        theme::init(theme::LoadThemes::JustBase, cx);
 366        client::init(&client, cx);
 367        crate::init_settings(cx);
 368
 369        Arc::new(Self {
 370            client,
 371            fs,
 372            languages,
 373            user_store,
 374            workspace_store,
 375            node_runtime: FakeNodeRuntime::new(),
 376            build_window_options: |_, _, _| Default::default(),
 377        })
 378    }
 379}
 380
 381struct DelayedDebouncedEditAction {
 382    task: Option<Task<()>>,
 383    cancel_channel: Option<oneshot::Sender<()>>,
 384}
 385
 386impl DelayedDebouncedEditAction {
 387    fn new() -> DelayedDebouncedEditAction {
 388        DelayedDebouncedEditAction {
 389            task: None,
 390            cancel_channel: None,
 391        }
 392    }
 393
 394    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 395    where
 396        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 397    {
 398        if let Some(channel) = self.cancel_channel.take() {
 399            _ = channel.send(());
 400        }
 401
 402        let (sender, mut receiver) = oneshot::channel::<()>();
 403        self.cancel_channel = Some(sender);
 404
 405        let previous_task = self.task.take();
 406        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 407            let mut timer = cx.background_executor().timer(delay).fuse();
 408            if let Some(previous_task) = previous_task {
 409                previous_task.await;
 410            }
 411
 412            futures::select_biased! {
 413                _ = receiver => return,
 414                    _ = timer => {}
 415            }
 416
 417            if let Some(result) = workspace
 418                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 419                .log_err()
 420            {
 421                result.await.log_err();
 422            }
 423        }));
 424    }
 425}
 426
 427pub enum Event {
 428    PaneAdded(View<Pane>),
 429    ContactRequestedJoin(u64),
 430    WorkspaceCreated(WeakView<Workspace>),
 431}
 432
 433pub enum OpenVisible {
 434    All,
 435    None,
 436    OnlyFiles,
 437    OnlyDirectories,
 438}
 439
 440pub struct Workspace {
 441    weak_self: WeakView<Self>,
 442    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 443    zoomed: Option<AnyWeakView>,
 444    zoomed_position: Option<DockPosition>,
 445    center: PaneGroup,
 446    left_dock: View<Dock>,
 447    bottom_dock: View<Dock>,
 448    right_dock: View<Dock>,
 449    panes: Vec<View<Pane>>,
 450    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 451    active_pane: View<Pane>,
 452    last_active_center_pane: Option<WeakView<Pane>>,
 453    last_active_view_id: Option<proto::ViewId>,
 454    status_bar: View<StatusBar>,
 455    modal_layer: View<ModalLayer>,
 456    titlebar_item: Option<AnyView>,
 457    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 458    project: Model<Project>,
 459    follower_states: HashMap<View<Pane>, FollowerState>,
 460    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 461    window_edited: bool,
 462    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 463    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 464    database_id: WorkspaceId,
 465    app_state: Arc<AppState>,
 466    _subscriptions: Vec<Subscription>,
 467    _apply_leader_updates: Task<Result<()>>,
 468    _observe_current_user: Task<Result<()>>,
 469    _schedule_serialize: Option<Task<()>>,
 470    pane_history_timestamp: Arc<AtomicUsize>,
 471    bounds: Bounds<Pixels>,
 472}
 473
 474impl EventEmitter<Event> for Workspace {}
 475
 476#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 477pub struct ViewId {
 478    pub creator: PeerId,
 479    pub id: u64,
 480}
 481
 482#[derive(Default)]
 483struct FollowerState {
 484    leader_id: PeerId,
 485    active_view_id: Option<ViewId>,
 486    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 487}
 488
 489impl Workspace {
 490    pub fn new(
 491        workspace_id: WorkspaceId,
 492        project: Model<Project>,
 493        app_state: Arc<AppState>,
 494        cx: &mut ViewContext<Self>,
 495    ) -> Self {
 496        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 497        cx.subscribe(&project, move |this, _, event, cx| {
 498            match event {
 499                project::Event::RemoteIdChanged(_) => {
 500                    this.update_window_title(cx);
 501                }
 502
 503                project::Event::CollaboratorLeft(peer_id) => {
 504                    this.collaborator_left(*peer_id, cx);
 505                }
 506
 507                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 508                    this.update_window_title(cx);
 509                    this.serialize_workspace(cx);
 510                }
 511
 512                project::Event::DisconnectedFromHost => {
 513                    this.update_window_edited(cx);
 514                    let panes_to_unfollow: Vec<View<Pane>> =
 515                        this.follower_states.keys().map(|k| k.clone()).collect();
 516                    for pane in panes_to_unfollow {
 517                        this.unfollow(&pane, cx);
 518                    }
 519                    cx.disable_focus();
 520                }
 521
 522                project::Event::Closed => {
 523                    cx.remove_window();
 524                }
 525
 526                project::Event::DeletedEntry(entry_id) => {
 527                    for pane in this.panes.iter() {
 528                        pane.update(cx, |pane, cx| {
 529                            pane.handle_deleted_project_item(*entry_id, cx)
 530                        });
 531                    }
 532                }
 533
 534                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 535                    cx.new_view(|_| MessageNotification::new(message.clone()))
 536                }),
 537
 538                _ => {}
 539            }
 540            cx.notify()
 541        })
 542        .detach();
 543
 544        cx.on_focus_lost(|this, cx| {
 545            let focus_handle = this.focus_handle(cx);
 546            cx.focus(&focus_handle);
 547        })
 548        .detach();
 549
 550        let weak_handle = cx.view().downgrade();
 551        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 552
 553        let center_pane = cx.new_view(|cx| {
 554            Pane::new(
 555                weak_handle.clone(),
 556                project.clone(),
 557                pane_history_timestamp.clone(),
 558                None,
 559                cx,
 560            )
 561        });
 562        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 563
 564        cx.focus_view(&center_pane);
 565        cx.emit(Event::PaneAdded(center_pane.clone()));
 566
 567        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 568        app_state.workspace_store.update(cx, |store, _| {
 569            store.workspaces.insert(window_handle);
 570        });
 571
 572        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 573        let mut connection_status = app_state.client.status();
 574        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 575            current_user.next().await;
 576            connection_status.next().await;
 577            let mut stream =
 578                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 579
 580            while stream.recv().await.is_some() {
 581                this.update(&mut cx, |_, cx| cx.notify())?;
 582            }
 583            anyhow::Ok(())
 584        });
 585
 586        // All leader updates are enqueued and then processed in a single task, so
 587        // that each asynchronous operation can be run in order.
 588        let (leader_updates_tx, mut leader_updates_rx) =
 589            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 590        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 591            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 592                Self::process_leader_update(&this, leader_id, update, &mut cx)
 593                    .await
 594                    .log_err();
 595            }
 596
 597            Ok(())
 598        });
 599
 600        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 601
 602        let left_dock = Dock::new(DockPosition::Left, cx);
 603        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 604        let right_dock = Dock::new(DockPosition::Right, cx);
 605        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 606        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 607        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 608        let status_bar = cx.new_view(|cx| {
 609            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 610            status_bar.add_left_item(left_dock_buttons, cx);
 611            status_bar.add_right_item(right_dock_buttons, cx);
 612            status_bar.add_right_item(bottom_dock_buttons, cx);
 613            status_bar
 614        });
 615
 616        let modal_layer = cx.new_view(|_| ModalLayer::new());
 617
 618        let mut active_call = None;
 619        if let Some(call) = cx.try_global::<Model<ActiveCall>>() {
 620            let call = call.clone();
 621            let mut subscriptions = Vec::new();
 622            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 623            active_call = Some((call, subscriptions));
 624        }
 625
 626        let subscriptions = vec![
 627            cx.observe_window_activation(Self::on_window_activation_changed),
 628            cx.observe_window_bounds(move |_, cx| {
 629                if let Some(display) = cx.display() {
 630                    // Transform fixed bounds to be stored in terms of the containing display
 631                    let mut bounds = cx.window_bounds();
 632                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 633                        let display_bounds = display.bounds();
 634                        window_bounds.origin.x -= display_bounds.origin.x;
 635                        window_bounds.origin.y -= display_bounds.origin.y;
 636                    }
 637
 638                    if let Some(display_uuid) = display.uuid().log_err() {
 639                        cx.background_executor()
 640                            .spawn(DB.set_window_bounds(
 641                                workspace_id,
 642                                SerializedWindowsBounds(bounds),
 643                                display_uuid,
 644                            ))
 645                            .detach_and_log_err(cx);
 646                    }
 647                }
 648                cx.notify();
 649            }),
 650            cx.observe(&left_dock, |this, _, cx| {
 651                this.serialize_workspace(cx);
 652                cx.notify();
 653            }),
 654            cx.observe(&bottom_dock, |this, _, cx| {
 655                this.serialize_workspace(cx);
 656                cx.notify();
 657            }),
 658            cx.observe(&right_dock, |this, _, cx| {
 659                this.serialize_workspace(cx);
 660                cx.notify();
 661            }),
 662            cx.on_release(|this, window, cx| {
 663                this.app_state.workspace_store.update(cx, |store, _| {
 664                    let window = window.downcast::<Self>().unwrap();
 665                    store.workspaces.remove(&window);
 666                })
 667            }),
 668        ];
 669
 670        cx.defer(|this, cx| {
 671            this.update_window_title(cx);
 672        });
 673        Workspace {
 674            weak_self: weak_handle.clone(),
 675            zoomed: None,
 676            zoomed_position: None,
 677            center: PaneGroup::new(center_pane.clone()),
 678            panes: vec![center_pane.clone()],
 679            panes_by_item: Default::default(),
 680            active_pane: center_pane.clone(),
 681            last_active_center_pane: Some(center_pane.downgrade()),
 682            last_active_view_id: None,
 683            status_bar,
 684            modal_layer,
 685            titlebar_item: None,
 686            notifications: Default::default(),
 687            left_dock,
 688            bottom_dock,
 689            right_dock,
 690            project: project.clone(),
 691            follower_states: Default::default(),
 692            last_leaders_by_pane: Default::default(),
 693            window_edited: false,
 694            active_call,
 695            database_id: workspace_id,
 696            app_state,
 697            _observe_current_user,
 698            _apply_leader_updates,
 699            _schedule_serialize: None,
 700            leader_updates_tx,
 701            _subscriptions: subscriptions,
 702            pane_history_timestamp,
 703            workspace_actions: Default::default(),
 704            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 705            bounds: Default::default(),
 706        }
 707    }
 708
 709    fn new_local(
 710        abs_paths: Vec<PathBuf>,
 711        app_state: Arc<AppState>,
 712        requesting_window: Option<WindowHandle<Workspace>>,
 713        cx: &mut AppContext,
 714    ) -> Task<
 715        anyhow::Result<(
 716            WindowHandle<Workspace>,
 717            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 718        )>,
 719    > {
 720        let project_handle = Project::local(
 721            app_state.client.clone(),
 722            app_state.node_runtime.clone(),
 723            app_state.user_store.clone(),
 724            app_state.languages.clone(),
 725            app_state.fs.clone(),
 726            cx,
 727        );
 728
 729        cx.spawn(|mut cx| async move {
 730            let serialized_workspace: Option<SerializedWorkspace> =
 731                persistence::DB.workspace_for_roots(abs_paths.as_slice());
 732
 733            let paths_to_open = Arc::new(abs_paths);
 734
 735            // Get project paths for all of the abs_paths
 736            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 737            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 738                Vec::with_capacity(paths_to_open.len());
 739            for path in paths_to_open.iter().cloned() {
 740                if let Some((worktree, project_entry)) = cx
 741                    .update(|cx| {
 742                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 743                    })?
 744                    .await
 745                    .log_err()
 746                {
 747                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 748                    project_paths.push((path, Some(project_entry)));
 749                } else {
 750                    project_paths.push((path, None));
 751                }
 752            }
 753
 754            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 755                serialized_workspace.id
 756            } else {
 757                DB.next_id().await.unwrap_or(0)
 758            };
 759
 760            let window = if let Some(window) = requesting_window {
 761                cx.update_window(window.into(), |_, cx| {
 762                    cx.replace_root_view(|cx| {
 763                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 764                    });
 765                })?;
 766                window
 767            } else {
 768                let window_bounds_override = window_bounds_env_override(&cx);
 769                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 770                    (Some(bounds), None)
 771                } else {
 772                    serialized_workspace
 773                        .as_ref()
 774                        .and_then(|serialized_workspace| {
 775                            let serialized_display = serialized_workspace.display?;
 776                            let mut bounds = serialized_workspace.bounds?;
 777
 778                            // Stored bounds are relative to the containing display.
 779                            // So convert back to global coordinates if that screen still exists
 780                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 781                                let screen = cx
 782                                    .update(|cx| {
 783                                        cx.displays().into_iter().find(|display| {
 784                                            display.uuid().ok() == Some(serialized_display)
 785                                        })
 786                                    })
 787                                    .ok()??;
 788                                let screen_bounds = screen.bounds();
 789                                window_bounds.origin.x += screen_bounds.origin.x;
 790                                window_bounds.origin.y += screen_bounds.origin.y;
 791                                bounds = WindowBounds::Fixed(window_bounds);
 792                            }
 793
 794                            Some((bounds, serialized_display))
 795                        })
 796                        .unzip()
 797                };
 798
 799                // Use the serialized workspace to construct the new window
 800                let options =
 801                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 802
 803                cx.open_window(options, {
 804                    let app_state = app_state.clone();
 805                    let workspace_id = workspace_id.clone();
 806                    let project_handle = project_handle.clone();
 807                    move |cx| {
 808                        cx.new_view(|cx| {
 809                            Workspace::new(workspace_id, project_handle, app_state, cx)
 810                        })
 811                    }
 812                })?
 813            };
 814
 815            window
 816                .update(&mut cx, |_, cx| cx.activate_window())
 817                .log_err();
 818
 819            notify_if_database_failed(window, &mut cx);
 820            let opened_items = window
 821                .update(&mut cx, |_workspace, cx| {
 822                    open_items(serialized_workspace, project_paths, app_state, cx)
 823                })?
 824                .await
 825                .unwrap_or_default();
 826
 827            Ok((window, opened_items))
 828        })
 829    }
 830
 831    pub fn weak_handle(&self) -> WeakView<Self> {
 832        self.weak_self.clone()
 833    }
 834
 835    pub fn left_dock(&self) -> &View<Dock> {
 836        &self.left_dock
 837    }
 838
 839    pub fn bottom_dock(&self) -> &View<Dock> {
 840        &self.bottom_dock
 841    }
 842
 843    pub fn right_dock(&self) -> &View<Dock> {
 844        &self.right_dock
 845    }
 846
 847    pub fn is_edited(&self) -> bool {
 848        self.window_edited
 849    }
 850
 851    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
 852        let dock = match panel.position(cx) {
 853            DockPosition::Left => &self.left_dock,
 854            DockPosition::Bottom => &self.bottom_dock,
 855            DockPosition::Right => &self.right_dock,
 856        };
 857
 858        dock.update(cx, |dock, cx| {
 859            dock.add_panel(panel, self.weak_self.clone(), cx)
 860        });
 861    }
 862
 863    pub fn status_bar(&self) -> &View<StatusBar> {
 864        &self.status_bar
 865    }
 866
 867    pub fn app_state(&self) -> &Arc<AppState> {
 868        &self.app_state
 869    }
 870
 871    pub fn user_store(&self) -> &Model<UserStore> {
 872        &self.app_state.user_store
 873    }
 874
 875    pub fn project(&self) -> &Model<Project> {
 876        &self.project
 877    }
 878
 879    pub fn recent_navigation_history(
 880        &self,
 881        limit: Option<usize>,
 882        cx: &AppContext,
 883    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 884        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 885        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 886        for pane in &self.panes {
 887            let pane = pane.read(cx);
 888            pane.nav_history()
 889                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 890                    if let Some(fs_path) = &fs_path {
 891                        abs_paths_opened
 892                            .entry(fs_path.clone())
 893                            .or_default()
 894                            .insert(project_path.clone());
 895                    }
 896                    let timestamp = entry.timestamp;
 897                    match history.entry(project_path) {
 898                        hash_map::Entry::Occupied(mut entry) => {
 899                            let (_, old_timestamp) = entry.get();
 900                            if &timestamp > old_timestamp {
 901                                entry.insert((fs_path, timestamp));
 902                            }
 903                        }
 904                        hash_map::Entry::Vacant(entry) => {
 905                            entry.insert((fs_path, timestamp));
 906                        }
 907                    }
 908                });
 909        }
 910
 911        history
 912            .into_iter()
 913            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 914            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 915            .rev()
 916            .filter(|(history_path, abs_path)| {
 917                let latest_project_path_opened = abs_path
 918                    .as_ref()
 919                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 920                    .and_then(|project_paths| {
 921                        project_paths
 922                            .iter()
 923                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 924                    });
 925
 926                match latest_project_path_opened {
 927                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 928                    None => true,
 929                }
 930            })
 931            .take(limit.unwrap_or(usize::MAX))
 932            .collect()
 933    }
 934
 935    fn navigate_history(
 936        &mut self,
 937        pane: WeakView<Pane>,
 938        mode: NavigationMode,
 939        cx: &mut ViewContext<Workspace>,
 940    ) -> Task<Result<()>> {
 941        let to_load = if let Some(pane) = pane.upgrade() {
 942            pane.update(cx, |pane, cx| {
 943                pane.focus(cx);
 944                loop {
 945                    // Retrieve the weak item handle from the history.
 946                    let entry = pane.nav_history_mut().pop(mode, cx)?;
 947
 948                    // If the item is still present in this pane, then activate it.
 949                    if let Some(index) = entry
 950                        .item
 951                        .upgrade()
 952                        .and_then(|v| pane.index_for_item(v.as_ref()))
 953                    {
 954                        let prev_active_item_index = pane.active_item_index();
 955                        pane.nav_history_mut().set_mode(mode);
 956                        pane.activate_item(index, true, true, cx);
 957                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 958
 959                        let mut navigated = prev_active_item_index != pane.active_item_index();
 960                        if let Some(data) = entry.data {
 961                            navigated |= pane.active_item()?.navigate(data, cx);
 962                        }
 963
 964                        if navigated {
 965                            break None;
 966                        }
 967                    }
 968                    // If the item is no longer present in this pane, then retrieve its
 969                    // project path in order to reopen it.
 970                    else {
 971                        break pane
 972                            .nav_history()
 973                            .path_for_item(entry.item.id())
 974                            .map(|(project_path, _)| (project_path, entry));
 975                    }
 976                }
 977            })
 978        } else {
 979            None
 980        };
 981
 982        if let Some((project_path, entry)) = to_load {
 983            // If the item was no longer present, then load it again from its previous path.
 984            let task = self.load_path(project_path, cx);
 985            cx.spawn(|workspace, mut cx| async move {
 986                let task = task.await;
 987                let mut navigated = false;
 988                if let Some((project_entry_id, build_item)) = task.log_err() {
 989                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
 990                        pane.nav_history_mut().set_mode(mode);
 991                        pane.active_item().map(|p| p.item_id())
 992                    })?;
 993
 994                    pane.update(&mut cx, |pane, cx| {
 995                        let item = pane.open_item(project_entry_id, true, cx, build_item);
 996                        navigated |= Some(item.item_id()) != prev_active_item_id;
 997                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
 998                        if let Some(data) = entry.data {
 999                            navigated |= item.navigate(data, cx);
1000                        }
1001                    })?;
1002                }
1003
1004                if !navigated {
1005                    workspace
1006                        .update(&mut cx, |workspace, cx| {
1007                            Self::navigate_history(workspace, pane, mode, cx)
1008                        })?
1009                        .await?;
1010                }
1011
1012                Ok(())
1013            })
1014        } else {
1015            Task::ready(Ok(()))
1016        }
1017    }
1018
1019    pub fn go_back(
1020        &mut self,
1021        pane: WeakView<Pane>,
1022        cx: &mut ViewContext<Workspace>,
1023    ) -> Task<Result<()>> {
1024        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1025    }
1026
1027    pub fn go_forward(
1028        &mut self,
1029        pane: WeakView<Pane>,
1030        cx: &mut ViewContext<Workspace>,
1031    ) -> Task<Result<()>> {
1032        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1033    }
1034
1035    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1036        self.navigate_history(
1037            self.active_pane().downgrade(),
1038            NavigationMode::ReopeningClosedItem,
1039            cx,
1040        )
1041    }
1042
1043    pub fn client(&self) -> &Client {
1044        &self.app_state.client
1045    }
1046
1047    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1048        self.titlebar_item = Some(item);
1049        cx.notify();
1050    }
1051
1052    pub fn titlebar_item(&self) -> Option<AnyView> {
1053        self.titlebar_item.clone()
1054    }
1055
1056    /// Call the given callback with a workspace whose project is local.
1057    ///
1058    /// If the given workspace has a local project, then it will be passed
1059    /// to the callback. Otherwise, a new empty window will be created.
1060    pub fn with_local_workspace<T, F>(
1061        &mut self,
1062        cx: &mut ViewContext<Self>,
1063        callback: F,
1064    ) -> Task<Result<T>>
1065    where
1066        T: 'static,
1067        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1068    {
1069        if self.project.read(cx).is_local() {
1070            Task::Ready(Some(Ok(callback(self, cx))))
1071        } else {
1072            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1073            cx.spawn(|_vh, mut cx| async move {
1074                let (workspace, _) = task.await?;
1075                workspace.update(&mut cx, callback)
1076            })
1077        }
1078    }
1079
1080    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1081        self.project.read(cx).worktrees()
1082    }
1083
1084    pub fn visible_worktrees<'a>(
1085        &self,
1086        cx: &'a AppContext,
1087    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1088        self.project.read(cx).visible_worktrees(cx)
1089    }
1090
1091    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1092        let futures = self
1093            .worktrees(cx)
1094            .filter_map(|worktree| worktree.read(cx).as_local())
1095            .map(|worktree| worktree.scan_complete())
1096            .collect::<Vec<_>>();
1097        async move {
1098            for future in futures {
1099                future.await;
1100            }
1101        }
1102    }
1103
1104    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1105        cx.defer(|cx| {
1106            cx.windows().iter().find(|window| {
1107                window
1108                    .update(cx, |_, window| {
1109                        if window.is_window_active() {
1110                            //This can only get called when the window's project connection has been lost
1111                            //so we don't need to prompt the user for anything and instead just close the window
1112                            window.remove_window();
1113                            true
1114                        } else {
1115                            false
1116                        }
1117                    })
1118                    .unwrap_or(false)
1119            });
1120        });
1121    }
1122
1123    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
1124        let window = cx.window_handle();
1125        let prepare = self.prepare_to_close(false, cx);
1126        cx.spawn(|_, mut cx| async move {
1127            if prepare.await? {
1128                window.update(&mut cx, |_, cx| {
1129                    cx.remove_window();
1130                })?;
1131            }
1132            anyhow::Ok(())
1133        })
1134        .detach_and_log_err(cx)
1135    }
1136
1137    pub fn prepare_to_close(
1138        &mut self,
1139        quitting: bool,
1140        cx: &mut ViewContext<Self>,
1141    ) -> Task<Result<bool>> {
1142        let active_call = self.active_call().cloned();
1143        let window = cx.window_handle();
1144
1145        cx.spawn(|this, mut cx| async move {
1146            let workspace_count = (*cx).update(|cx| {
1147                cx.windows()
1148                    .iter()
1149                    .filter(|window| window.downcast::<Workspace>().is_some())
1150                    .count()
1151            })?;
1152
1153            if let Some(active_call) = active_call {
1154                if !quitting
1155                    && workspace_count == 1
1156                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
1157                {
1158                    let answer = window.update(&mut cx, |_, cx| {
1159                        cx.prompt(
1160                            PromptLevel::Warning,
1161                            "Do you want to leave the current call?",
1162                            &["Close window and hang up", "Cancel"],
1163                        )
1164                    })?;
1165
1166                    if answer.await.log_err() == Some(1) {
1167                        return anyhow::Ok(false);
1168                    } else {
1169                        active_call
1170                            .update(&mut cx, |call, cx| call.hang_up(cx))?
1171                            .await
1172                            .log_err();
1173                    }
1174                }
1175            }
1176
1177            Ok(this
1178                .update(&mut cx, |this, cx| {
1179                    this.save_all_internal(SaveIntent::Close, cx)
1180                })?
1181                .await?)
1182        })
1183    }
1184
1185    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1186        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1187            .detach_and_log_err(cx);
1188    }
1189
1190    fn save_all_internal(
1191        &mut self,
1192        mut save_intent: SaveIntent,
1193        cx: &mut ViewContext<Self>,
1194    ) -> Task<Result<bool>> {
1195        if self.project.read(cx).is_disconnected() {
1196            return Task::ready(Ok(true));
1197        }
1198        let dirty_items = self
1199            .panes
1200            .iter()
1201            .flat_map(|pane| {
1202                pane.read(cx).items().filter_map(|item| {
1203                    if item.is_dirty(cx) {
1204                        Some((pane.downgrade(), item.boxed_clone()))
1205                    } else {
1206                        None
1207                    }
1208                })
1209            })
1210            .collect::<Vec<_>>();
1211
1212        let project = self.project.clone();
1213        cx.spawn(|workspace, mut cx| async move {
1214            // Override save mode and display "Save all files" prompt
1215            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1216                let answer = workspace.update(&mut cx, |_, cx| {
1217                    let prompt = Pane::file_names_for_prompt(
1218                        &mut dirty_items.iter().map(|(_, handle)| handle),
1219                        dirty_items.len(),
1220                        cx,
1221                    );
1222                    cx.prompt(
1223                        PromptLevel::Warning,
1224                        &prompt,
1225                        &["Save all", "Discard all", "Cancel"],
1226                    )
1227                })?;
1228                match answer.await.log_err() {
1229                    Some(0) => save_intent = SaveIntent::SaveAll,
1230                    Some(1) => save_intent = SaveIntent::Skip,
1231                    _ => {}
1232                }
1233            }
1234            for (pane, item) in dirty_items {
1235                let (singleton, project_entry_ids) =
1236                    cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1237                if singleton || !project_entry_ids.is_empty() {
1238                    if let Some(ix) =
1239                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1240                    {
1241                        if !Pane::save_item(
1242                            project.clone(),
1243                            &pane,
1244                            ix,
1245                            &*item,
1246                            save_intent,
1247                            &mut cx,
1248                        )
1249                        .await?
1250                        {
1251                            return Ok(false);
1252                        }
1253                    }
1254                }
1255            }
1256            Ok(true)
1257        })
1258    }
1259
1260    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1261        self.client()
1262            .telemetry()
1263            .report_app_event("open project".to_string());
1264        let paths = cx.prompt_for_paths(PathPromptOptions {
1265            files: true,
1266            directories: true,
1267            multiple: true,
1268        });
1269
1270        cx.spawn(|this, mut cx| async move {
1271            let Some(paths) = paths.await.log_err().flatten() else {
1272                return;
1273            };
1274
1275            if let Some(task) = this
1276                .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1277                .log_err()
1278            {
1279                task.await.log_err();
1280            }
1281        })
1282        .detach()
1283    }
1284
1285    pub fn open_workspace_for_paths(
1286        &mut self,
1287        paths: Vec<PathBuf>,
1288        cx: &mut ViewContext<Self>,
1289    ) -> Task<Result<()>> {
1290        let window = cx.window_handle().downcast::<Self>();
1291        let is_remote = self.project.read(cx).is_remote();
1292        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1293        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1294        let close_task = if is_remote || has_worktree || has_dirty_items {
1295            None
1296        } else {
1297            Some(self.prepare_to_close(false, cx))
1298        };
1299        let app_state = self.app_state.clone();
1300
1301        cx.spawn(|_, mut cx| async move {
1302            let window_to_replace = if let Some(close_task) = close_task {
1303                if !close_task.await? {
1304                    return Ok(());
1305                }
1306                window
1307            } else {
1308                None
1309            };
1310            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))?
1311                .await?;
1312            Ok(())
1313        })
1314    }
1315
1316    #[allow(clippy::type_complexity)]
1317    pub fn open_paths(
1318        &mut self,
1319        mut abs_paths: Vec<PathBuf>,
1320        visible: OpenVisible,
1321        pane: Option<WeakView<Pane>>,
1322        cx: &mut ViewContext<Self>,
1323    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1324        log::info!("open paths {abs_paths:?}");
1325
1326        let fs = self.app_state.fs.clone();
1327
1328        // Sort the paths to ensure we add worktrees for parents before their children.
1329        abs_paths.sort_unstable();
1330        cx.spawn(move |this, mut cx| async move {
1331            let mut tasks = Vec::with_capacity(abs_paths.len());
1332
1333            for abs_path in &abs_paths {
1334                let visible = match visible {
1335                    OpenVisible::All => Some(true),
1336                    OpenVisible::None => Some(false),
1337                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1338                        Some(Some(metadata)) => Some(!metadata.is_dir),
1339                        Some(None) => {
1340                            log::error!("No metadata for file {abs_path:?}");
1341                            None
1342                        }
1343                        None => None,
1344                    },
1345                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1346                        Some(Some(metadata)) => Some(metadata.is_dir),
1347                        Some(None) => {
1348                            log::error!("No metadata for file {abs_path:?}");
1349                            None
1350                        }
1351                        None => None,
1352                    },
1353                };
1354                let project_path = match visible {
1355                    Some(visible) => match this
1356                        .update(&mut cx, |this, cx| {
1357                            Workspace::project_path_for_path(
1358                                this.project.clone(),
1359                                abs_path,
1360                                visible,
1361                                cx,
1362                            )
1363                        })
1364                        .log_err()
1365                    {
1366                        Some(project_path) => project_path.await.log_err(),
1367                        None => None,
1368                    },
1369                    None => None,
1370                };
1371
1372                let this = this.clone();
1373                let abs_path = abs_path.clone();
1374                let fs = fs.clone();
1375                let pane = pane.clone();
1376                let task = cx.spawn(move |mut cx| async move {
1377                    let (worktree, project_path) = project_path?;
1378                    if fs.is_file(&abs_path).await {
1379                        Some(
1380                            this.update(&mut cx, |this, cx| {
1381                                this.open_path(project_path, pane, true, cx)
1382                            })
1383                            .log_err()?
1384                            .await,
1385                        )
1386                    } else {
1387                        this.update(&mut cx, |workspace, cx| {
1388                            let worktree = worktree.read(cx);
1389                            let worktree_abs_path = worktree.abs_path();
1390                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1391                                worktree.root_entry()
1392                            } else {
1393                                abs_path
1394                                    .strip_prefix(worktree_abs_path.as_ref())
1395                                    .ok()
1396                                    .and_then(|relative_path| {
1397                                        worktree.entry_for_path(relative_path)
1398                                    })
1399                            }
1400                            .map(|entry| entry.id);
1401                            if let Some(entry_id) = entry_id {
1402                                workspace.project.update(cx, |_, cx| {
1403                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1404                                })
1405                            }
1406                        })
1407                        .log_err()?;
1408                        None
1409                    }
1410                });
1411                tasks.push(task);
1412            }
1413
1414            futures::future::join_all(tasks).await
1415        })
1416    }
1417
1418    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1419        let paths = cx.prompt_for_paths(PathPromptOptions {
1420            files: false,
1421            directories: true,
1422            multiple: true,
1423        });
1424        cx.spawn(|this, mut cx| async move {
1425            if let Some(paths) = paths.await.log_err().flatten() {
1426                let results = this
1427                    .update(&mut cx, |this, cx| {
1428                        this.open_paths(paths, OpenVisible::All, None, cx)
1429                    })?
1430                    .await;
1431                for result in results.into_iter().flatten() {
1432                    result.log_err();
1433                }
1434            }
1435            anyhow::Ok(())
1436        })
1437        .detach_and_log_err(cx);
1438    }
1439
1440    fn project_path_for_path(
1441        project: Model<Project>,
1442        abs_path: &Path,
1443        visible: bool,
1444        cx: &mut AppContext,
1445    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1446        let entry = project.update(cx, |project, cx| {
1447            project.find_or_create_local_worktree(abs_path, visible, cx)
1448        });
1449        cx.spawn(|mut cx| async move {
1450            let (worktree, path) = entry.await?;
1451            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1452            Ok((
1453                worktree,
1454                ProjectPath {
1455                    worktree_id,
1456                    path: path.into(),
1457                },
1458            ))
1459        })
1460    }
1461
1462    pub fn items<'a>(
1463        &'a self,
1464        cx: &'a AppContext,
1465    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1466        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1467    }
1468
1469    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1470        self.items_of_type(cx).max_by_key(|item| item.item_id())
1471    }
1472
1473    pub fn items_of_type<'a, T: Item>(
1474        &'a self,
1475        cx: &'a AppContext,
1476    ) -> impl 'a + Iterator<Item = View<T>> {
1477        self.panes
1478            .iter()
1479            .flat_map(|pane| pane.read(cx).items_of_type())
1480    }
1481
1482    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1483        self.active_pane().read(cx).active_item()
1484    }
1485
1486    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1487        let item = self.active_item(cx)?;
1488        item.to_any().downcast::<I>().ok()
1489    }
1490
1491    fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
1492        self.active_item(cx).and_then(|item| item.project_path(cx))
1493    }
1494
1495    pub fn save_active_item(
1496        &mut self,
1497        save_intent: SaveIntent,
1498        cx: &mut WindowContext,
1499    ) -> Task<Result<()>> {
1500        let project = self.project.clone();
1501        let pane = self.active_pane();
1502        let item_ix = pane.read(cx).active_item_index();
1503        let item = pane.read(cx).active_item();
1504        let pane = pane.downgrade();
1505
1506        cx.spawn(|mut cx| async move {
1507            if let Some(item) = item {
1508                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1509                    .await
1510                    .map(|_| ())
1511            } else {
1512                Ok(())
1513            }
1514        })
1515    }
1516
1517    pub fn close_inactive_items_and_panes(
1518        &mut self,
1519        _: &CloseInactiveTabsAndPanes,
1520        cx: &mut ViewContext<Self>,
1521    ) {
1522        self.close_all_internal(true, SaveIntent::Close, cx)
1523            .map(|task| task.detach_and_log_err(cx));
1524    }
1525
1526    pub fn close_all_items_and_panes(
1527        &mut self,
1528        action: &CloseAllItemsAndPanes,
1529        cx: &mut ViewContext<Self>,
1530    ) {
1531        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1532            .map(|task| task.detach_and_log_err(cx));
1533    }
1534
1535    fn close_all_internal(
1536        &mut self,
1537        retain_active_pane: bool,
1538        save_intent: SaveIntent,
1539        cx: &mut ViewContext<Self>,
1540    ) -> Option<Task<Result<()>>> {
1541        let current_pane = self.active_pane();
1542
1543        let mut tasks = Vec::new();
1544
1545        if retain_active_pane {
1546            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1547                pane.close_inactive_items(&CloseInactiveItems, cx)
1548            }) {
1549                tasks.push(current_pane_close);
1550            };
1551        }
1552
1553        for pane in self.panes() {
1554            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1555                continue;
1556            }
1557
1558            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1559                pane.close_all_items(
1560                    &CloseAllItems {
1561                        save_intent: Some(save_intent),
1562                    },
1563                    cx,
1564                )
1565            }) {
1566                tasks.push(close_pane_items)
1567            }
1568        }
1569
1570        if tasks.is_empty() {
1571            None
1572        } else {
1573            Some(cx.spawn(|_, _| async move {
1574                for task in tasks {
1575                    task.await?
1576                }
1577                Ok(())
1578            }))
1579        }
1580    }
1581
1582    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1583        let dock = match dock_side {
1584            DockPosition::Left => &self.left_dock,
1585            DockPosition::Bottom => &self.bottom_dock,
1586            DockPosition::Right => &self.right_dock,
1587        };
1588        let mut focus_center = false;
1589        let mut reveal_dock = false;
1590        dock.update(cx, |dock, cx| {
1591            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1592            let was_visible = dock.is_open() && !other_is_zoomed;
1593            dock.set_open(!was_visible, cx);
1594
1595            if let Some(active_panel) = dock.active_panel() {
1596                if was_visible {
1597                    if active_panel.focus_handle(cx).contains_focused(cx) {
1598                        focus_center = true;
1599                    }
1600                } else {
1601                    let focus_handle = &active_panel.focus_handle(cx);
1602                    cx.focus(focus_handle);
1603                    reveal_dock = true;
1604                }
1605            }
1606        });
1607
1608        if reveal_dock {
1609            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1610        }
1611
1612        if focus_center {
1613            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1614        }
1615
1616        cx.notify();
1617        self.serialize_workspace(cx);
1618    }
1619
1620    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1621        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1622
1623        for dock in docks {
1624            dock.update(cx, |dock, cx| {
1625                dock.set_open(false, cx);
1626            });
1627        }
1628
1629        cx.focus_self();
1630        cx.notify();
1631        self.serialize_workspace(cx);
1632    }
1633
1634    /// Transfer focus to the panel of the given type.
1635    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1636        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1637        panel.to_any().downcast().ok()
1638    }
1639
1640    /// Focus the panel of the given type if it isn't already focused. If it is
1641    /// already focused, then transfer focus back to the workspace center.
1642    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1643        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1644            !panel.focus_handle(cx).contains_focused(cx)
1645        });
1646    }
1647
1648    /// Focus or unfocus the given panel type, depending on the given callback.
1649    fn focus_or_unfocus_panel<T: Panel>(
1650        &mut self,
1651        cx: &mut ViewContext<Self>,
1652        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1653    ) -> Option<Arc<dyn PanelHandle>> {
1654        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1655            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1656                let mut focus_center = false;
1657                let panel = dock.update(cx, |dock, cx| {
1658                    dock.activate_panel(panel_index, cx);
1659
1660                    let panel = dock.active_panel().cloned();
1661                    if let Some(panel) = panel.as_ref() {
1662                        if should_focus(&**panel, cx) {
1663                            dock.set_open(true, cx);
1664                            panel.focus_handle(cx).focus(cx);
1665                        } else {
1666                            focus_center = true;
1667                        }
1668                    }
1669                    panel
1670                });
1671
1672                if focus_center {
1673                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1674                }
1675
1676                self.serialize_workspace(cx);
1677                cx.notify();
1678                return panel;
1679            }
1680        }
1681        None
1682    }
1683
1684    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 WindowContext) {
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 WindowContext,
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 WindowContext,
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 WindowContext) -> 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 WindowContext) {
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 WindowContext) {
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 WindowContext,
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: &AppContext,
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 WindowContext) {
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 WindowContext) {
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 WindowContext) {
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 WindowContext) {
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 WindowContext,
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 WindowContext) {
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(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
3043            let left_dock = this.left_dock.read(cx);
3044            let left_visible = left_dock.is_open();
3045            let left_active_panel = left_dock
3046                .visible_panel()
3047                .and_then(|panel| Some(panel.persistent_name().to_string()));
3048            let left_dock_zoom = left_dock
3049                .visible_panel()
3050                .map(|panel| panel.is_zoomed(cx))
3051                .unwrap_or(false);
3052
3053            let right_dock = this.right_dock.read(cx);
3054            let right_visible = right_dock.is_open();
3055            let right_active_panel = right_dock
3056                .visible_panel()
3057                .and_then(|panel| Some(panel.persistent_name().to_string()));
3058            let right_dock_zoom = right_dock
3059                .visible_panel()
3060                .map(|panel| panel.is_zoomed(cx))
3061                .unwrap_or(false);
3062
3063            let bottom_dock = this.bottom_dock.read(cx);
3064            let bottom_visible = bottom_dock.is_open();
3065            let bottom_active_panel = bottom_dock
3066                .visible_panel()
3067                .and_then(|panel| Some(panel.persistent_name().to_string()));
3068            let bottom_dock_zoom = bottom_dock
3069                .visible_panel()
3070                .map(|panel| panel.is_zoomed(cx))
3071                .unwrap_or(false);
3072
3073            DockStructure {
3074                left: DockData {
3075                    visible: left_visible,
3076                    active_panel: left_active_panel,
3077                    zoom: left_dock_zoom,
3078                },
3079                right: DockData {
3080                    visible: right_visible,
3081                    active_panel: right_active_panel,
3082                    zoom: right_dock_zoom,
3083                },
3084                bottom: DockData {
3085                    visible: bottom_visible,
3086                    active_panel: bottom_active_panel,
3087                    zoom: bottom_dock_zoom,
3088                },
3089            }
3090        }
3091
3092        if let Some(location) = self.location(cx) {
3093            // Load bearing special case:
3094            //  - with_local_workspace() relies on this to not have other stuff open
3095            //    when you open your log
3096            if !location.paths().is_empty() {
3097                let center_group = build_serialized_pane_group(&self.center.root, cx);
3098                let docks = build_serialized_docks(self, cx);
3099
3100                let serialized_workspace = SerializedWorkspace {
3101                    id: self.database_id,
3102                    location,
3103                    center_group,
3104                    bounds: Default::default(),
3105                    display: Default::default(),
3106                    docks,
3107                };
3108
3109                cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
3110                    .detach();
3111            }
3112        }
3113    }
3114
3115    pub(crate) fn load_workspace(
3116        serialized_workspace: SerializedWorkspace,
3117        paths_to_open: Vec<Option<ProjectPath>>,
3118        cx: &mut ViewContext<Workspace>,
3119    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3120        cx.spawn(|workspace, mut cx| async move {
3121            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3122
3123            let mut center_group = None;
3124            let mut center_items = None;
3125
3126            // Traverse the splits tree and add to things
3127            if let Some((group, active_pane, items)) = serialized_workspace
3128                .center_group
3129                .deserialize(
3130                    &project,
3131                    serialized_workspace.id,
3132                    workspace.clone(),
3133                    &mut cx,
3134                )
3135                .await
3136            {
3137                center_items = Some(items);
3138                center_group = Some((group, active_pane))
3139            }
3140
3141            let mut items_by_project_path = cx.update(|cx| {
3142                center_items
3143                    .unwrap_or_default()
3144                    .into_iter()
3145                    .filter_map(|item| {
3146                        let item = item?;
3147                        let project_path = item.project_path(cx)?;
3148                        Some((project_path, item))
3149                    })
3150                    .collect::<HashMap<_, _>>()
3151            })?;
3152
3153            let opened_items = paths_to_open
3154                .into_iter()
3155                .map(|path_to_open| {
3156                    path_to_open
3157                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3158                })
3159                .collect::<Vec<_>>();
3160
3161            // Remove old panes from workspace panes list
3162            workspace.update(&mut cx, |workspace, cx| {
3163                if let Some((center_group, active_pane)) = center_group {
3164                    workspace.remove_panes(workspace.center.root.clone(), cx);
3165
3166                    // Swap workspace center group
3167                    workspace.center = PaneGroup::with_root(center_group);
3168                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3169                    if let Some(active_pane) = active_pane {
3170                        workspace.active_pane = active_pane;
3171                        cx.focus_self();
3172                    } else {
3173                        workspace.active_pane = workspace.center.first_pane().clone();
3174                    }
3175                }
3176
3177                let docks = serialized_workspace.docks;
3178                workspace.left_dock.update(cx, |dock, cx| {
3179                    dock.set_open(docks.left.visible, cx);
3180                    if let Some(active_panel) = docks.left.active_panel {
3181                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3182                            dock.activate_panel(ix, cx);
3183                        }
3184                    }
3185                    dock.active_panel()
3186                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3187                    if docks.left.visible && docks.left.zoom {
3188                        cx.focus_self()
3189                    }
3190                });
3191                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3192                workspace.right_dock.update(cx, |dock, cx| {
3193                    dock.set_open(docks.right.visible, cx);
3194                    if let Some(active_panel) = docks.right.active_panel {
3195                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3196                            dock.activate_panel(ix, cx);
3197                        }
3198                    }
3199                    dock.active_panel()
3200                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3201
3202                    if docks.right.visible && docks.right.zoom {
3203                        cx.focus_self()
3204                    }
3205                });
3206                workspace.bottom_dock.update(cx, |dock, cx| {
3207                    dock.set_open(docks.bottom.visible, cx);
3208                    if let Some(active_panel) = docks.bottom.active_panel {
3209                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3210                            dock.activate_panel(ix, cx);
3211                        }
3212                    }
3213
3214                    dock.active_panel()
3215                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3216
3217                    if docks.bottom.visible && docks.bottom.zoom {
3218                        cx.focus_self()
3219                    }
3220                });
3221
3222                cx.notify();
3223            })?;
3224
3225            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3226            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3227
3228            Ok(opened_items)
3229        })
3230    }
3231
3232    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3233        self.add_workspace_actions_listeners(div, cx)
3234            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3235            .on_action(cx.listener(Self::close_all_items_and_panes))
3236            .on_action(cx.listener(Self::save_all))
3237            .on_action(cx.listener(Self::add_folder_to_project))
3238            .on_action(cx.listener(Self::follow_next_collaborator))
3239            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3240                let pane = workspace.active_pane().clone();
3241                workspace.unfollow(&pane, cx);
3242            }))
3243            .on_action(cx.listener(|workspace, action: &Save, cx| {
3244                workspace
3245                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3246                    .detach_and_log_err(cx);
3247            }))
3248            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3249                workspace
3250                    .save_active_item(SaveIntent::SaveAs, cx)
3251                    .detach_and_log_err(cx);
3252            }))
3253            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3254                workspace.activate_previous_pane(cx)
3255            }))
3256            .on_action(
3257                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3258            )
3259            .on_action(
3260                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3261                    workspace.activate_pane_in_direction(action.0, cx)
3262                }),
3263            )
3264            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3265                workspace.swap_pane_in_direction(action.0, cx)
3266            }))
3267            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3268                this.toggle_dock(DockPosition::Left, cx);
3269            }))
3270            .on_action(
3271                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3272                    workspace.toggle_dock(DockPosition::Right, cx);
3273                }),
3274            )
3275            .on_action(
3276                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3277                    workspace.toggle_dock(DockPosition::Bottom, cx);
3278                }),
3279            )
3280            .on_action(
3281                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3282                    workspace.close_all_docks(cx);
3283                }),
3284            )
3285            .on_action(cx.listener(Workspace::open))
3286            .on_action(cx.listener(Workspace::close_window))
3287            .on_action(cx.listener(Workspace::activate_pane_at_index))
3288            .on_action(
3289                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3290                    workspace.reopen_closed_item(cx).detach();
3291                }),
3292            )
3293    }
3294
3295    #[cfg(any(test, feature = "test-support"))]
3296    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3297        use node_runtime::FakeNodeRuntime;
3298
3299        let client = project.read(cx).client();
3300        let user_store = project.read(cx).user_store();
3301
3302        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3303        cx.activate_window();
3304        let app_state = Arc::new(AppState {
3305            languages: project.read(cx).languages().clone(),
3306            workspace_store,
3307            client,
3308            user_store,
3309            fs: project.read(cx).fs().clone(),
3310            build_window_options: |_, _, _| Default::default(),
3311            node_runtime: FakeNodeRuntime::new(),
3312        });
3313        let workspace = Self::new(0, project, app_state, cx);
3314        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3315        workspace
3316    }
3317
3318    pub fn register_action<A: Action>(
3319        &mut self,
3320        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3321    ) -> &mut Self {
3322        let callback = Arc::new(callback);
3323
3324        self.workspace_actions.push(Box::new(move |div, cx| {
3325            let callback = callback.clone();
3326            div.on_action(
3327                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3328            )
3329        }));
3330        self
3331    }
3332
3333    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3334        let mut div = div
3335            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3336            .on_action(cx.listener(Self::close_all_items_and_panes))
3337            .on_action(cx.listener(Self::add_folder_to_project))
3338            .on_action(cx.listener(Self::save_all))
3339            .on_action(cx.listener(Self::open));
3340        for action in self.workspace_actions.iter() {
3341            div = (action)(div, cx)
3342        }
3343        div
3344    }
3345
3346    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3347        self.modal_layer.read(cx).has_active_modal()
3348    }
3349
3350    pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
3351        self.modal_layer.read(cx).active_modal()
3352    }
3353
3354    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
3355    where
3356        B: FnOnce(&mut ViewContext<V>) -> V,
3357    {
3358        self.modal_layer
3359            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3360    }
3361}
3362
3363fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3364    let display_origin = cx
3365        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3366        .ok()??;
3367    ZED_WINDOW_POSITION
3368        .zip(*ZED_WINDOW_SIZE)
3369        .map(|(position, size)| {
3370            WindowBounds::Fixed(Bounds {
3371                origin: display_origin + position,
3372                size,
3373            })
3374        })
3375}
3376
3377fn open_items(
3378    serialized_workspace: Option<SerializedWorkspace>,
3379    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3380    app_state: Arc<AppState>,
3381    cx: &mut ViewContext<Workspace>,
3382) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3383    let restored_items = serialized_workspace.map(|serialized_workspace| {
3384        Workspace::load_workspace(
3385            serialized_workspace,
3386            project_paths_to_open
3387                .iter()
3388                .map(|(_, project_path)| project_path)
3389                .cloned()
3390                .collect(),
3391            cx,
3392        )
3393    });
3394
3395    cx.spawn(|workspace, mut cx| async move {
3396        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3397
3398        if let Some(restored_items) = restored_items {
3399            let restored_items = restored_items.await?;
3400
3401            let restored_project_paths = restored_items
3402                .iter()
3403                .filter_map(|item| {
3404                    cx.update(|cx| item.as_ref()?.project_path(cx))
3405                        .ok()
3406                        .flatten()
3407                })
3408                .collect::<HashSet<_>>();
3409
3410            for restored_item in restored_items {
3411                opened_items.push(restored_item.map(Ok));
3412            }
3413
3414            project_paths_to_open
3415                .iter_mut()
3416                .for_each(|(_, project_path)| {
3417                    if let Some(project_path_to_open) = project_path {
3418                        if restored_project_paths.contains(project_path_to_open) {
3419                            *project_path = None;
3420                        }
3421                    }
3422                });
3423        } else {
3424            for _ in 0..project_paths_to_open.len() {
3425                opened_items.push(None);
3426            }
3427        }
3428        assert!(opened_items.len() == project_paths_to_open.len());
3429
3430        let tasks =
3431            project_paths_to_open
3432                .into_iter()
3433                .enumerate()
3434                .map(|(i, (abs_path, project_path))| {
3435                    let workspace = workspace.clone();
3436                    cx.spawn(|mut cx| {
3437                        let fs = app_state.fs.clone();
3438                        async move {
3439                            let file_project_path = project_path?;
3440                            if fs.is_file(&abs_path).await {
3441                                Some((
3442                                    i,
3443                                    workspace
3444                                        .update(&mut cx, |workspace, cx| {
3445                                            workspace.open_path(file_project_path, None, true, cx)
3446                                        })
3447                                        .log_err()?
3448                                        .await,
3449                                ))
3450                            } else {
3451                                None
3452                            }
3453                        }
3454                    })
3455                });
3456
3457        let tasks = tasks.collect::<Vec<_>>();
3458
3459        let tasks = futures::future::join_all(tasks.into_iter());
3460        for maybe_opened_path in tasks.await.into_iter() {
3461            if let Some((i, path_open_result)) = maybe_opened_path {
3462                opened_items[i] = Some(path_open_result);
3463            }
3464        }
3465
3466        Ok(opened_items)
3467    })
3468}
3469
3470fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3471    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3472
3473    workspace
3474        .update(cx, |workspace, cx| {
3475            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3476                workspace.show_notification_once(0, cx, |cx| {
3477                    cx.new_view(|_| {
3478                        MessageNotification::new("Failed to load the database file.")
3479                            .with_click_message("Click to let us know about this error")
3480                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3481                    })
3482                });
3483            }
3484        })
3485        .log_err();
3486}
3487
3488impl FocusableView for Workspace {
3489    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3490        self.active_pane.focus_handle(cx)
3491    }
3492}
3493
3494#[derive(Clone, Render)]
3495struct DraggedDock(DockPosition);
3496
3497impl Render for Workspace {
3498    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3499        let mut context = KeyContext::default();
3500        context.add("Workspace");
3501
3502        let (ui_font, ui_font_size) = {
3503            let theme_settings = ThemeSettings::get_global(cx);
3504            (
3505                theme_settings.ui_font.family.clone(),
3506                theme_settings.ui_font_size.clone(),
3507            )
3508        };
3509
3510        let theme = cx.theme().clone();
3511        let colors = theme.colors();
3512        cx.set_rem_size(ui_font_size);
3513
3514        self.actions(div(), cx)
3515            .key_context(context)
3516            .relative()
3517            .size_full()
3518            .flex()
3519            .flex_col()
3520            .font(ui_font)
3521            .gap_0()
3522            .justify_start()
3523            .items_start()
3524            .text_color(colors.text)
3525            .bg(colors.background)
3526            .border()
3527            .border_color(colors.border)
3528            .children(self.titlebar_item.clone())
3529            .child(
3530                div()
3531                    .id("workspace")
3532                    .relative()
3533                    .flex_1()
3534                    .w_full()
3535                    .flex()
3536                    .flex_col()
3537                    .overflow_hidden()
3538                    .border_t()
3539                    .border_b()
3540                    .border_color(colors.border)
3541                    .child(
3542                        canvas({
3543                            let this = cx.view().clone();
3544                            move |bounds, cx| {
3545                                this.update(cx, |this, _cx| {
3546                                    this.bounds = *bounds;
3547                                })
3548                            }
3549                        })
3550                        .absolute()
3551                        .size_full(),
3552                    )
3553                    .on_drag_move(
3554                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3555                            match e.drag(cx).0 {
3556                                DockPosition::Left => {
3557                                    let size = workspace.bounds.left() + e.event.position.x;
3558                                    workspace.left_dock.update(cx, |left_dock, cx| {
3559                                        left_dock.resize_active_panel(Some(size), cx);
3560                                    });
3561                                }
3562                                DockPosition::Right => {
3563                                    let size = workspace.bounds.right() - e.event.position.x;
3564                                    workspace.right_dock.update(cx, |right_dock, cx| {
3565                                        right_dock.resize_active_panel(Some(size), cx);
3566                                    });
3567                                }
3568                                DockPosition::Bottom => {
3569                                    let size = workspace.bounds.bottom() - e.event.position.y;
3570                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3571                                        bottom_dock.resize_active_panel(Some(size), cx);
3572                                    });
3573                                }
3574                            }
3575                        }),
3576                    )
3577                    .child(self.modal_layer.clone())
3578                    .child(
3579                        div()
3580                            .flex()
3581                            .flex_row()
3582                            .h_full()
3583                            // Left Dock
3584                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3585                                || {
3586                                    div()
3587                                        .flex()
3588                                        .flex_none()
3589                                        .overflow_hidden()
3590                                        .child(self.left_dock.clone())
3591                                },
3592                            ))
3593                            // Panes
3594                            .child(
3595                                div()
3596                                    .flex()
3597                                    .flex_col()
3598                                    .flex_1()
3599                                    .overflow_hidden()
3600                                    .child(self.center.render(
3601                                        &self.project,
3602                                        &self.follower_states,
3603                                        self.active_call(),
3604                                        &self.active_pane,
3605                                        self.zoomed.as_ref(),
3606                                        &self.app_state,
3607                                        cx,
3608                                    ))
3609                                    .children(
3610                                        self.zoomed_position
3611                                            .ne(&Some(DockPosition::Bottom))
3612                                            .then(|| self.bottom_dock.clone()),
3613                                    ),
3614                            )
3615                            // Right Dock
3616                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3617                                || {
3618                                    div()
3619                                        .flex()
3620                                        .flex_none()
3621                                        .overflow_hidden()
3622                                        .child(self.right_dock.clone())
3623                                },
3624                            )),
3625                    )
3626                    .children(self.render_notifications(cx))
3627                    .children(self.zoomed.as_ref().and_then(|view| {
3628                        let zoomed_view = view.upgrade()?;
3629                        let div = div()
3630                            .z_index(1)
3631                            .absolute()
3632                            .overflow_hidden()
3633                            .border_color(colors.border)
3634                            .bg(colors.background)
3635                            .child(zoomed_view)
3636                            .inset_0()
3637                            .shadow_lg();
3638
3639                        Some(match self.zoomed_position {
3640                            Some(DockPosition::Left) => div.right_2().border_r(),
3641                            Some(DockPosition::Right) => div.left_2().border_l(),
3642                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3643                            None => div.top_2().bottom_2().left_2().right_2().border(),
3644                        })
3645                    })),
3646            )
3647            .child(self.status_bar.clone())
3648            .children(if self.project.read(cx).is_disconnected() {
3649                Some(DisconnectedOverlay)
3650            } else {
3651                None
3652            })
3653    }
3654}
3655
3656impl WorkspaceStore {
3657    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3658        Self {
3659            workspaces: Default::default(),
3660            followers: Default::default(),
3661            _subscriptions: vec![
3662                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3663                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3664                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3665            ],
3666            client,
3667        }
3668    }
3669
3670    pub fn update_followers(
3671        &self,
3672        project_id: Option<u64>,
3673        update: proto::update_followers::Variant,
3674        cx: &AppContext,
3675    ) -> Option<()> {
3676        let active_call = cx.try_global::<Model<ActiveCall>>()?;
3677        let room_id = active_call.read(cx).room()?.read(cx).id();
3678        let follower_ids: Vec<_> = self
3679            .followers
3680            .iter()
3681            .filter_map(|follower| {
3682                if follower.project_id == project_id || project_id.is_none() {
3683                    Some(follower.peer_id.into())
3684                } else {
3685                    None
3686                }
3687            })
3688            .collect();
3689        if follower_ids.is_empty() {
3690            return None;
3691        }
3692        self.client
3693            .send(proto::UpdateFollowers {
3694                room_id,
3695                project_id,
3696                follower_ids,
3697                variant: Some(update),
3698            })
3699            .log_err()
3700    }
3701
3702    pub async fn handle_follow(
3703        this: Model<Self>,
3704        envelope: TypedEnvelope<proto::Follow>,
3705        _: Arc<Client>,
3706        mut cx: AsyncAppContext,
3707    ) -> Result<proto::FollowResponse> {
3708        this.update(&mut cx, |this, cx| {
3709            let follower = Follower {
3710                project_id: envelope.payload.project_id,
3711                peer_id: envelope.original_sender_id()?,
3712            };
3713            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3714
3715            let mut response = proto::FollowResponse::default();
3716            this.workspaces.retain(|workspace| {
3717                workspace
3718                    .update(cx, |workspace, cx| {
3719                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3720                        if response.views.is_empty() {
3721                            response.views = handler_response.views;
3722                        } else {
3723                            response.views.extend_from_slice(&handler_response.views);
3724                        }
3725
3726                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3727                            if response.active_view_id.is_none()
3728                                || Some(workspace.project.downgrade()) == active_project
3729                            {
3730                                response.active_view_id = Some(active_view_id);
3731                            }
3732                        }
3733                    })
3734                    .is_ok()
3735            });
3736
3737            if let Err(ix) = this.followers.binary_search(&follower) {
3738                this.followers.insert(ix, follower);
3739            }
3740
3741            Ok(response)
3742        })?
3743    }
3744
3745    async fn handle_unfollow(
3746        model: Model<Self>,
3747        envelope: TypedEnvelope<proto::Unfollow>,
3748        _: Arc<Client>,
3749        mut cx: AsyncAppContext,
3750    ) -> Result<()> {
3751        model.update(&mut cx, |this, _| {
3752            let follower = Follower {
3753                project_id: envelope.payload.project_id,
3754                peer_id: envelope.original_sender_id()?,
3755            };
3756            if let Ok(ix) = this.followers.binary_search(&follower) {
3757                this.followers.remove(ix);
3758            }
3759            Ok(())
3760        })?
3761    }
3762
3763    async fn handle_update_followers(
3764        this: Model<Self>,
3765        envelope: TypedEnvelope<proto::UpdateFollowers>,
3766        _: Arc<Client>,
3767        mut cx: AsyncAppContext,
3768    ) -> Result<()> {
3769        let leader_id = envelope.original_sender_id()?;
3770        let update = envelope.payload;
3771
3772        this.update(&mut cx, |this, cx| {
3773            this.workspaces.retain(|workspace| {
3774                workspace
3775                    .update(cx, |workspace, cx| {
3776                        let project_id = workspace.project.read(cx).remote_id();
3777                        if update.project_id != project_id && update.project_id.is_some() {
3778                            return;
3779                        }
3780                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3781                    })
3782                    .is_ok()
3783            });
3784            Ok(())
3785        })?
3786    }
3787}
3788
3789impl ViewId {
3790    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3791        Ok(Self {
3792            creator: message
3793                .creator
3794                .ok_or_else(|| anyhow!("creator is missing"))?,
3795            id: message.id,
3796        })
3797    }
3798
3799    pub(crate) fn to_proto(&self) -> proto::ViewId {
3800        proto::ViewId {
3801            creator: Some(self.creator),
3802            id: self.id,
3803        }
3804    }
3805}
3806
3807pub trait WorkspaceHandle {
3808    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3809}
3810
3811impl WorkspaceHandle for View<Workspace> {
3812    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3813        self.read(cx)
3814            .worktrees(cx)
3815            .flat_map(|worktree| {
3816                let worktree_id = worktree.read(cx).id();
3817                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3818                    worktree_id,
3819                    path: f.path.clone(),
3820                })
3821            })
3822            .collect::<Vec<_>>()
3823    }
3824}
3825
3826impl std::fmt::Debug for OpenPaths {
3827    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3828        f.debug_struct("OpenPaths")
3829            .field("paths", &self.paths)
3830            .finish()
3831    }
3832}
3833
3834pub fn activate_workspace_for_project(
3835    cx: &mut AppContext,
3836    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3837) -> Option<WindowHandle<Workspace>> {
3838    for window in cx.windows() {
3839        let Some(workspace) = window.downcast::<Workspace>() else {
3840            continue;
3841        };
3842
3843        let predicate = workspace
3844            .update(cx, |workspace, cx| {
3845                let project = workspace.project.read(cx);
3846                if predicate(project, cx) {
3847                    cx.activate_window();
3848                    true
3849                } else {
3850                    false
3851                }
3852            })
3853            .log_err()
3854            .unwrap_or(false);
3855
3856        if predicate {
3857            return Some(workspace);
3858        }
3859    }
3860
3861    None
3862}
3863
3864pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3865    DB.last_workspace().await.log_err().flatten()
3866}
3867
3868async fn join_channel_internal(
3869    channel_id: u64,
3870    app_state: &Arc<AppState>,
3871    requesting_window: Option<WindowHandle<Workspace>>,
3872    active_call: &Model<ActiveCall>,
3873    cx: &mut AsyncAppContext,
3874) -> Result<bool> {
3875    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3876        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3877            return (false, None);
3878        };
3879
3880        let already_in_channel = room.channel_id() == Some(channel_id);
3881        let should_prompt = room.is_sharing_project()
3882            && room.remote_participants().len() > 0
3883            && !already_in_channel;
3884        let open_room = if already_in_channel {
3885            active_call.room().cloned()
3886        } else {
3887            None
3888        };
3889        (should_prompt, open_room)
3890    })?;
3891
3892    if let Some(room) = open_room {
3893        let task = room.update(cx, |room, cx| {
3894            if let Some((project, host)) = room.most_active_project(cx) {
3895                return Some(join_remote_project(project, host, app_state.clone(), cx));
3896            }
3897
3898            None
3899        })?;
3900        if let Some(task) = task {
3901            task.await?;
3902        }
3903        return anyhow::Ok(true);
3904    }
3905
3906    if should_prompt {
3907        if let Some(workspace) = requesting_window {
3908            let answer  = workspace.update(cx, |_, cx| {
3909                cx.prompt(
3910                    PromptLevel::Warning,
3911                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3912                    &["Yes, Join Channel", "Cancel"],
3913                )
3914            })?.await;
3915
3916            if answer == Ok(1) {
3917                return Ok(false);
3918            }
3919        } else {
3920            return Ok(false); // unreachable!() hopefully
3921        }
3922    }
3923
3924    let client = cx.update(|cx| active_call.read(cx).client())?;
3925
3926    let mut client_status = client.status();
3927
3928    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3929    'outer: loop {
3930        let Some(status) = client_status.recv().await else {
3931            return Err(anyhow!("error connecting"));
3932        };
3933
3934        match status {
3935            Status::Connecting
3936            | Status::Authenticating
3937            | Status::Reconnecting
3938            | Status::Reauthenticating => continue,
3939            Status::Connected { .. } => break 'outer,
3940            Status::SignedOut => return Err(anyhow!("not signed in")),
3941            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3942            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3943                return Err(anyhow!("zed is offline"))
3944            }
3945        }
3946    }
3947
3948    let room = active_call
3949        .update(cx, |active_call, cx| {
3950            active_call.join_channel(channel_id, cx)
3951        })?
3952        .await?;
3953
3954    let Some(room) = room else {
3955        return anyhow::Ok(true);
3956    };
3957
3958    room.update(cx, |room, _| room.room_update_completed())?
3959        .await;
3960
3961    let task = room.update(cx, |room, cx| {
3962        if let Some((project, host)) = room.most_active_project(cx) {
3963            return Some(join_remote_project(project, host, app_state.clone(), cx));
3964        }
3965
3966        None
3967    })?;
3968    if let Some(task) = task {
3969        task.await?;
3970        return anyhow::Ok(true);
3971    }
3972    anyhow::Ok(false)
3973}
3974
3975pub fn join_channel(
3976    channel_id: u64,
3977    app_state: Arc<AppState>,
3978    requesting_window: Option<WindowHandle<Workspace>>,
3979    cx: &mut AppContext,
3980) -> Task<Result<()>> {
3981    let active_call = ActiveCall::global(cx);
3982    cx.spawn(|mut cx| async move {
3983        let result = join_channel_internal(
3984            channel_id,
3985            &app_state,
3986            requesting_window,
3987            &active_call,
3988            &mut cx,
3989        )
3990        .await;
3991
3992        // join channel succeeded, and opened a window
3993        if matches!(result, Ok(true)) {
3994            return anyhow::Ok(());
3995        }
3996
3997        // find an existing workspace to focus and show call controls
3998        let mut active_window =
3999            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4000        if active_window.is_none() {
4001            // no open workspaces, make one to show the error in (blergh)
4002            let (window_handle, _) = cx
4003                .update(|cx| {
4004                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4005                })?
4006                .await?;
4007
4008            active_window = Some(window_handle);
4009        }
4010
4011        if let Err(err) = result {
4012            log::error!("failed to join channel: {}", err);
4013            if let Some(active_window) = active_window {
4014                active_window
4015                    .update(&mut cx, |_, cx| {
4016                        cx.prompt(
4017                            PromptLevel::Critical,
4018                            &format!("Failed to join channel: {}", err),
4019                            &["Ok"],
4020                        )
4021                    })?
4022                    .await
4023                    .ok();
4024            }
4025        }
4026
4027        // return ok, we showed the error to the user.
4028        return anyhow::Ok(());
4029    })
4030}
4031
4032pub async fn get_any_active_workspace(
4033    app_state: Arc<AppState>,
4034    mut cx: AsyncAppContext,
4035) -> anyhow::Result<WindowHandle<Workspace>> {
4036    // find an existing workspace to focus and show call controls
4037    let active_window = activate_any_workspace_window(&mut cx);
4038    if active_window.is_none() {
4039        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4040            .await?;
4041    }
4042    activate_any_workspace_window(&mut cx).context("could not open zed")
4043}
4044
4045fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4046    cx.update(|cx| {
4047        for window in cx.windows() {
4048            if let Some(workspace_window) = window.downcast::<Workspace>() {
4049                workspace_window
4050                    .update(cx, |_, cx| cx.activate_window())
4051                    .ok();
4052                return Some(workspace_window);
4053            }
4054        }
4055        None
4056    })
4057    .ok()
4058    .flatten()
4059}
4060
4061#[allow(clippy::type_complexity)]
4062pub fn open_paths(
4063    abs_paths: &[PathBuf],
4064    app_state: &Arc<AppState>,
4065    requesting_window: Option<WindowHandle<Workspace>>,
4066    cx: &mut AppContext,
4067) -> Task<
4068    anyhow::Result<(
4069        WindowHandle<Workspace>,
4070        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4071    )>,
4072> {
4073    let app_state = app_state.clone();
4074    let abs_paths = abs_paths.to_vec();
4075    // Open paths in existing workspace if possible
4076    let existing = activate_workspace_for_project(cx, {
4077        let abs_paths = abs_paths.clone();
4078        move |project, cx| project.contains_paths(&abs_paths, cx)
4079    });
4080    cx.spawn(move |mut cx| async move {
4081        if let Some(existing) = existing {
4082            Ok((
4083                existing.clone(),
4084                existing
4085                    .update(&mut cx, |workspace, cx| {
4086                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4087                    })?
4088                    .await,
4089            ))
4090        } else {
4091            cx.update(move |cx| {
4092                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4093            })?
4094            .await
4095        }
4096    })
4097}
4098
4099pub fn open_new(
4100    app_state: &Arc<AppState>,
4101    cx: &mut AppContext,
4102    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4103) -> Task<()> {
4104    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4105    cx.spawn(|mut cx| async move {
4106        if let Some((workspace, opened_paths)) = task.await.log_err() {
4107            workspace
4108                .update(&mut cx, |workspace, cx| {
4109                    if opened_paths.is_empty() {
4110                        init(workspace, cx)
4111                    }
4112                })
4113                .log_err();
4114        }
4115    })
4116}
4117
4118pub fn create_and_open_local_file(
4119    path: &'static Path,
4120    cx: &mut ViewContext<Workspace>,
4121    default_content: impl 'static + Send + FnOnce() -> Rope,
4122) -> Task<Result<Box<dyn ItemHandle>>> {
4123    cx.spawn(|workspace, mut cx| async move {
4124        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4125        if !fs.is_file(path).await {
4126            fs.create_file(path, Default::default()).await?;
4127            fs.save(path, &default_content(), Default::default())
4128                .await?;
4129        }
4130
4131        let mut items = workspace
4132            .update(&mut cx, |workspace, cx| {
4133                workspace.with_local_workspace(cx, |workspace, cx| {
4134                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4135                })
4136            })?
4137            .await?
4138            .await;
4139
4140        let item = items.pop().flatten();
4141        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4142    })
4143}
4144
4145pub fn join_remote_project(
4146    project_id: u64,
4147    follow_user_id: u64,
4148    app_state: Arc<AppState>,
4149    cx: &mut AppContext,
4150) -> Task<Result<()>> {
4151    let windows = cx.windows();
4152    cx.spawn(|mut cx| async move {
4153        let existing_workspace = windows.into_iter().find_map(|window| {
4154            window.downcast::<Workspace>().and_then(|window| {
4155                window
4156                    .update(&mut cx, |workspace, cx| {
4157                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4158                            Some(window)
4159                        } else {
4160                            None
4161                        }
4162                    })
4163                    .unwrap_or(None)
4164            })
4165        });
4166
4167        let workspace = if let Some(existing_workspace) = existing_workspace {
4168            existing_workspace
4169        } else {
4170            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4171            let room = active_call
4172                .read_with(&cx, |call, _| call.room().cloned())?
4173                .ok_or_else(|| anyhow!("not in a call"))?;
4174            let project = room
4175                .update(&mut cx, |room, cx| {
4176                    room.join_project(
4177                        project_id,
4178                        app_state.languages.clone(),
4179                        app_state.fs.clone(),
4180                        cx,
4181                    )
4182                })?
4183                .await?;
4184
4185            let window_bounds_override = window_bounds_env_override(&cx);
4186            cx.update(|cx| {
4187                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4188                cx.open_window(options, |cx| {
4189                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4190                })
4191            })?
4192        };
4193
4194        workspace.update(&mut cx, |workspace, cx| {
4195            cx.activate(true);
4196            cx.activate_window();
4197
4198            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4199                let follow_peer_id = room
4200                    .read(cx)
4201                    .remote_participants()
4202                    .iter()
4203                    .find(|(_, participant)| participant.user.id == follow_user_id)
4204                    .map(|(_, p)| p.peer_id)
4205                    .or_else(|| {
4206                        // If we couldn't follow the given user, follow the host instead.
4207                        let collaborator = workspace
4208                            .project()
4209                            .read(cx)
4210                            .collaborators()
4211                            .values()
4212                            .find(|collaborator| collaborator.replica_id == 0)?;
4213                        Some(collaborator.peer_id)
4214                    });
4215
4216                if let Some(follow_peer_id) = follow_peer_id {
4217                    workspace.follow(follow_peer_id, cx);
4218                }
4219            }
4220        })?;
4221
4222        anyhow::Ok(())
4223    })
4224}
4225
4226pub fn restart(_: &Restart, cx: &mut AppContext) {
4227    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4228    let mut workspace_windows = cx
4229        .windows()
4230        .into_iter()
4231        .filter_map(|window| window.downcast::<Workspace>())
4232        .collect::<Vec<_>>();
4233
4234    // If multiple windows have unsaved changes, and need a save prompt,
4235    // prompt in the active window before switching to a different window.
4236    workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
4237
4238    let mut prompt = None;
4239    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4240        prompt = window
4241            .update(cx, |_, cx| {
4242                cx.prompt(
4243                    PromptLevel::Info,
4244                    "Are you sure you want to restart?",
4245                    &["Restart", "Cancel"],
4246                )
4247            })
4248            .ok();
4249    }
4250
4251    cx.spawn(|mut cx| async move {
4252        if let Some(prompt) = prompt {
4253            let answer = prompt.await?;
4254            if answer != 0 {
4255                return Ok(());
4256            }
4257        }
4258
4259        // If the user cancels any save prompt, then keep the app open.
4260        for window in workspace_windows {
4261            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4262                workspace.prepare_to_close(true, cx)
4263            }) {
4264                if !should_close.await? {
4265                    return Ok(());
4266                }
4267            }
4268        }
4269
4270        cx.update(|cx| cx.restart())
4271    })
4272    .detach_and_log_err(cx);
4273}
4274
4275fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4276    let mut parts = value.split(',');
4277    let x: usize = parts.next()?.parse().ok()?;
4278    let y: usize = parts.next()?.parse().ok()?;
4279    Some(point((x as f64).into(), (y as f64).into()))
4280}
4281
4282fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4283    let mut parts = value.split(',');
4284    let width: usize = parts.next()?.parse().ok()?;
4285    let height: usize = parts.next()?.parse().ok()?;
4286    Some(size((width as f64).into(), (height as f64).into()))
4287}
4288
4289pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4290    (1.75 * cx.rem_size()).max(px(32.))
4291}
4292
4293struct DisconnectedOverlay;
4294
4295impl Element for DisconnectedOverlay {
4296    type State = AnyElement;
4297
4298    fn request_layout(
4299        &mut self,
4300        _: Option<Self::State>,
4301        cx: &mut ElementContext,
4302    ) -> (LayoutId, Self::State) {
4303        let mut background = cx.theme().colors().elevated_surface_background;
4304        background.fade_out(0.2);
4305        let mut overlay = div()
4306            .bg(background)
4307            .absolute()
4308            .left_0()
4309            .top(titlebar_height(cx))
4310            .size_full()
4311            .flex()
4312            .items_center()
4313            .justify_center()
4314            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4315            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4316            .child(Label::new(
4317                "Your connection to the remote project has been lost.",
4318            ))
4319            .into_any();
4320        (overlay.request_layout(cx), overlay)
4321    }
4322
4323    fn paint(
4324        &mut self,
4325        bounds: Bounds<Pixels>,
4326        overlay: &mut Self::State,
4327        cx: &mut ElementContext,
4328    ) {
4329        cx.with_z_index(u16::MAX, |cx| {
4330            cx.add_opaque_layer(bounds);
4331            overlay.paint(cx);
4332        })
4333    }
4334}
4335
4336impl IntoElement for DisconnectedOverlay {
4337    type Element = Self;
4338
4339    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4340        None
4341    }
4342
4343    fn into_element(self) -> Self::Element {
4344        self
4345    }
4346}
4347
4348#[cfg(test)]
4349mod tests {
4350    use std::{cell::RefCell, rc::Rc};
4351
4352    use super::*;
4353    use crate::{
4354        dock::{test::TestPanel, PanelEvent},
4355        item::{
4356            test::{TestItem, TestProjectItem},
4357            ItemEvent,
4358        },
4359    };
4360    use fs::FakeFs;
4361    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4362    use project::{Project, ProjectEntryId};
4363    use serde_json::json;
4364    use settings::SettingsStore;
4365
4366    #[gpui::test]
4367    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4368        init_test(cx);
4369
4370        let fs = FakeFs::new(cx.executor());
4371        let project = Project::test(fs, [], cx).await;
4372        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4373
4374        // Adding an item with no ambiguity renders the tab without detail.
4375        let item1 = cx.new_view(|cx| {
4376            let mut item = TestItem::new(cx);
4377            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4378            item
4379        });
4380        workspace.update(cx, |workspace, cx| {
4381            workspace.add_item(Box::new(item1.clone()), cx);
4382        });
4383        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4384
4385        // Adding an item that creates ambiguity increases the level of detail on
4386        // both tabs.
4387        let item2 = cx.new_view(|cx| {
4388            let mut item = TestItem::new(cx);
4389            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4390            item
4391        });
4392        workspace.update(cx, |workspace, cx| {
4393            workspace.add_item(Box::new(item2.clone()), cx);
4394        });
4395        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4396        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4397
4398        // Adding an item that creates ambiguity increases the level of detail only
4399        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4400        // we stop at the highest detail available.
4401        let item3 = cx.new_view(|cx| {
4402            let mut item = TestItem::new(cx);
4403            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4404            item
4405        });
4406        workspace.update(cx, |workspace, cx| {
4407            workspace.add_item(Box::new(item3.clone()), cx);
4408        });
4409        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4410        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4411        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4412    }
4413
4414    #[gpui::test]
4415    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4416        init_test(cx);
4417
4418        let fs = FakeFs::new(cx.executor());
4419        fs.insert_tree(
4420            "/root1",
4421            json!({
4422                "one.txt": "",
4423                "two.txt": "",
4424            }),
4425        )
4426        .await;
4427        fs.insert_tree(
4428            "/root2",
4429            json!({
4430                "three.txt": "",
4431            }),
4432        )
4433        .await;
4434
4435        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4436        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4437        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4438        let worktree_id = project.update(cx, |project, cx| {
4439            project.worktrees().next().unwrap().read(cx).id()
4440        });
4441
4442        let item1 = cx.new_view(|cx| {
4443            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4444        });
4445        let item2 = cx.new_view(|cx| {
4446            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4447        });
4448
4449        // Add an item to an empty pane
4450        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4451        project.update(cx, |project, cx| {
4452            assert_eq!(
4453                project.active_entry(),
4454                project
4455                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4456                    .map(|e| e.id)
4457            );
4458        });
4459        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4460
4461        // Add a second item to a non-empty pane
4462        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4463        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4464        project.update(cx, |project, cx| {
4465            assert_eq!(
4466                project.active_entry(),
4467                project
4468                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4469                    .map(|e| e.id)
4470            );
4471        });
4472
4473        // Close the active item
4474        pane.update(cx, |pane, cx| {
4475            pane.close_active_item(&Default::default(), cx).unwrap()
4476        })
4477        .await
4478        .unwrap();
4479        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4480        project.update(cx, |project, cx| {
4481            assert_eq!(
4482                project.active_entry(),
4483                project
4484                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4485                    .map(|e| e.id)
4486            );
4487        });
4488
4489        // Add a project folder
4490        project
4491            .update(cx, |project, cx| {
4492                project.find_or_create_local_worktree("/root2", true, cx)
4493            })
4494            .await
4495            .unwrap();
4496        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4497
4498        // Remove a project folder
4499        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4500        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4501    }
4502
4503    #[gpui::test]
4504    async fn test_close_window(cx: &mut TestAppContext) {
4505        init_test(cx);
4506
4507        let fs = FakeFs::new(cx.executor());
4508        fs.insert_tree("/root", json!({ "one": "" })).await;
4509
4510        let project = Project::test(fs, ["root".as_ref()], cx).await;
4511        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4512
4513        // When there are no dirty items, there's nothing to do.
4514        let item1 = cx.new_view(|cx| TestItem::new(cx));
4515        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4516        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4517        assert!(task.await.unwrap());
4518
4519        // When there are dirty untitled items, prompt to save each one. If the user
4520        // cancels any prompt, then abort.
4521        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4522        let item3 = cx.new_view(|cx| {
4523            TestItem::new(cx)
4524                .with_dirty(true)
4525                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4526        });
4527        workspace.update(cx, |w, cx| {
4528            w.add_item(Box::new(item2.clone()), cx);
4529            w.add_item(Box::new(item3.clone()), cx);
4530        });
4531        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4532        cx.executor().run_until_parked();
4533        cx.simulate_prompt_answer(2); // cancel save all
4534        cx.executor().run_until_parked();
4535        cx.simulate_prompt_answer(2); // cancel save all
4536        cx.executor().run_until_parked();
4537        assert!(!cx.has_pending_prompt());
4538        assert!(!task.await.unwrap());
4539    }
4540
4541    #[gpui::test]
4542    async fn test_close_pane_items(cx: &mut TestAppContext) {
4543        init_test(cx);
4544
4545        let fs = FakeFs::new(cx.executor());
4546
4547        let project = Project::test(fs, None, cx).await;
4548        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4549
4550        let item1 = cx.new_view(|cx| {
4551            TestItem::new(cx)
4552                .with_dirty(true)
4553                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4554        });
4555        let item2 = cx.new_view(|cx| {
4556            TestItem::new(cx)
4557                .with_dirty(true)
4558                .with_conflict(true)
4559                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4560        });
4561        let item3 = cx.new_view(|cx| {
4562            TestItem::new(cx)
4563                .with_dirty(true)
4564                .with_conflict(true)
4565                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4566        });
4567        let item4 = cx.new_view(|cx| {
4568            TestItem::new(cx)
4569                .with_dirty(true)
4570                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4571        });
4572        let pane = workspace.update(cx, |workspace, cx| {
4573            workspace.add_item(Box::new(item1.clone()), cx);
4574            workspace.add_item(Box::new(item2.clone()), cx);
4575            workspace.add_item(Box::new(item3.clone()), cx);
4576            workspace.add_item(Box::new(item4.clone()), cx);
4577            workspace.active_pane().clone()
4578        });
4579
4580        let close_items = pane.update(cx, |pane, cx| {
4581            pane.activate_item(1, true, true, cx);
4582            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4583            let item1_id = item1.item_id();
4584            let item3_id = item3.item_id();
4585            let item4_id = item4.item_id();
4586            pane.close_items(cx, SaveIntent::Close, move |id| {
4587                [item1_id, item3_id, item4_id].contains(&id)
4588            })
4589        });
4590        cx.executor().run_until_parked();
4591
4592        assert!(cx.has_pending_prompt());
4593        // Ignore "Save all" prompt
4594        cx.simulate_prompt_answer(2);
4595        cx.executor().run_until_parked();
4596        // There's a prompt to save item 1.
4597        pane.update(cx, |pane, _| {
4598            assert_eq!(pane.items_len(), 4);
4599            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4600        });
4601        // Confirm saving item 1.
4602        cx.simulate_prompt_answer(0);
4603        cx.executor().run_until_parked();
4604
4605        // Item 1 is saved. There's a prompt to save item 3.
4606        pane.update(cx, |pane, cx| {
4607            assert_eq!(item1.read(cx).save_count, 1);
4608            assert_eq!(item1.read(cx).save_as_count, 0);
4609            assert_eq!(item1.read(cx).reload_count, 0);
4610            assert_eq!(pane.items_len(), 3);
4611            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4612        });
4613        assert!(cx.has_pending_prompt());
4614
4615        // Cancel saving item 3.
4616        cx.simulate_prompt_answer(1);
4617        cx.executor().run_until_parked();
4618
4619        // Item 3 is reloaded. There's a prompt to save item 4.
4620        pane.update(cx, |pane, cx| {
4621            assert_eq!(item3.read(cx).save_count, 0);
4622            assert_eq!(item3.read(cx).save_as_count, 0);
4623            assert_eq!(item3.read(cx).reload_count, 1);
4624            assert_eq!(pane.items_len(), 2);
4625            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4626        });
4627        assert!(cx.has_pending_prompt());
4628
4629        // Confirm saving item 4.
4630        cx.simulate_prompt_answer(0);
4631        cx.executor().run_until_parked();
4632
4633        // There's a prompt for a path for item 4.
4634        cx.simulate_new_path_selection(|_| Some(Default::default()));
4635        close_items.await.unwrap();
4636
4637        // The requested items are closed.
4638        pane.update(cx, |pane, cx| {
4639            assert_eq!(item4.read(cx).save_count, 0);
4640            assert_eq!(item4.read(cx).save_as_count, 1);
4641            assert_eq!(item4.read(cx).reload_count, 0);
4642            assert_eq!(pane.items_len(), 1);
4643            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4644        });
4645    }
4646
4647    #[gpui::test]
4648    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4649        init_test(cx);
4650
4651        let fs = FakeFs::new(cx.executor());
4652        let project = Project::test(fs, [], cx).await;
4653        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4654
4655        // Create several workspace items with single project entries, and two
4656        // workspace items with multiple project entries.
4657        let single_entry_items = (0..=4)
4658            .map(|project_entry_id| {
4659                cx.new_view(|cx| {
4660                    TestItem::new(cx)
4661                        .with_dirty(true)
4662                        .with_project_items(&[TestProjectItem::new(
4663                            project_entry_id,
4664                            &format!("{project_entry_id}.txt"),
4665                            cx,
4666                        )])
4667                })
4668            })
4669            .collect::<Vec<_>>();
4670        let item_2_3 = cx.new_view(|cx| {
4671            TestItem::new(cx)
4672                .with_dirty(true)
4673                .with_singleton(false)
4674                .with_project_items(&[
4675                    single_entry_items[2].read(cx).project_items[0].clone(),
4676                    single_entry_items[3].read(cx).project_items[0].clone(),
4677                ])
4678        });
4679        let item_3_4 = cx.new_view(|cx| {
4680            TestItem::new(cx)
4681                .with_dirty(true)
4682                .with_singleton(false)
4683                .with_project_items(&[
4684                    single_entry_items[3].read(cx).project_items[0].clone(),
4685                    single_entry_items[4].read(cx).project_items[0].clone(),
4686                ])
4687        });
4688
4689        // Create two panes that contain the following project entries:
4690        //   left pane:
4691        //     multi-entry items:   (2, 3)
4692        //     single-entry items:  0, 1, 2, 3, 4
4693        //   right pane:
4694        //     single-entry items:  1
4695        //     multi-entry items:   (3, 4)
4696        let left_pane = workspace.update(cx, |workspace, cx| {
4697            let left_pane = workspace.active_pane().clone();
4698            workspace.add_item(Box::new(item_2_3.clone()), cx);
4699            for item in single_entry_items {
4700                workspace.add_item(Box::new(item), cx);
4701            }
4702            left_pane.update(cx, |pane, cx| {
4703                pane.activate_item(2, true, true, cx);
4704            });
4705
4706            let right_pane = workspace
4707                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4708                .unwrap();
4709
4710            right_pane.update(cx, |pane, cx| {
4711                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4712            });
4713
4714            left_pane
4715        });
4716
4717        cx.focus_view(&left_pane);
4718
4719        // When closing all of the items in the left pane, we should be prompted twice:
4720        // once for project entry 0, and once for project entry 2. Project entries 1,
4721        // 3, and 4 are all still open in the other paten. After those two
4722        // prompts, the task should complete.
4723
4724        let close = left_pane.update(cx, |pane, cx| {
4725            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4726        });
4727        cx.executor().run_until_parked();
4728
4729        // Discard "Save all" prompt
4730        cx.simulate_prompt_answer(2);
4731
4732        cx.executor().run_until_parked();
4733        left_pane.update(cx, |pane, cx| {
4734            assert_eq!(
4735                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4736                &[ProjectEntryId::from_proto(0)]
4737            );
4738        });
4739        cx.simulate_prompt_answer(0);
4740
4741        cx.executor().run_until_parked();
4742        left_pane.update(cx, |pane, cx| {
4743            assert_eq!(
4744                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4745                &[ProjectEntryId::from_proto(2)]
4746            );
4747        });
4748        cx.simulate_prompt_answer(0);
4749
4750        cx.executor().run_until_parked();
4751        close.await.unwrap();
4752        left_pane.update(cx, |pane, _| {
4753            assert_eq!(pane.items_len(), 0);
4754        });
4755    }
4756
4757    #[gpui::test]
4758    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4759        init_test(cx);
4760
4761        let fs = FakeFs::new(cx.executor());
4762        let project = Project::test(fs, [], cx).await;
4763        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4764        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4765
4766        let item = cx.new_view(|cx| {
4767            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4768        });
4769        let item_id = item.entity_id();
4770        workspace.update(cx, |workspace, cx| {
4771            workspace.add_item(Box::new(item.clone()), cx);
4772        });
4773
4774        // Autosave on window change.
4775        item.update(cx, |item, cx| {
4776            cx.update_global(|settings: &mut SettingsStore, cx| {
4777                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4778                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4779                })
4780            });
4781            item.is_dirty = true;
4782        });
4783
4784        // Deactivating the window saves the file.
4785        cx.deactivate_window();
4786        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4787
4788        // Autosave on focus change.
4789        item.update(cx, |item, cx| {
4790            cx.focus_self();
4791            cx.update_global(|settings: &mut SettingsStore, cx| {
4792                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4793                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4794                })
4795            });
4796            item.is_dirty = true;
4797        });
4798
4799        // Blurring the item saves the file.
4800        item.update(cx, |_, cx| cx.blur());
4801        cx.executor().run_until_parked();
4802        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4803
4804        // Deactivating the window still saves the file.
4805        cx.update(|cx| cx.activate_window());
4806        item.update(cx, |item, cx| {
4807            cx.focus_self();
4808            item.is_dirty = true;
4809        });
4810        cx.deactivate_window();
4811
4812        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4813
4814        // Autosave after delay.
4815        item.update(cx, |item, cx| {
4816            cx.update_global(|settings: &mut SettingsStore, cx| {
4817                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4818                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4819                })
4820            });
4821            item.is_dirty = true;
4822            cx.emit(ItemEvent::Edit);
4823        });
4824
4825        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4826        cx.executor().advance_clock(Duration::from_millis(250));
4827        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4828
4829        // After delay expires, the file is saved.
4830        cx.executor().advance_clock(Duration::from_millis(250));
4831        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4832
4833        // Autosave on focus change, ensuring closing the tab counts as such.
4834        item.update(cx, |item, cx| {
4835            cx.update_global(|settings: &mut SettingsStore, cx| {
4836                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4837                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4838                })
4839            });
4840            item.is_dirty = true;
4841        });
4842
4843        pane.update(cx, |pane, cx| {
4844            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4845        })
4846        .await
4847        .unwrap();
4848        assert!(!cx.has_pending_prompt());
4849        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4850
4851        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4852        workspace.update(cx, |workspace, cx| {
4853            workspace.add_item(Box::new(item.clone()), cx);
4854        });
4855        item.update(cx, |item, cx| {
4856            item.project_items[0].update(cx, |item, _| {
4857                item.entry_id = None;
4858            });
4859            item.is_dirty = true;
4860            cx.blur();
4861        });
4862        cx.run_until_parked();
4863        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4864
4865        // Ensure autosave is prevented for deleted files also when closing the buffer.
4866        let _close_items = pane.update(cx, |pane, cx| {
4867            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4868        });
4869        cx.run_until_parked();
4870        assert!(cx.has_pending_prompt());
4871        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4872    }
4873
4874    #[gpui::test]
4875    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4876        init_test(cx);
4877
4878        let fs = FakeFs::new(cx.executor());
4879
4880        let project = Project::test(fs, [], cx).await;
4881        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4882
4883        let item = cx.new_view(|cx| {
4884            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4885        });
4886        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4887        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4888        let toolbar_notify_count = Rc::new(RefCell::new(0));
4889
4890        workspace.update(cx, |workspace, cx| {
4891            workspace.add_item(Box::new(item.clone()), cx);
4892            let toolbar_notification_count = toolbar_notify_count.clone();
4893            cx.observe(&toolbar, move |_, _, _| {
4894                *toolbar_notification_count.borrow_mut() += 1
4895            })
4896            .detach();
4897        });
4898
4899        pane.update(cx, |pane, _| {
4900            assert!(!pane.can_navigate_backward());
4901            assert!(!pane.can_navigate_forward());
4902        });
4903
4904        item.update(cx, |item, cx| {
4905            item.set_state("one".to_string(), cx);
4906        });
4907
4908        // Toolbar must be notified to re-render the navigation buttons
4909        assert_eq!(*toolbar_notify_count.borrow(), 1);
4910
4911        pane.update(cx, |pane, _| {
4912            assert!(pane.can_navigate_backward());
4913            assert!(!pane.can_navigate_forward());
4914        });
4915
4916        workspace
4917            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4918            .await
4919            .unwrap();
4920
4921        assert_eq!(*toolbar_notify_count.borrow(), 2);
4922        pane.update(cx, |pane, _| {
4923            assert!(!pane.can_navigate_backward());
4924            assert!(pane.can_navigate_forward());
4925        });
4926    }
4927
4928    #[gpui::test]
4929    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4930        init_test(cx);
4931        let fs = FakeFs::new(cx.executor());
4932
4933        let project = Project::test(fs, [], cx).await;
4934        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4935
4936        let panel = workspace.update(cx, |workspace, cx| {
4937            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4938            workspace.add_panel(panel.clone(), cx);
4939
4940            workspace
4941                .right_dock()
4942                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4943
4944            panel
4945        });
4946
4947        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4948        pane.update(cx, |pane, cx| {
4949            let item = cx.new_view(|cx| TestItem::new(cx));
4950            pane.add_item(Box::new(item), true, true, None, cx);
4951        });
4952
4953        // Transfer focus from center to panel
4954        workspace.update(cx, |workspace, cx| {
4955            workspace.toggle_panel_focus::<TestPanel>(cx);
4956        });
4957
4958        workspace.update(cx, |workspace, cx| {
4959            assert!(workspace.right_dock().read(cx).is_open());
4960            assert!(!panel.is_zoomed(cx));
4961            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4962        });
4963
4964        // Transfer focus from panel to center
4965        workspace.update(cx, |workspace, cx| {
4966            workspace.toggle_panel_focus::<TestPanel>(cx);
4967        });
4968
4969        workspace.update(cx, |workspace, cx| {
4970            assert!(workspace.right_dock().read(cx).is_open());
4971            assert!(!panel.is_zoomed(cx));
4972            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4973        });
4974
4975        // Close the dock
4976        workspace.update(cx, |workspace, cx| {
4977            workspace.toggle_dock(DockPosition::Right, cx);
4978        });
4979
4980        workspace.update(cx, |workspace, cx| {
4981            assert!(!workspace.right_dock().read(cx).is_open());
4982            assert!(!panel.is_zoomed(cx));
4983            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4984        });
4985
4986        // Open the dock
4987        workspace.update(cx, |workspace, cx| {
4988            workspace.toggle_dock(DockPosition::Right, cx);
4989        });
4990
4991        workspace.update(cx, |workspace, cx| {
4992            assert!(workspace.right_dock().read(cx).is_open());
4993            assert!(!panel.is_zoomed(cx));
4994            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4995        });
4996
4997        // Focus and zoom panel
4998        panel.update(cx, |panel, cx| {
4999            cx.focus_self();
5000            panel.set_zoomed(true, cx)
5001        });
5002
5003        workspace.update(cx, |workspace, cx| {
5004            assert!(workspace.right_dock().read(cx).is_open());
5005            assert!(panel.is_zoomed(cx));
5006            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5007        });
5008
5009        // Transfer focus to the center closes the dock
5010        workspace.update(cx, |workspace, cx| {
5011            workspace.toggle_panel_focus::<TestPanel>(cx);
5012        });
5013
5014        workspace.update(cx, |workspace, cx| {
5015            assert!(!workspace.right_dock().read(cx).is_open());
5016            assert!(panel.is_zoomed(cx));
5017            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5018        });
5019
5020        // Transferring focus back to the panel keeps it zoomed
5021        workspace.update(cx, |workspace, cx| {
5022            workspace.toggle_panel_focus::<TestPanel>(cx);
5023        });
5024
5025        workspace.update(cx, |workspace, cx| {
5026            assert!(workspace.right_dock().read(cx).is_open());
5027            assert!(panel.is_zoomed(cx));
5028            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5029        });
5030
5031        // Close the dock while it is zoomed
5032        workspace.update(cx, |workspace, cx| {
5033            workspace.toggle_dock(DockPosition::Right, cx)
5034        });
5035
5036        workspace.update(cx, |workspace, cx| {
5037            assert!(!workspace.right_dock().read(cx).is_open());
5038            assert!(panel.is_zoomed(cx));
5039            assert!(workspace.zoomed.is_none());
5040            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5041        });
5042
5043        // Opening the dock, when it's zoomed, retains focus
5044        workspace.update(cx, |workspace, cx| {
5045            workspace.toggle_dock(DockPosition::Right, cx)
5046        });
5047
5048        workspace.update(cx, |workspace, cx| {
5049            assert!(workspace.right_dock().read(cx).is_open());
5050            assert!(panel.is_zoomed(cx));
5051            assert!(workspace.zoomed.is_some());
5052            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5053        });
5054
5055        // Unzoom and close the panel, zoom the active pane.
5056        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5057        workspace.update(cx, |workspace, cx| {
5058            workspace.toggle_dock(DockPosition::Right, cx)
5059        });
5060        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5061
5062        // Opening a dock unzooms the pane.
5063        workspace.update(cx, |workspace, cx| {
5064            workspace.toggle_dock(DockPosition::Right, cx)
5065        });
5066        workspace.update(cx, |workspace, cx| {
5067            let pane = pane.read(cx);
5068            assert!(!pane.is_zoomed());
5069            assert!(!pane.focus_handle(cx).is_focused(cx));
5070            assert!(workspace.right_dock().read(cx).is_open());
5071            assert!(workspace.zoomed.is_none());
5072        });
5073    }
5074
5075    struct TestModal(FocusHandle);
5076
5077    impl TestModal {
5078        fn new(cx: &mut ViewContext<Self>) -> Self {
5079            Self(cx.focus_handle())
5080        }
5081    }
5082
5083    impl EventEmitter<DismissEvent> for TestModal {}
5084
5085    impl FocusableView for TestModal {
5086        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5087            self.0.clone()
5088        }
5089    }
5090
5091    impl ModalView for TestModal {}
5092
5093    impl Render for TestModal {
5094        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5095            div().track_focus(&self.0)
5096        }
5097    }
5098
5099    #[gpui::test]
5100    async fn test_panels(cx: &mut gpui::TestAppContext) {
5101        init_test(cx);
5102        let fs = FakeFs::new(cx.executor());
5103
5104        let project = Project::test(fs, [], cx).await;
5105        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5106
5107        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5108            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5109            workspace.add_panel(panel_1.clone(), cx);
5110            workspace
5111                .left_dock()
5112                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5113            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5114            workspace.add_panel(panel_2.clone(), cx);
5115            workspace
5116                .right_dock()
5117                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5118
5119            let left_dock = workspace.left_dock();
5120            assert_eq!(
5121                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5122                panel_1.panel_id()
5123            );
5124            assert_eq!(
5125                left_dock.read(cx).active_panel_size(cx).unwrap(),
5126                panel_1.size(cx)
5127            );
5128
5129            left_dock.update(cx, |left_dock, cx| {
5130                left_dock.resize_active_panel(Some(px(1337.)), cx)
5131            });
5132            assert_eq!(
5133                workspace
5134                    .right_dock()
5135                    .read(cx)
5136                    .visible_panel()
5137                    .unwrap()
5138                    .panel_id(),
5139                panel_2.panel_id(),
5140            );
5141
5142            (panel_1, panel_2)
5143        });
5144
5145        // Move panel_1 to the right
5146        panel_1.update(cx, |panel_1, cx| {
5147            panel_1.set_position(DockPosition::Right, cx)
5148        });
5149
5150        workspace.update(cx, |workspace, cx| {
5151            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5152            // Since it was the only panel on the left, the left dock should now be closed.
5153            assert!(!workspace.left_dock().read(cx).is_open());
5154            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5155            let right_dock = workspace.right_dock();
5156            assert_eq!(
5157                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5158                panel_1.panel_id()
5159            );
5160            assert_eq!(
5161                right_dock.read(cx).active_panel_size(cx).unwrap(),
5162                px(1337.)
5163            );
5164
5165            // Now we move panel_2 to the left
5166            panel_2.set_position(DockPosition::Left, cx);
5167        });
5168
5169        workspace.update(cx, |workspace, cx| {
5170            // Since panel_2 was not visible on the right, we don't open the left dock.
5171            assert!(!workspace.left_dock().read(cx).is_open());
5172            // And the right dock is unaffected in it's displaying of panel_1
5173            assert!(workspace.right_dock().read(cx).is_open());
5174            assert_eq!(
5175                workspace
5176                    .right_dock()
5177                    .read(cx)
5178                    .visible_panel()
5179                    .unwrap()
5180                    .panel_id(),
5181                panel_1.panel_id(),
5182            );
5183        });
5184
5185        // Move panel_1 back to the left
5186        panel_1.update(cx, |panel_1, cx| {
5187            panel_1.set_position(DockPosition::Left, cx)
5188        });
5189
5190        workspace.update(cx, |workspace, cx| {
5191            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5192            let left_dock = workspace.left_dock();
5193            assert!(left_dock.read(cx).is_open());
5194            assert_eq!(
5195                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5196                panel_1.panel_id()
5197            );
5198            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5199            // And the right dock should be closed as it no longer has any panels.
5200            assert!(!workspace.right_dock().read(cx).is_open());
5201
5202            // Now we move panel_1 to the bottom
5203            panel_1.set_position(DockPosition::Bottom, cx);
5204        });
5205
5206        workspace.update(cx, |workspace, cx| {
5207            // Since panel_1 was visible on the left, we close the left dock.
5208            assert!(!workspace.left_dock().read(cx).is_open());
5209            // The bottom dock is sized based on the panel's default size,
5210            // since the panel orientation changed from vertical to horizontal.
5211            let bottom_dock = workspace.bottom_dock();
5212            assert_eq!(
5213                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5214                panel_1.size(cx),
5215            );
5216            // Close bottom dock and move panel_1 back to the left.
5217            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5218            panel_1.set_position(DockPosition::Left, cx);
5219        });
5220
5221        // Emit activated event on panel 1
5222        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5223
5224        // Now the left dock is open and panel_1 is active and focused.
5225        workspace.update(cx, |workspace, cx| {
5226            let left_dock = workspace.left_dock();
5227            assert!(left_dock.read(cx).is_open());
5228            assert_eq!(
5229                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5230                panel_1.panel_id(),
5231            );
5232            assert!(panel_1.focus_handle(cx).is_focused(cx));
5233        });
5234
5235        // Emit closed event on panel 2, which is not active
5236        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5237
5238        // Wo don't close the left dock, because panel_2 wasn't the active panel
5239        workspace.update(cx, |workspace, cx| {
5240            let left_dock = workspace.left_dock();
5241            assert!(left_dock.read(cx).is_open());
5242            assert_eq!(
5243                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5244                panel_1.panel_id(),
5245            );
5246        });
5247
5248        // Emitting a ZoomIn event shows the panel as zoomed.
5249        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5250        workspace.update(cx, |workspace, _| {
5251            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5252            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5253        });
5254
5255        // Move panel to another dock while it is zoomed
5256        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5257        workspace.update(cx, |workspace, _| {
5258            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5259
5260            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5261        });
5262
5263        // This is a helper for getting a:
5264        // - valid focus on an element,
5265        // - that isn't a part of the panes and panels system of the Workspace,
5266        // - and doesn't trigger the 'on_focus_lost' API.
5267        let focus_other_view = {
5268            let workspace = workspace.clone();
5269            move |cx: &mut VisualTestContext| {
5270                workspace.update(cx, |workspace, cx| {
5271                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5272                        workspace.toggle_modal(cx, TestModal::new);
5273                        workspace.toggle_modal(cx, TestModal::new);
5274                    } else {
5275                        workspace.toggle_modal(cx, TestModal::new);
5276                    }
5277                })
5278            }
5279        };
5280
5281        // If focus is transferred to another view that's not a panel or another pane, we still show
5282        // the panel as zoomed.
5283        focus_other_view(cx);
5284        workspace.update(cx, |workspace, _| {
5285            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5286            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5287        });
5288
5289        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5290        workspace.update(cx, |_, cx| cx.focus_self());
5291        workspace.update(cx, |workspace, _| {
5292            assert_eq!(workspace.zoomed, None);
5293            assert_eq!(workspace.zoomed_position, None);
5294        });
5295
5296        // If focus is transferred again to another view that's not a panel or a pane, we won't
5297        // show the panel as zoomed because it wasn't zoomed before.
5298        focus_other_view(cx);
5299        workspace.update(cx, |workspace, _| {
5300            assert_eq!(workspace.zoomed, None);
5301            assert_eq!(workspace.zoomed_position, None);
5302        });
5303
5304        // When the panel is activated, it is zoomed again.
5305        cx.dispatch_action(ToggleRightDock);
5306        workspace.update(cx, |workspace, _| {
5307            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5308            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5309        });
5310
5311        // Emitting a ZoomOut event unzooms the panel.
5312        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5313        workspace.update(cx, |workspace, _| {
5314            assert_eq!(workspace.zoomed, None);
5315            assert_eq!(workspace.zoomed_position, None);
5316        });
5317
5318        // Emit closed event on panel 1, which is active
5319        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5320
5321        // Now the left dock is closed, because panel_1 was the active panel
5322        workspace.update(cx, |workspace, cx| {
5323            let right_dock = workspace.right_dock();
5324            assert!(!right_dock.read(cx).is_open());
5325        });
5326    }
5327
5328    pub fn init_test(cx: &mut TestAppContext) {
5329        cx.update(|cx| {
5330            let settings_store = SettingsStore::test(cx);
5331            cx.set_global(settings_store);
5332            theme::init(theme::LoadThemes::JustBase, cx);
5333            language::init(cx);
5334            crate::init_settings(cx);
5335            Project::init_settings(cx);
5336        });
5337    }
5338}