workspace.rs

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