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