workspace.rs

   1pub mod dock;
   2pub mod item;
   3mod modal_layer;
   4pub mod notifications;
   5pub mod pane;
   6pub mod pane_group;
   7mod persistence;
   8pub mod searchable;
   9pub mod shared_screen;
  10mod status_bar;
  11mod toolbar;
  12mod workspace_settings;
  13
  14use anyhow::{anyhow, Context as _, Result};
  15use call::ActiveCall;
  16use client::{
  17    proto::{self, PeerId},
  18    Client, Status, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  22use futures::{
  23    channel::{mpsc, oneshot},
  24    future::try_join_all,
  25    Future, FutureExt, StreamExt,
  26};
  27use gpui::{
  28    actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView,
  29    AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow, Bounds, Context,
  30    Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
  31    GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, ManagedView, Model,
  32    ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, Render, Size,
  33    Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds,
  34    WindowContext, WindowHandle, WindowOptions,
  35};
  36use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
  37use itertools::Itertools;
  38use language::{LanguageRegistry, Rope};
  39use lazy_static::lazy_static;
  40pub use modal_layer::*;
  41use node_runtime::NodeRuntime;
  42use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
  43pub use pane::*;
  44pub use pane_group::*;
  45use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
  46pub use persistence::{
  47    model::{ItemId, WorkspaceLocation},
  48    WorkspaceDb, DB as WORKSPACE_DB,
  49};
  50use postage::stream::Stream;
  51use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  52use serde::Deserialize;
  53use settings::Settings;
  54use shared_screen::SharedScreen;
  55use status_bar::StatusBar;
  56pub use status_bar::StatusItemView;
  57use std::{
  58    any::TypeId,
  59    borrow::Cow,
  60    cmp, env,
  61    path::{Path, PathBuf},
  62    sync::{atomic::AtomicUsize, Arc},
  63    time::Duration,
  64};
  65use theme::{ActiveTheme, ThemeSettings};
  66pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
  67pub use ui;
  68use ui::Label;
  69use util::ResultExt;
  70use uuid::Uuid;
  71pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
  72
  73use crate::persistence::{
  74    model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
  75    SerializedAxis,
  76};
  77
  78lazy_static! {
  79    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
  80        .ok()
  81        .as_deref()
  82        .and_then(parse_pixel_size_env_var);
  83    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
  84        .ok()
  85        .as_deref()
  86        .and_then(parse_pixel_position_env_var);
  87}
  88
  89#[derive(Clone, PartialEq)]
  90pub struct RemoveWorktreeFromProject(pub WorktreeId);
  91
  92actions!(
  93    workspace,
  94    [
  95        Open,
  96        NewFile,
  97        NewWindow,
  98        CloseWindow,
  99        CloseInactiveTabsAndPanes,
 100        AddFolderToProject,
 101        Unfollow,
 102        SaveAs,
 103        ReloadActiveItem,
 104        ActivatePreviousPane,
 105        ActivateNextPane,
 106        FollowNextCollaborator,
 107        NewTerminal,
 108        NewCenterTerminal,
 109        ToggleTerminalFocus,
 110        NewSearch,
 111        Feedback,
 112        Restart,
 113        Welcome,
 114        ToggleZoom,
 115        ToggleLeftDock,
 116        ToggleRightDock,
 117        ToggleBottomDock,
 118        CloseAllDocks,
 119    ]
 120);
 121
 122#[derive(Clone, PartialEq)]
 123pub struct OpenPaths {
 124    pub paths: Vec<PathBuf>,
 125}
 126
 127#[derive(Clone, Deserialize, PartialEq)]
 128pub struct ActivatePane(pub usize);
 129
 130#[derive(Clone, Deserialize, PartialEq)]
 131pub struct ActivatePaneInDirection(pub SplitDirection);
 132
 133#[derive(Clone, Deserialize, PartialEq)]
 134pub struct SwapPaneInDirection(pub SplitDirection);
 135
 136#[derive(Clone, Deserialize, PartialEq)]
 137pub struct NewFileInDirection(pub SplitDirection);
 138
 139#[derive(Clone, PartialEq, Debug, Deserialize)]
 140#[serde(rename_all = "camelCase")]
 141pub struct SaveAll {
 142    pub save_intent: Option<SaveIntent>,
 143}
 144
 145#[derive(Clone, PartialEq, Debug, Deserialize)]
 146#[serde(rename_all = "camelCase")]
 147pub struct Save {
 148    pub save_intent: Option<SaveIntent>,
 149}
 150
 151#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 152#[serde(rename_all = "camelCase")]
 153pub struct CloseAllItemsAndPanes {
 154    pub save_intent: Option<SaveIntent>,
 155}
 156
 157impl_actions!(
 158    workspace,
 159    [
 160        ActivatePane,
 161        ActivatePaneInDirection,
 162        CloseAllItemsAndPanes,
 163        NewFileInDirection,
 164        OpenTerminal,
 165        Save,
 166        SaveAll,
 167        SwapPaneInDirection,
 168    ]
 169);
 170
 171#[derive(Deserialize)]
 172pub struct Toast {
 173    id: usize,
 174    msg: Cow<'static, str>,
 175    #[serde(skip)]
 176    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 177}
 178
 179impl Toast {
 180    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 181        Toast {
 182            id,
 183            msg: msg.into(),
 184            on_click: None,
 185        }
 186    }
 187
 188    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 189    where
 190        M: Into<Cow<'static, str>>,
 191        F: Fn(&mut WindowContext) + 'static,
 192    {
 193        self.on_click = Some((message.into(), Arc::new(on_click)));
 194        self
 195    }
 196}
 197
 198impl PartialEq for Toast {
 199    fn eq(&self, other: &Self) -> bool {
 200        self.id == other.id
 201            && self.msg == other.msg
 202            && self.on_click.is_some() == other.on_click.is_some()
 203    }
 204}
 205
 206impl Clone for Toast {
 207    fn clone(&self) -> Self {
 208        Toast {
 209            id: self.id,
 210            msg: self.msg.to_owned(),
 211            on_click: self.on_click.clone(),
 212        }
 213    }
 214}
 215
 216#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 217pub struct OpenTerminal {
 218    pub working_directory: PathBuf,
 219}
 220
 221pub type WorkspaceId = i64;
 222
 223pub fn init_settings(cx: &mut AppContext) {
 224    WorkspaceSettings::register(cx);
 225    ItemSettings::register(cx);
 226}
 227
 228pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 229    init_settings(cx);
 230    notifications::init(cx);
 231
 232    cx.on_action(Workspace::close_global);
 233    cx.on_action(restart);
 234
 235    cx.on_action({
 236        let app_state = Arc::downgrade(&app_state);
 237        move |_: &Open, cx: &mut AppContext| {
 238            let paths = cx.prompt_for_paths(PathPromptOptions {
 239                files: true,
 240                directories: true,
 241                multiple: true,
 242            });
 243
 244            if let Some(app_state) = app_state.upgrade() {
 245                cx.spawn(move |cx| async move {
 246                    if let Some(paths) = paths.await.log_err().flatten() {
 247                        cx.update(|cx| {
 248                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 249                        })
 250                        .ok();
 251                    }
 252                })
 253                .detach();
 254            }
 255        }
 256    });
 257}
 258
 259type ProjectItemBuilders =
 260    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 261pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 262    let builders = cx.default_global::<ProjectItemBuilders>();
 263    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 264        let item = model.downcast::<I::Item>().unwrap();
 265        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
 266    });
 267}
 268
 269type FollowableItemBuilder = fn(
 270    View<Pane>,
 271    View<Workspace>,
 272    ViewId,
 273    &mut Option<proto::view::Variant>,
 274    &mut WindowContext,
 275) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 276type FollowableItemBuilders = HashMap<
 277    TypeId,
 278    (
 279        FollowableItemBuilder,
 280        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
 281    ),
 282>;
 283pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 284    let builders = cx.default_global::<FollowableItemBuilders>();
 285    builders.insert(
 286        TypeId::of::<I>(),
 287        (
 288            |pane, workspace, id, state, cx| {
 289                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 290                    cx.foreground_executor()
 291                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 292                })
 293            },
 294            |this| Box::new(this.clone().downcast::<I>().unwrap()),
 295        ),
 296    );
 297}
 298
 299type ItemDeserializers = HashMap<
 300    Arc<str>,
 301    fn(
 302        Model<Project>,
 303        WeakView<Workspace>,
 304        WorkspaceId,
 305        ItemId,
 306        &mut ViewContext<Pane>,
 307    ) -> Task<Result<Box<dyn ItemHandle>>>,
 308>;
 309pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 310    if let Some(serialized_item_kind) = I::serialized_item_kind() {
 311        let deserializers = cx.default_global::<ItemDeserializers>();
 312        deserializers.insert(
 313            Arc::from(serialized_item_kind),
 314            |project, workspace, workspace_id, item_id, cx| {
 315                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 316                cx.foreground_executor()
 317                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 318            },
 319        );
 320    }
 321}
 322
 323pub struct AppState {
 324    pub languages: Arc<LanguageRegistry>,
 325    pub client: Arc<Client>,
 326    pub user_store: Model<UserStore>,
 327    pub workspace_store: Model<WorkspaceStore>,
 328    pub fs: Arc<dyn fs::Fs>,
 329    pub build_window_options:
 330        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
 331    pub node_runtime: Arc<dyn NodeRuntime>,
 332}
 333
 334pub struct WorkspaceStore {
 335    workspaces: HashSet<WindowHandle<Workspace>>,
 336    followers: Vec<Follower>,
 337    client: Arc<Client>,
 338    _subscriptions: Vec<client::Subscription>,
 339}
 340
 341#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 342struct Follower {
 343    project_id: Option<u64>,
 344    peer_id: PeerId,
 345}
 346
 347impl AppState {
 348    #[cfg(any(test, feature = "test-support"))]
 349    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 350        use node_runtime::FakeNodeRuntime;
 351        use settings::SettingsStore;
 352
 353        if !cx.has_global::<SettingsStore>() {
 354            let settings_store = SettingsStore::test(cx);
 355            cx.set_global(settings_store);
 356        }
 357
 358        let fs = fs::FakeFs::new(cx.background_executor().clone());
 359        let languages = Arc::new(LanguageRegistry::test());
 360        let http_client = util::http::FakeHttpClient::with_404_response();
 361        let client = Client::new(http_client.clone(), cx);
 362        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 363        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 364
 365        theme::init(theme::LoadThemes::JustBase, cx);
 366        client::init(&client, cx);
 367        crate::init_settings(cx);
 368
 369        Arc::new(Self {
 370            client,
 371            fs,
 372            languages,
 373            user_store,
 374            workspace_store,
 375            node_runtime: FakeNodeRuntime::new(),
 376            build_window_options: |_, _, _| Default::default(),
 377        })
 378    }
 379}
 380
 381struct DelayedDebouncedEditAction {
 382    task: Option<Task<()>>,
 383    cancel_channel: Option<oneshot::Sender<()>>,
 384}
 385
 386impl DelayedDebouncedEditAction {
 387    fn new() -> DelayedDebouncedEditAction {
 388        DelayedDebouncedEditAction {
 389            task: None,
 390            cancel_channel: None,
 391        }
 392    }
 393
 394    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 395    where
 396        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 397    {
 398        if let Some(channel) = self.cancel_channel.take() {
 399            _ = channel.send(());
 400        }
 401
 402        let (sender, mut receiver) = oneshot::channel::<()>();
 403        self.cancel_channel = Some(sender);
 404
 405        let previous_task = self.task.take();
 406        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
 407            let mut timer = cx.background_executor().timer(delay).fuse();
 408            if let Some(previous_task) = previous_task {
 409                previous_task.await;
 410            }
 411
 412            futures::select_biased! {
 413                _ = receiver => return,
 414                    _ = timer => {}
 415            }
 416
 417            if let Some(result) = workspace
 418                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 419                .log_err()
 420            {
 421                result.await.log_err();
 422            }
 423        }));
 424    }
 425}
 426
 427pub enum Event {
 428    PaneAdded(View<Pane>),
 429    ContactRequestedJoin(u64),
 430    WorkspaceCreated(WeakView<Workspace>),
 431}
 432
 433pub enum OpenVisible {
 434    All,
 435    None,
 436    OnlyFiles,
 437    OnlyDirectories,
 438}
 439
 440pub struct Workspace {
 441    weak_self: WeakView<Self>,
 442    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
 443    zoomed: Option<AnyWeakView>,
 444    zoomed_position: Option<DockPosition>,
 445    center: PaneGroup,
 446    left_dock: View<Dock>,
 447    bottom_dock: View<Dock>,
 448    right_dock: View<Dock>,
 449    panes: Vec<View<Pane>>,
 450    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
 451    active_pane: View<Pane>,
 452    last_active_center_pane: Option<WeakView<Pane>>,
 453    last_active_view_id: Option<proto::ViewId>,
 454    status_bar: View<StatusBar>,
 455    modal_layer: View<ModalLayer>,
 456    titlebar_item: Option<AnyView>,
 457    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 458    project: Model<Project>,
 459    follower_states: HashMap<View<Pane>, FollowerState>,
 460    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
 461    window_edited: bool,
 462    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
 463    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 464    database_id: WorkspaceId,
 465    app_state: Arc<AppState>,
 466    _subscriptions: Vec<Subscription>,
 467    _apply_leader_updates: Task<Result<()>>,
 468    _observe_current_user: Task<Result<()>>,
 469    _schedule_serialize: Option<Task<()>>,
 470    pane_history_timestamp: Arc<AtomicUsize>,
 471    bounds: Bounds<Pixels>,
 472}
 473
 474impl EventEmitter<Event> for Workspace {}
 475
 476#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 477pub struct ViewId {
 478    pub creator: PeerId,
 479    pub id: u64,
 480}
 481
 482#[derive(Default)]
 483struct FollowerState {
 484    leader_id: PeerId,
 485    active_view_id: Option<ViewId>,
 486    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 487}
 488
 489impl Workspace {
 490    pub fn new(
 491        workspace_id: WorkspaceId,
 492        project: Model<Project>,
 493        app_state: Arc<AppState>,
 494        cx: &mut ViewContext<Self>,
 495    ) -> Self {
 496        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 497        cx.subscribe(&project, move |this, _, event, cx| {
 498            match event {
 499                project::Event::RemoteIdChanged(_) => {
 500                    this.update_window_title(cx);
 501                }
 502
 503                project::Event::CollaboratorLeft(peer_id) => {
 504                    this.collaborator_left(*peer_id, cx);
 505                }
 506
 507                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 508                    this.update_window_title(cx);
 509                    this.serialize_workspace(cx);
 510                }
 511
 512                project::Event::DisconnectedFromHost => {
 513                    this.update_window_edited(cx);
 514                    let panes_to_unfollow: Vec<View<Pane>> =
 515                        this.follower_states.keys().map(|k| k.clone()).collect();
 516                    for pane in panes_to_unfollow {
 517                        this.unfollow(&pane, cx);
 518                    }
 519                    cx.disable_focus();
 520                }
 521
 522                project::Event::Closed => {
 523                    cx.remove_window();
 524                }
 525
 526                project::Event::DeletedEntry(entry_id) => {
 527                    for pane in this.panes.iter() {
 528                        pane.update(cx, |pane, cx| {
 529                            pane.handle_deleted_project_item(*entry_id, cx)
 530                        });
 531                    }
 532                }
 533
 534                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 535                    cx.new_view(|_| MessageNotification::new(message.clone()))
 536                }),
 537
 538                _ => {}
 539            }
 540            cx.notify()
 541        })
 542        .detach();
 543
 544        cx.on_focus_lost(|this, cx| {
 545            let focus_handle = this.focus_handle(cx);
 546            cx.focus(&focus_handle);
 547        })
 548        .detach();
 549
 550        let weak_handle = cx.view().downgrade();
 551        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 552
 553        let center_pane = cx.new_view(|cx| {
 554            Pane::new(
 555                weak_handle.clone(),
 556                project.clone(),
 557                pane_history_timestamp.clone(),
 558                None,
 559                cx,
 560            )
 561        });
 562        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 563
 564        cx.focus_view(&center_pane);
 565        cx.emit(Event::PaneAdded(center_pane.clone()));
 566
 567        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
 568        app_state.workspace_store.update(cx, |store, _| {
 569            store.workspaces.insert(window_handle);
 570        });
 571
 572        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 573        let mut connection_status = app_state.client.status();
 574        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 575            current_user.next().await;
 576            connection_status.next().await;
 577            let mut stream =
 578                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 579
 580            while stream.recv().await.is_some() {
 581                this.update(&mut cx, |_, cx| cx.notify())?;
 582            }
 583            anyhow::Ok(())
 584        });
 585
 586        // All leader updates are enqueued and then processed in a single task, so
 587        // that each asynchronous operation can be run in order.
 588        let (leader_updates_tx, mut leader_updates_rx) =
 589            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 590        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 591            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 592                Self::process_leader_update(&this, leader_id, update, &mut cx)
 593                    .await
 594                    .log_err();
 595            }
 596
 597            Ok(())
 598        });
 599
 600        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
 601
 602        let left_dock = Dock::new(DockPosition::Left, cx);
 603        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
 604        let right_dock = Dock::new(DockPosition::Right, cx);
 605        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
 606        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
 607        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
 608        let status_bar = cx.new_view(|cx| {
 609            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 610            status_bar.add_left_item(left_dock_buttons, cx);
 611            status_bar.add_right_item(right_dock_buttons, cx);
 612            status_bar.add_right_item(bottom_dock_buttons, cx);
 613            status_bar
 614        });
 615
 616        let modal_layer = cx.new_view(|_| ModalLayer::new());
 617
 618        let mut active_call = None;
 619        if let Some(call) = cx.try_global::<Model<ActiveCall>>() {
 620            let call = call.clone();
 621            let mut subscriptions = Vec::new();
 622            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 623            active_call = Some((call, subscriptions));
 624        }
 625
 626        let subscriptions = vec![
 627            cx.observe_window_activation(Self::on_window_activation_changed),
 628            cx.observe_window_bounds(move |_, cx| {
 629                if let Some(display) = cx.display() {
 630                    // Transform fixed bounds to be stored in terms of the containing display
 631                    let mut bounds = cx.window_bounds();
 632                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
 633                        let display_bounds = display.bounds();
 634                        window_bounds.origin.x -= display_bounds.origin.x;
 635                        window_bounds.origin.y -= display_bounds.origin.y;
 636                    }
 637
 638                    if let Some(display_uuid) = display.uuid().log_err() {
 639                        cx.background_executor()
 640                            .spawn(DB.set_window_bounds(
 641                                workspace_id,
 642                                SerializedWindowsBounds(bounds),
 643                                display_uuid,
 644                            ))
 645                            .detach_and_log_err(cx);
 646                    }
 647                }
 648                cx.notify();
 649            }),
 650            cx.observe(&left_dock, |this, _, cx| {
 651                this.serialize_workspace(cx);
 652                cx.notify();
 653            }),
 654            cx.observe(&bottom_dock, |this, _, cx| {
 655                this.serialize_workspace(cx);
 656                cx.notify();
 657            }),
 658            cx.observe(&right_dock, |this, _, cx| {
 659                this.serialize_workspace(cx);
 660                cx.notify();
 661            }),
 662            cx.on_release(|this, window, cx| {
 663                this.app_state.workspace_store.update(cx, |store, _| {
 664                    let window = window.downcast::<Self>().unwrap();
 665                    store.workspaces.remove(&window);
 666                })
 667            }),
 668        ];
 669
 670        cx.defer(|this, cx| {
 671            this.update_window_title(cx);
 672        });
 673        Workspace {
 674            weak_self: weak_handle.clone(),
 675            zoomed: None,
 676            zoomed_position: None,
 677            center: PaneGroup::new(center_pane.clone()),
 678            panes: vec![center_pane.clone()],
 679            panes_by_item: Default::default(),
 680            active_pane: center_pane.clone(),
 681            last_active_center_pane: Some(center_pane.downgrade()),
 682            last_active_view_id: None,
 683            status_bar,
 684            modal_layer,
 685            titlebar_item: None,
 686            notifications: Default::default(),
 687            left_dock,
 688            bottom_dock,
 689            right_dock,
 690            project: project.clone(),
 691            follower_states: Default::default(),
 692            last_leaders_by_pane: Default::default(),
 693            window_edited: false,
 694            active_call,
 695            database_id: workspace_id,
 696            app_state,
 697            _observe_current_user,
 698            _apply_leader_updates,
 699            _schedule_serialize: None,
 700            leader_updates_tx,
 701            _subscriptions: subscriptions,
 702            pane_history_timestamp,
 703            workspace_actions: Default::default(),
 704            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
 705            bounds: Default::default(),
 706        }
 707    }
 708
 709    fn new_local(
 710        abs_paths: Vec<PathBuf>,
 711        app_state: Arc<AppState>,
 712        requesting_window: Option<WindowHandle<Workspace>>,
 713        cx: &mut AppContext,
 714    ) -> Task<
 715        anyhow::Result<(
 716            WindowHandle<Workspace>,
 717            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 718        )>,
 719    > {
 720        let project_handle = Project::local(
 721            app_state.client.clone(),
 722            app_state.node_runtime.clone(),
 723            app_state.user_store.clone(),
 724            app_state.languages.clone(),
 725            app_state.fs.clone(),
 726            cx,
 727        );
 728
 729        cx.spawn(|mut cx| async move {
 730            let serialized_workspace: Option<SerializedWorkspace> =
 731                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 732
 733            let paths_to_open = Arc::new(abs_paths);
 734
 735            // Get project paths for all of the abs_paths
 736            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 737            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 738                Vec::with_capacity(paths_to_open.len());
 739            for path in paths_to_open.iter().cloned() {
 740                if let Some((worktree, project_entry)) = cx
 741                    .update(|cx| {
 742                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 743                    })?
 744                    .await
 745                    .log_err()
 746                {
 747                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
 748                    project_paths.push((path, Some(project_entry)));
 749                } else {
 750                    project_paths.push((path, None));
 751                }
 752            }
 753
 754            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 755                serialized_workspace.id
 756            } else {
 757                DB.next_id().await.unwrap_or(0)
 758            };
 759
 760            let window = if let Some(window) = requesting_window {
 761                cx.update_window(window.into(), |_, cx| {
 762                    cx.replace_root_view(|cx| {
 763                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 764                    });
 765                })?;
 766                window
 767            } else {
 768                let window_bounds_override = window_bounds_env_override(&cx);
 769                let (bounds, display) = if let Some(bounds) = window_bounds_override {
 770                    (Some(bounds), None)
 771                } else {
 772                    serialized_workspace
 773                        .as_ref()
 774                        .and_then(|serialized_workspace| {
 775                            let serialized_display = serialized_workspace.display?;
 776                            let mut bounds = serialized_workspace.bounds?;
 777
 778                            // Stored bounds are relative to the containing display.
 779                            // So convert back to global coordinates if that screen still exists
 780                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
 781                                let screen = cx
 782                                    .update(|cx| {
 783                                        cx.displays().into_iter().find(|display| {
 784                                            display.uuid().ok() == Some(serialized_display)
 785                                        })
 786                                    })
 787                                    .ok()??;
 788                                let screen_bounds = screen.bounds();
 789                                window_bounds.origin.x += screen_bounds.origin.x;
 790                                window_bounds.origin.y += screen_bounds.origin.y;
 791                                bounds = WindowBounds::Fixed(window_bounds);
 792                            }
 793
 794                            Some((bounds, serialized_display))
 795                        })
 796                        .unzip()
 797                };
 798
 799                // Use the serialized workspace to construct the new window
 800                let options =
 801                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
 802
 803                cx.open_window(options, {
 804                    let app_state = app_state.clone();
 805                    let workspace_id = workspace_id.clone();
 806                    let project_handle = project_handle.clone();
 807                    move |cx| {
 808                        cx.new_view(|cx| {
 809                            Workspace::new(workspace_id, project_handle, app_state, cx)
 810                        })
 811                    }
 812                })?
 813            };
 814
 815            window
 816                .update(&mut cx, |_, cx| cx.activate_window())
 817                .log_err();
 818
 819            notify_if_database_failed(window, &mut cx);
 820            let opened_items = window
 821                .update(&mut cx, |_workspace, cx| {
 822                    open_items(serialized_workspace, project_paths, app_state, cx)
 823                })?
 824                .await
 825                .unwrap_or_default();
 826
 827            Ok((window, opened_items))
 828        })
 829    }
 830
 831    pub fn weak_handle(&self) -> WeakView<Self> {
 832        self.weak_self.clone()
 833    }
 834
 835    pub fn left_dock(&self) -> &View<Dock> {
 836        &self.left_dock
 837    }
 838
 839    pub fn bottom_dock(&self) -> &View<Dock> {
 840        &self.bottom_dock
 841    }
 842
 843    pub fn right_dock(&self) -> &View<Dock> {
 844        &self.right_dock
 845    }
 846
 847    pub fn is_edited(&self) -> bool {
 848        self.window_edited
 849    }
 850
 851    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut 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(cx.listener(|workspace, bounds, _| {
3543                            workspace.bounds = *bounds;
3544                        }))
3545                        .absolute()
3546                        .size_full(),
3547                    )
3548                    .on_drag_move(
3549                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3550                            match e.drag(cx).0 {
3551                                DockPosition::Left => {
3552                                    let size = workspace.bounds.left() + e.event.position.x;
3553                                    workspace.left_dock.update(cx, |left_dock, cx| {
3554                                        left_dock.resize_active_panel(Some(size), cx);
3555                                    });
3556                                }
3557                                DockPosition::Right => {
3558                                    let size = workspace.bounds.right() - e.event.position.x;
3559                                    workspace.right_dock.update(cx, |right_dock, cx| {
3560                                        right_dock.resize_active_panel(Some(size), cx);
3561                                    });
3562                                }
3563                                DockPosition::Bottom => {
3564                                    let size = workspace.bounds.bottom() - e.event.position.y;
3565                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3566                                        bottom_dock.resize_active_panel(Some(size), cx);
3567                                    });
3568                                }
3569                            }
3570                        }),
3571                    )
3572                    .child(self.modal_layer.clone())
3573                    .child(
3574                        div()
3575                            .flex()
3576                            .flex_row()
3577                            .h_full()
3578                            // Left Dock
3579                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3580                                || {
3581                                    div()
3582                                        .flex()
3583                                        .flex_none()
3584                                        .overflow_hidden()
3585                                        .child(self.left_dock.clone())
3586                                },
3587                            ))
3588                            // Panes
3589                            .child(
3590                                div()
3591                                    .flex()
3592                                    .flex_col()
3593                                    .flex_1()
3594                                    .overflow_hidden()
3595                                    .child(self.center.render(
3596                                        &self.project,
3597                                        &self.follower_states,
3598                                        self.active_call(),
3599                                        &self.active_pane,
3600                                        self.zoomed.as_ref(),
3601                                        &self.app_state,
3602                                        cx,
3603                                    ))
3604                                    .children(
3605                                        self.zoomed_position
3606                                            .ne(&Some(DockPosition::Bottom))
3607                                            .then(|| self.bottom_dock.clone()),
3608                                    ),
3609                            )
3610                            // Right Dock
3611                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3612                                || {
3613                                    div()
3614                                        .flex()
3615                                        .flex_none()
3616                                        .overflow_hidden()
3617                                        .child(self.right_dock.clone())
3618                                },
3619                            )),
3620                    )
3621                    .children(self.render_notifications(cx))
3622                    .children(self.zoomed.as_ref().and_then(|view| {
3623                        let zoomed_view = view.upgrade()?;
3624                        let div = div()
3625                            .z_index(1)
3626                            .absolute()
3627                            .overflow_hidden()
3628                            .border_color(colors.border)
3629                            .bg(colors.background)
3630                            .child(zoomed_view)
3631                            .inset_0()
3632                            .shadow_lg();
3633
3634                        Some(match self.zoomed_position {
3635                            Some(DockPosition::Left) => div.right_2().border_r(),
3636                            Some(DockPosition::Right) => div.left_2().border_l(),
3637                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3638                            None => div.top_2().bottom_2().left_2().right_2().border(),
3639                        })
3640                    })),
3641            )
3642            .child(self.status_bar.clone())
3643            .children(if self.project.read(cx).is_disconnected() {
3644                Some(DisconnectedOverlay)
3645            } else {
3646                None
3647            })
3648    }
3649}
3650
3651impl WorkspaceStore {
3652    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3653        Self {
3654            workspaces: Default::default(),
3655            followers: Default::default(),
3656            _subscriptions: vec![
3657                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3658                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3659                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3660            ],
3661            client,
3662        }
3663    }
3664
3665    pub fn update_followers(
3666        &self,
3667        project_id: Option<u64>,
3668        update: proto::update_followers::Variant,
3669        cx: &AppContext,
3670    ) -> Option<()> {
3671        let active_call = cx.try_global::<Model<ActiveCall>>()?;
3672        let room_id = active_call.read(cx).room()?.read(cx).id();
3673        let follower_ids: Vec<_> = self
3674            .followers
3675            .iter()
3676            .filter_map(|follower| {
3677                if follower.project_id == project_id || project_id.is_none() {
3678                    Some(follower.peer_id.into())
3679                } else {
3680                    None
3681                }
3682            })
3683            .collect();
3684        if follower_ids.is_empty() {
3685            return None;
3686        }
3687        self.client
3688            .send(proto::UpdateFollowers {
3689                room_id,
3690                project_id,
3691                follower_ids,
3692                variant: Some(update),
3693            })
3694            .log_err()
3695    }
3696
3697    pub async fn handle_follow(
3698        this: Model<Self>,
3699        envelope: TypedEnvelope<proto::Follow>,
3700        _: Arc<Client>,
3701        mut cx: AsyncAppContext,
3702    ) -> Result<proto::FollowResponse> {
3703        this.update(&mut cx, |this, cx| {
3704            let follower = Follower {
3705                project_id: envelope.payload.project_id,
3706                peer_id: envelope.original_sender_id()?,
3707            };
3708            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3709
3710            let mut response = proto::FollowResponse::default();
3711            this.workspaces.retain(|workspace| {
3712                workspace
3713                    .update(cx, |workspace, cx| {
3714                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3715                        if response.views.is_empty() {
3716                            response.views = handler_response.views;
3717                        } else {
3718                            response.views.extend_from_slice(&handler_response.views);
3719                        }
3720
3721                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3722                            if response.active_view_id.is_none()
3723                                || Some(workspace.project.downgrade()) == active_project
3724                            {
3725                                response.active_view_id = Some(active_view_id);
3726                            }
3727                        }
3728                    })
3729                    .is_ok()
3730            });
3731
3732            if let Err(ix) = this.followers.binary_search(&follower) {
3733                this.followers.insert(ix, follower);
3734            }
3735
3736            Ok(response)
3737        })?
3738    }
3739
3740    async fn handle_unfollow(
3741        model: Model<Self>,
3742        envelope: TypedEnvelope<proto::Unfollow>,
3743        _: Arc<Client>,
3744        mut cx: AsyncAppContext,
3745    ) -> Result<()> {
3746        model.update(&mut cx, |this, _| {
3747            let follower = Follower {
3748                project_id: envelope.payload.project_id,
3749                peer_id: envelope.original_sender_id()?,
3750            };
3751            if let Ok(ix) = this.followers.binary_search(&follower) {
3752                this.followers.remove(ix);
3753            }
3754            Ok(())
3755        })?
3756    }
3757
3758    async fn handle_update_followers(
3759        this: Model<Self>,
3760        envelope: TypedEnvelope<proto::UpdateFollowers>,
3761        _: Arc<Client>,
3762        mut cx: AsyncAppContext,
3763    ) -> Result<()> {
3764        let leader_id = envelope.original_sender_id()?;
3765        let update = envelope.payload;
3766
3767        this.update(&mut cx, |this, cx| {
3768            this.workspaces.retain(|workspace| {
3769                workspace
3770                    .update(cx, |workspace, cx| {
3771                        let project_id = workspace.project.read(cx).remote_id();
3772                        if update.project_id != project_id && update.project_id.is_some() {
3773                            return;
3774                        }
3775                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3776                    })
3777                    .is_ok()
3778            });
3779            Ok(())
3780        })?
3781    }
3782}
3783
3784impl ViewId {
3785    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3786        Ok(Self {
3787            creator: message
3788                .creator
3789                .ok_or_else(|| anyhow!("creator is missing"))?,
3790            id: message.id,
3791        })
3792    }
3793
3794    pub(crate) fn to_proto(&self) -> proto::ViewId {
3795        proto::ViewId {
3796            creator: Some(self.creator),
3797            id: self.id,
3798        }
3799    }
3800}
3801
3802pub trait WorkspaceHandle {
3803    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3804}
3805
3806impl WorkspaceHandle for View<Workspace> {
3807    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3808        self.read(cx)
3809            .worktrees(cx)
3810            .flat_map(|worktree| {
3811                let worktree_id = worktree.read(cx).id();
3812                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3813                    worktree_id,
3814                    path: f.path.clone(),
3815                })
3816            })
3817            .collect::<Vec<_>>()
3818    }
3819}
3820
3821impl std::fmt::Debug for OpenPaths {
3822    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3823        f.debug_struct("OpenPaths")
3824            .field("paths", &self.paths)
3825            .finish()
3826    }
3827}
3828
3829pub fn activate_workspace_for_project(
3830    cx: &mut AppContext,
3831    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3832) -> Option<WindowHandle<Workspace>> {
3833    for window in cx.windows() {
3834        let Some(workspace) = window.downcast::<Workspace>() else {
3835            continue;
3836        };
3837
3838        let predicate = workspace
3839            .update(cx, |workspace, cx| {
3840                let project = workspace.project.read(cx);
3841                if predicate(project, cx) {
3842                    cx.activate_window();
3843                    true
3844                } else {
3845                    false
3846                }
3847            })
3848            .log_err()
3849            .unwrap_or(false);
3850
3851        if predicate {
3852            return Some(workspace);
3853        }
3854    }
3855
3856    None
3857}
3858
3859pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3860    DB.last_workspace().await.log_err().flatten()
3861}
3862
3863async fn join_channel_internal(
3864    channel_id: u64,
3865    app_state: &Arc<AppState>,
3866    requesting_window: Option<WindowHandle<Workspace>>,
3867    active_call: &Model<ActiveCall>,
3868    cx: &mut AsyncAppContext,
3869) -> Result<bool> {
3870    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3871        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3872            return (false, None);
3873        };
3874
3875        let already_in_channel = room.channel_id() == Some(channel_id);
3876        let should_prompt = room.is_sharing_project()
3877            && room.remote_participants().len() > 0
3878            && !already_in_channel;
3879        let open_room = if already_in_channel {
3880            active_call.room().cloned()
3881        } else {
3882            None
3883        };
3884        (should_prompt, open_room)
3885    })?;
3886
3887    if let Some(room) = open_room {
3888        let task = room.update(cx, |room, cx| {
3889            if let Some((project, host)) = room.most_active_project(cx) {
3890                return Some(join_remote_project(project, host, app_state.clone(), cx));
3891            }
3892
3893            None
3894        })?;
3895        if let Some(task) = task {
3896            task.await?;
3897        }
3898        return anyhow::Ok(true);
3899    }
3900
3901    if should_prompt {
3902        if let Some(workspace) = requesting_window {
3903            let answer  = workspace.update(cx, |_, cx| {
3904                cx.prompt(
3905                    PromptLevel::Warning,
3906                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3907                    &["Yes, Join Channel", "Cancel"],
3908                )
3909            })?.await;
3910
3911            if answer == Ok(1) {
3912                return Ok(false);
3913            }
3914        } else {
3915            return Ok(false); // unreachable!() hopefully
3916        }
3917    }
3918
3919    let client = cx.update(|cx| active_call.read(cx).client())?;
3920
3921    let mut client_status = client.status();
3922
3923    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3924    'outer: loop {
3925        let Some(status) = client_status.recv().await else {
3926            return Err(anyhow!("error connecting"));
3927        };
3928
3929        match status {
3930            Status::Connecting
3931            | Status::Authenticating
3932            | Status::Reconnecting
3933            | Status::Reauthenticating => continue,
3934            Status::Connected { .. } => break 'outer,
3935            Status::SignedOut => return Err(anyhow!("not signed in")),
3936            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3937            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3938                return Err(anyhow!("zed is offline"))
3939            }
3940        }
3941    }
3942
3943    let room = active_call
3944        .update(cx, |active_call, cx| {
3945            active_call.join_channel(channel_id, cx)
3946        })?
3947        .await?;
3948
3949    let Some(room) = room else {
3950        return anyhow::Ok(true);
3951    };
3952
3953    room.update(cx, |room, _| room.room_update_completed())?
3954        .await;
3955
3956    let task = room.update(cx, |room, cx| {
3957        if let Some((project, host)) = room.most_active_project(cx) {
3958            return Some(join_remote_project(project, host, app_state.clone(), cx));
3959        }
3960
3961        None
3962    })?;
3963    if let Some(task) = task {
3964        task.await?;
3965        return anyhow::Ok(true);
3966    }
3967    anyhow::Ok(false)
3968}
3969
3970pub fn join_channel(
3971    channel_id: u64,
3972    app_state: Arc<AppState>,
3973    requesting_window: Option<WindowHandle<Workspace>>,
3974    cx: &mut AppContext,
3975) -> Task<Result<()>> {
3976    let active_call = ActiveCall::global(cx);
3977    cx.spawn(|mut cx| async move {
3978        let result = join_channel_internal(
3979            channel_id,
3980            &app_state,
3981            requesting_window,
3982            &active_call,
3983            &mut cx,
3984        )
3985        .await;
3986
3987        // join channel succeeded, and opened a window
3988        if matches!(result, Ok(true)) {
3989            return anyhow::Ok(());
3990        }
3991
3992        // find an existing workspace to focus and show call controls
3993        let mut active_window =
3994            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
3995        if active_window.is_none() {
3996            // no open workspaces, make one to show the error in (blergh)
3997            let (window_handle, _) = cx
3998                .update(|cx| {
3999                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4000                })?
4001                .await?;
4002
4003            active_window = Some(window_handle);
4004        }
4005
4006        if let Err(err) = result {
4007            log::error!("failed to join channel: {}", err);
4008            if let Some(active_window) = active_window {
4009                active_window
4010                    .update(&mut cx, |_, cx| {
4011                        cx.prompt(
4012                            PromptLevel::Critical,
4013                            &format!("Failed to join channel: {}", err),
4014                            &["Ok"],
4015                        )
4016                    })?
4017                    .await
4018                    .ok();
4019            }
4020        }
4021
4022        // return ok, we showed the error to the user.
4023        return anyhow::Ok(());
4024    })
4025}
4026
4027pub async fn get_any_active_workspace(
4028    app_state: Arc<AppState>,
4029    mut cx: AsyncAppContext,
4030) -> anyhow::Result<WindowHandle<Workspace>> {
4031    // find an existing workspace to focus and show call controls
4032    let active_window = activate_any_workspace_window(&mut cx);
4033    if active_window.is_none() {
4034        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4035            .await?;
4036    }
4037    activate_any_workspace_window(&mut cx).context("could not open zed")
4038}
4039
4040fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4041    cx.update(|cx| {
4042        for window in cx.windows() {
4043            if let Some(workspace_window) = window.downcast::<Workspace>() {
4044                workspace_window
4045                    .update(cx, |_, cx| cx.activate_window())
4046                    .ok();
4047                return Some(workspace_window);
4048            }
4049        }
4050        None
4051    })
4052    .ok()
4053    .flatten()
4054}
4055
4056#[allow(clippy::type_complexity)]
4057pub fn open_paths(
4058    abs_paths: &[PathBuf],
4059    app_state: &Arc<AppState>,
4060    requesting_window: Option<WindowHandle<Workspace>>,
4061    cx: &mut AppContext,
4062) -> Task<
4063    anyhow::Result<(
4064        WindowHandle<Workspace>,
4065        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4066    )>,
4067> {
4068    let app_state = app_state.clone();
4069    let abs_paths = abs_paths.to_vec();
4070    // Open paths in existing workspace if possible
4071    let existing = activate_workspace_for_project(cx, {
4072        let abs_paths = abs_paths.clone();
4073        move |project, cx| project.contains_paths(&abs_paths, cx)
4074    });
4075    cx.spawn(move |mut cx| async move {
4076        if let Some(existing) = existing {
4077            Ok((
4078                existing.clone(),
4079                existing
4080                    .update(&mut cx, |workspace, cx| {
4081                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4082                    })?
4083                    .await,
4084            ))
4085        } else {
4086            cx.update(move |cx| {
4087                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4088            })?
4089            .await
4090        }
4091    })
4092}
4093
4094pub fn open_new(
4095    app_state: &Arc<AppState>,
4096    cx: &mut AppContext,
4097    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4098) -> Task<()> {
4099    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4100    cx.spawn(|mut cx| async move {
4101        if let Some((workspace, opened_paths)) = task.await.log_err() {
4102            workspace
4103                .update(&mut cx, |workspace, cx| {
4104                    if opened_paths.is_empty() {
4105                        init(workspace, cx)
4106                    }
4107                })
4108                .log_err();
4109        }
4110    })
4111}
4112
4113pub fn create_and_open_local_file(
4114    path: &'static Path,
4115    cx: &mut ViewContext<Workspace>,
4116    default_content: impl 'static + Send + FnOnce() -> Rope,
4117) -> Task<Result<Box<dyn ItemHandle>>> {
4118    cx.spawn(|workspace, mut cx| async move {
4119        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4120        if !fs.is_file(path).await {
4121            fs.create_file(path, Default::default()).await?;
4122            fs.save(path, &default_content(), Default::default())
4123                .await?;
4124        }
4125
4126        let mut items = workspace
4127            .update(&mut cx, |workspace, cx| {
4128                workspace.with_local_workspace(cx, |workspace, cx| {
4129                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4130                })
4131            })?
4132            .await?
4133            .await;
4134
4135        let item = items.pop().flatten();
4136        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4137    })
4138}
4139
4140pub fn join_remote_project(
4141    project_id: u64,
4142    follow_user_id: u64,
4143    app_state: Arc<AppState>,
4144    cx: &mut AppContext,
4145) -> Task<Result<()>> {
4146    let windows = cx.windows();
4147    cx.spawn(|mut cx| async move {
4148        let existing_workspace = windows.into_iter().find_map(|window| {
4149            window.downcast::<Workspace>().and_then(|window| {
4150                window
4151                    .update(&mut cx, |workspace, cx| {
4152                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4153                            Some(window)
4154                        } else {
4155                            None
4156                        }
4157                    })
4158                    .unwrap_or(None)
4159            })
4160        });
4161
4162        let workspace = if let Some(existing_workspace) = existing_workspace {
4163            existing_workspace
4164        } else {
4165            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4166            let room = active_call
4167                .read_with(&cx, |call, _| call.room().cloned())?
4168                .ok_or_else(|| anyhow!("not in a call"))?;
4169            let project = room
4170                .update(&mut cx, |room, cx| {
4171                    room.join_project(
4172                        project_id,
4173                        app_state.languages.clone(),
4174                        app_state.fs.clone(),
4175                        cx,
4176                    )
4177                })?
4178                .await?;
4179
4180            let window_bounds_override = window_bounds_env_override(&cx);
4181            cx.update(|cx| {
4182                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4183                cx.open_window(options, |cx| {
4184                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4185                })
4186            })?
4187        };
4188
4189        workspace.update(&mut cx, |workspace, cx| {
4190            cx.activate(true);
4191            cx.activate_window();
4192
4193            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4194                let follow_peer_id = room
4195                    .read(cx)
4196                    .remote_participants()
4197                    .iter()
4198                    .find(|(_, participant)| participant.user.id == follow_user_id)
4199                    .map(|(_, p)| p.peer_id)
4200                    .or_else(|| {
4201                        // If we couldn't follow the given user, follow the host instead.
4202                        let collaborator = workspace
4203                            .project()
4204                            .read(cx)
4205                            .collaborators()
4206                            .values()
4207                            .find(|collaborator| collaborator.replica_id == 0)?;
4208                        Some(collaborator.peer_id)
4209                    });
4210
4211                if let Some(follow_peer_id) = follow_peer_id {
4212                    workspace.follow(follow_peer_id, cx);
4213                }
4214            }
4215        })?;
4216
4217        anyhow::Ok(())
4218    })
4219}
4220
4221pub fn restart(_: &Restart, cx: &mut AppContext) {
4222    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4223    let mut workspace_windows = cx
4224        .windows()
4225        .into_iter()
4226        .filter_map(|window| window.downcast::<Workspace>())
4227        .collect::<Vec<_>>();
4228
4229    // If multiple windows have unsaved changes, and need a save prompt,
4230    // prompt in the active window before switching to a different window.
4231    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4232
4233    let mut prompt = None;
4234    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4235        prompt = window
4236            .update(cx, |_, cx| {
4237                cx.prompt(
4238                    PromptLevel::Info,
4239                    "Are you sure you want to restart?",
4240                    &["Restart", "Cancel"],
4241                )
4242            })
4243            .ok();
4244    }
4245
4246    cx.spawn(|mut cx| async move {
4247        if let Some(prompt) = prompt {
4248            let answer = prompt.await?;
4249            if answer != 0 {
4250                return Ok(());
4251            }
4252        }
4253
4254        // If the user cancels any save prompt, then keep the app open.
4255        for window in workspace_windows {
4256            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4257                workspace.prepare_to_close(true, cx)
4258            }) {
4259                if !should_close.await? {
4260                    return Ok(());
4261                }
4262            }
4263        }
4264
4265        cx.update(|cx| cx.restart())
4266    })
4267    .detach_and_log_err(cx);
4268}
4269
4270fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4271    let mut parts = value.split(',');
4272    let x: usize = parts.next()?.parse().ok()?;
4273    let y: usize = parts.next()?.parse().ok()?;
4274    Some(point((x as f64).into(), (y as f64).into()))
4275}
4276
4277fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4278    let mut parts = value.split(',');
4279    let width: usize = parts.next()?.parse().ok()?;
4280    let height: usize = parts.next()?.parse().ok()?;
4281    Some(size((width as f64).into(), (height as f64).into()))
4282}
4283
4284pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
4285    (1.75 * cx.rem_size()).max(px(32.))
4286}
4287
4288struct DisconnectedOverlay;
4289
4290impl Element for DisconnectedOverlay {
4291    type State = AnyElement;
4292
4293    fn request_layout(
4294        &mut self,
4295        _: Option<Self::State>,
4296        cx: &mut WindowContext,
4297    ) -> (LayoutId, Self::State) {
4298        let mut background = cx.theme().colors().elevated_surface_background;
4299        background.fade_out(0.2);
4300        let mut overlay = div()
4301            .bg(background)
4302            .absolute()
4303            .left_0()
4304            .top(titlebar_height(cx))
4305            .size_full()
4306            .flex()
4307            .items_center()
4308            .justify_center()
4309            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4310            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4311            .child(Label::new(
4312                "Your connection to the remote project has been lost.",
4313            ))
4314            .into_any();
4315        (overlay.request_layout(cx), overlay)
4316    }
4317
4318    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
4319        cx.with_z_index(u8::MAX, |cx| {
4320            cx.add_opaque_layer(bounds);
4321            overlay.paint(cx);
4322        })
4323    }
4324}
4325
4326impl IntoElement for DisconnectedOverlay {
4327    type Element = Self;
4328
4329    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4330        None
4331    }
4332
4333    fn into_element(self) -> Self::Element {
4334        self
4335    }
4336}
4337
4338#[cfg(test)]
4339mod tests {
4340    use std::{cell::RefCell, rc::Rc};
4341
4342    use super::*;
4343    use crate::{
4344        dock::{test::TestPanel, PanelEvent},
4345        item::{
4346            test::{TestItem, TestProjectItem},
4347            ItemEvent,
4348        },
4349    };
4350    use fs::FakeFs;
4351    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4352    use project::{Project, ProjectEntryId};
4353    use serde_json::json;
4354    use settings::SettingsStore;
4355
4356    #[gpui::test]
4357    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4358        init_test(cx);
4359
4360        let fs = FakeFs::new(cx.executor());
4361        let project = Project::test(fs, [], cx).await;
4362        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4363
4364        // Adding an item with no ambiguity renders the tab without detail.
4365        let item1 = cx.new_view(|cx| {
4366            let mut item = TestItem::new(cx);
4367            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4368            item
4369        });
4370        workspace.update(cx, |workspace, cx| {
4371            workspace.add_item(Box::new(item1.clone()), cx);
4372        });
4373        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4374
4375        // Adding an item that creates ambiguity increases the level of detail on
4376        // both tabs.
4377        let item2 = cx.new_view(|cx| {
4378            let mut item = TestItem::new(cx);
4379            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4380            item
4381        });
4382        workspace.update(cx, |workspace, cx| {
4383            workspace.add_item(Box::new(item2.clone()), cx);
4384        });
4385        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4386        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4387
4388        // Adding an item that creates ambiguity increases the level of detail only
4389        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4390        // we stop at the highest detail available.
4391        let item3 = cx.new_view(|cx| {
4392            let mut item = TestItem::new(cx);
4393            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4394            item
4395        });
4396        workspace.update(cx, |workspace, cx| {
4397            workspace.add_item(Box::new(item3.clone()), cx);
4398        });
4399        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4400        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4401        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4402    }
4403
4404    #[gpui::test]
4405    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4406        init_test(cx);
4407
4408        let fs = FakeFs::new(cx.executor());
4409        fs.insert_tree(
4410            "/root1",
4411            json!({
4412                "one.txt": "",
4413                "two.txt": "",
4414            }),
4415        )
4416        .await;
4417        fs.insert_tree(
4418            "/root2",
4419            json!({
4420                "three.txt": "",
4421            }),
4422        )
4423        .await;
4424
4425        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4426        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4427        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4428        let worktree_id = project.update(cx, |project, cx| {
4429            project.worktrees().next().unwrap().read(cx).id()
4430        });
4431
4432        let item1 = cx.new_view(|cx| {
4433            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4434        });
4435        let item2 = cx.new_view(|cx| {
4436            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4437        });
4438
4439        // Add an item to an empty pane
4440        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4441        project.update(cx, |project, cx| {
4442            assert_eq!(
4443                project.active_entry(),
4444                project
4445                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4446                    .map(|e| e.id)
4447            );
4448        });
4449        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4450
4451        // Add a second item to a non-empty pane
4452        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4453        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4454        project.update(cx, |project, cx| {
4455            assert_eq!(
4456                project.active_entry(),
4457                project
4458                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4459                    .map(|e| e.id)
4460            );
4461        });
4462
4463        // Close the active item
4464        pane.update(cx, |pane, cx| {
4465            pane.close_active_item(&Default::default(), cx).unwrap()
4466        })
4467        .await
4468        .unwrap();
4469        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4470        project.update(cx, |project, cx| {
4471            assert_eq!(
4472                project.active_entry(),
4473                project
4474                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4475                    .map(|e| e.id)
4476            );
4477        });
4478
4479        // Add a project folder
4480        project
4481            .update(cx, |project, cx| {
4482                project.find_or_create_local_worktree("/root2", true, cx)
4483            })
4484            .await
4485            .unwrap();
4486        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4487
4488        // Remove a project folder
4489        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4490        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4491    }
4492
4493    #[gpui::test]
4494    async fn test_close_window(cx: &mut TestAppContext) {
4495        init_test(cx);
4496
4497        let fs = FakeFs::new(cx.executor());
4498        fs.insert_tree("/root", json!({ "one": "" })).await;
4499
4500        let project = Project::test(fs, ["root".as_ref()], cx).await;
4501        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4502
4503        // When there are no dirty items, there's nothing to do.
4504        let item1 = cx.new_view(|cx| TestItem::new(cx));
4505        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4506        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4507        assert!(task.await.unwrap());
4508
4509        // When there are dirty untitled items, prompt to save each one. If the user
4510        // cancels any prompt, then abort.
4511        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4512        let item3 = cx.new_view(|cx| {
4513            TestItem::new(cx)
4514                .with_dirty(true)
4515                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4516        });
4517        workspace.update(cx, |w, cx| {
4518            w.add_item(Box::new(item2.clone()), cx);
4519            w.add_item(Box::new(item3.clone()), cx);
4520        });
4521        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4522        cx.executor().run_until_parked();
4523        cx.simulate_prompt_answer(2); // cancel save all
4524        cx.executor().run_until_parked();
4525        cx.simulate_prompt_answer(2); // cancel save all
4526        cx.executor().run_until_parked();
4527        assert!(!cx.has_pending_prompt());
4528        assert!(!task.await.unwrap());
4529    }
4530
4531    #[gpui::test]
4532    async fn test_close_pane_items(cx: &mut TestAppContext) {
4533        init_test(cx);
4534
4535        let fs = FakeFs::new(cx.executor());
4536
4537        let project = Project::test(fs, None, cx).await;
4538        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4539
4540        let item1 = cx.new_view(|cx| {
4541            TestItem::new(cx)
4542                .with_dirty(true)
4543                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4544        });
4545        let item2 = cx.new_view(|cx| {
4546            TestItem::new(cx)
4547                .with_dirty(true)
4548                .with_conflict(true)
4549                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4550        });
4551        let item3 = cx.new_view(|cx| {
4552            TestItem::new(cx)
4553                .with_dirty(true)
4554                .with_conflict(true)
4555                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4556        });
4557        let item4 = cx.new_view(|cx| {
4558            TestItem::new(cx)
4559                .with_dirty(true)
4560                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4561        });
4562        let pane = workspace.update(cx, |workspace, cx| {
4563            workspace.add_item(Box::new(item1.clone()), cx);
4564            workspace.add_item(Box::new(item2.clone()), cx);
4565            workspace.add_item(Box::new(item3.clone()), cx);
4566            workspace.add_item(Box::new(item4.clone()), cx);
4567            workspace.active_pane().clone()
4568        });
4569
4570        let close_items = pane.update(cx, |pane, cx| {
4571            pane.activate_item(1, true, true, cx);
4572            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4573            let item1_id = item1.item_id();
4574            let item3_id = item3.item_id();
4575            let item4_id = item4.item_id();
4576            pane.close_items(cx, SaveIntent::Close, move |id| {
4577                [item1_id, item3_id, item4_id].contains(&id)
4578            })
4579        });
4580        cx.executor().run_until_parked();
4581
4582        assert!(cx.has_pending_prompt());
4583        // Ignore "Save all" prompt
4584        cx.simulate_prompt_answer(2);
4585        cx.executor().run_until_parked();
4586        // There's a prompt to save item 1.
4587        pane.update(cx, |pane, _| {
4588            assert_eq!(pane.items_len(), 4);
4589            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4590        });
4591        // Confirm saving item 1.
4592        cx.simulate_prompt_answer(0);
4593        cx.executor().run_until_parked();
4594
4595        // Item 1 is saved. There's a prompt to save item 3.
4596        pane.update(cx, |pane, cx| {
4597            assert_eq!(item1.read(cx).save_count, 1);
4598            assert_eq!(item1.read(cx).save_as_count, 0);
4599            assert_eq!(item1.read(cx).reload_count, 0);
4600            assert_eq!(pane.items_len(), 3);
4601            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4602        });
4603        assert!(cx.has_pending_prompt());
4604
4605        // Cancel saving item 3.
4606        cx.simulate_prompt_answer(1);
4607        cx.executor().run_until_parked();
4608
4609        // Item 3 is reloaded. There's a prompt to save item 4.
4610        pane.update(cx, |pane, cx| {
4611            assert_eq!(item3.read(cx).save_count, 0);
4612            assert_eq!(item3.read(cx).save_as_count, 0);
4613            assert_eq!(item3.read(cx).reload_count, 1);
4614            assert_eq!(pane.items_len(), 2);
4615            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4616        });
4617        assert!(cx.has_pending_prompt());
4618
4619        // Confirm saving item 4.
4620        cx.simulate_prompt_answer(0);
4621        cx.executor().run_until_parked();
4622
4623        // There's a prompt for a path for item 4.
4624        cx.simulate_new_path_selection(|_| Some(Default::default()));
4625        close_items.await.unwrap();
4626
4627        // The requested items are closed.
4628        pane.update(cx, |pane, cx| {
4629            assert_eq!(item4.read(cx).save_count, 0);
4630            assert_eq!(item4.read(cx).save_as_count, 1);
4631            assert_eq!(item4.read(cx).reload_count, 0);
4632            assert_eq!(pane.items_len(), 1);
4633            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4634        });
4635    }
4636
4637    #[gpui::test]
4638    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4639        init_test(cx);
4640
4641        let fs = FakeFs::new(cx.executor());
4642        let project = Project::test(fs, [], cx).await;
4643        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4644
4645        // Create several workspace items with single project entries, and two
4646        // workspace items with multiple project entries.
4647        let single_entry_items = (0..=4)
4648            .map(|project_entry_id| {
4649                cx.new_view(|cx| {
4650                    TestItem::new(cx)
4651                        .with_dirty(true)
4652                        .with_project_items(&[TestProjectItem::new(
4653                            project_entry_id,
4654                            &format!("{project_entry_id}.txt"),
4655                            cx,
4656                        )])
4657                })
4658            })
4659            .collect::<Vec<_>>();
4660        let item_2_3 = cx.new_view(|cx| {
4661            TestItem::new(cx)
4662                .with_dirty(true)
4663                .with_singleton(false)
4664                .with_project_items(&[
4665                    single_entry_items[2].read(cx).project_items[0].clone(),
4666                    single_entry_items[3].read(cx).project_items[0].clone(),
4667                ])
4668        });
4669        let item_3_4 = cx.new_view(|cx| {
4670            TestItem::new(cx)
4671                .with_dirty(true)
4672                .with_singleton(false)
4673                .with_project_items(&[
4674                    single_entry_items[3].read(cx).project_items[0].clone(),
4675                    single_entry_items[4].read(cx).project_items[0].clone(),
4676                ])
4677        });
4678
4679        // Create two panes that contain the following project entries:
4680        //   left pane:
4681        //     multi-entry items:   (2, 3)
4682        //     single-entry items:  0, 1, 2, 3, 4
4683        //   right pane:
4684        //     single-entry items:  1
4685        //     multi-entry items:   (3, 4)
4686        let left_pane = workspace.update(cx, |workspace, cx| {
4687            let left_pane = workspace.active_pane().clone();
4688            workspace.add_item(Box::new(item_2_3.clone()), cx);
4689            for item in single_entry_items {
4690                workspace.add_item(Box::new(item), cx);
4691            }
4692            left_pane.update(cx, |pane, cx| {
4693                pane.activate_item(2, true, true, cx);
4694            });
4695
4696            let right_pane = workspace
4697                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4698                .unwrap();
4699
4700            right_pane.update(cx, |pane, cx| {
4701                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4702            });
4703
4704            left_pane
4705        });
4706
4707        cx.focus_view(&left_pane);
4708
4709        // When closing all of the items in the left pane, we should be prompted twice:
4710        // once for project entry 0, and once for project entry 2. Project entries 1,
4711        // 3, and 4 are all still open in the other paten. After those two
4712        // prompts, the task should complete.
4713
4714        let close = left_pane.update(cx, |pane, cx| {
4715            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4716        });
4717        cx.executor().run_until_parked();
4718
4719        // Discard "Save all" prompt
4720        cx.simulate_prompt_answer(2);
4721
4722        cx.executor().run_until_parked();
4723        left_pane.update(cx, |pane, cx| {
4724            assert_eq!(
4725                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4726                &[ProjectEntryId::from_proto(0)]
4727            );
4728        });
4729        cx.simulate_prompt_answer(0);
4730
4731        cx.executor().run_until_parked();
4732        left_pane.update(cx, |pane, cx| {
4733            assert_eq!(
4734                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4735                &[ProjectEntryId::from_proto(2)]
4736            );
4737        });
4738        cx.simulate_prompt_answer(0);
4739
4740        cx.executor().run_until_parked();
4741        close.await.unwrap();
4742        left_pane.update(cx, |pane, _| {
4743            assert_eq!(pane.items_len(), 0);
4744        });
4745    }
4746
4747    #[gpui::test]
4748    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4749        init_test(cx);
4750
4751        let fs = FakeFs::new(cx.executor());
4752        let project = Project::test(fs, [], cx).await;
4753        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4754        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4755
4756        let item = cx.new_view(|cx| {
4757            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4758        });
4759        let item_id = item.entity_id();
4760        workspace.update(cx, |workspace, cx| {
4761            workspace.add_item(Box::new(item.clone()), cx);
4762        });
4763
4764        // Autosave on window change.
4765        item.update(cx, |item, cx| {
4766            cx.update_global(|settings: &mut SettingsStore, cx| {
4767                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4768                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4769                })
4770            });
4771            item.is_dirty = true;
4772        });
4773
4774        // Deactivating the window saves the file.
4775        cx.deactivate_window();
4776        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4777
4778        // Autosave on focus change.
4779        item.update(cx, |item, cx| {
4780            cx.focus_self();
4781            cx.update_global(|settings: &mut SettingsStore, cx| {
4782                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4783                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4784                })
4785            });
4786            item.is_dirty = true;
4787        });
4788
4789        // Blurring the item saves the file.
4790        item.update(cx, |_, cx| cx.blur());
4791        cx.executor().run_until_parked();
4792        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4793
4794        // Deactivating the window still saves the file.
4795        cx.update(|cx| cx.activate_window());
4796        item.update(cx, |item, cx| {
4797            cx.focus_self();
4798            item.is_dirty = true;
4799        });
4800        cx.deactivate_window();
4801
4802        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4803
4804        // Autosave after delay.
4805        item.update(cx, |item, cx| {
4806            cx.update_global(|settings: &mut SettingsStore, cx| {
4807                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4808                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4809                })
4810            });
4811            item.is_dirty = true;
4812            cx.emit(ItemEvent::Edit);
4813        });
4814
4815        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4816        cx.executor().advance_clock(Duration::from_millis(250));
4817        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4818
4819        // After delay expires, the file is saved.
4820        cx.executor().advance_clock(Duration::from_millis(250));
4821        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4822
4823        // Autosave on focus change, ensuring closing the tab counts as such.
4824        item.update(cx, |item, cx| {
4825            cx.update_global(|settings: &mut SettingsStore, cx| {
4826                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4827                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4828                })
4829            });
4830            item.is_dirty = true;
4831        });
4832
4833        pane.update(cx, |pane, cx| {
4834            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4835        })
4836        .await
4837        .unwrap();
4838        assert!(!cx.has_pending_prompt());
4839        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4840
4841        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4842        workspace.update(cx, |workspace, cx| {
4843            workspace.add_item(Box::new(item.clone()), cx);
4844        });
4845        item.update(cx, |item, cx| {
4846            item.project_items[0].update(cx, |item, _| {
4847                item.entry_id = None;
4848            });
4849            item.is_dirty = true;
4850            cx.blur();
4851        });
4852        cx.run_until_parked();
4853        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4854
4855        // Ensure autosave is prevented for deleted files also when closing the buffer.
4856        let _close_items = pane.update(cx, |pane, cx| {
4857            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4858        });
4859        cx.run_until_parked();
4860        assert!(cx.has_pending_prompt());
4861        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4862    }
4863
4864    #[gpui::test]
4865    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4866        init_test(cx);
4867
4868        let fs = FakeFs::new(cx.executor());
4869
4870        let project = Project::test(fs, [], cx).await;
4871        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4872
4873        let item = cx.new_view(|cx| {
4874            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4875        });
4876        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4877        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4878        let toolbar_notify_count = Rc::new(RefCell::new(0));
4879
4880        workspace.update(cx, |workspace, cx| {
4881            workspace.add_item(Box::new(item.clone()), cx);
4882            let toolbar_notification_count = toolbar_notify_count.clone();
4883            cx.observe(&toolbar, move |_, _, _| {
4884                *toolbar_notification_count.borrow_mut() += 1
4885            })
4886            .detach();
4887        });
4888
4889        pane.update(cx, |pane, _| {
4890            assert!(!pane.can_navigate_backward());
4891            assert!(!pane.can_navigate_forward());
4892        });
4893
4894        item.update(cx, |item, cx| {
4895            item.set_state("one".to_string(), cx);
4896        });
4897
4898        // Toolbar must be notified to re-render the navigation buttons
4899        assert_eq!(*toolbar_notify_count.borrow(), 1);
4900
4901        pane.update(cx, |pane, _| {
4902            assert!(pane.can_navigate_backward());
4903            assert!(!pane.can_navigate_forward());
4904        });
4905
4906        workspace
4907            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4908            .await
4909            .unwrap();
4910
4911        assert_eq!(*toolbar_notify_count.borrow(), 2);
4912        pane.update(cx, |pane, _| {
4913            assert!(!pane.can_navigate_backward());
4914            assert!(pane.can_navigate_forward());
4915        });
4916    }
4917
4918    #[gpui::test]
4919    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4920        init_test(cx);
4921        let fs = FakeFs::new(cx.executor());
4922
4923        let project = Project::test(fs, [], cx).await;
4924        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4925
4926        let panel = workspace.update(cx, |workspace, cx| {
4927            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4928            workspace.add_panel(panel.clone(), cx);
4929
4930            workspace
4931                .right_dock()
4932                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4933
4934            panel
4935        });
4936
4937        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4938        pane.update(cx, |pane, cx| {
4939            let item = cx.new_view(|cx| TestItem::new(cx));
4940            pane.add_item(Box::new(item), true, true, None, cx);
4941        });
4942
4943        // Transfer focus from center to panel
4944        workspace.update(cx, |workspace, cx| {
4945            workspace.toggle_panel_focus::<TestPanel>(cx);
4946        });
4947
4948        workspace.update(cx, |workspace, cx| {
4949            assert!(workspace.right_dock().read(cx).is_open());
4950            assert!(!panel.is_zoomed(cx));
4951            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4952        });
4953
4954        // Transfer focus from panel to center
4955        workspace.update(cx, |workspace, cx| {
4956            workspace.toggle_panel_focus::<TestPanel>(cx);
4957        });
4958
4959        workspace.update(cx, |workspace, cx| {
4960            assert!(workspace.right_dock().read(cx).is_open());
4961            assert!(!panel.is_zoomed(cx));
4962            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4963        });
4964
4965        // Close the dock
4966        workspace.update(cx, |workspace, cx| {
4967            workspace.toggle_dock(DockPosition::Right, cx);
4968        });
4969
4970        workspace.update(cx, |workspace, cx| {
4971            assert!(!workspace.right_dock().read(cx).is_open());
4972            assert!(!panel.is_zoomed(cx));
4973            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4974        });
4975
4976        // Open the dock
4977        workspace.update(cx, |workspace, cx| {
4978            workspace.toggle_dock(DockPosition::Right, cx);
4979        });
4980
4981        workspace.update(cx, |workspace, cx| {
4982            assert!(workspace.right_dock().read(cx).is_open());
4983            assert!(!panel.is_zoomed(cx));
4984            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4985        });
4986
4987        // Focus and zoom panel
4988        panel.update(cx, |panel, cx| {
4989            cx.focus_self();
4990            panel.set_zoomed(true, cx)
4991        });
4992
4993        workspace.update(cx, |workspace, cx| {
4994            assert!(workspace.right_dock().read(cx).is_open());
4995            assert!(panel.is_zoomed(cx));
4996            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4997        });
4998
4999        // Transfer focus to the center closes the dock
5000        workspace.update(cx, |workspace, cx| {
5001            workspace.toggle_panel_focus::<TestPanel>(cx);
5002        });
5003
5004        workspace.update(cx, |workspace, cx| {
5005            assert!(!workspace.right_dock().read(cx).is_open());
5006            assert!(panel.is_zoomed(cx));
5007            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5008        });
5009
5010        // Transferring focus back to the panel keeps it zoomed
5011        workspace.update(cx, |workspace, cx| {
5012            workspace.toggle_panel_focus::<TestPanel>(cx);
5013        });
5014
5015        workspace.update(cx, |workspace, cx| {
5016            assert!(workspace.right_dock().read(cx).is_open());
5017            assert!(panel.is_zoomed(cx));
5018            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5019        });
5020
5021        // Close the dock while it is zoomed
5022        workspace.update(cx, |workspace, cx| {
5023            workspace.toggle_dock(DockPosition::Right, cx)
5024        });
5025
5026        workspace.update(cx, |workspace, cx| {
5027            assert!(!workspace.right_dock().read(cx).is_open());
5028            assert!(panel.is_zoomed(cx));
5029            assert!(workspace.zoomed.is_none());
5030            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5031        });
5032
5033        // Opening the dock, when it's zoomed, retains focus
5034        workspace.update(cx, |workspace, cx| {
5035            workspace.toggle_dock(DockPosition::Right, cx)
5036        });
5037
5038        workspace.update(cx, |workspace, cx| {
5039            assert!(workspace.right_dock().read(cx).is_open());
5040            assert!(panel.is_zoomed(cx));
5041            assert!(workspace.zoomed.is_some());
5042            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5043        });
5044
5045        // Unzoom and close the panel, zoom the active pane.
5046        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5047        workspace.update(cx, |workspace, cx| {
5048            workspace.toggle_dock(DockPosition::Right, cx)
5049        });
5050        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5051
5052        // Opening a dock unzooms the pane.
5053        workspace.update(cx, |workspace, cx| {
5054            workspace.toggle_dock(DockPosition::Right, cx)
5055        });
5056        workspace.update(cx, |workspace, cx| {
5057            let pane = pane.read(cx);
5058            assert!(!pane.is_zoomed());
5059            assert!(!pane.focus_handle(cx).is_focused(cx));
5060            assert!(workspace.right_dock().read(cx).is_open());
5061            assert!(workspace.zoomed.is_none());
5062        });
5063    }
5064
5065    struct TestModal(FocusHandle);
5066
5067    impl TestModal {
5068        fn new(cx: &mut ViewContext<Self>) -> Self {
5069            Self(cx.focus_handle())
5070        }
5071    }
5072
5073    impl EventEmitter<DismissEvent> for TestModal {}
5074
5075    impl FocusableView for TestModal {
5076        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5077            self.0.clone()
5078        }
5079    }
5080
5081    impl ModalView for TestModal {}
5082
5083    impl Render for TestModal {
5084        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5085            div().track_focus(&self.0)
5086        }
5087    }
5088
5089    #[gpui::test]
5090    async fn test_panels(cx: &mut gpui::TestAppContext) {
5091        init_test(cx);
5092        let fs = FakeFs::new(cx.executor());
5093
5094        let project = Project::test(fs, [], cx).await;
5095        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5096
5097        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5098            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5099            workspace.add_panel(panel_1.clone(), cx);
5100            workspace
5101                .left_dock()
5102                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5103            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5104            workspace.add_panel(panel_2.clone(), cx);
5105            workspace
5106                .right_dock()
5107                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5108
5109            let left_dock = workspace.left_dock();
5110            assert_eq!(
5111                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5112                panel_1.panel_id()
5113            );
5114            assert_eq!(
5115                left_dock.read(cx).active_panel_size(cx).unwrap(),
5116                panel_1.size(cx)
5117            );
5118
5119            left_dock.update(cx, |left_dock, cx| {
5120                left_dock.resize_active_panel(Some(px(1337.)), cx)
5121            });
5122            assert_eq!(
5123                workspace
5124                    .right_dock()
5125                    .read(cx)
5126                    .visible_panel()
5127                    .unwrap()
5128                    .panel_id(),
5129                panel_2.panel_id(),
5130            );
5131
5132            (panel_1, panel_2)
5133        });
5134
5135        // Move panel_1 to the right
5136        panel_1.update(cx, |panel_1, cx| {
5137            panel_1.set_position(DockPosition::Right, cx)
5138        });
5139
5140        workspace.update(cx, |workspace, cx| {
5141            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5142            // Since it was the only panel on the left, the left dock should now be closed.
5143            assert!(!workspace.left_dock().read(cx).is_open());
5144            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5145            let right_dock = workspace.right_dock();
5146            assert_eq!(
5147                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5148                panel_1.panel_id()
5149            );
5150            assert_eq!(
5151                right_dock.read(cx).active_panel_size(cx).unwrap(),
5152                px(1337.)
5153            );
5154
5155            // Now we move panel_2 to the left
5156            panel_2.set_position(DockPosition::Left, cx);
5157        });
5158
5159        workspace.update(cx, |workspace, cx| {
5160            // Since panel_2 was not visible on the right, we don't open the left dock.
5161            assert!(!workspace.left_dock().read(cx).is_open());
5162            // And the right dock is unaffected in it's displaying of panel_1
5163            assert!(workspace.right_dock().read(cx).is_open());
5164            assert_eq!(
5165                workspace
5166                    .right_dock()
5167                    .read(cx)
5168                    .visible_panel()
5169                    .unwrap()
5170                    .panel_id(),
5171                panel_1.panel_id(),
5172            );
5173        });
5174
5175        // Move panel_1 back to the left
5176        panel_1.update(cx, |panel_1, cx| {
5177            panel_1.set_position(DockPosition::Left, cx)
5178        });
5179
5180        workspace.update(cx, |workspace, cx| {
5181            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5182            let left_dock = workspace.left_dock();
5183            assert!(left_dock.read(cx).is_open());
5184            assert_eq!(
5185                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5186                panel_1.panel_id()
5187            );
5188            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5189            // And the right dock should be closed as it no longer has any panels.
5190            assert!(!workspace.right_dock().read(cx).is_open());
5191
5192            // Now we move panel_1 to the bottom
5193            panel_1.set_position(DockPosition::Bottom, cx);
5194        });
5195
5196        workspace.update(cx, |workspace, cx| {
5197            // Since panel_1 was visible on the left, we close the left dock.
5198            assert!(!workspace.left_dock().read(cx).is_open());
5199            // The bottom dock is sized based on the panel's default size,
5200            // since the panel orientation changed from vertical to horizontal.
5201            let bottom_dock = workspace.bottom_dock();
5202            assert_eq!(
5203                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5204                panel_1.size(cx),
5205            );
5206            // Close bottom dock and move panel_1 back to the left.
5207            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5208            panel_1.set_position(DockPosition::Left, cx);
5209        });
5210
5211        // Emit activated event on panel 1
5212        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5213
5214        // Now the left dock is open and panel_1 is active and focused.
5215        workspace.update(cx, |workspace, cx| {
5216            let left_dock = workspace.left_dock();
5217            assert!(left_dock.read(cx).is_open());
5218            assert_eq!(
5219                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5220                panel_1.panel_id(),
5221            );
5222            assert!(panel_1.focus_handle(cx).is_focused(cx));
5223        });
5224
5225        // Emit closed event on panel 2, which is not active
5226        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5227
5228        // Wo don't close the left dock, because panel_2 wasn't the active panel
5229        workspace.update(cx, |workspace, cx| {
5230            let left_dock = workspace.left_dock();
5231            assert!(left_dock.read(cx).is_open());
5232            assert_eq!(
5233                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5234                panel_1.panel_id(),
5235            );
5236        });
5237
5238        // Emitting a ZoomIn event shows the panel as zoomed.
5239        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5240        workspace.update(cx, |workspace, _| {
5241            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5242            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5243        });
5244
5245        // Move panel to another dock while it is zoomed
5246        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5247        workspace.update(cx, |workspace, _| {
5248            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5249
5250            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5251        });
5252
5253        // This is a helper for getting a:
5254        // - valid focus on an element,
5255        // - that isn't a part of the panes and panels system of the Workspace,
5256        // - and doesn't trigger the 'on_focus_lost' API.
5257        let focus_other_view = {
5258            let workspace = workspace.clone();
5259            move |cx: &mut VisualTestContext| {
5260                workspace.update(cx, |workspace, cx| {
5261                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5262                        workspace.toggle_modal(cx, TestModal::new);
5263                        workspace.toggle_modal(cx, TestModal::new);
5264                    } else {
5265                        workspace.toggle_modal(cx, TestModal::new);
5266                    }
5267                })
5268            }
5269        };
5270
5271        // If focus is transferred to another view that's not a panel or another pane, we still show
5272        // the panel as zoomed.
5273        focus_other_view(cx);
5274        workspace.update(cx, |workspace, _| {
5275            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5276            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5277        });
5278
5279        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5280        workspace.update(cx, |_, cx| cx.focus_self());
5281        workspace.update(cx, |workspace, _| {
5282            assert_eq!(workspace.zoomed, None);
5283            assert_eq!(workspace.zoomed_position, None);
5284        });
5285
5286        // If focus is transferred again to another view that's not a panel or a pane, we won't
5287        // show the panel as zoomed because it wasn't zoomed before.
5288        focus_other_view(cx);
5289        workspace.update(cx, |workspace, _| {
5290            assert_eq!(workspace.zoomed, None);
5291            assert_eq!(workspace.zoomed_position, None);
5292        });
5293
5294        // When the panel is activated, it is zoomed again.
5295        cx.dispatch_action(ToggleRightDock);
5296        workspace.update(cx, |workspace, _| {
5297            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5298            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5299        });
5300
5301        // Emitting a ZoomOut event unzooms the panel.
5302        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5303        workspace.update(cx, |workspace, _| {
5304            assert_eq!(workspace.zoomed, None);
5305            assert_eq!(workspace.zoomed_position, None);
5306        });
5307
5308        // Emit closed event on panel 1, which is active
5309        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5310
5311        // Now the left dock is closed, because panel_1 was the active panel
5312        workspace.update(cx, |workspace, cx| {
5313            let right_dock = workspace.right_dock();
5314            assert!(!right_dock.read(cx).is_open());
5315        });
5316    }
5317
5318    pub fn init_test(cx: &mut TestAppContext) {
5319        cx.update(|cx| {
5320            let settings_store = SettingsStore::test(cx);
5321            cx.set_global(settings_store);
5322            theme::init(theme::LoadThemes::JustBase, cx);
5323            language::init(cx);
5324            crate::init_settings(cx);
5325            Project::init_settings(cx);
5326        });
5327    }
5328}