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