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    pub fn register_action<A: Action>(
3328        &mut self,
3329        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3330    ) -> &mut Self {
3331        let callback = Arc::new(callback);
3332
3333        self.workspace_actions.push(Box::new(move |div, cx| {
3334            let callback = callback.clone();
3335            div.on_action(
3336                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3337            )
3338        }));
3339        self
3340    }
3341
3342    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3343        let mut div = div
3344            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3345            .on_action(cx.listener(Self::close_all_items_and_panes))
3346            .on_action(cx.listener(Self::add_folder_to_project))
3347            .on_action(cx.listener(Self::save_all))
3348            .on_action(cx.listener(Self::open));
3349        for action in self.workspace_actions.iter() {
3350            div = (action)(div, cx)
3351        }
3352        div
3353    }
3354
3355    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3356        self.modal_layer.read(cx).has_active_modal()
3357    }
3358
3359    pub fn active_modal<V: ManagedView + 'static>(
3360        &mut self,
3361        cx: &ViewContext<Self>,
3362    ) -> Option<View<V>> {
3363        self.modal_layer.read(cx).active_modal()
3364    }
3365
3366    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3367    where
3368        B: FnOnce(&mut ViewContext<V>) -> V,
3369    {
3370        self.modal_layer
3371            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3372    }
3373}
3374
3375fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3376    let display_origin = cx
3377        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3378        .ok()??;
3379    ZED_WINDOW_POSITION
3380        .zip(*ZED_WINDOW_SIZE)
3381        .map(|(position, size)| {
3382            WindowBounds::Fixed(Bounds {
3383                origin: display_origin + position,
3384                size,
3385            })
3386        })
3387}
3388
3389fn open_items(
3390    serialized_workspace: Option<SerializedWorkspace>,
3391    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3392    app_state: Arc<AppState>,
3393    cx: &mut ViewContext<Workspace>,
3394) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3395    let restored_items = serialized_workspace.map(|serialized_workspace| {
3396        Workspace::load_workspace(
3397            serialized_workspace,
3398            project_paths_to_open
3399                .iter()
3400                .map(|(_, project_path)| project_path)
3401                .cloned()
3402                .collect(),
3403            cx,
3404        )
3405    });
3406
3407    cx.spawn(|workspace, mut cx| async move {
3408        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3409
3410        if let Some(restored_items) = restored_items {
3411            let restored_items = restored_items.await?;
3412
3413            let restored_project_paths = restored_items
3414                .iter()
3415                .filter_map(|item| {
3416                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3417                        .ok()
3418                        .flatten()
3419                })
3420                .collect::<HashSet<_>>();
3421
3422            for restored_item in restored_items {
3423                opened_items.push(restored_item.map(Ok));
3424            }
3425
3426            project_paths_to_open
3427                .iter_mut()
3428                .for_each(|(_, project_path)| {
3429                    if let Some(project_path_to_open) = project_path {
3430                        if restored_project_paths.contains(project_path_to_open) {
3431                            *project_path = None;
3432                        }
3433                    }
3434                });
3435        } else {
3436            for _ in 0..project_paths_to_open.len() {
3437                opened_items.push(None);
3438            }
3439        }
3440        assert!(opened_items.len() == project_paths_to_open.len());
3441
3442        let tasks =
3443            project_paths_to_open
3444                .into_iter()
3445                .enumerate()
3446                .map(|(i, (abs_path, project_path))| {
3447                    let workspace = workspace.clone();
3448                    cx.spawn(|mut cx| {
3449                        let fs = app_state.fs.clone();
3450                        async move {
3451                            let file_project_path = project_path?;
3452                            if fs.is_file(&abs_path).await {
3453                                Some((
3454                                    i,
3455                                    workspace
3456                                        .update(&mut cx, |workspace, cx| {
3457                                            workspace.open_path(file_project_path, None, true, cx)
3458                                        })
3459                                        .log_err()?
3460                                        .await,
3461                                ))
3462                            } else {
3463                                None
3464                            }
3465                        }
3466                    })
3467                });
3468
3469        let tasks = tasks.collect::<Vec<_>>();
3470
3471        let tasks = futures::future::join_all(tasks.into_iter());
3472        for maybe_opened_path in tasks.await.into_iter() {
3473            if let Some((i, path_open_result)) = maybe_opened_path {
3474                opened_items[i] = Some(path_open_result);
3475            }
3476        }
3477
3478        Ok(opened_items)
3479    })
3480}
3481
3482fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3483    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3484
3485    workspace
3486        .update(cx, |workspace, cx| {
3487            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3488                workspace.show_notification_once(0, cx, |cx| {
3489                    cx.new_view(|_| {
3490                        MessageNotification::new("Failed to load the database file.")
3491                            .with_click_message("Click to let us know about this error")
3492                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3493                    })
3494                });
3495            }
3496        })
3497        .log_err();
3498}
3499
3500impl FocusableView for Workspace {
3501    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3502        self.active_pane.focus_handle(cx)
3503    }
3504}
3505
3506#[derive(Clone, Render)]
3507struct DraggedDock(DockPosition);
3508
3509impl Render for Workspace {
3510    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3511        let mut context = KeyContext::default();
3512        context.add("Workspace");
3513
3514        let (ui_font, ui_font_size) = {
3515            let theme_settings = ThemeSettings::get_global(cx);
3516            (
3517                theme_settings.ui_font.family.clone(),
3518                theme_settings.ui_font_size.clone(),
3519            )
3520        };
3521
3522        let theme = cx.theme().clone();
3523        let colors = theme.colors();
3524        cx.set_rem_size(ui_font_size);
3525
3526        self.actions(div(), cx)
3527            .key_context(context)
3528            .relative()
3529            .size_full()
3530            .flex()
3531            .flex_col()
3532            .font(ui_font)
3533            .gap_0()
3534            .justify_start()
3535            .items_start()
3536            .text_color(colors.text)
3537            .bg(colors.background)
3538            .border()
3539            .border_color(colors.border)
3540            .children(self.titlebar_item.clone())
3541            .child(
3542                div()
3543                    .id("workspace")
3544                    .relative()
3545                    .flex_1()
3546                    .w_full()
3547                    .flex()
3548                    .flex_col()
3549                    .overflow_hidden()
3550                    .border_t()
3551                    .border_b()
3552                    .border_color(colors.border)
3553                    .child(
3554                        canvas(cx.listener(|workspace, bounds, _| {
3555                            workspace.bounds = *bounds;
3556                        }))
3557                        .absolute()
3558                        .size_full(),
3559                    )
3560                    .on_drag_move(
3561                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3562                            match e.drag(cx).0 {
3563                                DockPosition::Left => {
3564                                    let size = workspace.bounds.left() + e.event.position.x;
3565                                    workspace.left_dock.update(cx, |left_dock, cx| {
3566                                        left_dock.resize_active_panel(Some(size), cx);
3567                                    });
3568                                }
3569                                DockPosition::Right => {
3570                                    let size = workspace.bounds.right() - e.event.position.x;
3571                                    workspace.right_dock.update(cx, |right_dock, cx| {
3572                                        right_dock.resize_active_panel(Some(size), cx);
3573                                    });
3574                                }
3575                                DockPosition::Bottom => {
3576                                    let size = workspace.bounds.bottom() - e.event.position.y;
3577                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3578                                        bottom_dock.resize_active_panel(Some(size), cx);
3579                                    });
3580                                }
3581                            }
3582                        }),
3583                    )
3584                    .child(self.modal_layer.clone())
3585                    .child(
3586                        div()
3587                            .flex()
3588                            .flex_row()
3589                            .h_full()
3590                            // Left Dock
3591                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3592                                || {
3593                                    div()
3594                                        .flex()
3595                                        .flex_none()
3596                                        .overflow_hidden()
3597                                        .child(self.left_dock.clone())
3598                                },
3599                            ))
3600                            // Panes
3601                            .child(
3602                                div()
3603                                    .flex()
3604                                    .flex_col()
3605                                    .flex_1()
3606                                    .overflow_hidden()
3607                                    .child(self.center.render(
3608                                        &self.project,
3609                                        &self.follower_states,
3610                                        self.active_call(),
3611                                        &self.active_pane,
3612                                        self.zoomed.as_ref(),
3613                                        &self.app_state,
3614                                        cx,
3615                                    ))
3616                                    .children(
3617                                        self.zoomed_position
3618                                            .ne(&Some(DockPosition::Bottom))
3619                                            .then(|| self.bottom_dock.clone()),
3620                                    ),
3621                            )
3622                            // Right Dock
3623                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3624                                || {
3625                                    div()
3626                                        .flex()
3627                                        .flex_none()
3628                                        .overflow_hidden()
3629                                        .child(self.right_dock.clone())
3630                                },
3631                            )),
3632                    )
3633                    .children(self.render_notifications(cx))
3634                    .children(self.zoomed.as_ref().and_then(|view| {
3635                        let zoomed_view = view.upgrade()?;
3636                        let div = div()
3637                            .z_index(1)
3638                            .absolute()
3639                            .overflow_hidden()
3640                            .border_color(colors.border)
3641                            .bg(colors.background)
3642                            .child(zoomed_view)
3643                            .inset_0()
3644                            .shadow_lg();
3645
3646                        Some(match self.zoomed_position {
3647                            Some(DockPosition::Left) => div.right_2().border_r(),
3648                            Some(DockPosition::Right) => div.left_2().border_l(),
3649                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3650                            None => div.top_2().bottom_2().left_2().right_2().border(),
3651                        })
3652                    })),
3653            )
3654            .child(self.status_bar.clone())
3655            .children(if self.project.read(cx).is_disconnected() {
3656                Some(DisconnectedOverlay)
3657            } else {
3658                None
3659            })
3660    }
3661}
3662
3663impl WorkspaceStore {
3664    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3665        Self {
3666            workspaces: Default::default(),
3667            followers: Default::default(),
3668            _subscriptions: vec![
3669                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3670                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3671                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3672            ],
3673            client,
3674        }
3675    }
3676
3677    pub fn update_followers(
3678        &self,
3679        project_id: Option<u64>,
3680        update: proto::update_followers::Variant,
3681        cx: &AppContext,
3682    ) -> Option<()> {
3683        if !cx.has_global::<Model<ActiveCall>>() {
3684            return None;
3685        }
3686
3687        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3688        let follower_ids: Vec<_> = self
3689            .followers
3690            .iter()
3691            .filter_map(|follower| {
3692                if follower.project_id == project_id || project_id.is_none() {
3693                    Some(follower.peer_id.into())
3694                } else {
3695                    None
3696                }
3697            })
3698            .collect();
3699        if follower_ids.is_empty() {
3700            return None;
3701        }
3702        self.client
3703            .send(proto::UpdateFollowers {
3704                room_id,
3705                project_id,
3706                follower_ids,
3707                variant: Some(update),
3708            })
3709            .log_err()
3710    }
3711
3712    pub async fn handle_follow(
3713        this: Model<Self>,
3714        envelope: TypedEnvelope<proto::Follow>,
3715        _: Arc<Client>,
3716        mut cx: AsyncAppContext,
3717    ) -> Result<proto::FollowResponse> {
3718        this.update(&mut cx, |this, cx| {
3719            let follower = Follower {
3720                project_id: envelope.payload.project_id,
3721                peer_id: envelope.original_sender_id()?,
3722            };
3723            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3724
3725            let mut response = proto::FollowResponse::default();
3726            this.workspaces.retain(|workspace| {
3727                workspace
3728                    .update(cx, |workspace, cx| {
3729                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3730                        if response.views.is_empty() {
3731                            response.views = handler_response.views;
3732                        } else {
3733                            response.views.extend_from_slice(&handler_response.views);
3734                        }
3735
3736                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3737                            if response.active_view_id.is_none()
3738                                || Some(workspace.project.downgrade()) == active_project
3739                            {
3740                                response.active_view_id = Some(active_view_id);
3741                            }
3742                        }
3743                    })
3744                    .is_ok()
3745            });
3746
3747            if let Err(ix) = this.followers.binary_search(&follower) {
3748                this.followers.insert(ix, follower);
3749            }
3750
3751            Ok(response)
3752        })?
3753    }
3754
3755    async fn handle_unfollow(
3756        model: Model<Self>,
3757        envelope: TypedEnvelope<proto::Unfollow>,
3758        _: Arc<Client>,
3759        mut cx: AsyncAppContext,
3760    ) -> Result<()> {
3761        model.update(&mut cx, |this, _| {
3762            let follower = Follower {
3763                project_id: envelope.payload.project_id,
3764                peer_id: envelope.original_sender_id()?,
3765            };
3766            if let Ok(ix) = this.followers.binary_search(&follower) {
3767                this.followers.remove(ix);
3768            }
3769            Ok(())
3770        })?
3771    }
3772
3773    async fn handle_update_followers(
3774        this: Model<Self>,
3775        envelope: TypedEnvelope<proto::UpdateFollowers>,
3776        _: Arc<Client>,
3777        mut cx: AsyncAppContext,
3778    ) -> Result<()> {
3779        let leader_id = envelope.original_sender_id()?;
3780        let update = envelope.payload;
3781
3782        this.update(&mut cx, |this, cx| {
3783            this.workspaces.retain(|workspace| {
3784                workspace
3785                    .update(cx, |workspace, cx| {
3786                        let project_id = workspace.project.read(cx).remote_id();
3787                        if update.project_id != project_id && update.project_id.is_some() {
3788                            return;
3789                        }
3790                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3791                    })
3792                    .is_ok()
3793            });
3794            Ok(())
3795        })?
3796    }
3797}
3798
3799impl ViewId {
3800    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3801        Ok(Self {
3802            creator: message
3803                .creator
3804                .ok_or_else(|| anyhow!("creator is missing"))?,
3805            id: message.id,
3806        })
3807    }
3808
3809    pub(crate) fn to_proto(&self) -> proto::ViewId {
3810        proto::ViewId {
3811            creator: Some(self.creator),
3812            id: self.id,
3813        }
3814    }
3815}
3816
3817pub trait WorkspaceHandle {
3818    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3819}
3820
3821impl WorkspaceHandle for View<Workspace> {
3822    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3823        self.read(cx)
3824            .worktrees(cx)
3825            .flat_map(|worktree| {
3826                let worktree_id = worktree.read(cx).id();
3827                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3828                    worktree_id,
3829                    path: f.path.clone(),
3830                })
3831            })
3832            .collect::<Vec<_>>()
3833    }
3834}
3835
3836impl std::fmt::Debug for OpenPaths {
3837    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3838        f.debug_struct("OpenPaths")
3839            .field("paths", &self.paths)
3840            .finish()
3841    }
3842}
3843
3844pub fn activate_workspace_for_project(
3845    cx: &mut AppContext,
3846    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3847) -> Option<WindowHandle<Workspace>> {
3848    for window in cx.windows() {
3849        let Some(workspace) = window.downcast::<Workspace>() else {
3850            continue;
3851        };
3852
3853        let predicate = workspace
3854            .update(cx, |workspace, cx| {
3855                let project = workspace.project.read(cx);
3856                if predicate(project, cx) {
3857                    cx.activate_window();
3858                    true
3859                } else {
3860                    false
3861                }
3862            })
3863            .log_err()
3864            .unwrap_or(false);
3865
3866        if predicate {
3867            return Some(workspace);
3868        }
3869    }
3870
3871    None
3872}
3873
3874pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3875    DB.last_workspace().await.log_err().flatten()
3876}
3877
3878async fn join_channel_internal(
3879    channel_id: u64,
3880    app_state: &Arc<AppState>,
3881    requesting_window: Option<WindowHandle<Workspace>>,
3882    active_call: &Model<ActiveCall>,
3883    cx: &mut AsyncAppContext,
3884) -> Result<bool> {
3885    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3886        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3887            return (false, None);
3888        };
3889
3890        let already_in_channel = room.channel_id() == Some(channel_id);
3891        let should_prompt = room.is_sharing_project()
3892            && room.remote_participants().len() > 0
3893            && !already_in_channel;
3894        let open_room = if already_in_channel {
3895            active_call.room().cloned()
3896        } else {
3897            None
3898        };
3899        (should_prompt, open_room)
3900    })?;
3901
3902    if let Some(room) = open_room {
3903        let task = room.update(cx, |room, cx| {
3904            if let Some((project, host)) = room.most_active_project(cx) {
3905                return Some(join_remote_project(project, host, app_state.clone(), cx));
3906            }
3907
3908            None
3909        })?;
3910        if let Some(task) = task {
3911            task.await?;
3912        }
3913        return anyhow::Ok(true);
3914    }
3915
3916    if should_prompt {
3917        if let Some(workspace) = requesting_window {
3918            let answer  = workspace.update(cx, |_, cx| {
3919                cx.prompt(
3920                    PromptLevel::Warning,
3921                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3922                    &["Yes, Join Channel", "Cancel"],
3923                )
3924            })?.await;
3925
3926            if answer == Ok(1) {
3927                return Ok(false);
3928            }
3929        } else {
3930            return Ok(false); // unreachable!() hopefully
3931        }
3932    }
3933
3934    let client = cx.update(|cx| active_call.read(cx).client())?;
3935
3936    let mut client_status = client.status();
3937
3938    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3939    'outer: loop {
3940        let Some(status) = client_status.recv().await else {
3941            return Err(anyhow!("error connecting"));
3942        };
3943
3944        match status {
3945            Status::Connecting
3946            | Status::Authenticating
3947            | Status::Reconnecting
3948            | Status::Reauthenticating => continue,
3949            Status::Connected { .. } => break 'outer,
3950            Status::SignedOut => return Err(anyhow!("not signed in")),
3951            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3952            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3953                return Err(anyhow!("zed is offline"))
3954            }
3955        }
3956    }
3957
3958    let room = active_call
3959        .update(cx, |active_call, cx| {
3960            active_call.join_channel(channel_id, cx)
3961        })?
3962        .await?;
3963
3964    let Some(room) = room else {
3965        return anyhow::Ok(true);
3966    };
3967
3968    room.update(cx, |room, _| room.room_update_completed())?
3969        .await;
3970
3971    let task = room.update(cx, |room, cx| {
3972        if let Some((project, host)) = room.most_active_project(cx) {
3973            return Some(join_remote_project(project, host, app_state.clone(), cx));
3974        }
3975
3976        None
3977    })?;
3978    if let Some(task) = task {
3979        task.await?;
3980        return anyhow::Ok(true);
3981    }
3982    anyhow::Ok(false)
3983}
3984
3985pub fn join_channel(
3986    channel_id: u64,
3987    app_state: Arc<AppState>,
3988    requesting_window: Option<WindowHandle<Workspace>>,
3989    cx: &mut AppContext,
3990) -> Task<Result<()>> {
3991    let active_call = ActiveCall::global(cx);
3992    cx.spawn(|mut cx| async move {
3993        let result = join_channel_internal(
3994            channel_id,
3995            &app_state,
3996            requesting_window,
3997            &active_call,
3998            &mut cx,
3999        )
4000        .await;
4001
4002        // join channel succeeded, and opened a window
4003        if matches!(result, Ok(true)) {
4004            return anyhow::Ok(());
4005        }
4006
4007        // find an existing workspace to focus and show call controls
4008        let mut active_window =
4009            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4010        if active_window.is_none() {
4011            // no open workspaces, make one to show the error in (blergh)
4012            let (window_handle, _) = cx
4013                .update(|cx| {
4014                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4015                })?
4016                .await?;
4017
4018            active_window = Some(window_handle);
4019        }
4020
4021        if let Err(err) = result {
4022            log::error!("failed to join channel: {}", err);
4023            if let Some(active_window) = active_window {
4024                active_window
4025                    .update(&mut cx, |_, cx| {
4026                        cx.prompt(
4027                            PromptLevel::Critical,
4028                            &format!("Failed to join channel: {}", err),
4029                            &["Ok"],
4030                        )
4031                    })?
4032                    .await
4033                    .ok();
4034            }
4035        }
4036
4037        // return ok, we showed the error to the user.
4038        return anyhow::Ok(());
4039    })
4040}
4041
4042pub async fn get_any_active_workspace(
4043    app_state: Arc<AppState>,
4044    mut cx: AsyncAppContext,
4045) -> anyhow::Result<WindowHandle<Workspace>> {
4046    // find an existing workspace to focus and show call controls
4047    let active_window = activate_any_workspace_window(&mut cx);
4048    if active_window.is_none() {
4049        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4050            .await?;
4051    }
4052    activate_any_workspace_window(&mut cx).context("could not open zed")
4053}
4054
4055fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4056    cx.update(|cx| {
4057        for window in cx.windows() {
4058            if let Some(workspace_window) = window.downcast::<Workspace>() {
4059                workspace_window
4060                    .update(cx, |_, cx| cx.activate_window())
4061                    .ok();
4062                return Some(workspace_window);
4063            }
4064        }
4065        None
4066    })
4067    .ok()
4068    .flatten()
4069}
4070
4071#[allow(clippy::type_complexity)]
4072pub fn open_paths(
4073    abs_paths: &[PathBuf],
4074    app_state: &Arc<AppState>,
4075    requesting_window: Option<WindowHandle<Workspace>>,
4076    cx: &mut AppContext,
4077) -> Task<
4078    anyhow::Result<(
4079        WindowHandle<Workspace>,
4080        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4081    )>,
4082> {
4083    let app_state = app_state.clone();
4084    let abs_paths = abs_paths.to_vec();
4085    // Open paths in existing workspace if possible
4086    let existing = activate_workspace_for_project(cx, {
4087        let abs_paths = abs_paths.clone();
4088        move |project, cx| project.contains_paths(&abs_paths, cx)
4089    });
4090    cx.spawn(move |mut cx| async move {
4091        if let Some(existing) = existing {
4092            Ok((
4093                existing.clone(),
4094                existing
4095                    .update(&mut cx, |workspace, cx| {
4096                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4097                    })?
4098                    .await,
4099            ))
4100        } else {
4101            cx.update(move |cx| {
4102                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4103            })?
4104            .await
4105        }
4106    })
4107}
4108
4109pub fn open_new(
4110    app_state: &Arc<AppState>,
4111    cx: &mut AppContext,
4112    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4113) -> Task<()> {
4114    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4115    cx.spawn(|mut cx| async move {
4116        if let Some((workspace, opened_paths)) = task.await.log_err() {
4117            workspace
4118                .update(&mut cx, |workspace, cx| {
4119                    if opened_paths.is_empty() {
4120                        init(workspace, cx)
4121                    }
4122                })
4123                .log_err();
4124        }
4125    })
4126}
4127
4128pub fn create_and_open_local_file(
4129    path: &'static Path,
4130    cx: &mut ViewContext<Workspace>,
4131    default_content: impl 'static + Send + FnOnce() -> Rope,
4132) -> Task<Result<Box<dyn ItemHandle>>> {
4133    cx.spawn(|workspace, mut cx| async move {
4134        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4135        if !fs.is_file(path).await {
4136            fs.create_file(path, Default::default()).await?;
4137            fs.save(path, &default_content(), Default::default())
4138                .await?;
4139        }
4140
4141        let mut items = workspace
4142            .update(&mut cx, |workspace, cx| {
4143                workspace.with_local_workspace(cx, |workspace, cx| {
4144                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4145                })
4146            })?
4147            .await?
4148            .await;
4149
4150        let item = items.pop().flatten();
4151        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4152    })
4153}
4154
4155pub fn join_remote_project(
4156    project_id: u64,
4157    follow_user_id: u64,
4158    app_state: Arc<AppState>,
4159    cx: &mut AppContext,
4160) -> Task<Result<()>> {
4161    let windows = cx.windows();
4162    cx.spawn(|mut cx| async move {
4163        let existing_workspace = windows.into_iter().find_map(|window| {
4164            window.downcast::<Workspace>().and_then(|window| {
4165                window
4166                    .update(&mut cx, |workspace, cx| {
4167                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4168                            Some(window)
4169                        } else {
4170                            None
4171                        }
4172                    })
4173                    .unwrap_or(None)
4174            })
4175        });
4176
4177        let workspace = if let Some(existing_workspace) = existing_workspace {
4178            existing_workspace
4179        } else {
4180            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4181            let room = active_call
4182                .read_with(&cx, |call, _| call.room().cloned())?
4183                .ok_or_else(|| anyhow!("not in a call"))?;
4184            let project = room
4185                .update(&mut cx, |room, cx| {
4186                    room.join_project(
4187                        project_id,
4188                        app_state.languages.clone(),
4189                        app_state.fs.clone(),
4190                        cx,
4191                    )
4192                })?
4193                .await?;
4194
4195            let window_bounds_override = window_bounds_env_override(&cx);
4196            cx.update(|cx| {
4197                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4198                cx.open_window(options, |cx| {
4199                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4200                })
4201            })?
4202        };
4203
4204        workspace.update(&mut cx, |workspace, cx| {
4205            cx.activate(true);
4206            cx.activate_window();
4207
4208            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4209                let follow_peer_id = room
4210                    .read(cx)
4211                    .remote_participants()
4212                    .iter()
4213                    .find(|(_, participant)| participant.user.id == follow_user_id)
4214                    .map(|(_, p)| p.peer_id)
4215                    .or_else(|| {
4216                        // If we couldn't follow the given user, follow the host instead.
4217                        let collaborator = workspace
4218                            .project()
4219                            .read(cx)
4220                            .collaborators()
4221                            .values()
4222                            .find(|collaborator| collaborator.replica_id == 0)?;
4223                        Some(collaborator.peer_id)
4224                    });
4225
4226                if let Some(follow_peer_id) = follow_peer_id {
4227                    workspace.follow(follow_peer_id, cx);
4228                }
4229            }
4230        })?;
4231
4232        anyhow::Ok(())
4233    })
4234}
4235
4236pub fn restart(_: &Restart, cx: &mut AppContext) {
4237    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4238    let mut workspace_windows = cx
4239        .windows()
4240        .into_iter()
4241        .filter_map(|window| window.downcast::<Workspace>())
4242        .collect::<Vec<_>>();
4243
4244    // If multiple windows have unsaved changes, and need a save prompt,
4245    // prompt in the active window before switching to a different window.
4246    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4247
4248    let mut prompt = None;
4249    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4250        prompt = window
4251            .update(cx, |_, cx| {
4252                cx.prompt(
4253                    PromptLevel::Info,
4254                    "Are you sure you want to restart?",
4255                    &["Restart", "Cancel"],
4256                )
4257            })
4258            .ok();
4259    }
4260
4261    cx.spawn(|mut cx| async move {
4262        if let Some(prompt) = prompt {
4263            let answer = prompt.await?;
4264            if answer != 0 {
4265                return Ok(());
4266            }
4267        }
4268
4269        // If the user cancels any save prompt, then keep the app open.
4270        for window in workspace_windows {
4271            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4272                workspace.prepare_to_close(true, cx)
4273            }) {
4274                if !should_close.await? {
4275                    return Ok(());
4276                }
4277            }
4278        }
4279
4280        cx.update(|cx| cx.restart())
4281    })
4282    .detach_and_log_err(cx);
4283}
4284
4285fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4286    let mut parts = value.split(',');
4287    let x: usize = parts.next()?.parse().ok()?;
4288    let y: usize = parts.next()?.parse().ok()?;
4289    Some(point((x as f64).into(), (y as f64).into()))
4290}
4291
4292fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4293    let mut parts = value.split(',');
4294    let width: usize = parts.next()?.parse().ok()?;
4295    let height: usize = parts.next()?.parse().ok()?;
4296    Some(size((width as f64).into(), (height as f64).into()))
4297}
4298
4299struct DisconnectedOverlay;
4300
4301impl Element for DisconnectedOverlay {
4302    type State = AnyElement;
4303
4304    fn request_layout(
4305        &mut self,
4306        _: Option<Self::State>,
4307        cx: &mut WindowContext,
4308    ) -> (LayoutId, Self::State) {
4309        let mut background = cx.theme().colors().elevated_surface_background;
4310        background.fade_out(0.2);
4311        let mut overlay = div()
4312            .bg(background)
4313            .absolute()
4314            .left_0()
4315            .top_0()
4316            .size_full()
4317            .flex()
4318            .items_center()
4319            .justify_center()
4320            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4321            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4322            .child(Label::new(
4323                "Your connection to the remote project has been lost.",
4324            ))
4325            .into_any();
4326        (overlay.request_layout(cx), overlay)
4327    }
4328
4329    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
4330        cx.with_z_index(u8::MAX, |cx| {
4331            cx.add_opaque_layer(bounds);
4332            overlay.paint(cx);
4333        })
4334    }
4335}
4336
4337impl IntoElement for DisconnectedOverlay {
4338    type Element = Self;
4339
4340    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4341        None
4342    }
4343
4344    fn into_element(self) -> Self::Element {
4345        self
4346    }
4347}
4348
4349#[cfg(test)]
4350mod tests {
4351    use std::{cell::RefCell, rc::Rc};
4352
4353    use super::*;
4354    use crate::{
4355        dock::{test::TestPanel, PanelEvent},
4356        item::{
4357            test::{TestItem, TestProjectItem},
4358            ItemEvent,
4359        },
4360    };
4361    use fs::FakeFs;
4362    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4363    use project::{Project, ProjectEntryId};
4364    use serde_json::json;
4365    use settings::SettingsStore;
4366
4367    #[gpui::test]
4368    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4369        init_test(cx);
4370
4371        let fs = FakeFs::new(cx.executor());
4372        let project = Project::test(fs, [], cx).await;
4373        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4374
4375        // Adding an item with no ambiguity renders the tab without detail.
4376        let item1 = cx.new_view(|cx| {
4377            let mut item = TestItem::new(cx);
4378            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4379            item
4380        });
4381        workspace.update(cx, |workspace, cx| {
4382            workspace.add_item(Box::new(item1.clone()), cx);
4383        });
4384        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4385
4386        // Adding an item that creates ambiguity increases the level of detail on
4387        // both tabs.
4388        let item2 = cx.new_view(|cx| {
4389            let mut item = TestItem::new(cx);
4390            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4391            item
4392        });
4393        workspace.update(cx, |workspace, cx| {
4394            workspace.add_item(Box::new(item2.clone()), cx);
4395        });
4396        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4397        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4398
4399        // Adding an item that creates ambiguity increases the level of detail only
4400        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4401        // we stop at the highest detail available.
4402        let item3 = cx.new_view(|cx| {
4403            let mut item = TestItem::new(cx);
4404            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4405            item
4406        });
4407        workspace.update(cx, |workspace, cx| {
4408            workspace.add_item(Box::new(item3.clone()), cx);
4409        });
4410        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4411        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4412        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4413    }
4414
4415    #[gpui::test]
4416    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4417        init_test(cx);
4418
4419        let fs = FakeFs::new(cx.executor());
4420        fs.insert_tree(
4421            "/root1",
4422            json!({
4423                "one.txt": "",
4424                "two.txt": "",
4425            }),
4426        )
4427        .await;
4428        fs.insert_tree(
4429            "/root2",
4430            json!({
4431                "three.txt": "",
4432            }),
4433        )
4434        .await;
4435
4436        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4437        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4438        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4439        let worktree_id = project.update(cx, |project, cx| {
4440            project.worktrees().next().unwrap().read(cx).id()
4441        });
4442
4443        let item1 = cx.new_view(|cx| {
4444            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4445        });
4446        let item2 = cx.new_view(|cx| {
4447            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4448        });
4449
4450        // Add an item to an empty pane
4451        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4452        project.update(cx, |project, cx| {
4453            assert_eq!(
4454                project.active_entry(),
4455                project
4456                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4457                    .map(|e| e.id)
4458            );
4459        });
4460        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4461
4462        // Add a second item to a non-empty pane
4463        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4464        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4465        project.update(cx, |project, cx| {
4466            assert_eq!(
4467                project.active_entry(),
4468                project
4469                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4470                    .map(|e| e.id)
4471            );
4472        });
4473
4474        // Close the active item
4475        pane.update(cx, |pane, cx| {
4476            pane.close_active_item(&Default::default(), cx).unwrap()
4477        })
4478        .await
4479        .unwrap();
4480        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4481        project.update(cx, |project, cx| {
4482            assert_eq!(
4483                project.active_entry(),
4484                project
4485                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4486                    .map(|e| e.id)
4487            );
4488        });
4489
4490        // Add a project folder
4491        project
4492            .update(cx, |project, cx| {
4493                project.find_or_create_local_worktree("/root2", true, cx)
4494            })
4495            .await
4496            .unwrap();
4497        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4498
4499        // Remove a project folder
4500        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4501        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4502    }
4503
4504    #[gpui::test]
4505    async fn test_close_window(cx: &mut TestAppContext) {
4506        init_test(cx);
4507
4508        let fs = FakeFs::new(cx.executor());
4509        fs.insert_tree("/root", json!({ "one": "" })).await;
4510
4511        let project = Project::test(fs, ["root".as_ref()], cx).await;
4512        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4513
4514        // When there are no dirty items, there's nothing to do.
4515        let item1 = cx.new_view(|cx| TestItem::new(cx));
4516        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4517        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4518        assert!(task.await.unwrap());
4519
4520        // When there are dirty untitled items, prompt to save each one. If the user
4521        // cancels any prompt, then abort.
4522        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4523        let item3 = cx.new_view(|cx| {
4524            TestItem::new(cx)
4525                .with_dirty(true)
4526                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4527        });
4528        workspace.update(cx, |w, cx| {
4529            w.add_item(Box::new(item2.clone()), cx);
4530            w.add_item(Box::new(item3.clone()), cx);
4531        });
4532        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4533        cx.executor().run_until_parked();
4534        cx.simulate_prompt_answer(2); // cancel save all
4535        cx.executor().run_until_parked();
4536        cx.simulate_prompt_answer(2); // cancel save all
4537        cx.executor().run_until_parked();
4538        assert!(!cx.has_pending_prompt());
4539        assert!(!task.await.unwrap());
4540    }
4541
4542    #[gpui::test]
4543    async fn test_close_pane_items(cx: &mut TestAppContext) {
4544        init_test(cx);
4545
4546        let fs = FakeFs::new(cx.executor());
4547
4548        let project = Project::test(fs, None, cx).await;
4549        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4550
4551        let item1 = cx.new_view(|cx| {
4552            TestItem::new(cx)
4553                .with_dirty(true)
4554                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4555        });
4556        let item2 = cx.new_view(|cx| {
4557            TestItem::new(cx)
4558                .with_dirty(true)
4559                .with_conflict(true)
4560                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4561        });
4562        let item3 = cx.new_view(|cx| {
4563            TestItem::new(cx)
4564                .with_dirty(true)
4565                .with_conflict(true)
4566                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4567        });
4568        let item4 = cx.new_view(|cx| {
4569            TestItem::new(cx)
4570                .with_dirty(true)
4571                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4572        });
4573        let pane = workspace.update(cx, |workspace, cx| {
4574            workspace.add_item(Box::new(item1.clone()), cx);
4575            workspace.add_item(Box::new(item2.clone()), cx);
4576            workspace.add_item(Box::new(item3.clone()), cx);
4577            workspace.add_item(Box::new(item4.clone()), cx);
4578            workspace.active_pane().clone()
4579        });
4580
4581        let close_items = pane.update(cx, |pane, cx| {
4582            pane.activate_item(1, true, true, cx);
4583            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4584            let item1_id = item1.item_id();
4585            let item3_id = item3.item_id();
4586            let item4_id = item4.item_id();
4587            pane.close_items(cx, SaveIntent::Close, move |id| {
4588                [item1_id, item3_id, item4_id].contains(&id)
4589            })
4590        });
4591        cx.executor().run_until_parked();
4592
4593        assert!(cx.has_pending_prompt());
4594        // Ignore "Save all" prompt
4595        cx.simulate_prompt_answer(2);
4596        cx.executor().run_until_parked();
4597        // There's a prompt to save item 1.
4598        pane.update(cx, |pane, _| {
4599            assert_eq!(pane.items_len(), 4);
4600            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4601        });
4602        // Confirm saving item 1.
4603        cx.simulate_prompt_answer(0);
4604        cx.executor().run_until_parked();
4605
4606        // Item 1 is saved. There's a prompt to save item 3.
4607        pane.update(cx, |pane, cx| {
4608            assert_eq!(item1.read(cx).save_count, 1);
4609            assert_eq!(item1.read(cx).save_as_count, 0);
4610            assert_eq!(item1.read(cx).reload_count, 0);
4611            assert_eq!(pane.items_len(), 3);
4612            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4613        });
4614        assert!(cx.has_pending_prompt());
4615
4616        // Cancel saving item 3.
4617        cx.simulate_prompt_answer(1);
4618        cx.executor().run_until_parked();
4619
4620        // Item 3 is reloaded. There's a prompt to save item 4.
4621        pane.update(cx, |pane, cx| {
4622            assert_eq!(item3.read(cx).save_count, 0);
4623            assert_eq!(item3.read(cx).save_as_count, 0);
4624            assert_eq!(item3.read(cx).reload_count, 1);
4625            assert_eq!(pane.items_len(), 2);
4626            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4627        });
4628        assert!(cx.has_pending_prompt());
4629
4630        // Confirm saving item 4.
4631        cx.simulate_prompt_answer(0);
4632        cx.executor().run_until_parked();
4633
4634        // There's a prompt for a path for item 4.
4635        cx.simulate_new_path_selection(|_| Some(Default::default()));
4636        close_items.await.unwrap();
4637
4638        // The requested items are closed.
4639        pane.update(cx, |pane, cx| {
4640            assert_eq!(item4.read(cx).save_count, 0);
4641            assert_eq!(item4.read(cx).save_as_count, 1);
4642            assert_eq!(item4.read(cx).reload_count, 0);
4643            assert_eq!(pane.items_len(), 1);
4644            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4645        });
4646    }
4647
4648    #[gpui::test]
4649    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4650        init_test(cx);
4651
4652        let fs = FakeFs::new(cx.executor());
4653        let project = Project::test(fs, [], cx).await;
4654        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4655
4656        // Create several workspace items with single project entries, and two
4657        // workspace items with multiple project entries.
4658        let single_entry_items = (0..=4)
4659            .map(|project_entry_id| {
4660                cx.new_view(|cx| {
4661                    TestItem::new(cx)
4662                        .with_dirty(true)
4663                        .with_project_items(&[TestProjectItem::new(
4664                            project_entry_id,
4665                            &format!("{project_entry_id}.txt"),
4666                            cx,
4667                        )])
4668                })
4669            })
4670            .collect::<Vec<_>>();
4671        let item_2_3 = cx.new_view(|cx| {
4672            TestItem::new(cx)
4673                .with_dirty(true)
4674                .with_singleton(false)
4675                .with_project_items(&[
4676                    single_entry_items[2].read(cx).project_items[0].clone(),
4677                    single_entry_items[3].read(cx).project_items[0].clone(),
4678                ])
4679        });
4680        let item_3_4 = cx.new_view(|cx| {
4681            TestItem::new(cx)
4682                .with_dirty(true)
4683                .with_singleton(false)
4684                .with_project_items(&[
4685                    single_entry_items[3].read(cx).project_items[0].clone(),
4686                    single_entry_items[4].read(cx).project_items[0].clone(),
4687                ])
4688        });
4689
4690        // Create two panes that contain the following project entries:
4691        //   left pane:
4692        //     multi-entry items:   (2, 3)
4693        //     single-entry items:  0, 1, 2, 3, 4
4694        //   right pane:
4695        //     single-entry items:  1
4696        //     multi-entry items:   (3, 4)
4697        let left_pane = workspace.update(cx, |workspace, cx| {
4698            let left_pane = workspace.active_pane().clone();
4699            workspace.add_item(Box::new(item_2_3.clone()), cx);
4700            for item in single_entry_items {
4701                workspace.add_item(Box::new(item), cx);
4702            }
4703            left_pane.update(cx, |pane, cx| {
4704                pane.activate_item(2, true, true, cx);
4705            });
4706
4707            let right_pane = workspace
4708                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4709                .unwrap();
4710
4711            right_pane.update(cx, |pane, cx| {
4712                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4713            });
4714
4715            left_pane
4716        });
4717
4718        cx.focus_view(&left_pane);
4719
4720        // When closing all of the items in the left pane, we should be prompted twice:
4721        // once for project entry 0, and once for project entry 2. Project entries 1,
4722        // 3, and 4 are all still open in the other paten. After those two
4723        // prompts, the task should complete.
4724
4725        let close = left_pane.update(cx, |pane, cx| {
4726            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4727        });
4728        cx.executor().run_until_parked();
4729
4730        // Discard "Save all" prompt
4731        cx.simulate_prompt_answer(2);
4732
4733        cx.executor().run_until_parked();
4734        left_pane.update(cx, |pane, cx| {
4735            assert_eq!(
4736                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4737                &[ProjectEntryId::from_proto(0)]
4738            );
4739        });
4740        cx.simulate_prompt_answer(0);
4741
4742        cx.executor().run_until_parked();
4743        left_pane.update(cx, |pane, cx| {
4744            assert_eq!(
4745                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4746                &[ProjectEntryId::from_proto(2)]
4747            );
4748        });
4749        cx.simulate_prompt_answer(0);
4750
4751        cx.executor().run_until_parked();
4752        close.await.unwrap();
4753        left_pane.update(cx, |pane, _| {
4754            assert_eq!(pane.items_len(), 0);
4755        });
4756    }
4757
4758    #[gpui::test]
4759    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4760        init_test(cx);
4761
4762        let fs = FakeFs::new(cx.executor());
4763        let project = Project::test(fs, [], cx).await;
4764        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4765        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4766
4767        let item = cx.new_view(|cx| {
4768            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4769        });
4770        let item_id = item.entity_id();
4771        workspace.update(cx, |workspace, cx| {
4772            workspace.add_item(Box::new(item.clone()), cx);
4773        });
4774
4775        // Autosave on window change.
4776        item.update(cx, |item, cx| {
4777            cx.update_global(|settings: &mut SettingsStore, cx| {
4778                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4779                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4780                })
4781            });
4782            item.is_dirty = true;
4783        });
4784
4785        // Deactivating the window saves the file.
4786        cx.deactivate_window();
4787        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4788
4789        // Autosave on focus change.
4790        item.update(cx, |item, cx| {
4791            cx.focus_self();
4792            cx.update_global(|settings: &mut SettingsStore, cx| {
4793                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4794                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4795                })
4796            });
4797            item.is_dirty = true;
4798        });
4799
4800        // Blurring the item saves the file.
4801        item.update(cx, |_, cx| cx.blur());
4802        cx.executor().run_until_parked();
4803        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4804
4805        // Deactivating the window still saves the file.
4806        cx.update(|cx| cx.activate_window());
4807        item.update(cx, |item, cx| {
4808            cx.focus_self();
4809            item.is_dirty = true;
4810        });
4811        cx.deactivate_window();
4812
4813        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4814
4815        // Autosave after delay.
4816        item.update(cx, |item, cx| {
4817            cx.update_global(|settings: &mut SettingsStore, cx| {
4818                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4819                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4820                })
4821            });
4822            item.is_dirty = true;
4823            cx.emit(ItemEvent::Edit);
4824        });
4825
4826        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4827        cx.executor().advance_clock(Duration::from_millis(250));
4828        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4829
4830        // After delay expires, the file is saved.
4831        cx.executor().advance_clock(Duration::from_millis(250));
4832        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4833
4834        // Autosave on focus change, ensuring closing the tab counts as such.
4835        item.update(cx, |item, cx| {
4836            cx.update_global(|settings: &mut SettingsStore, cx| {
4837                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4838                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4839                })
4840            });
4841            item.is_dirty = true;
4842        });
4843
4844        pane.update(cx, |pane, cx| {
4845            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4846        })
4847        .await
4848        .unwrap();
4849        assert!(!cx.has_pending_prompt());
4850        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4851
4852        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4853        workspace.update(cx, |workspace, cx| {
4854            workspace.add_item(Box::new(item.clone()), cx);
4855        });
4856        item.update(cx, |item, cx| {
4857            item.project_items[0].update(cx, |item, _| {
4858                item.entry_id = None;
4859            });
4860            item.is_dirty = true;
4861            cx.blur();
4862        });
4863        cx.run_until_parked();
4864        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4865
4866        // Ensure autosave is prevented for deleted files also when closing the buffer.
4867        let _close_items = pane.update(cx, |pane, cx| {
4868            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4869        });
4870        cx.run_until_parked();
4871        assert!(cx.has_pending_prompt());
4872        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4873    }
4874
4875    #[gpui::test]
4876    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4877        init_test(cx);
4878
4879        let fs = FakeFs::new(cx.executor());
4880
4881        let project = Project::test(fs, [], cx).await;
4882        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4883
4884        let item = cx.new_view(|cx| {
4885            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4886        });
4887        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4888        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4889        let toolbar_notify_count = Rc::new(RefCell::new(0));
4890
4891        workspace.update(cx, |workspace, cx| {
4892            workspace.add_item(Box::new(item.clone()), cx);
4893            let toolbar_notification_count = toolbar_notify_count.clone();
4894            cx.observe(&toolbar, move |_, _, _| {
4895                *toolbar_notification_count.borrow_mut() += 1
4896            })
4897            .detach();
4898        });
4899
4900        pane.update(cx, |pane, _| {
4901            assert!(!pane.can_navigate_backward());
4902            assert!(!pane.can_navigate_forward());
4903        });
4904
4905        item.update(cx, |item, cx| {
4906            item.set_state("one".to_string(), cx);
4907        });
4908
4909        // Toolbar must be notified to re-render the navigation buttons
4910        assert_eq!(*toolbar_notify_count.borrow(), 1);
4911
4912        pane.update(cx, |pane, _| {
4913            assert!(pane.can_navigate_backward());
4914            assert!(!pane.can_navigate_forward());
4915        });
4916
4917        workspace
4918            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4919            .await
4920            .unwrap();
4921
4922        assert_eq!(*toolbar_notify_count.borrow(), 2);
4923        pane.update(cx, |pane, _| {
4924            assert!(!pane.can_navigate_backward());
4925            assert!(pane.can_navigate_forward());
4926        });
4927    }
4928
4929    #[gpui::test]
4930    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4931        init_test(cx);
4932        let fs = FakeFs::new(cx.executor());
4933
4934        let project = Project::test(fs, [], cx).await;
4935        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4936
4937        let panel = workspace.update(cx, |workspace, cx| {
4938            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4939            workspace.add_panel(panel.clone(), cx);
4940
4941            workspace
4942                .right_dock()
4943                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4944
4945            panel
4946        });
4947
4948        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4949        pane.update(cx, |pane, cx| {
4950            let item = cx.new_view(|cx| TestItem::new(cx));
4951            pane.add_item(Box::new(item), true, true, None, cx);
4952        });
4953
4954        // Transfer focus from center to panel
4955        workspace.update(cx, |workspace, cx| {
4956            workspace.toggle_panel_focus::<TestPanel>(cx);
4957        });
4958
4959        workspace.update(cx, |workspace, cx| {
4960            assert!(workspace.right_dock().read(cx).is_open());
4961            assert!(!panel.is_zoomed(cx));
4962            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4963        });
4964
4965        // Transfer focus from panel to center
4966        workspace.update(cx, |workspace, cx| {
4967            workspace.toggle_panel_focus::<TestPanel>(cx);
4968        });
4969
4970        workspace.update(cx, |workspace, cx| {
4971            assert!(workspace.right_dock().read(cx).is_open());
4972            assert!(!panel.is_zoomed(cx));
4973            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4974        });
4975
4976        // Close the dock
4977        workspace.update(cx, |workspace, cx| {
4978            workspace.toggle_dock(DockPosition::Right, cx);
4979        });
4980
4981        workspace.update(cx, |workspace, cx| {
4982            assert!(!workspace.right_dock().read(cx).is_open());
4983            assert!(!panel.is_zoomed(cx));
4984            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4985        });
4986
4987        // Open the dock
4988        workspace.update(cx, |workspace, cx| {
4989            workspace.toggle_dock(DockPosition::Right, cx);
4990        });
4991
4992        workspace.update(cx, |workspace, cx| {
4993            assert!(workspace.right_dock().read(cx).is_open());
4994            assert!(!panel.is_zoomed(cx));
4995            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4996        });
4997
4998        // Focus and zoom panel
4999        panel.update(cx, |panel, cx| {
5000            cx.focus_self();
5001            panel.set_zoomed(true, cx)
5002        });
5003
5004        workspace.update(cx, |workspace, cx| {
5005            assert!(workspace.right_dock().read(cx).is_open());
5006            assert!(panel.is_zoomed(cx));
5007            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5008        });
5009
5010        // Transfer focus to the center closes the dock
5011        workspace.update(cx, |workspace, cx| {
5012            workspace.toggle_panel_focus::<TestPanel>(cx);
5013        });
5014
5015        workspace.update(cx, |workspace, cx| {
5016            assert!(!workspace.right_dock().read(cx).is_open());
5017            assert!(panel.is_zoomed(cx));
5018            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5019        });
5020
5021        // Transferring focus back to the panel keeps it zoomed
5022        workspace.update(cx, |workspace, cx| {
5023            workspace.toggle_panel_focus::<TestPanel>(cx);
5024        });
5025
5026        workspace.update(cx, |workspace, cx| {
5027            assert!(workspace.right_dock().read(cx).is_open());
5028            assert!(panel.is_zoomed(cx));
5029            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5030        });
5031
5032        // Close the dock while it is zoomed
5033        workspace.update(cx, |workspace, cx| {
5034            workspace.toggle_dock(DockPosition::Right, cx)
5035        });
5036
5037        workspace.update(cx, |workspace, cx| {
5038            assert!(!workspace.right_dock().read(cx).is_open());
5039            assert!(panel.is_zoomed(cx));
5040            assert!(workspace.zoomed.is_none());
5041            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5042        });
5043
5044        // Opening the dock, when it's zoomed, retains focus
5045        workspace.update(cx, |workspace, cx| {
5046            workspace.toggle_dock(DockPosition::Right, cx)
5047        });
5048
5049        workspace.update(cx, |workspace, cx| {
5050            assert!(workspace.right_dock().read(cx).is_open());
5051            assert!(panel.is_zoomed(cx));
5052            assert!(workspace.zoomed.is_some());
5053            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5054        });
5055
5056        // Unzoom and close the panel, zoom the active pane.
5057        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5058        workspace.update(cx, |workspace, cx| {
5059            workspace.toggle_dock(DockPosition::Right, cx)
5060        });
5061        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5062
5063        // Opening a dock unzooms the pane.
5064        workspace.update(cx, |workspace, cx| {
5065            workspace.toggle_dock(DockPosition::Right, cx)
5066        });
5067        workspace.update(cx, |workspace, cx| {
5068            let pane = pane.read(cx);
5069            assert!(!pane.is_zoomed());
5070            assert!(!pane.focus_handle(cx).is_focused(cx));
5071            assert!(workspace.right_dock().read(cx).is_open());
5072            assert!(workspace.zoomed.is_none());
5073        });
5074    }
5075
5076    struct TestModal(FocusHandle);
5077
5078    impl TestModal {
5079        fn new(cx: &mut ViewContext<Self>) -> Self {
5080            Self(cx.focus_handle())
5081        }
5082    }
5083
5084    impl EventEmitter<DismissEvent> for TestModal {}
5085
5086    impl FocusableView for TestModal {
5087        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5088            self.0.clone()
5089        }
5090    }
5091
5092    impl ModalView for TestModal {}
5093
5094    impl Render for TestModal {
5095        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5096            div().track_focus(&self.0)
5097        }
5098    }
5099
5100    #[gpui::test]
5101    async fn test_panels(cx: &mut gpui::TestAppContext) {
5102        init_test(cx);
5103        let fs = FakeFs::new(cx.executor());
5104
5105        let project = Project::test(fs, [], cx).await;
5106        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5107
5108        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5109            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5110            workspace.add_panel(panel_1.clone(), cx);
5111            workspace
5112                .left_dock()
5113                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5114            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5115            workspace.add_panel(panel_2.clone(), cx);
5116            workspace
5117                .right_dock()
5118                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5119
5120            let left_dock = workspace.left_dock();
5121            assert_eq!(
5122                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5123                panel_1.panel_id()
5124            );
5125            assert_eq!(
5126                left_dock.read(cx).active_panel_size(cx).unwrap(),
5127                panel_1.size(cx)
5128            );
5129
5130            left_dock.update(cx, |left_dock, cx| {
5131                left_dock.resize_active_panel(Some(px(1337.)), cx)
5132            });
5133            assert_eq!(
5134                workspace
5135                    .right_dock()
5136                    .read(cx)
5137                    .visible_panel()
5138                    .unwrap()
5139                    .panel_id(),
5140                panel_2.panel_id(),
5141            );
5142
5143            (panel_1, panel_2)
5144        });
5145
5146        // Move panel_1 to the right
5147        panel_1.update(cx, |panel_1, cx| {
5148            panel_1.set_position(DockPosition::Right, cx)
5149        });
5150
5151        workspace.update(cx, |workspace, cx| {
5152            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5153            // Since it was the only panel on the left, the left dock should now be closed.
5154            assert!(!workspace.left_dock().read(cx).is_open());
5155            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5156            let right_dock = workspace.right_dock();
5157            assert_eq!(
5158                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5159                panel_1.panel_id()
5160            );
5161            assert_eq!(
5162                right_dock.read(cx).active_panel_size(cx).unwrap(),
5163                px(1337.)
5164            );
5165
5166            // Now we move panel_2 to the left
5167            panel_2.set_position(DockPosition::Left, cx);
5168        });
5169
5170        workspace.update(cx, |workspace, cx| {
5171            // Since panel_2 was not visible on the right, we don't open the left dock.
5172            assert!(!workspace.left_dock().read(cx).is_open());
5173            // And the right dock is unaffected in it's displaying of panel_1
5174            assert!(workspace.right_dock().read(cx).is_open());
5175            assert_eq!(
5176                workspace
5177                    .right_dock()
5178                    .read(cx)
5179                    .visible_panel()
5180                    .unwrap()
5181                    .panel_id(),
5182                panel_1.panel_id(),
5183            );
5184        });
5185
5186        // Move panel_1 back to the left
5187        panel_1.update(cx, |panel_1, cx| {
5188            panel_1.set_position(DockPosition::Left, cx)
5189        });
5190
5191        workspace.update(cx, |workspace, cx| {
5192            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5193            let left_dock = workspace.left_dock();
5194            assert!(left_dock.read(cx).is_open());
5195            assert_eq!(
5196                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5197                panel_1.panel_id()
5198            );
5199            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5200            // And the right dock should be closed as it no longer has any panels.
5201            assert!(!workspace.right_dock().read(cx).is_open());
5202
5203            // Now we move panel_1 to the bottom
5204            panel_1.set_position(DockPosition::Bottom, cx);
5205        });
5206
5207        workspace.update(cx, |workspace, cx| {
5208            // Since panel_1 was visible on the left, we close the left dock.
5209            assert!(!workspace.left_dock().read(cx).is_open());
5210            // The bottom dock is sized based on the panel's default size,
5211            // since the panel orientation changed from vertical to horizontal.
5212            let bottom_dock = workspace.bottom_dock();
5213            assert_eq!(
5214                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5215                panel_1.size(cx),
5216            );
5217            // Close bottom dock and move panel_1 back to the left.
5218            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5219            panel_1.set_position(DockPosition::Left, cx);
5220        });
5221
5222        // Emit activated event on panel 1
5223        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5224
5225        // Now the left dock is open and panel_1 is active and focused.
5226        workspace.update(cx, |workspace, cx| {
5227            let left_dock = workspace.left_dock();
5228            assert!(left_dock.read(cx).is_open());
5229            assert_eq!(
5230                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5231                panel_1.panel_id(),
5232            );
5233            assert!(panel_1.focus_handle(cx).is_focused(cx));
5234        });
5235
5236        // Emit closed event on panel 2, which is not active
5237        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5238
5239        // Wo don't close the left dock, because panel_2 wasn't the active panel
5240        workspace.update(cx, |workspace, cx| {
5241            let left_dock = workspace.left_dock();
5242            assert!(left_dock.read(cx).is_open());
5243            assert_eq!(
5244                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5245                panel_1.panel_id(),
5246            );
5247        });
5248
5249        // Emitting a ZoomIn event shows the panel as zoomed.
5250        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5251        workspace.update(cx, |workspace, _| {
5252            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5253            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5254        });
5255
5256        // Move panel to another dock while it is zoomed
5257        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5258        workspace.update(cx, |workspace, _| {
5259            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5260
5261            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5262        });
5263
5264        // This is a helper for getting a:
5265        // - valid focus on an element,
5266        // - that isn't a part of the panes and panels system of the Workspace,
5267        // - and doesn't trigger the 'on_focus_lost' API.
5268        let focus_other_view = {
5269            let workspace = workspace.clone();
5270            move |cx: &mut VisualTestContext| {
5271                workspace.update(cx, |workspace, cx| {
5272                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5273                        workspace.toggle_modal(cx, TestModal::new);
5274                        workspace.toggle_modal(cx, TestModal::new);
5275                    } else {
5276                        workspace.toggle_modal(cx, TestModal::new);
5277                    }
5278                })
5279            }
5280        };
5281
5282        // If focus is transferred to another view that's not a panel or another pane, we still show
5283        // the panel as zoomed.
5284        focus_other_view(cx);
5285        workspace.update(cx, |workspace, _| {
5286            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5287            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5288        });
5289
5290        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5291        workspace.update(cx, |_, cx| cx.focus_self());
5292        workspace.update(cx, |workspace, _| {
5293            assert_eq!(workspace.zoomed, None);
5294            assert_eq!(workspace.zoomed_position, None);
5295        });
5296
5297        // If focus is transferred again to another view that's not a panel or a pane, we won't
5298        // show the panel as zoomed because it wasn't zoomed before.
5299        focus_other_view(cx);
5300        workspace.update(cx, |workspace, _| {
5301            assert_eq!(workspace.zoomed, None);
5302            assert_eq!(workspace.zoomed_position, None);
5303        });
5304
5305        // When the panel is activated, it is zoomed again.
5306        cx.dispatch_action(ToggleRightDock);
5307        workspace.update(cx, |workspace, _| {
5308            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5309            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5310        });
5311
5312        // Emitting a ZoomOut event unzooms the panel.
5313        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5314        workspace.update(cx, |workspace, _| {
5315            assert_eq!(workspace.zoomed, None);
5316            assert_eq!(workspace.zoomed_position, None);
5317        });
5318
5319        // Emit closed event on panel 1, which is active
5320        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5321
5322        // Now the left dock is closed, because panel_1 was the active panel
5323        workspace.update(cx, |workspace, cx| {
5324            let right_dock = workspace.right_dock();
5325            assert!(!right_dock.read(cx).is_open());
5326        });
5327    }
5328
5329    pub fn init_test(cx: &mut TestAppContext) {
5330        cx.update(|cx| {
5331            let settings_store = SettingsStore::test(cx);
5332            cx.set_global(settings_store);
5333            theme::init(theme::LoadThemes::JustBase, cx);
5334            language::init(cx);
5335            crate::init_settings(cx);
5336            Project::init_settings(cx);
5337        });
5338    }
5339}