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
2612                            // If the item belongs to a particular project, then it should
2613                            // only be included if this project is shared, and the follower
2614                            // is in thie project.
2615                            //
2616                            // Some items, like channel notes, do not belong to a particular
2617                            // project, so they should be included regardless of whether the
2618                            // current project is shared, or what project the follower is in.
2619                            if item.is_project_item(cx)
2620                                && (project_id.is_none() || project_id != follower_project_id)
2621                            {
2622                                return None;
2623                            }
2624
2625                            let id = item.remote_id(client, cx)?.to_proto();
2626                            let variant = item.to_state_proto(cx)?;
2627                            Some(proto::View {
2628                                id: Some(id),
2629                                leader_id,
2630                                variant: Some(variant),
2631                            })
2632                        }
2633                    })
2634                })
2635                .collect(),
2636        }
2637    }
2638
2639    fn handle_update_followers(
2640        &mut self,
2641        leader_id: PeerId,
2642        message: proto::UpdateFollowers,
2643        _cx: &mut ViewContext<Self>,
2644    ) {
2645        self.leader_updates_tx
2646            .unbounded_send((leader_id, message))
2647            .ok();
2648    }
2649
2650    async fn process_leader_update(
2651        this: &WeakView<Self>,
2652        leader_id: PeerId,
2653        update: proto::UpdateFollowers,
2654        cx: &mut AsyncWindowContext,
2655    ) -> Result<()> {
2656        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2657            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2658                this.update(cx, |this, _| {
2659                    for (_, state) in &mut this.follower_states {
2660                        if state.leader_id == leader_id {
2661                            state.active_view_id =
2662                                if let Some(active_view_id) = update_active_view.id.clone() {
2663                                    Some(ViewId::from_proto(active_view_id)?)
2664                                } else {
2665                                    None
2666                                };
2667                        }
2668                    }
2669                    anyhow::Ok(())
2670                })??;
2671            }
2672            proto::update_followers::Variant::UpdateView(update_view) => {
2673                let variant = update_view
2674                    .variant
2675                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2676                let id = update_view
2677                    .id
2678                    .ok_or_else(|| anyhow!("missing update view id"))?;
2679                let mut tasks = Vec::new();
2680                this.update(cx, |this, cx| {
2681                    let project = this.project.clone();
2682                    for (_, state) in &mut this.follower_states {
2683                        if state.leader_id == leader_id {
2684                            let view_id = ViewId::from_proto(id.clone())?;
2685                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2686                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2687                            }
2688                        }
2689                    }
2690                    anyhow::Ok(())
2691                })??;
2692                try_join_all(tasks).await.log_err();
2693            }
2694            proto::update_followers::Variant::CreateView(view) => {
2695                let panes = this.update(cx, |this, _| {
2696                    this.follower_states
2697                        .iter()
2698                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2699                        .cloned()
2700                        .collect()
2701                })?;
2702                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2703            }
2704        }
2705        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2706        Ok(())
2707    }
2708
2709    async fn add_views_from_leader(
2710        this: WeakView<Self>,
2711        leader_id: PeerId,
2712        panes: Vec<View<Pane>>,
2713        views: Vec<proto::View>,
2714        cx: &mut AsyncWindowContext,
2715    ) -> Result<()> {
2716        let this = this.upgrade().context("workspace dropped")?;
2717
2718        let item_builders = cx.update(|_, cx| {
2719            cx.default_global::<FollowableItemBuilders>()
2720                .values()
2721                .map(|b| b.0)
2722                .collect::<Vec<_>>()
2723        })?;
2724
2725        let mut item_tasks_by_pane = HashMap::default();
2726        for pane in panes {
2727            let mut item_tasks = Vec::new();
2728            let mut leader_view_ids = Vec::new();
2729            for view in &views {
2730                let Some(id) = &view.id else { continue };
2731                let id = ViewId::from_proto(id.clone())?;
2732                let mut variant = view.variant.clone();
2733                if variant.is_none() {
2734                    Err(anyhow!("missing view variant"))?;
2735                }
2736                for build_item in &item_builders {
2737                    let task = cx.update(|_, cx| {
2738                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2739                    })?;
2740                    if let Some(task) = task {
2741                        item_tasks.push(task);
2742                        leader_view_ids.push(id);
2743                        break;
2744                    } else {
2745                        assert!(variant.is_some());
2746                    }
2747                }
2748            }
2749
2750            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2751        }
2752
2753        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2754            let items = futures::future::try_join_all(item_tasks).await?;
2755            this.update(cx, |this, cx| {
2756                let state = this.follower_states.get_mut(&pane)?;
2757                for (id, item) in leader_view_ids.into_iter().zip(items) {
2758                    item.set_leader_peer_id(Some(leader_id), cx);
2759                    state.items_by_leader_view_id.insert(id, item);
2760                }
2761
2762                Some(())
2763            })?;
2764        }
2765        Ok(())
2766    }
2767
2768    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2769        let mut is_project_item = true;
2770        let mut update = proto::UpdateActiveView::default();
2771
2772        if let Some(item) = self.active_item(cx) {
2773            if item.focus_handle(cx).contains_focused(cx) {
2774                if let Some(item) = item.to_followable_item_handle(cx) {
2775                    is_project_item = item.is_project_item(cx);
2776                    update = proto::UpdateActiveView {
2777                        id: item
2778                            .remote_id(&self.app_state.client, cx)
2779                            .map(|id| id.to_proto()),
2780                        leader_id: self.leader_for_pane(&self.active_pane),
2781                    };
2782                }
2783            }
2784        }
2785
2786        if update.id != self.last_active_view_id {
2787            self.last_active_view_id = update.id.clone();
2788            self.update_followers(
2789                is_project_item,
2790                proto::update_followers::Variant::UpdateActiveView(update),
2791                cx,
2792            );
2793        }
2794    }
2795
2796    fn update_followers(
2797        &self,
2798        project_only: bool,
2799        update: proto::update_followers::Variant,
2800        cx: &mut WindowContext,
2801    ) -> Option<()> {
2802        // If this update only applies to for followers in the current project,
2803        // then skip it unless this project is shared. If it applies to all
2804        // followers, regardless of project, then set `project_id` to none,
2805        // indicating that it goes to all followers.
2806        let project_id = if project_only {
2807            Some(self.project.read(cx).remote_id()?)
2808        } else {
2809            None
2810        };
2811        self.app_state().workspace_store.update(cx, |store, cx| {
2812            store.update_followers(project_id, update, cx)
2813        })
2814    }
2815
2816    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2817        self.follower_states.get(pane).map(|state| state.leader_id)
2818    }
2819
2820    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2821        cx.notify();
2822
2823        let call = self.active_call()?;
2824        let room = call.read(cx).room()?.read(cx);
2825        let participant = room.remote_participant_for_peer_id(leader_id)?;
2826        let mut items_to_activate = Vec::new();
2827
2828        let leader_in_this_app;
2829        let leader_in_this_project;
2830        match participant.location {
2831            call::ParticipantLocation::SharedProject { project_id } => {
2832                leader_in_this_app = true;
2833                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2834            }
2835            call::ParticipantLocation::UnsharedProject => {
2836                leader_in_this_app = true;
2837                leader_in_this_project = false;
2838            }
2839            call::ParticipantLocation::External => {
2840                leader_in_this_app = false;
2841                leader_in_this_project = false;
2842            }
2843        };
2844
2845        for (pane, state) in &self.follower_states {
2846            if state.leader_id != leader_id {
2847                continue;
2848            }
2849            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2850                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2851                    if leader_in_this_project || !item.is_project_item(cx) {
2852                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2853                    }
2854                }
2855                continue;
2856            }
2857
2858            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2859                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2860            }
2861        }
2862
2863        for (pane, item) in items_to_activate {
2864            let pane_was_focused = pane.read(cx).has_focus(cx);
2865            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2866                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2867            } else {
2868                pane.update(cx, |pane, cx| {
2869                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2870                });
2871            }
2872
2873            if pane_was_focused {
2874                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2875            }
2876        }
2877
2878        None
2879    }
2880
2881    fn shared_screen_for_peer(
2882        &self,
2883        peer_id: PeerId,
2884        pane: &View<Pane>,
2885        cx: &mut ViewContext<Self>,
2886    ) -> Option<View<SharedScreen>> {
2887        let call = self.active_call()?;
2888        let room = call.read(cx).room()?.read(cx);
2889        let participant = room.remote_participant_for_peer_id(peer_id)?;
2890        let track = participant.video_tracks.values().next()?.clone();
2891        let user = participant.user.clone();
2892
2893        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2894            if item.read(cx).peer_id == peer_id {
2895                return Some(item);
2896            }
2897        }
2898
2899        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2900    }
2901
2902    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2903        if cx.is_window_active() {
2904            self.update_active_view_for_followers(cx);
2905            cx.background_executor()
2906                .spawn(persistence::DB.update_timestamp(self.database_id()))
2907                .detach();
2908        } else {
2909            for pane in &self.panes {
2910                pane.update(cx, |pane, cx| {
2911                    if let Some(item) = pane.active_item() {
2912                        item.workspace_deactivated(cx);
2913                    }
2914                    if matches!(
2915                        WorkspaceSettings::get_global(cx).autosave,
2916                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2917                    ) {
2918                        for item in pane.items() {
2919                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2920                                .detach_and_log_err(cx);
2921                        }
2922                    }
2923                });
2924            }
2925        }
2926    }
2927
2928    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2929        self.active_call.as_ref().map(|(call, _)| call)
2930    }
2931
2932    fn on_active_call_event(
2933        &mut self,
2934        _: Model<ActiveCall>,
2935        event: &call::room::Event,
2936        cx: &mut ViewContext<Self>,
2937    ) {
2938        match event {
2939            call::room::Event::ParticipantLocationChanged { participant_id }
2940            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2941                self.leader_updated(*participant_id, cx);
2942            }
2943            _ => {}
2944        }
2945    }
2946
2947    pub fn database_id(&self) -> WorkspaceId {
2948        self.database_id
2949    }
2950
2951    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2952        let project = self.project().read(cx);
2953
2954        if project.is_local() {
2955            Some(
2956                project
2957                    .visible_worktrees(cx)
2958                    .map(|worktree| worktree.read(cx).abs_path())
2959                    .collect::<Vec<_>>()
2960                    .into(),
2961            )
2962        } else {
2963            None
2964        }
2965    }
2966
2967    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2968        match member {
2969            Member::Axis(PaneAxis { members, .. }) => {
2970                for child in members.iter() {
2971                    self.remove_panes(child.clone(), cx)
2972                }
2973            }
2974            Member::Pane(pane) => {
2975                self.force_remove_pane(&pane, cx);
2976            }
2977        }
2978    }
2979
2980    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2981        self.panes.retain(|p| p != pane);
2982        self.panes
2983            .last()
2984            .unwrap()
2985            .update(cx, |pane, cx| pane.focus(cx));
2986        if self.last_active_center_pane == Some(pane.downgrade()) {
2987            self.last_active_center_pane = None;
2988        }
2989        cx.notify();
2990    }
2991
2992    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2993        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
2994            cx.background_executor()
2995                .timer(Duration::from_millis(100))
2996                .await;
2997            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
2998                .log_err();
2999        }));
3000    }
3001
3002    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
3003        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3004            let (items, active) = {
3005                let pane = pane_handle.read(cx);
3006                let active_item_id = pane.active_item().map(|item| item.item_id());
3007                (
3008                    pane.items()
3009                        .filter_map(|item_handle| {
3010                            Some(SerializedItem {
3011                                kind: Arc::from(item_handle.serialized_item_kind()?),
3012                                item_id: item_handle.item_id().as_u64(),
3013                                active: Some(item_handle.item_id()) == active_item_id,
3014                            })
3015                        })
3016                        .collect::<Vec<_>>(),
3017                    pane.has_focus(cx),
3018                )
3019            };
3020
3021            SerializedPane::new(items, active)
3022        }
3023
3024        fn build_serialized_pane_group(
3025            pane_group: &Member,
3026            cx: &WindowContext,
3027        ) -> SerializedPaneGroup {
3028            match pane_group {
3029                Member::Axis(PaneAxis {
3030                    axis,
3031                    members,
3032                    flexes,
3033                    bounding_boxes: _,
3034                }) => SerializedPaneGroup::Group {
3035                    axis: SerializedAxis(*axis),
3036                    children: members
3037                        .iter()
3038                        .map(|member| build_serialized_pane_group(member, cx))
3039                        .collect::<Vec<_>>(),
3040                    flexes: Some(flexes.lock().clone()),
3041                },
3042                Member::Pane(pane_handle) => {
3043                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3044                }
3045            }
3046        }
3047
3048        fn build_serialized_docks(
3049            this: &Workspace,
3050            cx: &mut ViewContext<Workspace>,
3051        ) -> DockStructure {
3052            let left_dock = this.left_dock.read(cx);
3053            let left_visible = left_dock.is_open();
3054            let left_active_panel = left_dock
3055                .visible_panel()
3056                .and_then(|panel| Some(panel.persistent_name().to_string()));
3057            let left_dock_zoom = left_dock
3058                .visible_panel()
3059                .map(|panel| panel.is_zoomed(cx))
3060                .unwrap_or(false);
3061
3062            let right_dock = this.right_dock.read(cx);
3063            let right_visible = right_dock.is_open();
3064            let right_active_panel = right_dock
3065                .visible_panel()
3066                .and_then(|panel| Some(panel.persistent_name().to_string()));
3067            let right_dock_zoom = right_dock
3068                .visible_panel()
3069                .map(|panel| panel.is_zoomed(cx))
3070                .unwrap_or(false);
3071
3072            let bottom_dock = this.bottom_dock.read(cx);
3073            let bottom_visible = bottom_dock.is_open();
3074            let bottom_active_panel = bottom_dock
3075                .visible_panel()
3076                .and_then(|panel| Some(panel.persistent_name().to_string()));
3077            let bottom_dock_zoom = bottom_dock
3078                .visible_panel()
3079                .map(|panel| panel.is_zoomed(cx))
3080                .unwrap_or(false);
3081
3082            DockStructure {
3083                left: DockData {
3084                    visible: left_visible,
3085                    active_panel: left_active_panel,
3086                    zoom: left_dock_zoom,
3087                },
3088                right: DockData {
3089                    visible: right_visible,
3090                    active_panel: right_active_panel,
3091                    zoom: right_dock_zoom,
3092                },
3093                bottom: DockData {
3094                    visible: bottom_visible,
3095                    active_panel: bottom_active_panel,
3096                    zoom: bottom_dock_zoom,
3097                },
3098            }
3099        }
3100
3101        if let Some(location) = self.location(cx) {
3102            // Load bearing special case:
3103            //  - with_local_workspace() relies on this to not have other stuff open
3104            //    when you open your log
3105            if !location.paths().is_empty() {
3106                let center_group = build_serialized_pane_group(&self.center.root, cx);
3107                let docks = build_serialized_docks(self, cx);
3108
3109                let serialized_workspace = SerializedWorkspace {
3110                    id: self.database_id,
3111                    location,
3112                    center_group,
3113                    bounds: Default::default(),
3114                    display: Default::default(),
3115                    docks,
3116                };
3117
3118                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3119                    .detach();
3120            }
3121        }
3122    }
3123
3124    pub(crate) fn load_workspace(
3125        serialized_workspace: SerializedWorkspace,
3126        paths_to_open: Vec<Option<ProjectPath>>,
3127        cx: &mut ViewContext<Workspace>,
3128    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3129        cx.spawn(|workspace, mut cx| async move {
3130            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3131
3132            let mut center_group = None;
3133            let mut center_items = None;
3134
3135            // Traverse the splits tree and add to things
3136            if let Some((group, active_pane, items)) = serialized_workspace
3137                .center_group
3138                .deserialize(
3139                    &project,
3140                    serialized_workspace.id,
3141                    workspace.clone(),
3142                    &mut cx,
3143                )
3144                .await
3145            {
3146                center_items = Some(items);
3147                center_group = Some((group, active_pane))
3148            }
3149
3150            let mut items_by_project_path = cx.update(|_, cx| {
3151                center_items
3152                    .unwrap_or_default()
3153                    .into_iter()
3154                    .filter_map(|item| {
3155                        let item = item?;
3156                        let project_path = item.project_path(cx)?;
3157                        Some((project_path, item))
3158                    })
3159                    .collect::<HashMap<_, _>>()
3160            })?;
3161
3162            let opened_items = paths_to_open
3163                .into_iter()
3164                .map(|path_to_open| {
3165                    path_to_open
3166                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3167                })
3168                .collect::<Vec<_>>();
3169
3170            // Remove old panes from workspace panes list
3171            workspace.update(&mut cx, |workspace, cx| {
3172                if let Some((center_group, active_pane)) = center_group {
3173                    workspace.remove_panes(workspace.center.root.clone(), cx);
3174
3175                    // Swap workspace center group
3176                    workspace.center = PaneGroup::with_root(center_group);
3177                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3178                    if let Some(active_pane) = active_pane {
3179                        workspace.active_pane = active_pane;
3180                        cx.focus_self();
3181                    } else {
3182                        workspace.active_pane = workspace.center.first_pane().clone();
3183                    }
3184                }
3185
3186                let docks = serialized_workspace.docks;
3187                workspace.left_dock.update(cx, |dock, cx| {
3188                    dock.set_open(docks.left.visible, cx);
3189                    if let Some(active_panel) = docks.left.active_panel {
3190                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3191                            dock.activate_panel(ix, cx);
3192                        }
3193                    }
3194                    dock.active_panel()
3195                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3196                    if docks.left.visible && docks.left.zoom {
3197                        cx.focus_self()
3198                    }
3199                });
3200                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3201                workspace.right_dock.update(cx, |dock, cx| {
3202                    dock.set_open(docks.right.visible, cx);
3203                    if let Some(active_panel) = docks.right.active_panel {
3204                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3205                            dock.activate_panel(ix, cx);
3206                        }
3207                    }
3208                    dock.active_panel()
3209                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3210
3211                    if docks.right.visible && docks.right.zoom {
3212                        cx.focus_self()
3213                    }
3214                });
3215                workspace.bottom_dock.update(cx, |dock, cx| {
3216                    dock.set_open(docks.bottom.visible, cx);
3217                    if let Some(active_panel) = docks.bottom.active_panel {
3218                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3219                            dock.activate_panel(ix, cx);
3220                        }
3221                    }
3222
3223                    dock.active_panel()
3224                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3225
3226                    if docks.bottom.visible && docks.bottom.zoom {
3227                        cx.focus_self()
3228                    }
3229                });
3230
3231                cx.notify();
3232            })?;
3233
3234            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3235            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3236
3237            Ok(opened_items)
3238        })
3239    }
3240
3241    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3242        self.add_workspace_actions_listeners(div, cx)
3243            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3244            .on_action(cx.listener(Self::close_all_items_and_panes))
3245            .on_action(cx.listener(Self::save_all))
3246            .on_action(cx.listener(Self::add_folder_to_project))
3247            .on_action(cx.listener(Self::follow_next_collaborator))
3248            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3249                let pane = workspace.active_pane().clone();
3250                workspace.unfollow(&pane, cx);
3251            }))
3252            .on_action(cx.listener(|workspace, action: &Save, cx| {
3253                workspace
3254                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3255                    .detach_and_log_err(cx);
3256            }))
3257            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3258                workspace
3259                    .save_active_item(SaveIntent::SaveAs, cx)
3260                    .detach_and_log_err(cx);
3261            }))
3262            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3263                workspace.activate_previous_pane(cx)
3264            }))
3265            .on_action(
3266                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3267            )
3268            .on_action(
3269                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3270                    workspace.activate_pane_in_direction(action.0, cx)
3271                }),
3272            )
3273            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3274                workspace.swap_pane_in_direction(action.0, cx)
3275            }))
3276            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3277                this.toggle_dock(DockPosition::Left, cx);
3278            }))
3279            .on_action(
3280                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3281                    workspace.toggle_dock(DockPosition::Right, cx);
3282                }),
3283            )
3284            .on_action(
3285                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3286                    workspace.toggle_dock(DockPosition::Bottom, cx);
3287                }),
3288            )
3289            .on_action(
3290                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3291                    workspace.close_all_docks(cx);
3292                }),
3293            )
3294            .on_action(cx.listener(Workspace::open))
3295            .on_action(cx.listener(Workspace::close_window))
3296            .on_action(cx.listener(Workspace::activate_pane_at_index))
3297            .on_action(
3298                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3299                    workspace.reopen_closed_item(cx).detach();
3300                }),
3301            )
3302    }
3303
3304    #[cfg(any(test, feature = "test-support"))]
3305    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3306        use node_runtime::FakeNodeRuntime;
3307
3308        let client = project.read(cx).client();
3309        let user_store = project.read(cx).user_store();
3310
3311        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3312        cx.activate_window();
3313        let app_state = Arc::new(AppState {
3314            languages: project.read(cx).languages().clone(),
3315            workspace_store,
3316            client,
3317            user_store,
3318            fs: project.read(cx).fs().clone(),
3319            build_window_options: |_, _, _| Default::default(),
3320            node_runtime: FakeNodeRuntime::new(),
3321        });
3322        let workspace = Self::new(0, project, app_state, cx);
3323        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3324        workspace
3325    }
3326
3327    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3328    //         let dock = match position {
3329    //             DockPosition::Left => &self.left_dock,
3330    //             DockPosition::Right => &self.right_dock,
3331    //             DockPosition::Bottom => &self.bottom_dock,
3332    //         };
3333    //         let active_panel = dock.read(cx).visible_panel()?;
3334    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3335    //             dock.read(cx).render_placeholder(cx)
3336    //         } else {
3337    //             ChildView::new(dock, cx).into_any()
3338    //         };
3339
3340    //         Some(
3341    //             element
3342    //                 .constrained()
3343    //                 .dynamically(move |constraint, _, cx| match position {
3344    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3345    //                         Vector2F::new(20., constraint.min.y()),
3346    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3347    //                     ),
3348    //                     DockPosition::Bottom => SizeConstraint::new(
3349    //                         Vector2F::new(constraint.min.x(), 20.),
3350    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3351    //                     ),
3352    //                 })
3353    //                 .into_any(),
3354    //         )
3355    //     }
3356    // }
3357    pub fn register_action<A: Action>(
3358        &mut self,
3359        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3360    ) -> &mut Self {
3361        let callback = Arc::new(callback);
3362
3363        self.workspace_actions.push(Box::new(move |div, cx| {
3364            let callback = callback.clone();
3365            div.on_action(
3366                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3367            )
3368        }));
3369        self
3370    }
3371
3372    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3373        let mut div = div
3374            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3375            .on_action(cx.listener(Self::close_all_items_and_panes))
3376            .on_action(cx.listener(Self::add_folder_to_project))
3377            .on_action(cx.listener(Self::save_all))
3378            .on_action(cx.listener(Self::open));
3379        for action in self.workspace_actions.iter() {
3380            div = (action)(div, cx)
3381        }
3382        div
3383    }
3384
3385    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3386        self.modal_layer.read(cx).has_active_modal()
3387    }
3388
3389    pub fn active_modal<V: ManagedView + 'static>(
3390        &mut self,
3391        cx: &ViewContext<Self>,
3392    ) -> Option<View<V>> {
3393        self.modal_layer.read(cx).active_modal()
3394    }
3395
3396    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3397    where
3398        B: FnOnce(&mut ViewContext<V>) -> V,
3399    {
3400        self.modal_layer
3401            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3402    }
3403}
3404
3405fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3406    let display_origin = cx
3407        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3408        .ok()??;
3409    ZED_WINDOW_POSITION
3410        .zip(*ZED_WINDOW_SIZE)
3411        .map(|(position, size)| {
3412            WindowBounds::Fixed(Bounds {
3413                origin: display_origin + position,
3414                size,
3415            })
3416        })
3417}
3418
3419fn open_items(
3420    serialized_workspace: Option<SerializedWorkspace>,
3421    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3422    app_state: Arc<AppState>,
3423    cx: &mut ViewContext<Workspace>,
3424) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3425    let restored_items = serialized_workspace.map(|serialized_workspace| {
3426        Workspace::load_workspace(
3427            serialized_workspace,
3428            project_paths_to_open
3429                .iter()
3430                .map(|(_, project_path)| project_path)
3431                .cloned()
3432                .collect(),
3433            cx,
3434        )
3435    });
3436
3437    cx.spawn(|workspace, mut cx| async move {
3438        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3439
3440        if let Some(restored_items) = restored_items {
3441            let restored_items = restored_items.await?;
3442
3443            let restored_project_paths = restored_items
3444                .iter()
3445                .filter_map(|item| {
3446                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3447                        .ok()
3448                        .flatten()
3449                })
3450                .collect::<HashSet<_>>();
3451
3452            for restored_item in restored_items {
3453                opened_items.push(restored_item.map(Ok));
3454            }
3455
3456            project_paths_to_open
3457                .iter_mut()
3458                .for_each(|(_, project_path)| {
3459                    if let Some(project_path_to_open) = project_path {
3460                        if restored_project_paths.contains(project_path_to_open) {
3461                            *project_path = None;
3462                        }
3463                    }
3464                });
3465        } else {
3466            for _ in 0..project_paths_to_open.len() {
3467                opened_items.push(None);
3468            }
3469        }
3470        assert!(opened_items.len() == project_paths_to_open.len());
3471
3472        let tasks =
3473            project_paths_to_open
3474                .into_iter()
3475                .enumerate()
3476                .map(|(i, (abs_path, project_path))| {
3477                    let workspace = workspace.clone();
3478                    cx.spawn(|mut cx| {
3479                        let fs = app_state.fs.clone();
3480                        async move {
3481                            let file_project_path = project_path?;
3482                            if fs.is_file(&abs_path).await {
3483                                Some((
3484                                    i,
3485                                    workspace
3486                                        .update(&mut cx, |workspace, cx| {
3487                                            workspace.open_path(file_project_path, None, true, cx)
3488                                        })
3489                                        .log_err()?
3490                                        .await,
3491                                ))
3492                            } else {
3493                                None
3494                            }
3495                        }
3496                    })
3497                });
3498
3499        let tasks = tasks.collect::<Vec<_>>();
3500
3501        let tasks = futures::future::join_all(tasks.into_iter());
3502        for maybe_opened_path in tasks.await.into_iter() {
3503            if let Some((i, path_open_result)) = maybe_opened_path {
3504                opened_items[i] = Some(path_open_result);
3505            }
3506        }
3507
3508        Ok(opened_items)
3509    })
3510}
3511
3512fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3513    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3514
3515    workspace
3516        .update(cx, |workspace, cx| {
3517            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3518                workspace.show_notification_once(0, cx, |cx| {
3519                    cx.new_view(|_| {
3520                        MessageNotification::new("Failed to load the database file.")
3521                            .with_click_message("Click to let us know about this error")
3522                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3523                    })
3524                });
3525            }
3526        })
3527        .log_err();
3528}
3529
3530impl FocusableView for Workspace {
3531    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3532        self.active_pane.focus_handle(cx)
3533    }
3534}
3535
3536#[derive(Clone, Render)]
3537struct DraggedDock(DockPosition);
3538
3539impl Render for Workspace {
3540    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3541        let mut context = KeyContext::default();
3542        context.add("Workspace");
3543
3544        let (ui_font, ui_font_size) = {
3545            let theme_settings = ThemeSettings::get_global(cx);
3546            (
3547                theme_settings.ui_font.family.clone(),
3548                theme_settings.ui_font_size.clone(),
3549            )
3550        };
3551
3552        let theme = cx.theme().clone();
3553        let colors = theme.colors();
3554        cx.set_rem_size(ui_font_size);
3555
3556        self.actions(div(), cx)
3557            .key_context(context)
3558            .relative()
3559            .size_full()
3560            .flex()
3561            .flex_col()
3562            .font(ui_font)
3563            .gap_0()
3564            .justify_start()
3565            .items_start()
3566            .text_color(colors.text)
3567            .bg(colors.background)
3568            .border()
3569            .border_color(colors.border)
3570            .children(self.titlebar_item.clone())
3571            .child(
3572                div()
3573                    .id("workspace")
3574                    .relative()
3575                    .flex_1()
3576                    .w_full()
3577                    .flex()
3578                    .flex_col()
3579                    .overflow_hidden()
3580                    .border_t()
3581                    .border_b()
3582                    .border_color(colors.border)
3583                    .child(
3584                        canvas(cx.listener(|workspace, bounds, _| {
3585                            workspace.bounds = *bounds;
3586                        }))
3587                        .absolute()
3588                        .size_full(),
3589                    )
3590                    .on_drag_move(
3591                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3592                            match e.drag(cx).0 {
3593                                DockPosition::Left => {
3594                                    let size = workspace.bounds.left() + e.event.position.x;
3595                                    workspace.left_dock.update(cx, |left_dock, cx| {
3596                                        left_dock.resize_active_panel(Some(size), cx);
3597                                    });
3598                                }
3599                                DockPosition::Right => {
3600                                    let size = workspace.bounds.right() - e.event.position.x;
3601                                    workspace.right_dock.update(cx, |right_dock, cx| {
3602                                        right_dock.resize_active_panel(Some(size), cx);
3603                                    });
3604                                }
3605                                DockPosition::Bottom => {
3606                                    let size = workspace.bounds.bottom() - e.event.position.y;
3607                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3608                                        bottom_dock.resize_active_panel(Some(size), cx);
3609                                    });
3610                                }
3611                            }
3612                        }),
3613                    )
3614                    .child(self.modal_layer.clone())
3615                    .child(
3616                        div()
3617                            .flex()
3618                            .flex_row()
3619                            .h_full()
3620                            // Left Dock
3621                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3622                                || {
3623                                    div()
3624                                        .flex()
3625                                        .flex_none()
3626                                        .overflow_hidden()
3627                                        .child(self.left_dock.clone())
3628                                },
3629                            ))
3630                            // Panes
3631                            .child(
3632                                div()
3633                                    .flex()
3634                                    .flex_col()
3635                                    .flex_1()
3636                                    .overflow_hidden()
3637                                    .child(self.center.render(
3638                                        &self.project,
3639                                        &self.follower_states,
3640                                        self.active_call(),
3641                                        &self.active_pane,
3642                                        self.zoomed.as_ref(),
3643                                        &self.app_state,
3644                                        cx,
3645                                    ))
3646                                    .children(
3647                                        self.zoomed_position
3648                                            .ne(&Some(DockPosition::Bottom))
3649                                            .then(|| self.bottom_dock.clone()),
3650                                    ),
3651                            )
3652                            // Right Dock
3653                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3654                                || {
3655                                    div()
3656                                        .flex()
3657                                        .flex_none()
3658                                        .overflow_hidden()
3659                                        .child(self.right_dock.clone())
3660                                },
3661                            )),
3662                    )
3663                    .children(self.render_notifications(cx))
3664                    .children(self.zoomed.as_ref().and_then(|view| {
3665                        let zoomed_view = view.upgrade()?;
3666                        let div = div()
3667                            .z_index(1)
3668                            .absolute()
3669                            .overflow_hidden()
3670                            .border_color(colors.border)
3671                            .bg(colors.background)
3672                            .child(zoomed_view)
3673                            .inset_0()
3674                            .shadow_lg();
3675
3676                        Some(match self.zoomed_position {
3677                            Some(DockPosition::Left) => div.right_2().border_r(),
3678                            Some(DockPosition::Right) => div.left_2().border_l(),
3679                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3680                            None => div.top_2().bottom_2().left_2().right_2().border(),
3681                        })
3682                    })),
3683            )
3684            .child(self.status_bar.clone())
3685            .children(if self.project.read(cx).is_disconnected() {
3686                Some(DisconnectedOverlay)
3687            } else {
3688                None
3689            })
3690    }
3691}
3692
3693impl WorkspaceStore {
3694    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3695        Self {
3696            workspaces: Default::default(),
3697            followers: Default::default(),
3698            _subscriptions: vec![
3699                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3700                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3701                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3702            ],
3703            client,
3704        }
3705    }
3706
3707    pub fn update_followers(
3708        &self,
3709        project_id: Option<u64>,
3710        update: proto::update_followers::Variant,
3711        cx: &AppContext,
3712    ) -> Option<()> {
3713        if !cx.has_global::<Model<ActiveCall>>() {
3714            return None;
3715        }
3716
3717        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3718        let follower_ids: Vec<_> = self
3719            .followers
3720            .iter()
3721            .filter_map(|follower| {
3722                if follower.project_id == project_id || project_id.is_none() {
3723                    Some(follower.peer_id.into())
3724                } else {
3725                    None
3726                }
3727            })
3728            .collect();
3729        if follower_ids.is_empty() {
3730            return None;
3731        }
3732        self.client
3733            .send(proto::UpdateFollowers {
3734                room_id,
3735                project_id,
3736                follower_ids,
3737                variant: Some(update),
3738            })
3739            .log_err()
3740    }
3741
3742    pub async fn handle_follow(
3743        this: Model<Self>,
3744        envelope: TypedEnvelope<proto::Follow>,
3745        _: Arc<Client>,
3746        mut cx: AsyncAppContext,
3747    ) -> Result<proto::FollowResponse> {
3748        this.update(&mut cx, |this, cx| {
3749            let follower = Follower {
3750                project_id: envelope.payload.project_id,
3751                peer_id: envelope.original_sender_id()?,
3752            };
3753            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3754
3755            let mut response = proto::FollowResponse::default();
3756            this.workspaces.retain(|workspace| {
3757                workspace
3758                    .update(cx, |workspace, cx| {
3759                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3760                        if response.views.is_empty() {
3761                            response.views = handler_response.views;
3762                        } else {
3763                            response.views.extend_from_slice(&handler_response.views);
3764                        }
3765
3766                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3767                            if response.active_view_id.is_none()
3768                                || Some(workspace.project.downgrade()) == active_project
3769                            {
3770                                response.active_view_id = Some(active_view_id);
3771                            }
3772                        }
3773                    })
3774                    .is_ok()
3775            });
3776
3777            if let Err(ix) = this.followers.binary_search(&follower) {
3778                this.followers.insert(ix, follower);
3779            }
3780
3781            Ok(response)
3782        })?
3783    }
3784
3785    async fn handle_unfollow(
3786        model: Model<Self>,
3787        envelope: TypedEnvelope<proto::Unfollow>,
3788        _: Arc<Client>,
3789        mut cx: AsyncAppContext,
3790    ) -> Result<()> {
3791        model.update(&mut cx, |this, _| {
3792            let follower = Follower {
3793                project_id: envelope.payload.project_id,
3794                peer_id: envelope.original_sender_id()?,
3795            };
3796            if let Ok(ix) = this.followers.binary_search(&follower) {
3797                this.followers.remove(ix);
3798            }
3799            Ok(())
3800        })?
3801    }
3802
3803    async fn handle_update_followers(
3804        this: Model<Self>,
3805        envelope: TypedEnvelope<proto::UpdateFollowers>,
3806        _: Arc<Client>,
3807        mut cx: AsyncAppContext,
3808    ) -> Result<()> {
3809        let leader_id = envelope.original_sender_id()?;
3810        let update = envelope.payload;
3811
3812        this.update(&mut cx, |this, cx| {
3813            this.workspaces.retain(|workspace| {
3814                workspace
3815                    .update(cx, |workspace, cx| {
3816                        let project_id = workspace.project.read(cx).remote_id();
3817                        if update.project_id != project_id && update.project_id.is_some() {
3818                            return;
3819                        }
3820                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3821                    })
3822                    .is_ok()
3823            });
3824            Ok(())
3825        })?
3826    }
3827}
3828
3829impl ViewId {
3830    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3831        Ok(Self {
3832            creator: message
3833                .creator
3834                .ok_or_else(|| anyhow!("creator is missing"))?,
3835            id: message.id,
3836        })
3837    }
3838
3839    pub(crate) fn to_proto(&self) -> proto::ViewId {
3840        proto::ViewId {
3841            creator: Some(self.creator),
3842            id: self.id,
3843        }
3844    }
3845}
3846
3847pub trait WorkspaceHandle {
3848    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3849}
3850
3851impl WorkspaceHandle for View<Workspace> {
3852    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3853        self.read(cx)
3854            .worktrees(cx)
3855            .flat_map(|worktree| {
3856                let worktree_id = worktree.read(cx).id();
3857                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3858                    worktree_id,
3859                    path: f.path.clone(),
3860                })
3861            })
3862            .collect::<Vec<_>>()
3863    }
3864}
3865
3866impl std::fmt::Debug for OpenPaths {
3867    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3868        f.debug_struct("OpenPaths")
3869            .field("paths", &self.paths)
3870            .finish()
3871    }
3872}
3873
3874pub fn activate_workspace_for_project(
3875    cx: &mut AppContext,
3876    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3877) -> Option<WindowHandle<Workspace>> {
3878    for window in cx.windows() {
3879        let Some(workspace) = window.downcast::<Workspace>() else {
3880            continue;
3881        };
3882
3883        let predicate = workspace
3884            .update(cx, |workspace, cx| {
3885                let project = workspace.project.read(cx);
3886                if predicate(project, cx) {
3887                    cx.activate_window();
3888                    true
3889                } else {
3890                    false
3891                }
3892            })
3893            .log_err()
3894            .unwrap_or(false);
3895
3896        if predicate {
3897            return Some(workspace);
3898        }
3899    }
3900
3901    None
3902}
3903
3904pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3905    DB.last_workspace().await.log_err().flatten()
3906}
3907
3908async fn join_channel_internal(
3909    channel_id: u64,
3910    app_state: &Arc<AppState>,
3911    requesting_window: Option<WindowHandle<Workspace>>,
3912    active_call: &Model<ActiveCall>,
3913    cx: &mut AsyncAppContext,
3914) -> Result<bool> {
3915    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3916        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3917            return (false, None);
3918        };
3919
3920        let already_in_channel = room.channel_id() == Some(channel_id);
3921        let should_prompt = room.is_sharing_project()
3922            && room.remote_participants().len() > 0
3923            && !already_in_channel;
3924        let open_room = if already_in_channel {
3925            active_call.room().cloned()
3926        } else {
3927            None
3928        };
3929        (should_prompt, open_room)
3930    })?;
3931
3932    if let Some(room) = open_room {
3933        let task = room.update(cx, |room, cx| {
3934            if let Some((project, host)) = room.most_active_project(cx) {
3935                return Some(join_remote_project(project, host, app_state.clone(), cx));
3936            }
3937
3938            None
3939        })?;
3940        if let Some(task) = task {
3941            task.await?;
3942        }
3943        return anyhow::Ok(true);
3944    }
3945
3946    if should_prompt {
3947        if let Some(workspace) = requesting_window {
3948            let answer  = workspace.update(cx, |_, cx| {
3949                cx.prompt(
3950                    PromptLevel::Warning,
3951                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3952                    &["Yes, Join Channel", "Cancel"],
3953                )
3954            })?.await;
3955
3956            if answer == Ok(1) {
3957                return Ok(false);
3958            }
3959        } else {
3960            return Ok(false); // unreachable!() hopefully
3961        }
3962    }
3963
3964    let client = cx.update(|cx| active_call.read(cx).client())?;
3965
3966    let mut client_status = client.status();
3967
3968    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3969    'outer: loop {
3970        let Some(status) = client_status.recv().await else {
3971            return Err(anyhow!("error connecting"));
3972        };
3973
3974        match status {
3975            Status::Connecting
3976            | Status::Authenticating
3977            | Status::Reconnecting
3978            | Status::Reauthenticating => continue,
3979            Status::Connected { .. } => break 'outer,
3980            Status::SignedOut => return Err(anyhow!("not signed in")),
3981            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3982            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3983                return Err(anyhow!("zed is offline"))
3984            }
3985        }
3986    }
3987
3988    let room = active_call
3989        .update(cx, |active_call, cx| {
3990            active_call.join_channel(channel_id, cx)
3991        })?
3992        .await?;
3993
3994    let Some(room) = room else {
3995        return anyhow::Ok(true);
3996    };
3997
3998    room.update(cx, |room, _| room.room_update_completed())?
3999        .await;
4000
4001    let task = room.update(cx, |room, cx| {
4002        if let Some((project, host)) = room.most_active_project(cx) {
4003            return Some(join_remote_project(project, host, app_state.clone(), cx));
4004        }
4005
4006        None
4007    })?;
4008    if let Some(task) = task {
4009        task.await?;
4010        return anyhow::Ok(true);
4011    }
4012    anyhow::Ok(false)
4013}
4014
4015pub fn join_channel(
4016    channel_id: u64,
4017    app_state: Arc<AppState>,
4018    requesting_window: Option<WindowHandle<Workspace>>,
4019    cx: &mut AppContext,
4020) -> Task<Result<()>> {
4021    let active_call = ActiveCall::global(cx);
4022    cx.spawn(|mut cx| async move {
4023        let result = join_channel_internal(
4024            channel_id,
4025            &app_state,
4026            requesting_window,
4027            &active_call,
4028            &mut cx,
4029        )
4030        .await;
4031
4032        // join channel succeeded, and opened a window
4033        if matches!(result, Ok(true)) {
4034            return anyhow::Ok(());
4035        }
4036
4037        // find an existing workspace to focus and show call controls
4038        let mut active_window =
4039            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4040        if active_window.is_none() {
4041            // no open workspaces, make one to show the error in (blergh)
4042            let (window_handle, _) = cx
4043                .update(|cx| {
4044                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4045                })?
4046                .await?;
4047
4048            active_window = Some(window_handle);
4049        }
4050
4051        if let Err(err) = result {
4052            log::error!("failed to join channel: {}", err);
4053            if let Some(active_window) = active_window {
4054                active_window
4055                    .update(&mut cx, |_, cx| {
4056                        cx.prompt(
4057                            PromptLevel::Critical,
4058                            &format!("Failed to join channel: {}", err),
4059                            &["Ok"],
4060                        )
4061                    })?
4062                    .await
4063                    .ok();
4064            }
4065        }
4066
4067        // return ok, we showed the error to the user.
4068        return anyhow::Ok(());
4069    })
4070}
4071
4072pub async fn get_any_active_workspace(
4073    app_state: Arc<AppState>,
4074    mut cx: AsyncAppContext,
4075) -> anyhow::Result<WindowHandle<Workspace>> {
4076    // find an existing workspace to focus and show call controls
4077    let active_window = activate_any_workspace_window(&mut cx);
4078    if active_window.is_none() {
4079        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4080            .await?;
4081    }
4082    activate_any_workspace_window(&mut cx).context("could not open zed")
4083}
4084
4085fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4086    cx.update(|cx| {
4087        for window in cx.windows() {
4088            if let Some(workspace_window) = window.downcast::<Workspace>() {
4089                workspace_window
4090                    .update(cx, |_, cx| cx.activate_window())
4091                    .ok();
4092                return Some(workspace_window);
4093            }
4094        }
4095        None
4096    })
4097    .ok()
4098    .flatten()
4099}
4100
4101#[allow(clippy::type_complexity)]
4102pub fn open_paths(
4103    abs_paths: &[PathBuf],
4104    app_state: &Arc<AppState>,
4105    requesting_window: Option<WindowHandle<Workspace>>,
4106    cx: &mut AppContext,
4107) -> Task<
4108    anyhow::Result<(
4109        WindowHandle<Workspace>,
4110        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4111    )>,
4112> {
4113    let app_state = app_state.clone();
4114    let abs_paths = abs_paths.to_vec();
4115    // Open paths in existing workspace if possible
4116    let existing = activate_workspace_for_project(cx, {
4117        let abs_paths = abs_paths.clone();
4118        move |project, cx| project.contains_paths(&abs_paths, cx)
4119    });
4120    cx.spawn(move |mut cx| async move {
4121        if let Some(existing) = existing {
4122            Ok((
4123                existing.clone(),
4124                existing
4125                    .update(&mut cx, |workspace, cx| {
4126                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4127                    })?
4128                    .await,
4129            ))
4130        } else {
4131            cx.update(move |cx| {
4132                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4133            })?
4134            .await
4135        }
4136    })
4137}
4138
4139pub fn open_new(
4140    app_state: &Arc<AppState>,
4141    cx: &mut AppContext,
4142    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4143) -> Task<()> {
4144    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4145    cx.spawn(|mut cx| async move {
4146        if let Some((workspace, opened_paths)) = task.await.log_err() {
4147            workspace
4148                .update(&mut cx, |workspace, cx| {
4149                    if opened_paths.is_empty() {
4150                        init(workspace, cx)
4151                    }
4152                })
4153                .log_err();
4154        }
4155    })
4156}
4157
4158pub fn create_and_open_local_file(
4159    path: &'static Path,
4160    cx: &mut ViewContext<Workspace>,
4161    default_content: impl 'static + Send + FnOnce() -> Rope,
4162) -> Task<Result<Box<dyn ItemHandle>>> {
4163    cx.spawn(|workspace, mut cx| async move {
4164        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4165        if !fs.is_file(path).await {
4166            fs.create_file(path, Default::default()).await?;
4167            fs.save(path, &default_content(), Default::default())
4168                .await?;
4169        }
4170
4171        let mut items = workspace
4172            .update(&mut cx, |workspace, cx| {
4173                workspace.with_local_workspace(cx, |workspace, cx| {
4174                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4175                })
4176            })?
4177            .await?
4178            .await;
4179
4180        let item = items.pop().flatten();
4181        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4182    })
4183}
4184
4185pub fn join_remote_project(
4186    project_id: u64,
4187    follow_user_id: u64,
4188    app_state: Arc<AppState>,
4189    cx: &mut AppContext,
4190) -> Task<Result<()>> {
4191    let windows = cx.windows();
4192    cx.spawn(|mut cx| async move {
4193        let existing_workspace = windows.into_iter().find_map(|window| {
4194            window.downcast::<Workspace>().and_then(|window| {
4195                window
4196                    .update(&mut cx, |workspace, cx| {
4197                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4198                            Some(window)
4199                        } else {
4200                            None
4201                        }
4202                    })
4203                    .unwrap_or(None)
4204            })
4205        });
4206
4207        let workspace = if let Some(existing_workspace) = existing_workspace {
4208            existing_workspace
4209        } else {
4210            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4211            let room = active_call
4212                .read_with(&cx, |call, _| call.room().cloned())?
4213                .ok_or_else(|| anyhow!("not in a call"))?;
4214            let project = room
4215                .update(&mut cx, |room, cx| {
4216                    room.join_project(
4217                        project_id,
4218                        app_state.languages.clone(),
4219                        app_state.fs.clone(),
4220                        cx,
4221                    )
4222                })?
4223                .await?;
4224
4225            let window_bounds_override = window_bounds_env_override(&cx);
4226            cx.update(|cx| {
4227                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4228                cx.open_window(options, |cx| {
4229                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4230                })
4231            })?
4232        };
4233
4234        workspace.update(&mut cx, |workspace, cx| {
4235            cx.activate(true);
4236            cx.activate_window();
4237
4238            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4239                let follow_peer_id = room
4240                    .read(cx)
4241                    .remote_participants()
4242                    .iter()
4243                    .find(|(_, participant)| participant.user.id == follow_user_id)
4244                    .map(|(_, p)| p.peer_id)
4245                    .or_else(|| {
4246                        // If we couldn't follow the given user, follow the host instead.
4247                        let collaborator = workspace
4248                            .project()
4249                            .read(cx)
4250                            .collaborators()
4251                            .values()
4252                            .find(|collaborator| collaborator.replica_id == 0)?;
4253                        Some(collaborator.peer_id)
4254                    });
4255
4256                if let Some(follow_peer_id) = follow_peer_id {
4257                    workspace.follow(follow_peer_id, cx);
4258                }
4259            }
4260        })?;
4261
4262        anyhow::Ok(())
4263    })
4264}
4265
4266pub fn restart(_: &Restart, cx: &mut AppContext) {
4267    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4268    let mut workspace_windows = cx
4269        .windows()
4270        .into_iter()
4271        .filter_map(|window| window.downcast::<Workspace>())
4272        .collect::<Vec<_>>();
4273
4274    // If multiple windows have unsaved changes, and need a save prompt,
4275    // prompt in the active window before switching to a different window.
4276    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4277
4278    let mut prompt = None;
4279    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4280        prompt = window
4281            .update(cx, |_, cx| {
4282                cx.prompt(
4283                    PromptLevel::Info,
4284                    "Are you sure you want to restart?",
4285                    &["Restart", "Cancel"],
4286                )
4287            })
4288            .ok();
4289    }
4290
4291    cx.spawn(|mut cx| async move {
4292        if let Some(prompt) = prompt {
4293            let answer = prompt.await?;
4294            if answer != 0 {
4295                return Ok(());
4296            }
4297        }
4298
4299        // If the user cancels any save prompt, then keep the app open.
4300        for window in workspace_windows {
4301            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4302                workspace.prepare_to_close(true, cx)
4303            }) {
4304                if !should_close.await? {
4305                    return Ok(());
4306                }
4307            }
4308        }
4309
4310        cx.update(|cx| cx.restart())
4311    })
4312    .detach_and_log_err(cx);
4313}
4314
4315fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4316    let mut parts = value.split(',');
4317    let x: usize = parts.next()?.parse().ok()?;
4318    let y: usize = parts.next()?.parse().ok()?;
4319    Some(point((x as f64).into(), (y as f64).into()))
4320}
4321
4322fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4323    let mut parts = value.split(',');
4324    let width: usize = parts.next()?.parse().ok()?;
4325    let height: usize = parts.next()?.parse().ok()?;
4326    Some(size((width as f64).into(), (height as f64).into()))
4327}
4328
4329struct DisconnectedOverlay;
4330
4331impl Element for DisconnectedOverlay {
4332    type State = AnyElement;
4333
4334    fn request_layout(
4335        &mut self,
4336        _: Option<Self::State>,
4337        cx: &mut WindowContext,
4338    ) -> (LayoutId, Self::State) {
4339        let mut background = cx.theme().colors().elevated_surface_background;
4340        background.fade_out(0.2);
4341        let mut overlay = div()
4342            .bg(background)
4343            .absolute()
4344            .left_0()
4345            .top_0()
4346            .size_full()
4347            .flex()
4348            .items_center()
4349            .justify_center()
4350            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4351            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4352            .child(Label::new(
4353                "Your connection to the remote project has been lost.",
4354            ))
4355            .into_any();
4356        (overlay.request_layout(cx), overlay)
4357    }
4358
4359    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
4360        cx.with_z_index(u8::MAX, |cx| {
4361            cx.add_opaque_layer(bounds);
4362            overlay.paint(cx);
4363        })
4364    }
4365}
4366
4367impl IntoElement for DisconnectedOverlay {
4368    type Element = Self;
4369
4370    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4371        None
4372    }
4373
4374    fn into_element(self) -> Self::Element {
4375        self
4376    }
4377}
4378
4379#[cfg(test)]
4380mod tests {
4381    use std::{cell::RefCell, rc::Rc};
4382
4383    use super::*;
4384    use crate::{
4385        dock::{test::TestPanel, PanelEvent},
4386        item::{
4387            test::{TestItem, TestProjectItem},
4388            ItemEvent,
4389        },
4390    };
4391    use fs::FakeFs;
4392    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4393    use project::{Project, ProjectEntryId};
4394    use serde_json::json;
4395    use settings::SettingsStore;
4396
4397    #[gpui::test]
4398    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4399        init_test(cx);
4400
4401        let fs = FakeFs::new(cx.executor());
4402        let project = Project::test(fs, [], cx).await;
4403        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4404
4405        // Adding an item with no ambiguity renders the tab without detail.
4406        let item1 = cx.new_view(|cx| {
4407            let mut item = TestItem::new(cx);
4408            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4409            item
4410        });
4411        workspace.update(cx, |workspace, cx| {
4412            workspace.add_item(Box::new(item1.clone()), cx);
4413        });
4414        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4415
4416        // Adding an item that creates ambiguity increases the level of detail on
4417        // both tabs.
4418        let item2 = cx.new_view(|cx| {
4419            let mut item = TestItem::new(cx);
4420            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4421            item
4422        });
4423        workspace.update(cx, |workspace, cx| {
4424            workspace.add_item(Box::new(item2.clone()), cx);
4425        });
4426        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4427        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4428
4429        // Adding an item that creates ambiguity increases the level of detail only
4430        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4431        // we stop at the highest detail available.
4432        let item3 = cx.new_view(|cx| {
4433            let mut item = TestItem::new(cx);
4434            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4435            item
4436        });
4437        workspace.update(cx, |workspace, cx| {
4438            workspace.add_item(Box::new(item3.clone()), cx);
4439        });
4440        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4441        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4442        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4443    }
4444
4445    #[gpui::test]
4446    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4447        init_test(cx);
4448
4449        let fs = FakeFs::new(cx.executor());
4450        fs.insert_tree(
4451            "/root1",
4452            json!({
4453                "one.txt": "",
4454                "two.txt": "",
4455            }),
4456        )
4457        .await;
4458        fs.insert_tree(
4459            "/root2",
4460            json!({
4461                "three.txt": "",
4462            }),
4463        )
4464        .await;
4465
4466        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4467        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4468        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4469        let worktree_id = project.update(cx, |project, cx| {
4470            project.worktrees().next().unwrap().read(cx).id()
4471        });
4472
4473        let item1 = cx.new_view(|cx| {
4474            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4475        });
4476        let item2 = cx.new_view(|cx| {
4477            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4478        });
4479
4480        // Add an item to an empty pane
4481        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4482        project.update(cx, |project, cx| {
4483            assert_eq!(
4484                project.active_entry(),
4485                project
4486                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4487                    .map(|e| e.id)
4488            );
4489        });
4490        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4491
4492        // Add a second item to a non-empty pane
4493        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4494        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4495        project.update(cx, |project, cx| {
4496            assert_eq!(
4497                project.active_entry(),
4498                project
4499                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4500                    .map(|e| e.id)
4501            );
4502        });
4503
4504        // Close the active item
4505        pane.update(cx, |pane, cx| {
4506            pane.close_active_item(&Default::default(), cx).unwrap()
4507        })
4508        .await
4509        .unwrap();
4510        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4511        project.update(cx, |project, cx| {
4512            assert_eq!(
4513                project.active_entry(),
4514                project
4515                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4516                    .map(|e| e.id)
4517            );
4518        });
4519
4520        // Add a project folder
4521        project
4522            .update(cx, |project, cx| {
4523                project.find_or_create_local_worktree("/root2", true, cx)
4524            })
4525            .await
4526            .unwrap();
4527        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4528
4529        // Remove a project folder
4530        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4531        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4532    }
4533
4534    #[gpui::test]
4535    async fn test_close_window(cx: &mut TestAppContext) {
4536        init_test(cx);
4537
4538        let fs = FakeFs::new(cx.executor());
4539        fs.insert_tree("/root", json!({ "one": "" })).await;
4540
4541        let project = Project::test(fs, ["root".as_ref()], cx).await;
4542        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4543
4544        // When there are no dirty items, there's nothing to do.
4545        let item1 = cx.new_view(|cx| TestItem::new(cx));
4546        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4547        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4548        assert!(task.await.unwrap());
4549
4550        // When there are dirty untitled items, prompt to save each one. If the user
4551        // cancels any prompt, then abort.
4552        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4553        let item3 = cx.new_view(|cx| {
4554            TestItem::new(cx)
4555                .with_dirty(true)
4556                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4557        });
4558        workspace.update(cx, |w, cx| {
4559            w.add_item(Box::new(item2.clone()), cx);
4560            w.add_item(Box::new(item3.clone()), cx);
4561        });
4562        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4563        cx.executor().run_until_parked();
4564        cx.simulate_prompt_answer(2); // cancel save all
4565        cx.executor().run_until_parked();
4566        cx.simulate_prompt_answer(2); // cancel save all
4567        cx.executor().run_until_parked();
4568        assert!(!cx.has_pending_prompt());
4569        assert!(!task.await.unwrap());
4570    }
4571
4572    #[gpui::test]
4573    async fn test_close_pane_items(cx: &mut TestAppContext) {
4574        init_test(cx);
4575
4576        let fs = FakeFs::new(cx.executor());
4577
4578        let project = Project::test(fs, None, cx).await;
4579        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4580
4581        let item1 = cx.new_view(|cx| {
4582            TestItem::new(cx)
4583                .with_dirty(true)
4584                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4585        });
4586        let item2 = cx.new_view(|cx| {
4587            TestItem::new(cx)
4588                .with_dirty(true)
4589                .with_conflict(true)
4590                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4591        });
4592        let item3 = cx.new_view(|cx| {
4593            TestItem::new(cx)
4594                .with_dirty(true)
4595                .with_conflict(true)
4596                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4597        });
4598        let item4 = cx.new_view(|cx| {
4599            TestItem::new(cx)
4600                .with_dirty(true)
4601                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4602        });
4603        let pane = workspace.update(cx, |workspace, cx| {
4604            workspace.add_item(Box::new(item1.clone()), cx);
4605            workspace.add_item(Box::new(item2.clone()), cx);
4606            workspace.add_item(Box::new(item3.clone()), cx);
4607            workspace.add_item(Box::new(item4.clone()), cx);
4608            workspace.active_pane().clone()
4609        });
4610
4611        let close_items = pane.update(cx, |pane, cx| {
4612            pane.activate_item(1, true, true, cx);
4613            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4614            let item1_id = item1.item_id();
4615            let item3_id = item3.item_id();
4616            let item4_id = item4.item_id();
4617            pane.close_items(cx, SaveIntent::Close, move |id| {
4618                [item1_id, item3_id, item4_id].contains(&id)
4619            })
4620        });
4621        cx.executor().run_until_parked();
4622
4623        assert!(cx.has_pending_prompt());
4624        // Ignore "Save all" prompt
4625        cx.simulate_prompt_answer(2);
4626        cx.executor().run_until_parked();
4627        // There's a prompt to save item 1.
4628        pane.update(cx, |pane, _| {
4629            assert_eq!(pane.items_len(), 4);
4630            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4631        });
4632        // Confirm saving item 1.
4633        cx.simulate_prompt_answer(0);
4634        cx.executor().run_until_parked();
4635
4636        // Item 1 is saved. There's a prompt to save item 3.
4637        pane.update(cx, |pane, cx| {
4638            assert_eq!(item1.read(cx).save_count, 1);
4639            assert_eq!(item1.read(cx).save_as_count, 0);
4640            assert_eq!(item1.read(cx).reload_count, 0);
4641            assert_eq!(pane.items_len(), 3);
4642            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4643        });
4644        assert!(cx.has_pending_prompt());
4645
4646        // Cancel saving item 3.
4647        cx.simulate_prompt_answer(1);
4648        cx.executor().run_until_parked();
4649
4650        // Item 3 is reloaded. There's a prompt to save item 4.
4651        pane.update(cx, |pane, cx| {
4652            assert_eq!(item3.read(cx).save_count, 0);
4653            assert_eq!(item3.read(cx).save_as_count, 0);
4654            assert_eq!(item3.read(cx).reload_count, 1);
4655            assert_eq!(pane.items_len(), 2);
4656            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4657        });
4658        assert!(cx.has_pending_prompt());
4659
4660        // Confirm saving item 4.
4661        cx.simulate_prompt_answer(0);
4662        cx.executor().run_until_parked();
4663
4664        // There's a prompt for a path for item 4.
4665        cx.simulate_new_path_selection(|_| Some(Default::default()));
4666        close_items.await.unwrap();
4667
4668        // The requested items are closed.
4669        pane.update(cx, |pane, cx| {
4670            assert_eq!(item4.read(cx).save_count, 0);
4671            assert_eq!(item4.read(cx).save_as_count, 1);
4672            assert_eq!(item4.read(cx).reload_count, 0);
4673            assert_eq!(pane.items_len(), 1);
4674            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4675        });
4676    }
4677
4678    #[gpui::test]
4679    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4680        init_test(cx);
4681
4682        let fs = FakeFs::new(cx.executor());
4683        let project = Project::test(fs, [], cx).await;
4684        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4685
4686        // Create several workspace items with single project entries, and two
4687        // workspace items with multiple project entries.
4688        let single_entry_items = (0..=4)
4689            .map(|project_entry_id| {
4690                cx.new_view(|cx| {
4691                    TestItem::new(cx)
4692                        .with_dirty(true)
4693                        .with_project_items(&[TestProjectItem::new(
4694                            project_entry_id,
4695                            &format!("{project_entry_id}.txt"),
4696                            cx,
4697                        )])
4698                })
4699            })
4700            .collect::<Vec<_>>();
4701        let item_2_3 = cx.new_view(|cx| {
4702            TestItem::new(cx)
4703                .with_dirty(true)
4704                .with_singleton(false)
4705                .with_project_items(&[
4706                    single_entry_items[2].read(cx).project_items[0].clone(),
4707                    single_entry_items[3].read(cx).project_items[0].clone(),
4708                ])
4709        });
4710        let item_3_4 = cx.new_view(|cx| {
4711            TestItem::new(cx)
4712                .with_dirty(true)
4713                .with_singleton(false)
4714                .with_project_items(&[
4715                    single_entry_items[3].read(cx).project_items[0].clone(),
4716                    single_entry_items[4].read(cx).project_items[0].clone(),
4717                ])
4718        });
4719
4720        // Create two panes that contain the following project entries:
4721        //   left pane:
4722        //     multi-entry items:   (2, 3)
4723        //     single-entry items:  0, 1, 2, 3, 4
4724        //   right pane:
4725        //     single-entry items:  1
4726        //     multi-entry items:   (3, 4)
4727        let left_pane = workspace.update(cx, |workspace, cx| {
4728            let left_pane = workspace.active_pane().clone();
4729            workspace.add_item(Box::new(item_2_3.clone()), cx);
4730            for item in single_entry_items {
4731                workspace.add_item(Box::new(item), cx);
4732            }
4733            left_pane.update(cx, |pane, cx| {
4734                pane.activate_item(2, true, true, cx);
4735            });
4736
4737            let right_pane = workspace
4738                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4739                .unwrap();
4740
4741            right_pane.update(cx, |pane, cx| {
4742                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4743            });
4744
4745            left_pane
4746        });
4747
4748        cx.focus_view(&left_pane);
4749
4750        // When closing all of the items in the left pane, we should be prompted twice:
4751        // once for project entry 0, and once for project entry 2. Project entries 1,
4752        // 3, and 4 are all still open in the other paten. After those two
4753        // prompts, the task should complete.
4754
4755        let close = left_pane.update(cx, |pane, cx| {
4756            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4757        });
4758        cx.executor().run_until_parked();
4759
4760        // Discard "Save all" prompt
4761        cx.simulate_prompt_answer(2);
4762
4763        cx.executor().run_until_parked();
4764        left_pane.update(cx, |pane, cx| {
4765            assert_eq!(
4766                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4767                &[ProjectEntryId::from_proto(0)]
4768            );
4769        });
4770        cx.simulate_prompt_answer(0);
4771
4772        cx.executor().run_until_parked();
4773        left_pane.update(cx, |pane, cx| {
4774            assert_eq!(
4775                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4776                &[ProjectEntryId::from_proto(2)]
4777            );
4778        });
4779        cx.simulate_prompt_answer(0);
4780
4781        cx.executor().run_until_parked();
4782        close.await.unwrap();
4783        left_pane.update(cx, |pane, _| {
4784            assert_eq!(pane.items_len(), 0);
4785        });
4786    }
4787
4788    #[gpui::test]
4789    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4790        init_test(cx);
4791
4792        let fs = FakeFs::new(cx.executor());
4793        let project = Project::test(fs, [], cx).await;
4794        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4795        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4796
4797        let item = cx.new_view(|cx| {
4798            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4799        });
4800        let item_id = item.entity_id();
4801        workspace.update(cx, |workspace, cx| {
4802            workspace.add_item(Box::new(item.clone()), cx);
4803        });
4804
4805        // Autosave on window change.
4806        item.update(cx, |item, cx| {
4807            cx.update_global(|settings: &mut SettingsStore, cx| {
4808                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4809                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4810                })
4811            });
4812            item.is_dirty = true;
4813        });
4814
4815        // Deactivating the window saves the file.
4816        cx.deactivate_window();
4817        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4818
4819        // Autosave on focus change.
4820        item.update(cx, |item, cx| {
4821            cx.focus_self();
4822            cx.update_global(|settings: &mut SettingsStore, cx| {
4823                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4824                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4825                })
4826            });
4827            item.is_dirty = true;
4828        });
4829
4830        // Blurring the item saves the file.
4831        item.update(cx, |_, cx| cx.blur());
4832        cx.executor().run_until_parked();
4833        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4834
4835        // Deactivating the window still saves the file.
4836        cx.update(|cx| cx.activate_window());
4837        item.update(cx, |item, cx| {
4838            cx.focus_self();
4839            item.is_dirty = true;
4840        });
4841        cx.deactivate_window();
4842
4843        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4844
4845        // Autosave after delay.
4846        item.update(cx, |item, cx| {
4847            cx.update_global(|settings: &mut SettingsStore, cx| {
4848                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4849                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4850                })
4851            });
4852            item.is_dirty = true;
4853            cx.emit(ItemEvent::Edit);
4854        });
4855
4856        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4857        cx.executor().advance_clock(Duration::from_millis(250));
4858        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4859
4860        // After delay expires, the file is saved.
4861        cx.executor().advance_clock(Duration::from_millis(250));
4862        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4863
4864        // Autosave on focus change, ensuring closing the tab counts as such.
4865        item.update(cx, |item, cx| {
4866            cx.update_global(|settings: &mut SettingsStore, cx| {
4867                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4868                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4869                })
4870            });
4871            item.is_dirty = true;
4872        });
4873
4874        pane.update(cx, |pane, cx| {
4875            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4876        })
4877        .await
4878        .unwrap();
4879        assert!(!cx.has_pending_prompt());
4880        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4881
4882        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4883        workspace.update(cx, |workspace, cx| {
4884            workspace.add_item(Box::new(item.clone()), cx);
4885        });
4886        item.update(cx, |item, cx| {
4887            item.project_items[0].update(cx, |item, _| {
4888                item.entry_id = None;
4889            });
4890            item.is_dirty = true;
4891            cx.blur();
4892        });
4893        cx.run_until_parked();
4894        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4895
4896        // Ensure autosave is prevented for deleted files also when closing the buffer.
4897        let _close_items = pane.update(cx, |pane, cx| {
4898            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4899        });
4900        cx.run_until_parked();
4901        assert!(cx.has_pending_prompt());
4902        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4903    }
4904
4905    #[gpui::test]
4906    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4907        init_test(cx);
4908
4909        let fs = FakeFs::new(cx.executor());
4910
4911        let project = Project::test(fs, [], cx).await;
4912        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4913
4914        let item = cx.new_view(|cx| {
4915            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4916        });
4917        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4918        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4919        let toolbar_notify_count = Rc::new(RefCell::new(0));
4920
4921        workspace.update(cx, |workspace, cx| {
4922            workspace.add_item(Box::new(item.clone()), cx);
4923            let toolbar_notification_count = toolbar_notify_count.clone();
4924            cx.observe(&toolbar, move |_, _, _| {
4925                *toolbar_notification_count.borrow_mut() += 1
4926            })
4927            .detach();
4928        });
4929
4930        pane.update(cx, |pane, _| {
4931            assert!(!pane.can_navigate_backward());
4932            assert!(!pane.can_navigate_forward());
4933        });
4934
4935        item.update(cx, |item, cx| {
4936            item.set_state("one".to_string(), cx);
4937        });
4938
4939        // Toolbar must be notified to re-render the navigation buttons
4940        assert_eq!(*toolbar_notify_count.borrow(), 1);
4941
4942        pane.update(cx, |pane, _| {
4943            assert!(pane.can_navigate_backward());
4944            assert!(!pane.can_navigate_forward());
4945        });
4946
4947        workspace
4948            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4949            .await
4950            .unwrap();
4951
4952        assert_eq!(*toolbar_notify_count.borrow(), 2);
4953        pane.update(cx, |pane, _| {
4954            assert!(!pane.can_navigate_backward());
4955            assert!(pane.can_navigate_forward());
4956        });
4957    }
4958
4959    #[gpui::test]
4960    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4961        init_test(cx);
4962        let fs = FakeFs::new(cx.executor());
4963
4964        let project = Project::test(fs, [], cx).await;
4965        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4966
4967        let panel = workspace.update(cx, |workspace, cx| {
4968            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4969            workspace.add_panel(panel.clone(), cx);
4970
4971            workspace
4972                .right_dock()
4973                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4974
4975            panel
4976        });
4977
4978        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4979        pane.update(cx, |pane, cx| {
4980            let item = cx.new_view(|cx| TestItem::new(cx));
4981            pane.add_item(Box::new(item), true, true, None, cx);
4982        });
4983
4984        // Transfer focus from center to panel
4985        workspace.update(cx, |workspace, cx| {
4986            workspace.toggle_panel_focus::<TestPanel>(cx);
4987        });
4988
4989        workspace.update(cx, |workspace, cx| {
4990            assert!(workspace.right_dock().read(cx).is_open());
4991            assert!(!panel.is_zoomed(cx));
4992            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4993        });
4994
4995        // Transfer focus from panel to center
4996        workspace.update(cx, |workspace, cx| {
4997            workspace.toggle_panel_focus::<TestPanel>(cx);
4998        });
4999
5000        workspace.update(cx, |workspace, cx| {
5001            assert!(workspace.right_dock().read(cx).is_open());
5002            assert!(!panel.is_zoomed(cx));
5003            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5004        });
5005
5006        // Close the dock
5007        workspace.update(cx, |workspace, cx| {
5008            workspace.toggle_dock(DockPosition::Right, cx);
5009        });
5010
5011        workspace.update(cx, |workspace, cx| {
5012            assert!(!workspace.right_dock().read(cx).is_open());
5013            assert!(!panel.is_zoomed(cx));
5014            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5015        });
5016
5017        // Open the dock
5018        workspace.update(cx, |workspace, cx| {
5019            workspace.toggle_dock(DockPosition::Right, cx);
5020        });
5021
5022        workspace.update(cx, |workspace, cx| {
5023            assert!(workspace.right_dock().read(cx).is_open());
5024            assert!(!panel.is_zoomed(cx));
5025            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5026        });
5027
5028        // Focus and zoom panel
5029        panel.update(cx, |panel, cx| {
5030            cx.focus_self();
5031            panel.set_zoomed(true, cx)
5032        });
5033
5034        workspace.update(cx, |workspace, cx| {
5035            assert!(workspace.right_dock().read(cx).is_open());
5036            assert!(panel.is_zoomed(cx));
5037            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5038        });
5039
5040        // Transfer focus to the center closes the dock
5041        workspace.update(cx, |workspace, cx| {
5042            workspace.toggle_panel_focus::<TestPanel>(cx);
5043        });
5044
5045        workspace.update(cx, |workspace, cx| {
5046            assert!(!workspace.right_dock().read(cx).is_open());
5047            assert!(panel.is_zoomed(cx));
5048            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5049        });
5050
5051        // Transferring focus back to the panel keeps it zoomed
5052        workspace.update(cx, |workspace, cx| {
5053            workspace.toggle_panel_focus::<TestPanel>(cx);
5054        });
5055
5056        workspace.update(cx, |workspace, cx| {
5057            assert!(workspace.right_dock().read(cx).is_open());
5058            assert!(panel.is_zoomed(cx));
5059            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5060        });
5061
5062        // Close the dock while it is zoomed
5063        workspace.update(cx, |workspace, cx| {
5064            workspace.toggle_dock(DockPosition::Right, cx)
5065        });
5066
5067        workspace.update(cx, |workspace, cx| {
5068            assert!(!workspace.right_dock().read(cx).is_open());
5069            assert!(panel.is_zoomed(cx));
5070            assert!(workspace.zoomed.is_none());
5071            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5072        });
5073
5074        // Opening the dock, when it's zoomed, retains focus
5075        workspace.update(cx, |workspace, cx| {
5076            workspace.toggle_dock(DockPosition::Right, cx)
5077        });
5078
5079        workspace.update(cx, |workspace, cx| {
5080            assert!(workspace.right_dock().read(cx).is_open());
5081            assert!(panel.is_zoomed(cx));
5082            assert!(workspace.zoomed.is_some());
5083            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5084        });
5085
5086        // Unzoom and close the panel, zoom the active pane.
5087        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5088        workspace.update(cx, |workspace, cx| {
5089            workspace.toggle_dock(DockPosition::Right, cx)
5090        });
5091        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5092
5093        // Opening a dock unzooms the pane.
5094        workspace.update(cx, |workspace, cx| {
5095            workspace.toggle_dock(DockPosition::Right, cx)
5096        });
5097        workspace.update(cx, |workspace, cx| {
5098            let pane = pane.read(cx);
5099            assert!(!pane.is_zoomed());
5100            assert!(!pane.focus_handle(cx).is_focused(cx));
5101            assert!(workspace.right_dock().read(cx).is_open());
5102            assert!(workspace.zoomed.is_none());
5103        });
5104    }
5105
5106    struct TestModal(FocusHandle);
5107
5108    impl TestModal {
5109        fn new(cx: &mut ViewContext<Self>) -> Self {
5110            Self(cx.focus_handle())
5111        }
5112    }
5113
5114    impl EventEmitter<DismissEvent> for TestModal {}
5115
5116    impl FocusableView for TestModal {
5117        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5118            self.0.clone()
5119        }
5120    }
5121
5122    impl ModalView for TestModal {}
5123
5124    impl Render for TestModal {
5125        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5126            div().track_focus(&self.0)
5127        }
5128    }
5129
5130    #[gpui::test]
5131    async fn test_panels(cx: &mut gpui::TestAppContext) {
5132        init_test(cx);
5133        let fs = FakeFs::new(cx.executor());
5134
5135        let project = Project::test(fs, [], cx).await;
5136        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5137
5138        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5139            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5140            workspace.add_panel(panel_1.clone(), cx);
5141            workspace
5142                .left_dock()
5143                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5144            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5145            workspace.add_panel(panel_2.clone(), cx);
5146            workspace
5147                .right_dock()
5148                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5149
5150            let left_dock = workspace.left_dock();
5151            assert_eq!(
5152                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5153                panel_1.panel_id()
5154            );
5155            assert_eq!(
5156                left_dock.read(cx).active_panel_size(cx).unwrap(),
5157                panel_1.size(cx)
5158            );
5159
5160            left_dock.update(cx, |left_dock, cx| {
5161                left_dock.resize_active_panel(Some(px(1337.)), cx)
5162            });
5163            assert_eq!(
5164                workspace
5165                    .right_dock()
5166                    .read(cx)
5167                    .visible_panel()
5168                    .unwrap()
5169                    .panel_id(),
5170                panel_2.panel_id(),
5171            );
5172
5173            (panel_1, panel_2)
5174        });
5175
5176        // Move panel_1 to the right
5177        panel_1.update(cx, |panel_1, cx| {
5178            panel_1.set_position(DockPosition::Right, cx)
5179        });
5180
5181        workspace.update(cx, |workspace, cx| {
5182            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5183            // Since it was the only panel on the left, the left dock should now be closed.
5184            assert!(!workspace.left_dock().read(cx).is_open());
5185            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5186            let right_dock = workspace.right_dock();
5187            assert_eq!(
5188                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5189                panel_1.panel_id()
5190            );
5191            assert_eq!(
5192                right_dock.read(cx).active_panel_size(cx).unwrap(),
5193                px(1337.)
5194            );
5195
5196            // Now we move panel_2 to the left
5197            panel_2.set_position(DockPosition::Left, cx);
5198        });
5199
5200        workspace.update(cx, |workspace, cx| {
5201            // Since panel_2 was not visible on the right, we don't open the left dock.
5202            assert!(!workspace.left_dock().read(cx).is_open());
5203            // And the right dock is unaffected in it's displaying of panel_1
5204            assert!(workspace.right_dock().read(cx).is_open());
5205            assert_eq!(
5206                workspace
5207                    .right_dock()
5208                    .read(cx)
5209                    .visible_panel()
5210                    .unwrap()
5211                    .panel_id(),
5212                panel_1.panel_id(),
5213            );
5214        });
5215
5216        // Move panel_1 back to the left
5217        panel_1.update(cx, |panel_1, cx| {
5218            panel_1.set_position(DockPosition::Left, cx)
5219        });
5220
5221        workspace.update(cx, |workspace, cx| {
5222            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5223            let left_dock = workspace.left_dock();
5224            assert!(left_dock.read(cx).is_open());
5225            assert_eq!(
5226                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5227                panel_1.panel_id()
5228            );
5229            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5230            // And the right dock should be closed as it no longer has any panels.
5231            assert!(!workspace.right_dock().read(cx).is_open());
5232
5233            // Now we move panel_1 to the bottom
5234            panel_1.set_position(DockPosition::Bottom, cx);
5235        });
5236
5237        workspace.update(cx, |workspace, cx| {
5238            // Since panel_1 was visible on the left, we close the left dock.
5239            assert!(!workspace.left_dock().read(cx).is_open());
5240            // The bottom dock is sized based on the panel's default size,
5241            // since the panel orientation changed from vertical to horizontal.
5242            let bottom_dock = workspace.bottom_dock();
5243            assert_eq!(
5244                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5245                panel_1.size(cx),
5246            );
5247            // Close bottom dock and move panel_1 back to the left.
5248            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5249            panel_1.set_position(DockPosition::Left, cx);
5250        });
5251
5252        // Emit activated event on panel 1
5253        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5254
5255        // Now the left dock is open and panel_1 is active and focused.
5256        workspace.update(cx, |workspace, cx| {
5257            let left_dock = workspace.left_dock();
5258            assert!(left_dock.read(cx).is_open());
5259            assert_eq!(
5260                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5261                panel_1.panel_id(),
5262            );
5263            assert!(panel_1.focus_handle(cx).is_focused(cx));
5264        });
5265
5266        // Emit closed event on panel 2, which is not active
5267        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5268
5269        // Wo don't close the left dock, because panel_2 wasn't the active panel
5270        workspace.update(cx, |workspace, cx| {
5271            let left_dock = workspace.left_dock();
5272            assert!(left_dock.read(cx).is_open());
5273            assert_eq!(
5274                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5275                panel_1.panel_id(),
5276            );
5277        });
5278
5279        // Emitting a ZoomIn event shows the panel as zoomed.
5280        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5281        workspace.update(cx, |workspace, _| {
5282            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5283            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5284        });
5285
5286        // Move panel to another dock while it is zoomed
5287        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5288        workspace.update(cx, |workspace, _| {
5289            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5290
5291            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5292        });
5293
5294        // This is a helper for getting a:
5295        // - valid focus on an element,
5296        // - that isn't a part of the panes and panels system of the Workspace,
5297        // - and doesn't trigger the 'on_focus_lost' API.
5298        let focus_other_view = {
5299            let workspace = workspace.clone();
5300            move |cx: &mut VisualTestContext| {
5301                workspace.update(cx, |workspace, cx| {
5302                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5303                        workspace.toggle_modal(cx, TestModal::new);
5304                        workspace.toggle_modal(cx, TestModal::new);
5305                    } else {
5306                        workspace.toggle_modal(cx, TestModal::new);
5307                    }
5308                })
5309            }
5310        };
5311
5312        // If focus is transferred to another view that's not a panel or another pane, we still show
5313        // the panel as zoomed.
5314        focus_other_view(cx);
5315        workspace.update(cx, |workspace, _| {
5316            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5317            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5318        });
5319
5320        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5321        workspace.update(cx, |_, cx| cx.focus_self());
5322        workspace.update(cx, |workspace, _| {
5323            assert_eq!(workspace.zoomed, None);
5324            assert_eq!(workspace.zoomed_position, None);
5325        });
5326
5327        // If focus is transferred again to another view that's not a panel or a pane, we won't
5328        // show the panel as zoomed because it wasn't zoomed before.
5329        focus_other_view(cx);
5330        workspace.update(cx, |workspace, _| {
5331            assert_eq!(workspace.zoomed, None);
5332            assert_eq!(workspace.zoomed_position, None);
5333        });
5334
5335        // When the panel is activated, it is zoomed again.
5336        cx.dispatch_action(ToggleRightDock);
5337        workspace.update(cx, |workspace, _| {
5338            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5339            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5340        });
5341
5342        // Emitting a ZoomOut event unzooms the panel.
5343        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5344        workspace.update(cx, |workspace, _| {
5345            assert_eq!(workspace.zoomed, None);
5346            assert_eq!(workspace.zoomed_position, None);
5347        });
5348
5349        // Emit closed event on panel 1, which is active
5350        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5351
5352        // Now the left dock is closed, because panel_1 was the active panel
5353        workspace.update(cx, |workspace, cx| {
5354            let right_dock = workspace.right_dock();
5355            assert!(!right_dock.read(cx).is_open());
5356        });
5357    }
5358
5359    pub fn init_test(cx: &mut TestAppContext) {
5360        cx.update(|cx| {
5361            let settings_store = SettingsStore::test(cx);
5362            cx.set_global(settings_store);
5363            theme::init(theme::LoadThemes::JustBase, cx);
5364            language::init(cx);
5365            crate::init_settings(cx);
5366            Project::init_settings(cx);
5367        });
5368    }
5369}