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