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()
1275            .telemetry()
1276            .report_app_event("open project".to_string());
1277        let paths = cx.prompt_for_paths(PathPromptOptions {
1278            files: true,
1279            directories: true,
1280            multiple: true,
1281        });
1282
1283        cx.spawn(|this, mut cx| async move {
1284            let Some(paths) = paths.await.log_err().flatten() else {
1285                return;
1286            };
1287
1288            if let Some(task) = this
1289                .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1290                .log_err()
1291            {
1292                task.await.log_err();
1293            }
1294        })
1295        .detach()
1296    }
1297
1298    pub fn open_workspace_for_paths(
1299        &mut self,
1300        paths: Vec<PathBuf>,
1301        cx: &mut ViewContext<Self>,
1302    ) -> Task<Result<()>> {
1303        let window = cx.window_handle().downcast::<Self>();
1304        let is_remote = self.project.read(cx).is_remote();
1305        let has_worktree = self.project.read(cx).worktrees().next().is_some();
1306        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1307        let close_task = if is_remote || has_worktree || has_dirty_items {
1308            None
1309        } else {
1310            Some(self.prepare_to_close(false, cx))
1311        };
1312        let app_state = self.app_state.clone();
1313
1314        cx.spawn(|_, mut cx| async move {
1315            let window_to_replace = if let Some(close_task) = close_task {
1316                if !close_task.await? {
1317                    return Ok(());
1318                }
1319                window
1320            } else {
1321                None
1322            };
1323            cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))?
1324                .await?;
1325            Ok(())
1326        })
1327    }
1328
1329    #[allow(clippy::type_complexity)]
1330    pub fn open_paths(
1331        &mut self,
1332        mut abs_paths: Vec<PathBuf>,
1333        visible: OpenVisible,
1334        pane: Option<WeakView<Pane>>,
1335        cx: &mut ViewContext<Self>,
1336    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1337        log::info!("open paths {abs_paths:?}");
1338
1339        let fs = self.app_state.fs.clone();
1340
1341        // Sort the paths to ensure we add worktrees for parents before their children.
1342        abs_paths.sort_unstable();
1343        cx.spawn(move |this, mut cx| async move {
1344            let mut tasks = Vec::with_capacity(abs_paths.len());
1345
1346            for abs_path in &abs_paths {
1347                let visible = match visible {
1348                    OpenVisible::All => Some(true),
1349                    OpenVisible::None => Some(false),
1350                    OpenVisible::OnlyFiles => match fs.metadata(abs_path).await.log_err() {
1351                        Some(Some(metadata)) => Some(!metadata.is_dir),
1352                        Some(None) => {
1353                            log::error!("No metadata for file {abs_path:?}");
1354                            None
1355                        }
1356                        None => None,
1357                    },
1358                    OpenVisible::OnlyDirectories => match fs.metadata(abs_path).await.log_err() {
1359                        Some(Some(metadata)) => Some(metadata.is_dir),
1360                        Some(None) => {
1361                            log::error!("No metadata for file {abs_path:?}");
1362                            None
1363                        }
1364                        None => None,
1365                    },
1366                };
1367                let project_path = match visible {
1368                    Some(visible) => match this
1369                        .update(&mut cx, |this, cx| {
1370                            Workspace::project_path_for_path(
1371                                this.project.clone(),
1372                                abs_path,
1373                                visible,
1374                                cx,
1375                            )
1376                        })
1377                        .log_err()
1378                    {
1379                        Some(project_path) => project_path.await.log_err(),
1380                        None => None,
1381                    },
1382                    None => None,
1383                };
1384
1385                let this = this.clone();
1386                let abs_path = abs_path.clone();
1387                let fs = fs.clone();
1388                let pane = pane.clone();
1389                let task = cx.spawn(move |mut cx| async move {
1390                    let (worktree, project_path) = project_path?;
1391                    if fs.is_file(&abs_path).await {
1392                        Some(
1393                            this.update(&mut cx, |this, cx| {
1394                                this.open_path(project_path, pane, true, cx)
1395                            })
1396                            .log_err()?
1397                            .await,
1398                        )
1399                    } else {
1400                        this.update(&mut cx, |workspace, cx| {
1401                            let worktree = worktree.read(cx);
1402                            let worktree_abs_path = worktree.abs_path();
1403                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
1404                                worktree.root_entry()
1405                            } else {
1406                                abs_path
1407                                    .strip_prefix(worktree_abs_path.as_ref())
1408                                    .ok()
1409                                    .and_then(|relative_path| {
1410                                        worktree.entry_for_path(relative_path)
1411                                    })
1412                            }
1413                            .map(|entry| entry.id);
1414                            if let Some(entry_id) = entry_id {
1415                                workspace.project.update(cx, |_, cx| {
1416                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1417                                })
1418                            }
1419                        })
1420                        .log_err()?;
1421                        None
1422                    }
1423                });
1424                tasks.push(task);
1425            }
1426
1427            futures::future::join_all(tasks).await
1428        })
1429    }
1430
1431    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1432        let paths = cx.prompt_for_paths(PathPromptOptions {
1433            files: false,
1434            directories: true,
1435            multiple: true,
1436        });
1437        cx.spawn(|this, mut cx| async move {
1438            if let Some(paths) = paths.await.log_err().flatten() {
1439                let results = this
1440                    .update(&mut cx, |this, cx| {
1441                        this.open_paths(paths, OpenVisible::All, None, cx)
1442                    })?
1443                    .await;
1444                for result in results.into_iter().flatten() {
1445                    result.log_err();
1446                }
1447            }
1448            anyhow::Ok(())
1449        })
1450        .detach_and_log_err(cx);
1451    }
1452
1453    fn project_path_for_path(
1454        project: Model<Project>,
1455        abs_path: &Path,
1456        visible: bool,
1457        cx: &mut AppContext,
1458    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1459        let entry = project.update(cx, |project, cx| {
1460            project.find_or_create_local_worktree(abs_path, visible, cx)
1461        });
1462        cx.spawn(|mut cx| async move {
1463            let (worktree, path) = entry.await?;
1464            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1465            Ok((
1466                worktree,
1467                ProjectPath {
1468                    worktree_id,
1469                    path: path.into(),
1470                },
1471            ))
1472        })
1473    }
1474
1475    pub fn items<'a>(
1476        &'a self,
1477        cx: &'a AppContext,
1478    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1479        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1480    }
1481
1482    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1483        self.items_of_type(cx).max_by_key(|item| item.item_id())
1484    }
1485
1486    pub fn items_of_type<'a, T: Item>(
1487        &'a self,
1488        cx: &'a AppContext,
1489    ) -> impl 'a + Iterator<Item = View<T>> {
1490        self.panes
1491            .iter()
1492            .flat_map(|pane| pane.read(cx).items_of_type())
1493    }
1494
1495    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1496        self.active_pane().read(cx).active_item()
1497    }
1498
1499    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1500        let item = self.active_item(cx)?;
1501        item.to_any().downcast::<I>().ok()
1502    }
1503
1504    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1505        self.active_item(cx).and_then(|item| item.project_path(cx))
1506    }
1507
1508    pub fn save_active_item(
1509        &mut self,
1510        save_intent: SaveIntent,
1511        cx: &mut ViewContext<Self>,
1512    ) -> Task<Result<()>> {
1513        let project = self.project.clone();
1514        let pane = self.active_pane();
1515        let item_ix = pane.read(cx).active_item_index();
1516        let item = pane.read(cx).active_item();
1517        let pane = pane.downgrade();
1518
1519        cx.spawn(|_, mut cx| async move {
1520            if let Some(item) = item {
1521                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1522                    .await
1523                    .map(|_| ())
1524            } else {
1525                Ok(())
1526            }
1527        })
1528    }
1529
1530    pub fn close_inactive_items_and_panes(
1531        &mut self,
1532        _: &CloseInactiveTabsAndPanes,
1533        cx: &mut ViewContext<Self>,
1534    ) {
1535        self.close_all_internal(true, SaveIntent::Close, cx)
1536            .map(|task| task.detach_and_log_err(cx));
1537    }
1538
1539    pub fn close_all_items_and_panes(
1540        &mut self,
1541        action: &CloseAllItemsAndPanes,
1542        cx: &mut ViewContext<Self>,
1543    ) {
1544        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1545            .map(|task| task.detach_and_log_err(cx));
1546    }
1547
1548    fn close_all_internal(
1549        &mut self,
1550        retain_active_pane: bool,
1551        save_intent: SaveIntent,
1552        cx: &mut ViewContext<Self>,
1553    ) -> Option<Task<Result<()>>> {
1554        let current_pane = self.active_pane();
1555
1556        let mut tasks = Vec::new();
1557
1558        if retain_active_pane {
1559            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1560                pane.close_inactive_items(&CloseInactiveItems, cx)
1561            }) {
1562                tasks.push(current_pane_close);
1563            };
1564        }
1565
1566        for pane in self.panes() {
1567            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1568                continue;
1569            }
1570
1571            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1572                pane.close_all_items(
1573                    &CloseAllItems {
1574                        save_intent: Some(save_intent),
1575                    },
1576                    cx,
1577                )
1578            }) {
1579                tasks.push(close_pane_items)
1580            }
1581        }
1582
1583        if tasks.is_empty() {
1584            None
1585        } else {
1586            Some(cx.spawn(|_, _| async move {
1587                for task in tasks {
1588                    task.await?
1589                }
1590                Ok(())
1591            }))
1592        }
1593    }
1594
1595    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1596        let dock = match dock_side {
1597            DockPosition::Left => &self.left_dock,
1598            DockPosition::Bottom => &self.bottom_dock,
1599            DockPosition::Right => &self.right_dock,
1600        };
1601        let mut focus_center = false;
1602        let mut reveal_dock = false;
1603        dock.update(cx, |dock, cx| {
1604            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1605            let was_visible = dock.is_open() && !other_is_zoomed;
1606            dock.set_open(!was_visible, cx);
1607
1608            if let Some(active_panel) = dock.active_panel() {
1609                if was_visible {
1610                    if active_panel.focus_handle(cx).contains_focused(cx) {
1611                        focus_center = true;
1612                    }
1613                } else {
1614                    let focus_handle = &active_panel.focus_handle(cx);
1615                    cx.focus(focus_handle);
1616                    reveal_dock = true;
1617                }
1618            }
1619        });
1620
1621        if reveal_dock {
1622            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1623        }
1624
1625        if focus_center {
1626            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1627        }
1628
1629        cx.notify();
1630        self.serialize_workspace(cx);
1631    }
1632
1633    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1634        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1635
1636        for dock in docks {
1637            dock.update(cx, |dock, cx| {
1638                dock.set_open(false, cx);
1639            });
1640        }
1641
1642        cx.focus_self();
1643        cx.notify();
1644        self.serialize_workspace(cx);
1645    }
1646
1647    /// Transfer focus to the panel of the given type.
1648    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1649        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1650        panel.to_any().downcast().ok()
1651    }
1652
1653    /// Focus the panel of the given type if it isn't already focused. If it is
1654    /// already focused, then transfer focus back to the workspace center.
1655    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1656        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
1657            !panel.focus_handle(cx).contains_focused(cx)
1658        });
1659    }
1660
1661    /// Focus or unfocus the given panel type, depending on the given callback.
1662    fn focus_or_unfocus_panel<T: Panel>(
1663        &mut self,
1664        cx: &mut ViewContext<Self>,
1665        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1666    ) -> Option<Arc<dyn PanelHandle>> {
1667        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1668            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1669                let mut focus_center = false;
1670                let panel = dock.update(cx, |dock, cx| {
1671                    dock.activate_panel(panel_index, cx);
1672
1673                    let panel = dock.active_panel().cloned();
1674                    if let Some(panel) = panel.as_ref() {
1675                        if should_focus(&**panel, cx) {
1676                            dock.set_open(true, cx);
1677                            panel.focus_handle(cx).focus(cx);
1678                        } else {
1679                            focus_center = true;
1680                        }
1681                    }
1682                    panel
1683                });
1684
1685                if focus_center {
1686                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1687                }
1688
1689                self.serialize_workspace(cx);
1690                cx.notify();
1691                return panel;
1692            }
1693        }
1694        None
1695    }
1696
1697    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1698        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1699            let dock = dock.read(cx);
1700            if let Some(panel) = dock.panel::<T>() {
1701                return Some(panel);
1702            }
1703        }
1704        None
1705    }
1706
1707    fn dismiss_zoomed_items_to_reveal(
1708        &mut self,
1709        dock_to_reveal: Option<DockPosition>,
1710        cx: &mut ViewContext<Self>,
1711    ) {
1712        // If a center pane is zoomed, unzoom it.
1713        for pane in &self.panes {
1714            if pane != &self.active_pane || dock_to_reveal.is_some() {
1715                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1716            }
1717        }
1718
1719        // If another dock is zoomed, hide it.
1720        let mut focus_center = false;
1721        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1722            dock.update(cx, |dock, cx| {
1723                if Some(dock.position()) != dock_to_reveal {
1724                    if let Some(panel) = dock.active_panel() {
1725                        if panel.is_zoomed(cx) {
1726                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
1727                            dock.set_open(false, cx);
1728                        }
1729                    }
1730                }
1731            });
1732        }
1733
1734        if focus_center {
1735            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1736        }
1737
1738        if self.zoomed_position != dock_to_reveal {
1739            self.zoomed = None;
1740            self.zoomed_position = None;
1741        }
1742
1743        cx.notify();
1744    }
1745
1746    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1747        let pane = cx.new_view(|cx| {
1748            Pane::new(
1749                self.weak_handle(),
1750                self.project.clone(),
1751                self.pane_history_timestamp.clone(),
1752                None,
1753                cx,
1754            )
1755        });
1756        cx.subscribe(&pane, Self::handle_pane_event).detach();
1757        self.panes.push(pane.clone());
1758        cx.focus_view(&pane);
1759        cx.emit(Event::PaneAdded(pane.clone()));
1760        pane
1761    }
1762
1763    pub fn add_item_to_center(
1764        &mut self,
1765        item: Box<dyn ItemHandle>,
1766        cx: &mut ViewContext<Self>,
1767    ) -> bool {
1768        if let Some(center_pane) = self.last_active_center_pane.clone() {
1769            if let Some(center_pane) = center_pane.upgrade() {
1770                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1771                true
1772            } else {
1773                false
1774            }
1775        } else {
1776            false
1777        }
1778    }
1779
1780    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1781        if let Some(text) = item.telemetry_event_text(cx) {
1782            self.client()
1783                .telemetry()
1784                .report_app_event(format!("{}: open", text));
1785        }
1786
1787        self.active_pane
1788            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1789    }
1790
1791    pub fn split_item(
1792        &mut self,
1793        split_direction: SplitDirection,
1794        item: Box<dyn ItemHandle>,
1795        cx: &mut ViewContext<Self>,
1796    ) {
1797        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1798        new_pane.update(cx, move |new_pane, cx| {
1799            new_pane.add_item(item, true, true, None, cx)
1800        })
1801    }
1802
1803    pub fn open_abs_path(
1804        &mut self,
1805        abs_path: PathBuf,
1806        visible: bool,
1807        cx: &mut ViewContext<Self>,
1808    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1809        cx.spawn(|workspace, mut cx| async move {
1810            let open_paths_task_result = workspace
1811                .update(&mut cx, |workspace, cx| {
1812                    workspace.open_paths(
1813                        vec![abs_path.clone()],
1814                        if visible {
1815                            OpenVisible::All
1816                        } else {
1817                            OpenVisible::None
1818                        },
1819                        None,
1820                        cx,
1821                    )
1822                })
1823                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1824                .await;
1825            anyhow::ensure!(
1826                open_paths_task_result.len() == 1,
1827                "open abs path {abs_path:?} task returned incorrect number of results"
1828            );
1829            match open_paths_task_result
1830                .into_iter()
1831                .next()
1832                .expect("ensured single task result")
1833            {
1834                Some(open_result) => {
1835                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1836                }
1837                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1838            }
1839        })
1840    }
1841
1842    pub fn split_abs_path(
1843        &mut self,
1844        abs_path: PathBuf,
1845        visible: bool,
1846        cx: &mut ViewContext<Self>,
1847    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1848        let project_path_task =
1849            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1850        cx.spawn(|this, mut cx| async move {
1851            let (_, path) = project_path_task.await?;
1852            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1853                .await
1854        })
1855    }
1856
1857    pub fn open_path(
1858        &mut self,
1859        path: impl Into<ProjectPath>,
1860        pane: Option<WeakView<Pane>>,
1861        focus_item: bool,
1862        cx: &mut ViewContext<Self>,
1863    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1864        let pane = pane.unwrap_or_else(|| {
1865            self.last_active_center_pane.clone().unwrap_or_else(|| {
1866                self.panes
1867                    .first()
1868                    .expect("There must be an active pane")
1869                    .downgrade()
1870            })
1871        });
1872
1873        let task = self.load_path(path.into(), cx);
1874        cx.spawn(move |_, mut cx| async move {
1875            let (project_entry_id, build_item) = task.await?;
1876            pane.update(&mut cx, |pane, cx| {
1877                pane.open_item(project_entry_id, focus_item, cx, build_item)
1878            })
1879        })
1880    }
1881
1882    pub fn split_path(
1883        &mut self,
1884        path: impl Into<ProjectPath>,
1885        cx: &mut ViewContext<Self>,
1886    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1887        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1888            self.panes
1889                .first()
1890                .expect("There must be an active pane")
1891                .downgrade()
1892        });
1893
1894        if let Member::Pane(center_pane) = &self.center.root {
1895            if center_pane.read(cx).items_len() == 0 {
1896                return self.open_path(path, Some(pane), true, cx);
1897            }
1898        }
1899
1900        let task = self.load_path(path.into(), cx);
1901        cx.spawn(|this, mut cx| async move {
1902            let (project_entry_id, build_item) = task.await?;
1903            this.update(&mut cx, move |this, cx| -> Option<_> {
1904                let pane = pane.upgrade()?;
1905                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
1906                new_pane.update(cx, |new_pane, cx| {
1907                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
1908                })
1909            })
1910            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
1911        })
1912    }
1913
1914    fn load_path(
1915        &mut self,
1916        path: ProjectPath,
1917        cx: &mut ViewContext<Self>,
1918    ) -> Task<
1919        Result<(
1920            Option<ProjectEntryId>,
1921            impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1922        )>,
1923    > {
1924        let project = self.project().clone();
1925        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1926        cx.spawn(|_, mut cx| async move {
1927            let (project_entry_id, project_item) = project_item.await?;
1928            let build_item = cx.update(|_, cx| {
1929                cx.default_global::<ProjectItemBuilders>()
1930                    .get(&project_item.entity_type())
1931                    .ok_or_else(|| anyhow!("no item builder for project item"))
1932                    .cloned()
1933            })??;
1934            let build_item =
1935                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1936            Ok((project_entry_id, build_item))
1937        })
1938    }
1939
1940    pub fn open_project_item<T>(
1941        &mut self,
1942        project_item: Model<T::Item>,
1943        cx: &mut ViewContext<Self>,
1944    ) -> View<T>
1945    where
1946        T: ProjectItem,
1947    {
1948        use project::Item as _;
1949
1950        let entry_id = project_item.read(cx).entry_id(cx);
1951        if let Some(item) = entry_id
1952            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1953            .and_then(|item| item.downcast())
1954        {
1955            self.activate_item(&item, cx);
1956            return item;
1957        }
1958
1959        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1960        self.add_item(Box::new(item.clone()), cx);
1961        item
1962    }
1963
1964    pub fn split_project_item<T>(
1965        &mut self,
1966        project_item: Model<T::Item>,
1967        cx: &mut ViewContext<Self>,
1968    ) -> View<T>
1969    where
1970        T: ProjectItem,
1971    {
1972        use project::Item as _;
1973
1974        let entry_id = project_item.read(cx).entry_id(cx);
1975        if let Some(item) = entry_id
1976            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1977            .and_then(|item| item.downcast())
1978        {
1979            self.activate_item(&item, cx);
1980            return item;
1981        }
1982
1983        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1984        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
1985        item
1986    }
1987
1988    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1989        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1990            self.active_pane.update(cx, |pane, cx| {
1991                pane.add_item(Box::new(shared_screen), false, true, None, cx)
1992            });
1993        }
1994    }
1995
1996    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1997        let result = self.panes.iter().find_map(|pane| {
1998            pane.read(cx)
1999                .index_for_item(item)
2000                .map(|ix| (pane.clone(), ix))
2001        });
2002        if let Some((pane, ix)) = result {
2003            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2004            true
2005        } else {
2006            false
2007        }
2008    }
2009
2010    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2011        let panes = self.center.panes();
2012        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2013            cx.focus_view(&pane);
2014        } else {
2015            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2016        }
2017    }
2018
2019    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2020        let panes = self.center.panes();
2021        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2022            let next_ix = (ix + 1) % panes.len();
2023            let next_pane = panes[next_ix].clone();
2024            cx.focus_view(&next_pane);
2025        }
2026    }
2027
2028    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2029        let panes = self.center.panes();
2030        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2031            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2032            let prev_pane = panes[prev_ix].clone();
2033            cx.focus_view(&prev_pane);
2034        }
2035    }
2036
2037    pub fn activate_pane_in_direction(
2038        &mut self,
2039        direction: SplitDirection,
2040        cx: &mut ViewContext<Self>,
2041    ) {
2042        if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2043            cx.focus_view(pane);
2044        }
2045    }
2046
2047    pub fn swap_pane_in_direction(
2048        &mut self,
2049        direction: SplitDirection,
2050        cx: &mut ViewContext<Self>,
2051    ) {
2052        if let Some(to) = self
2053            .find_pane_in_direction(direction, cx)
2054            .map(|pane| pane.clone())
2055        {
2056            self.center.swap(&self.active_pane.clone(), &to);
2057            cx.notify();
2058        }
2059    }
2060
2061    fn find_pane_in_direction(
2062        &mut self,
2063        direction: SplitDirection,
2064        cx: &mut ViewContext<Self>,
2065    ) -> Option<&View<Pane>> {
2066        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2067            return None;
2068        };
2069        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2070        let center = match cursor {
2071            Some(cursor) if bounding_box.contains(&cursor) => cursor,
2072            _ => bounding_box.center(),
2073        };
2074
2075        let distance_to_next = pane_group::HANDLE_HITBOX_SIZE;
2076
2077        let target = match direction {
2078            SplitDirection::Left => {
2079                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
2080            }
2081            SplitDirection::Right => {
2082                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2083            }
2084            SplitDirection::Up => {
2085                Point::new(center.x, bounding_box.top() - distance_to_next.into())
2086            }
2087            SplitDirection::Down => {
2088                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
2089            }
2090        };
2091        self.center.pane_at_pixel_position(target)
2092    }
2093
2094    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2095        if self.active_pane != pane {
2096            self.active_pane = pane.clone();
2097            self.status_bar.update(cx, |status_bar, cx| {
2098                status_bar.set_active_pane(&self.active_pane, cx);
2099            });
2100            self.active_item_path_changed(cx);
2101            self.last_active_center_pane = Some(pane.downgrade());
2102        }
2103
2104        self.dismiss_zoomed_items_to_reveal(None, cx);
2105        if pane.read(cx).is_zoomed() {
2106            self.zoomed = Some(pane.downgrade().into());
2107        } else {
2108            self.zoomed = None;
2109        }
2110        self.zoomed_position = None;
2111        self.update_active_view_for_followers(cx);
2112
2113        cx.notify();
2114    }
2115
2116    fn handle_pane_event(
2117        &mut self,
2118        pane: View<Pane>,
2119        event: &pane::Event,
2120        cx: &mut ViewContext<Self>,
2121    ) {
2122        match event {
2123            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2124            pane::Event::Split(direction) => {
2125                self.split_and_clone(pane, *direction, cx);
2126            }
2127            pane::Event::Remove => self.remove_pane(pane, cx),
2128            pane::Event::ActivateItem { local } => {
2129                if *local {
2130                    self.unfollow(&pane, cx);
2131                }
2132                if &pane == self.active_pane() {
2133                    self.active_item_path_changed(cx);
2134                    self.update_active_view_for_followers(cx);
2135                }
2136            }
2137            pane::Event::ChangeItemTitle => {
2138                if pane == self.active_pane {
2139                    self.active_item_path_changed(cx);
2140                }
2141                self.update_window_edited(cx);
2142            }
2143            pane::Event::RemoveItem { item_id } => {
2144                self.update_window_edited(cx);
2145                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2146                    if entry.get().entity_id() == pane.entity_id() {
2147                        entry.remove();
2148                    }
2149                }
2150            }
2151            pane::Event::Focus => {
2152                self.handle_pane_focused(pane.clone(), cx);
2153            }
2154            pane::Event::ZoomIn => {
2155                if pane == self.active_pane {
2156                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2157                    if pane.read(cx).has_focus(cx) {
2158                        self.zoomed = Some(pane.downgrade().into());
2159                        self.zoomed_position = None;
2160                    }
2161                    cx.notify();
2162                }
2163            }
2164            pane::Event::ZoomOut => {
2165                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2166                if self.zoomed_position.is_none() {
2167                    self.zoomed = None;
2168                }
2169                cx.notify();
2170            }
2171        }
2172
2173        self.serialize_workspace(cx);
2174    }
2175
2176    pub fn split_pane(
2177        &mut self,
2178        pane_to_split: View<Pane>,
2179        split_direction: SplitDirection,
2180        cx: &mut ViewContext<Self>,
2181    ) -> View<Pane> {
2182        let new_pane = self.add_pane(cx);
2183        self.center
2184            .split(&pane_to_split, &new_pane, split_direction)
2185            .unwrap();
2186        cx.notify();
2187        new_pane
2188    }
2189
2190    pub fn split_and_clone(
2191        &mut self,
2192        pane: View<Pane>,
2193        direction: SplitDirection,
2194        cx: &mut ViewContext<Self>,
2195    ) -> Option<View<Pane>> {
2196        let item = pane.read(cx).active_item()?;
2197        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2198            let new_pane = self.add_pane(cx);
2199            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2200            self.center.split(&pane, &new_pane, direction).unwrap();
2201            Some(new_pane)
2202        } else {
2203            None
2204        };
2205        cx.notify();
2206        maybe_pane_handle
2207    }
2208
2209    pub fn split_pane_with_item(
2210        &mut self,
2211        pane_to_split: WeakView<Pane>,
2212        split_direction: SplitDirection,
2213        from: WeakView<Pane>,
2214        item_id_to_move: EntityId,
2215        cx: &mut ViewContext<Self>,
2216    ) {
2217        let Some(pane_to_split) = pane_to_split.upgrade() else {
2218            return;
2219        };
2220        let Some(from) = from.upgrade() else {
2221            return;
2222        };
2223
2224        let new_pane = self.add_pane(cx);
2225        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2226        self.center
2227            .split(&pane_to_split, &new_pane, split_direction)
2228            .unwrap();
2229        cx.notify();
2230    }
2231
2232    pub fn split_pane_with_project_entry(
2233        &mut self,
2234        pane_to_split: WeakView<Pane>,
2235        split_direction: SplitDirection,
2236        project_entry: ProjectEntryId,
2237        cx: &mut ViewContext<Self>,
2238    ) -> Option<Task<Result<()>>> {
2239        let pane_to_split = pane_to_split.upgrade()?;
2240        let new_pane = self.add_pane(cx);
2241        self.center
2242            .split(&pane_to_split, &new_pane, split_direction)
2243            .unwrap();
2244
2245        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2246        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2247        Some(cx.foreground_executor().spawn(async move {
2248            task.await?;
2249            Ok(())
2250        }))
2251    }
2252
2253    pub fn move_item(
2254        &mut self,
2255        source: View<Pane>,
2256        destination: View<Pane>,
2257        item_id_to_move: EntityId,
2258        destination_index: usize,
2259        cx: &mut ViewContext<Self>,
2260    ) {
2261        let item_to_move = source
2262            .read(cx)
2263            .items()
2264            .enumerate()
2265            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move);
2266
2267        if item_to_move.is_none() {
2268            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2269            return;
2270        }
2271        let (item_ix, item_handle) = item_to_move.unwrap();
2272        let item_handle = item_handle.clone();
2273
2274        if source != destination {
2275            // Close item from previous pane
2276            source.update(cx, |source, cx| {
2277                source.remove_item(item_ix, false, cx);
2278            });
2279        }
2280
2281        // This automatically removes duplicate items in the pane
2282        destination.update(cx, |destination, cx| {
2283            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2284            destination.focus(cx)
2285        });
2286    }
2287
2288    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2289        if self.center.remove(&pane).unwrap() {
2290            self.force_remove_pane(&pane, cx);
2291            self.unfollow(&pane, cx);
2292            self.last_leaders_by_pane.remove(&pane.downgrade());
2293            for removed_item in pane.read(cx).items() {
2294                self.panes_by_item.remove(&removed_item.item_id());
2295            }
2296
2297            cx.notify();
2298        } else {
2299            self.active_item_path_changed(cx);
2300        }
2301    }
2302
2303    pub fn panes(&self) -> &[View<Pane>] {
2304        &self.panes
2305    }
2306
2307    pub fn active_pane(&self) -> &View<Pane> {
2308        &self.active_pane
2309    }
2310
2311    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2312        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2313        weak_pane.upgrade()
2314    }
2315
2316    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2317        self.follower_states.retain(|_, state| {
2318            if state.leader_id == peer_id {
2319                for item in state.items_by_leader_view_id.values() {
2320                    item.set_leader_peer_id(None, cx);
2321                }
2322                false
2323            } else {
2324                true
2325            }
2326        });
2327        cx.notify();
2328    }
2329
2330    pub fn start_following(
2331        &mut self,
2332        leader_id: PeerId,
2333        cx: &mut ViewContext<Self>,
2334    ) -> Option<Task<Result<()>>> {
2335        let pane = self.active_pane().clone();
2336
2337        self.last_leaders_by_pane
2338            .insert(pane.downgrade(), leader_id);
2339        self.unfollow(&pane, cx);
2340        self.follower_states.insert(
2341            pane.clone(),
2342            FollowerState {
2343                leader_id,
2344                active_view_id: None,
2345                items_by_leader_view_id: Default::default(),
2346            },
2347        );
2348        cx.notify();
2349
2350        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2351        let project_id = self.project.read(cx).remote_id();
2352        let request = self.app_state.client.request(proto::Follow {
2353            room_id,
2354            project_id,
2355            leader_id: Some(leader_id),
2356        });
2357
2358        Some(cx.spawn(|this, mut cx| async move {
2359            let response = request.await?;
2360            this.update(&mut cx, |this, _| {
2361                let state = this
2362                    .follower_states
2363                    .get_mut(&pane)
2364                    .ok_or_else(|| anyhow!("following interrupted"))?;
2365                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2366                    Some(ViewId::from_proto(active_view_id)?)
2367                } else {
2368                    None
2369                };
2370                Ok::<_, anyhow::Error>(())
2371            })??;
2372            Self::add_views_from_leader(
2373                this.clone(),
2374                leader_id,
2375                vec![pane],
2376                response.views,
2377                &mut cx,
2378            )
2379            .await?;
2380            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2381            Ok(())
2382        }))
2383    }
2384
2385    pub fn follow_next_collaborator(
2386        &mut self,
2387        _: &FollowNextCollaborator,
2388        cx: &mut ViewContext<Self>,
2389    ) {
2390        let collaborators = self.project.read(cx).collaborators();
2391        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2392            let mut collaborators = collaborators.keys().copied();
2393            for peer_id in collaborators.by_ref() {
2394                if peer_id == leader_id {
2395                    break;
2396                }
2397            }
2398            collaborators.next()
2399        } else if let Some(last_leader_id) =
2400            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2401        {
2402            if collaborators.contains_key(last_leader_id) {
2403                Some(*last_leader_id)
2404            } else {
2405                None
2406            }
2407        } else {
2408            None
2409        };
2410
2411        let pane = self.active_pane.clone();
2412        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2413        else {
2414            return;
2415        };
2416        if Some(leader_id) == self.unfollow(&pane, cx) {
2417            return;
2418        }
2419        self.start_following(leader_id, cx)
2420            .map(|task| task.detach_and_log_err(cx));
2421    }
2422
2423    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
2424        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
2425            return;
2426        };
2427        let room = room.read(cx);
2428        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2429            return;
2430        };
2431
2432        let project = self.project.read(cx);
2433
2434        let other_project_id = match remote_participant.location {
2435            call::ParticipantLocation::External => None,
2436            call::ParticipantLocation::UnsharedProject => None,
2437            call::ParticipantLocation::SharedProject { project_id } => {
2438                if Some(project_id) == project.remote_id() {
2439                    None
2440                } else {
2441                    Some(project_id)
2442                }
2443            }
2444        };
2445
2446        // if they are active in another project, follow there.
2447        if let Some(project_id) = other_project_id {
2448            let app_state = self.app_state.clone();
2449            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
2450                .detach_and_log_err(cx);
2451        }
2452
2453        // if you're already following, find the right pane and focus it.
2454        for (pane, state) in &self.follower_states {
2455            if leader_id == state.leader_id {
2456                cx.focus_view(pane);
2457                return;
2458            }
2459        }
2460
2461        // Otherwise, follow.
2462        self.start_following(leader_id, cx)
2463            .map(|task| task.detach_and_log_err(cx));
2464    }
2465
2466    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2467        let state = self.follower_states.remove(pane)?;
2468        let leader_id = state.leader_id;
2469        for (_, item) in state.items_by_leader_view_id {
2470            item.set_leader_peer_id(None, cx);
2471        }
2472
2473        if self
2474            .follower_states
2475            .values()
2476            .all(|state| state.leader_id != state.leader_id)
2477        {
2478            let project_id = self.project.read(cx).remote_id();
2479            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2480            self.app_state
2481                .client
2482                .send(proto::Unfollow {
2483                    room_id,
2484                    project_id,
2485                    leader_id: Some(leader_id),
2486                })
2487                .log_err();
2488        }
2489
2490        cx.notify();
2491        Some(leader_id)
2492    }
2493
2494    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2495        self.follower_states
2496            .values()
2497            .any(|state| state.leader_id == peer_id)
2498    }
2499
2500    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2501        let active_entry = self.active_project_path(cx);
2502        self.project
2503            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2504        self.update_window_title(cx);
2505    }
2506
2507    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2508        let project = self.project().read(cx);
2509        let mut title = String::new();
2510
2511        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2512            let filename = path
2513                .path
2514                .file_name()
2515                .map(|s| s.to_string_lossy())
2516                .or_else(|| {
2517                    Some(Cow::Borrowed(
2518                        project
2519                            .worktree_for_id(path.worktree_id, cx)?
2520                            .read(cx)
2521                            .root_name(),
2522                    ))
2523                });
2524
2525            if let Some(filename) = filename {
2526                title.push_str(filename.as_ref());
2527                title.push_str("");
2528            }
2529        }
2530
2531        for (i, name) in project.worktree_root_names(cx).enumerate() {
2532            if i > 0 {
2533                title.push_str(", ");
2534            }
2535            title.push_str(name);
2536        }
2537
2538        if title.is_empty() {
2539            title = "empty project".to_string();
2540        }
2541
2542        if project.is_remote() {
2543            title.push_str("");
2544        } else if project.is_shared() {
2545            title.push_str("");
2546        }
2547
2548        cx.set_window_title(&title);
2549    }
2550
2551    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2552        let is_edited = !self.project.read(cx).is_disconnected()
2553            && self
2554                .items(cx)
2555                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2556        if is_edited != self.window_edited {
2557            self.window_edited = is_edited;
2558            cx.set_window_edited(self.window_edited)
2559        }
2560    }
2561
2562    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
2563        if self.notifications.is_empty() {
2564            None
2565        } else {
2566            Some(
2567                div()
2568                    .absolute()
2569                    .z_index(100)
2570                    .right_3()
2571                    .bottom_3()
2572                    .w_96()
2573                    .h_full()
2574                    .flex()
2575                    .flex_col()
2576                    .justify_end()
2577                    .gap_2()
2578                    .children(
2579                        self.notifications
2580                            .iter()
2581                            .map(|(_, _, notification)| notification.to_any()),
2582                    ),
2583            )
2584        }
2585    }
2586
2587    // RPC handlers
2588
2589    fn handle_follow(
2590        &mut self,
2591        follower_project_id: Option<u64>,
2592        cx: &mut ViewContext<Self>,
2593    ) -> proto::FollowResponse {
2594        let client = &self.app_state.client;
2595        let project_id = self.project.read(cx).remote_id();
2596
2597        let active_view_id = self.active_item(cx).and_then(|i| {
2598            Some(
2599                i.to_followable_item_handle(cx)?
2600                    .remote_id(client, cx)?
2601                    .to_proto(),
2602            )
2603        });
2604
2605        cx.notify();
2606
2607        self.last_active_view_id = active_view_id.clone();
2608        proto::FollowResponse {
2609            active_view_id,
2610            views: self
2611                .panes()
2612                .iter()
2613                .flat_map(|pane| {
2614                    let leader_id = self.leader_for_pane(pane);
2615                    pane.read(cx).items().filter_map({
2616                        let cx = &cx;
2617                        move |item| {
2618                            let item = item.to_followable_item_handle(cx)?;
2619
2620                            // If the item belongs to a particular project, then it should
2621                            // only be included if this project is shared, and the follower
2622                            // is in thie project.
2623                            //
2624                            // Some items, like channel notes, do not belong to a particular
2625                            // project, so they should be included regardless of whether the
2626                            // current project is shared, or what project the follower is in.
2627                            if item.is_project_item(cx)
2628                                && (project_id.is_none() || project_id != follower_project_id)
2629                            {
2630                                return None;
2631                            }
2632
2633                            let id = item.remote_id(client, cx)?.to_proto();
2634                            let variant = item.to_state_proto(cx)?;
2635                            Some(proto::View {
2636                                id: Some(id),
2637                                leader_id,
2638                                variant: Some(variant),
2639                            })
2640                        }
2641                    })
2642                })
2643                .collect(),
2644        }
2645    }
2646
2647    fn handle_update_followers(
2648        &mut self,
2649        leader_id: PeerId,
2650        message: proto::UpdateFollowers,
2651        _cx: &mut ViewContext<Self>,
2652    ) {
2653        self.leader_updates_tx
2654            .unbounded_send((leader_id, message))
2655            .ok();
2656    }
2657
2658    async fn process_leader_update(
2659        this: &WeakView<Self>,
2660        leader_id: PeerId,
2661        update: proto::UpdateFollowers,
2662        cx: &mut AsyncWindowContext,
2663    ) -> Result<()> {
2664        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2665            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2666                this.update(cx, |this, _| {
2667                    for (_, state) in &mut this.follower_states {
2668                        if state.leader_id == leader_id {
2669                            state.active_view_id =
2670                                if let Some(active_view_id) = update_active_view.id.clone() {
2671                                    Some(ViewId::from_proto(active_view_id)?)
2672                                } else {
2673                                    None
2674                                };
2675                        }
2676                    }
2677                    anyhow::Ok(())
2678                })??;
2679            }
2680            proto::update_followers::Variant::UpdateView(update_view) => {
2681                let variant = update_view
2682                    .variant
2683                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2684                let id = update_view
2685                    .id
2686                    .ok_or_else(|| anyhow!("missing update view id"))?;
2687                let mut tasks = Vec::new();
2688                this.update(cx, |this, cx| {
2689                    let project = this.project.clone();
2690                    for (_, state) in &mut this.follower_states {
2691                        if state.leader_id == leader_id {
2692                            let view_id = ViewId::from_proto(id.clone())?;
2693                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2694                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2695                            }
2696                        }
2697                    }
2698                    anyhow::Ok(())
2699                })??;
2700                try_join_all(tasks).await.log_err();
2701            }
2702            proto::update_followers::Variant::CreateView(view) => {
2703                let panes = this.update(cx, |this, _| {
2704                    this.follower_states
2705                        .iter()
2706                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2707                        .cloned()
2708                        .collect()
2709                })?;
2710                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2711            }
2712        }
2713        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2714        Ok(())
2715    }
2716
2717    async fn add_views_from_leader(
2718        this: WeakView<Self>,
2719        leader_id: PeerId,
2720        panes: Vec<View<Pane>>,
2721        views: Vec<proto::View>,
2722        cx: &mut AsyncWindowContext,
2723    ) -> Result<()> {
2724        let this = this.upgrade().context("workspace dropped")?;
2725
2726        let item_builders = cx.update(|_, cx| {
2727            cx.default_global::<FollowableItemBuilders>()
2728                .values()
2729                .map(|b| b.0)
2730                .collect::<Vec<_>>()
2731        })?;
2732
2733        let mut item_tasks_by_pane = HashMap::default();
2734        for pane in panes {
2735            let mut item_tasks = Vec::new();
2736            let mut leader_view_ids = Vec::new();
2737            for view in &views {
2738                let Some(id) = &view.id else { continue };
2739                let id = ViewId::from_proto(id.clone())?;
2740                let mut variant = view.variant.clone();
2741                if variant.is_none() {
2742                    Err(anyhow!("missing view variant"))?;
2743                }
2744                for build_item in &item_builders {
2745                    let task = cx.update(|_, cx| {
2746                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2747                    })?;
2748                    if let Some(task) = task {
2749                        item_tasks.push(task);
2750                        leader_view_ids.push(id);
2751                        break;
2752                    } else {
2753                        assert!(variant.is_some());
2754                    }
2755                }
2756            }
2757
2758            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2759        }
2760
2761        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2762            let items = futures::future::try_join_all(item_tasks).await?;
2763            this.update(cx, |this, cx| {
2764                let state = this.follower_states.get_mut(&pane)?;
2765                for (id, item) in leader_view_ids.into_iter().zip(items) {
2766                    item.set_leader_peer_id(Some(leader_id), cx);
2767                    state.items_by_leader_view_id.insert(id, item);
2768                }
2769
2770                Some(())
2771            })?;
2772        }
2773        Ok(())
2774    }
2775
2776    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2777        let mut is_project_item = true;
2778        let mut update = proto::UpdateActiveView::default();
2779
2780        if let Some(item) = self.active_item(cx) {
2781            if item.focus_handle(cx).contains_focused(cx) {
2782                if let Some(item) = item.to_followable_item_handle(cx) {
2783                    is_project_item = item.is_project_item(cx);
2784                    update = proto::UpdateActiveView {
2785                        id: item
2786                            .remote_id(&self.app_state.client, cx)
2787                            .map(|id| id.to_proto()),
2788                        leader_id: self.leader_for_pane(&self.active_pane),
2789                    };
2790                }
2791            }
2792        }
2793
2794        if update.id != self.last_active_view_id {
2795            self.last_active_view_id = update.id.clone();
2796            self.update_followers(
2797                is_project_item,
2798                proto::update_followers::Variant::UpdateActiveView(update),
2799                cx,
2800            );
2801        }
2802    }
2803
2804    fn update_followers(
2805        &self,
2806        project_only: bool,
2807        update: proto::update_followers::Variant,
2808        cx: &mut WindowContext,
2809    ) -> Option<()> {
2810        // If this update only applies to for followers in the current project,
2811        // then skip it unless this project is shared. If it applies to all
2812        // followers, regardless of project, then set `project_id` to none,
2813        // indicating that it goes to all followers.
2814        let project_id = if project_only {
2815            Some(self.project.read(cx).remote_id()?)
2816        } else {
2817            None
2818        };
2819        self.app_state().workspace_store.update(cx, |store, cx| {
2820            store.update_followers(project_id, update, cx)
2821        })
2822    }
2823
2824    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2825        self.follower_states.get(pane).map(|state| state.leader_id)
2826    }
2827
2828    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2829        cx.notify();
2830
2831        let call = self.active_call()?;
2832        let room = call.read(cx).room()?.read(cx);
2833        let participant = room.remote_participant_for_peer_id(leader_id)?;
2834        let mut items_to_activate = Vec::new();
2835
2836        let leader_in_this_app;
2837        let leader_in_this_project;
2838        match participant.location {
2839            call::ParticipantLocation::SharedProject { project_id } => {
2840                leader_in_this_app = true;
2841                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2842            }
2843            call::ParticipantLocation::UnsharedProject => {
2844                leader_in_this_app = true;
2845                leader_in_this_project = false;
2846            }
2847            call::ParticipantLocation::External => {
2848                leader_in_this_app = false;
2849                leader_in_this_project = false;
2850            }
2851        };
2852
2853        for (pane, state) in &self.follower_states {
2854            if state.leader_id != leader_id {
2855                continue;
2856            }
2857            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2858                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2859                    if leader_in_this_project || !item.is_project_item(cx) {
2860                        items_to_activate.push((pane.clone(), item.boxed_clone()));
2861                    }
2862                }
2863                continue;
2864            }
2865
2866            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2867                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2868            }
2869        }
2870
2871        for (pane, item) in items_to_activate {
2872            let pane_was_focused = pane.read(cx).has_focus(cx);
2873            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2874                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2875            } else {
2876                pane.update(cx, |pane, cx| {
2877                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2878                });
2879            }
2880
2881            if pane_was_focused {
2882                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2883            }
2884        }
2885
2886        None
2887    }
2888
2889    fn shared_screen_for_peer(
2890        &self,
2891        peer_id: PeerId,
2892        pane: &View<Pane>,
2893        cx: &mut ViewContext<Self>,
2894    ) -> Option<View<SharedScreen>> {
2895        let call = self.active_call()?;
2896        let room = call.read(cx).room()?.read(cx);
2897        let participant = room.remote_participant_for_peer_id(peer_id)?;
2898        let track = participant.video_tracks.values().next()?.clone();
2899        let user = participant.user.clone();
2900
2901        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2902            if item.read(cx).peer_id == peer_id {
2903                return Some(item);
2904            }
2905        }
2906
2907        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2908    }
2909
2910    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2911        if cx.is_window_active() {
2912            self.update_active_view_for_followers(cx);
2913            cx.background_executor()
2914                .spawn(persistence::DB.update_timestamp(self.database_id()))
2915                .detach();
2916        } else {
2917            for pane in &self.panes {
2918                pane.update(cx, |pane, cx| {
2919                    if let Some(item) = pane.active_item() {
2920                        item.workspace_deactivated(cx);
2921                    }
2922                    if matches!(
2923                        WorkspaceSettings::get_global(cx).autosave,
2924                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2925                    ) {
2926                        for item in pane.items() {
2927                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2928                                .detach_and_log_err(cx);
2929                        }
2930                    }
2931                });
2932            }
2933        }
2934    }
2935
2936    fn active_call(&self) -> Option<&Model<ActiveCall>> {
2937        self.active_call.as_ref().map(|(call, _)| call)
2938    }
2939
2940    fn on_active_call_event(
2941        &mut self,
2942        _: Model<ActiveCall>,
2943        event: &call::room::Event,
2944        cx: &mut ViewContext<Self>,
2945    ) {
2946        match event {
2947            call::room::Event::ParticipantLocationChanged { participant_id }
2948            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2949                self.leader_updated(*participant_id, cx);
2950            }
2951            _ => {}
2952        }
2953    }
2954
2955    pub fn database_id(&self) -> WorkspaceId {
2956        self.database_id
2957    }
2958
2959    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2960        let project = self.project().read(cx);
2961
2962        if project.is_local() {
2963            Some(
2964                project
2965                    .visible_worktrees(cx)
2966                    .map(|worktree| worktree.read(cx).abs_path())
2967                    .collect::<Vec<_>>()
2968                    .into(),
2969            )
2970        } else {
2971            None
2972        }
2973    }
2974
2975    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2976        match member {
2977            Member::Axis(PaneAxis { members, .. }) => {
2978                for child in members.iter() {
2979                    self.remove_panes(child.clone(), cx)
2980                }
2981            }
2982            Member::Pane(pane) => {
2983                self.force_remove_pane(&pane, cx);
2984            }
2985        }
2986    }
2987
2988    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2989        self.panes.retain(|p| p != pane);
2990        self.panes
2991            .last()
2992            .unwrap()
2993            .update(cx, |pane, cx| pane.focus(cx));
2994        if self.last_active_center_pane == Some(pane.downgrade()) {
2995            self.last_active_center_pane = None;
2996        }
2997        cx.notify();
2998    }
2999
3000    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3001        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3002            cx.background_executor()
3003                .timer(Duration::from_millis(100))
3004                .await;
3005            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3006                .log_err();
3007        }));
3008    }
3009
3010    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
3011        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3012            let (items, active) = {
3013                let pane = pane_handle.read(cx);
3014                let active_item_id = pane.active_item().map(|item| item.item_id());
3015                (
3016                    pane.items()
3017                        .filter_map(|item_handle| {
3018                            Some(SerializedItem {
3019                                kind: Arc::from(item_handle.serialized_item_kind()?),
3020                                item_id: item_handle.item_id().as_u64(),
3021                                active: Some(item_handle.item_id()) == active_item_id,
3022                            })
3023                        })
3024                        .collect::<Vec<_>>(),
3025                    pane.has_focus(cx),
3026                )
3027            };
3028
3029            SerializedPane::new(items, active)
3030        }
3031
3032        fn build_serialized_pane_group(
3033            pane_group: &Member,
3034            cx: &WindowContext,
3035        ) -> SerializedPaneGroup {
3036            match pane_group {
3037                Member::Axis(PaneAxis {
3038                    axis,
3039                    members,
3040                    flexes,
3041                    bounding_boxes: _,
3042                }) => SerializedPaneGroup::Group {
3043                    axis: SerializedAxis(*axis),
3044                    children: members
3045                        .iter()
3046                        .map(|member| build_serialized_pane_group(member, cx))
3047                        .collect::<Vec<_>>(),
3048                    flexes: Some(flexes.lock().clone()),
3049                },
3050                Member::Pane(pane_handle) => {
3051                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3052                }
3053            }
3054        }
3055
3056        fn build_serialized_docks(
3057            this: &Workspace,
3058            cx: &mut ViewContext<Workspace>,
3059        ) -> DockStructure {
3060            let left_dock = this.left_dock.read(cx);
3061            let left_visible = left_dock.is_open();
3062            let left_active_panel = left_dock
3063                .visible_panel()
3064                .and_then(|panel| Some(panel.persistent_name().to_string()));
3065            let left_dock_zoom = left_dock
3066                .visible_panel()
3067                .map(|panel| panel.is_zoomed(cx))
3068                .unwrap_or(false);
3069
3070            let right_dock = this.right_dock.read(cx);
3071            let right_visible = right_dock.is_open();
3072            let right_active_panel = right_dock
3073                .visible_panel()
3074                .and_then(|panel| Some(panel.persistent_name().to_string()));
3075            let right_dock_zoom = right_dock
3076                .visible_panel()
3077                .map(|panel| panel.is_zoomed(cx))
3078                .unwrap_or(false);
3079
3080            let bottom_dock = this.bottom_dock.read(cx);
3081            let bottom_visible = bottom_dock.is_open();
3082            let bottom_active_panel = bottom_dock
3083                .visible_panel()
3084                .and_then(|panel| Some(panel.persistent_name().to_string()));
3085            let bottom_dock_zoom = bottom_dock
3086                .visible_panel()
3087                .map(|panel| panel.is_zoomed(cx))
3088                .unwrap_or(false);
3089
3090            DockStructure {
3091                left: DockData {
3092                    visible: left_visible,
3093                    active_panel: left_active_panel,
3094                    zoom: left_dock_zoom,
3095                },
3096                right: DockData {
3097                    visible: right_visible,
3098                    active_panel: right_active_panel,
3099                    zoom: right_dock_zoom,
3100                },
3101                bottom: DockData {
3102                    visible: bottom_visible,
3103                    active_panel: bottom_active_panel,
3104                    zoom: bottom_dock_zoom,
3105                },
3106            }
3107        }
3108
3109        if let Some(location) = self.location(cx) {
3110            // Load bearing special case:
3111            //  - with_local_workspace() relies on this to not have other stuff open
3112            //    when you open your log
3113            if !location.paths().is_empty() {
3114                let center_group = build_serialized_pane_group(&self.center.root, cx);
3115                let docks = build_serialized_docks(self, cx);
3116
3117                let serialized_workspace = SerializedWorkspace {
3118                    id: self.database_id,
3119                    location,
3120                    center_group,
3121                    bounds: Default::default(),
3122                    display: Default::default(),
3123                    docks,
3124                };
3125
3126                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3127                    .detach();
3128            }
3129        }
3130    }
3131
3132    pub(crate) fn load_workspace(
3133        serialized_workspace: SerializedWorkspace,
3134        paths_to_open: Vec<Option<ProjectPath>>,
3135        cx: &mut ViewContext<Workspace>,
3136    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3137        cx.spawn(|workspace, mut cx| async move {
3138            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
3139
3140            let mut center_group = None;
3141            let mut center_items = None;
3142
3143            // Traverse the splits tree and add to things
3144            if let Some((group, active_pane, items)) = serialized_workspace
3145                .center_group
3146                .deserialize(
3147                    &project,
3148                    serialized_workspace.id,
3149                    workspace.clone(),
3150                    &mut cx,
3151                )
3152                .await
3153            {
3154                center_items = Some(items);
3155                center_group = Some((group, active_pane))
3156            }
3157
3158            let mut items_by_project_path = cx.update(|_, cx| {
3159                center_items
3160                    .unwrap_or_default()
3161                    .into_iter()
3162                    .filter_map(|item| {
3163                        let item = item?;
3164                        let project_path = item.project_path(cx)?;
3165                        Some((project_path, item))
3166                    })
3167                    .collect::<HashMap<_, _>>()
3168            })?;
3169
3170            let opened_items = paths_to_open
3171                .into_iter()
3172                .map(|path_to_open| {
3173                    path_to_open
3174                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3175                })
3176                .collect::<Vec<_>>();
3177
3178            // Remove old panes from workspace panes list
3179            workspace.update(&mut cx, |workspace, cx| {
3180                if let Some((center_group, active_pane)) = center_group {
3181                    workspace.remove_panes(workspace.center.root.clone(), cx);
3182
3183                    // Swap workspace center group
3184                    workspace.center = PaneGroup::with_root(center_group);
3185                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3186                    if let Some(active_pane) = active_pane {
3187                        workspace.active_pane = active_pane;
3188                        cx.focus_self();
3189                    } else {
3190                        workspace.active_pane = workspace.center.first_pane().clone();
3191                    }
3192                }
3193
3194                let docks = serialized_workspace.docks;
3195                workspace.left_dock.update(cx, |dock, cx| {
3196                    dock.set_open(docks.left.visible, cx);
3197                    if let Some(active_panel) = docks.left.active_panel {
3198                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3199                            dock.activate_panel(ix, cx);
3200                        }
3201                    }
3202                    dock.active_panel()
3203                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3204                    if docks.left.visible && docks.left.zoom {
3205                        cx.focus_self()
3206                    }
3207                });
3208                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3209                workspace.right_dock.update(cx, |dock, cx| {
3210                    dock.set_open(docks.right.visible, cx);
3211                    if let Some(active_panel) = docks.right.active_panel {
3212                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3213                            dock.activate_panel(ix, cx);
3214                        }
3215                    }
3216                    dock.active_panel()
3217                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3218
3219                    if docks.right.visible && docks.right.zoom {
3220                        cx.focus_self()
3221                    }
3222                });
3223                workspace.bottom_dock.update(cx, |dock, cx| {
3224                    dock.set_open(docks.bottom.visible, cx);
3225                    if let Some(active_panel) = docks.bottom.active_panel {
3226                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3227                            dock.activate_panel(ix, cx);
3228                        }
3229                    }
3230
3231                    dock.active_panel()
3232                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3233
3234                    if docks.bottom.visible && docks.bottom.zoom {
3235                        cx.focus_self()
3236                    }
3237                });
3238
3239                cx.notify();
3240            })?;
3241
3242            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3243            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3244
3245            Ok(opened_items)
3246        })
3247    }
3248
3249    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3250        self.add_workspace_actions_listeners(div, cx)
3251            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3252            .on_action(cx.listener(Self::close_all_items_and_panes))
3253            .on_action(cx.listener(Self::save_all))
3254            .on_action(cx.listener(Self::add_folder_to_project))
3255            .on_action(cx.listener(Self::follow_next_collaborator))
3256            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3257                let pane = workspace.active_pane().clone();
3258                workspace.unfollow(&pane, cx);
3259            }))
3260            .on_action(cx.listener(|workspace, action: &Save, cx| {
3261                workspace
3262                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3263                    .detach_and_log_err(cx);
3264            }))
3265            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3266                workspace
3267                    .save_active_item(SaveIntent::SaveAs, cx)
3268                    .detach_and_log_err(cx);
3269            }))
3270            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3271                workspace.activate_previous_pane(cx)
3272            }))
3273            .on_action(
3274                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3275            )
3276            .on_action(
3277                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3278                    workspace.activate_pane_in_direction(action.0, cx)
3279                }),
3280            )
3281            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3282                workspace.swap_pane_in_direction(action.0, cx)
3283            }))
3284            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
3285                this.toggle_dock(DockPosition::Left, cx);
3286            }))
3287            .on_action(
3288                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3289                    workspace.toggle_dock(DockPosition::Right, cx);
3290                }),
3291            )
3292            .on_action(
3293                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3294                    workspace.toggle_dock(DockPosition::Bottom, cx);
3295                }),
3296            )
3297            .on_action(
3298                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3299                    workspace.close_all_docks(cx);
3300                }),
3301            )
3302            .on_action(cx.listener(Workspace::open))
3303            .on_action(cx.listener(Workspace::close_window))
3304            .on_action(cx.listener(Workspace::activate_pane_at_index))
3305            .on_action(
3306                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3307                    workspace.reopen_closed_item(cx).detach();
3308                }),
3309            )
3310    }
3311
3312    #[cfg(any(test, feature = "test-support"))]
3313    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3314        use node_runtime::FakeNodeRuntime;
3315
3316        let client = project.read(cx).client();
3317        let user_store = project.read(cx).user_store();
3318
3319        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
3320        cx.activate_window();
3321        let app_state = Arc::new(AppState {
3322            languages: project.read(cx).languages().clone(),
3323            workspace_store,
3324            client,
3325            user_store,
3326            fs: project.read(cx).fs().clone(),
3327            build_window_options: |_, _, _| Default::default(),
3328            node_runtime: FakeNodeRuntime::new(),
3329        });
3330        let workspace = Self::new(0, project, app_state, cx);
3331        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3332        workspace
3333    }
3334
3335    pub fn register_action<A: Action>(
3336        &mut self,
3337        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3338    ) -> &mut Self {
3339        let callback = Arc::new(callback);
3340
3341        self.workspace_actions.push(Box::new(move |div, cx| {
3342            let callback = callback.clone();
3343            div.on_action(
3344                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3345            )
3346        }));
3347        self
3348    }
3349
3350    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3351        let mut div = div
3352            .on_action(cx.listener(Self::close_inactive_items_and_panes))
3353            .on_action(cx.listener(Self::close_all_items_and_panes))
3354            .on_action(cx.listener(Self::add_folder_to_project))
3355            .on_action(cx.listener(Self::save_all))
3356            .on_action(cx.listener(Self::open));
3357        for action in self.workspace_actions.iter() {
3358            div = (action)(div, cx)
3359        }
3360        div
3361    }
3362
3363    pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool {
3364        self.modal_layer.read(cx).has_active_modal()
3365    }
3366
3367    pub fn active_modal<V: ManagedView + 'static>(
3368        &mut self,
3369        cx: &ViewContext<Self>,
3370    ) -> Option<View<V>> {
3371        self.modal_layer.read(cx).active_modal()
3372    }
3373
3374    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3375    where
3376        B: FnOnce(&mut ViewContext<V>) -> V,
3377    {
3378        self.modal_layer
3379            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3380    }
3381}
3382
3383fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3384    let display_origin = cx
3385        .update(|cx| Some(cx.displays().first()?.bounds().origin))
3386        .ok()??;
3387    ZED_WINDOW_POSITION
3388        .zip(*ZED_WINDOW_SIZE)
3389        .map(|(position, size)| {
3390            WindowBounds::Fixed(Bounds {
3391                origin: display_origin + position,
3392                size,
3393            })
3394        })
3395}
3396
3397fn open_items(
3398    serialized_workspace: Option<SerializedWorkspace>,
3399    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3400    app_state: Arc<AppState>,
3401    cx: &mut ViewContext<Workspace>,
3402) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3403    let restored_items = serialized_workspace.map(|serialized_workspace| {
3404        Workspace::load_workspace(
3405            serialized_workspace,
3406            project_paths_to_open
3407                .iter()
3408                .map(|(_, project_path)| project_path)
3409                .cloned()
3410                .collect(),
3411            cx,
3412        )
3413    });
3414
3415    cx.spawn(|workspace, mut cx| async move {
3416        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3417
3418        if let Some(restored_items) = restored_items {
3419            let restored_items = restored_items.await?;
3420
3421            let restored_project_paths = restored_items
3422                .iter()
3423                .filter_map(|item| {
3424                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
3425                        .ok()
3426                        .flatten()
3427                })
3428                .collect::<HashSet<_>>();
3429
3430            for restored_item in restored_items {
3431                opened_items.push(restored_item.map(Ok));
3432            }
3433
3434            project_paths_to_open
3435                .iter_mut()
3436                .for_each(|(_, project_path)| {
3437                    if let Some(project_path_to_open) = project_path {
3438                        if restored_project_paths.contains(project_path_to_open) {
3439                            *project_path = None;
3440                        }
3441                    }
3442                });
3443        } else {
3444            for _ in 0..project_paths_to_open.len() {
3445                opened_items.push(None);
3446            }
3447        }
3448        assert!(opened_items.len() == project_paths_to_open.len());
3449
3450        let tasks =
3451            project_paths_to_open
3452                .into_iter()
3453                .enumerate()
3454                .map(|(i, (abs_path, project_path))| {
3455                    let workspace = workspace.clone();
3456                    cx.spawn(|mut cx| {
3457                        let fs = app_state.fs.clone();
3458                        async move {
3459                            let file_project_path = project_path?;
3460                            if fs.is_file(&abs_path).await {
3461                                Some((
3462                                    i,
3463                                    workspace
3464                                        .update(&mut cx, |workspace, cx| {
3465                                            workspace.open_path(file_project_path, None, true, cx)
3466                                        })
3467                                        .log_err()?
3468                                        .await,
3469                                ))
3470                            } else {
3471                                None
3472                            }
3473                        }
3474                    })
3475                });
3476
3477        let tasks = tasks.collect::<Vec<_>>();
3478
3479        let tasks = futures::future::join_all(tasks.into_iter());
3480        for maybe_opened_path in tasks.await.into_iter() {
3481            if let Some((i, path_open_result)) = maybe_opened_path {
3482                opened_items[i] = Some(path_open_result);
3483            }
3484        }
3485
3486        Ok(opened_items)
3487    })
3488}
3489
3490fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3491    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3492
3493    workspace
3494        .update(cx, |workspace, cx| {
3495            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3496                workspace.show_notification_once(0, cx, |cx| {
3497                    cx.new_view(|_| {
3498                        MessageNotification::new("Failed to load the database file.")
3499                            .with_click_message("Click to let us know about this error")
3500                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3501                    })
3502                });
3503            }
3504        })
3505        .log_err();
3506}
3507
3508impl FocusableView for Workspace {
3509    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3510        self.active_pane.focus_handle(cx)
3511    }
3512}
3513
3514#[derive(Clone, Render)]
3515struct DraggedDock(DockPosition);
3516
3517impl Render for Workspace {
3518    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3519        let mut context = KeyContext::default();
3520        context.add("Workspace");
3521
3522        let (ui_font, ui_font_size) = {
3523            let theme_settings = ThemeSettings::get_global(cx);
3524            (
3525                theme_settings.ui_font.family.clone(),
3526                theme_settings.ui_font_size.clone(),
3527            )
3528        };
3529
3530        let theme = cx.theme().clone();
3531        let colors = theme.colors();
3532        cx.set_rem_size(ui_font_size);
3533
3534        self.actions(div(), cx)
3535            .key_context(context)
3536            .relative()
3537            .size_full()
3538            .flex()
3539            .flex_col()
3540            .font(ui_font)
3541            .gap_0()
3542            .justify_start()
3543            .items_start()
3544            .text_color(colors.text)
3545            .bg(colors.background)
3546            .border()
3547            .border_color(colors.border)
3548            .children(self.titlebar_item.clone())
3549            .child(
3550                div()
3551                    .id("workspace")
3552                    .relative()
3553                    .flex_1()
3554                    .w_full()
3555                    .flex()
3556                    .flex_col()
3557                    .overflow_hidden()
3558                    .border_t()
3559                    .border_b()
3560                    .border_color(colors.border)
3561                    .child(
3562                        canvas(cx.listener(|workspace, bounds, _| {
3563                            workspace.bounds = *bounds;
3564                        }))
3565                        .absolute()
3566                        .size_full(),
3567                    )
3568                    .on_drag_move(
3569                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
3570                            match e.drag(cx).0 {
3571                                DockPosition::Left => {
3572                                    let size = workspace.bounds.left() + e.event.position.x;
3573                                    workspace.left_dock.update(cx, |left_dock, cx| {
3574                                        left_dock.resize_active_panel(Some(size), cx);
3575                                    });
3576                                }
3577                                DockPosition::Right => {
3578                                    let size = workspace.bounds.right() - e.event.position.x;
3579                                    workspace.right_dock.update(cx, |right_dock, cx| {
3580                                        right_dock.resize_active_panel(Some(size), cx);
3581                                    });
3582                                }
3583                                DockPosition::Bottom => {
3584                                    let size = workspace.bounds.bottom() - e.event.position.y;
3585                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
3586                                        bottom_dock.resize_active_panel(Some(size), cx);
3587                                    });
3588                                }
3589                            }
3590                        }),
3591                    )
3592                    .child(self.modal_layer.clone())
3593                    .child(
3594                        div()
3595                            .flex()
3596                            .flex_row()
3597                            .h_full()
3598                            // Left Dock
3599                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
3600                                || {
3601                                    div()
3602                                        .flex()
3603                                        .flex_none()
3604                                        .overflow_hidden()
3605                                        .child(self.left_dock.clone())
3606                                },
3607                            ))
3608                            // Panes
3609                            .child(
3610                                div()
3611                                    .flex()
3612                                    .flex_col()
3613                                    .flex_1()
3614                                    .overflow_hidden()
3615                                    .child(self.center.render(
3616                                        &self.project,
3617                                        &self.follower_states,
3618                                        self.active_call(),
3619                                        &self.active_pane,
3620                                        self.zoomed.as_ref(),
3621                                        &self.app_state,
3622                                        cx,
3623                                    ))
3624                                    .children(
3625                                        self.zoomed_position
3626                                            .ne(&Some(DockPosition::Bottom))
3627                                            .then(|| self.bottom_dock.clone()),
3628                                    ),
3629                            )
3630                            // Right Dock
3631                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
3632                                || {
3633                                    div()
3634                                        .flex()
3635                                        .flex_none()
3636                                        .overflow_hidden()
3637                                        .child(self.right_dock.clone())
3638                                },
3639                            )),
3640                    )
3641                    .children(self.render_notifications(cx))
3642                    .children(self.zoomed.as_ref().and_then(|view| {
3643                        let zoomed_view = view.upgrade()?;
3644                        let div = div()
3645                            .z_index(1)
3646                            .absolute()
3647                            .overflow_hidden()
3648                            .border_color(colors.border)
3649                            .bg(colors.background)
3650                            .child(zoomed_view)
3651                            .inset_0()
3652                            .shadow_lg();
3653
3654                        Some(match self.zoomed_position {
3655                            Some(DockPosition::Left) => div.right_2().border_r(),
3656                            Some(DockPosition::Right) => div.left_2().border_l(),
3657                            Some(DockPosition::Bottom) => div.top_2().border_t(),
3658                            None => div.top_2().bottom_2().left_2().right_2().border(),
3659                        })
3660                    })),
3661            )
3662            .child(self.status_bar.clone())
3663            .children(if self.project.read(cx).is_disconnected() {
3664                Some(DisconnectedOverlay)
3665            } else {
3666                None
3667            })
3668    }
3669}
3670
3671impl WorkspaceStore {
3672    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3673        Self {
3674            workspaces: Default::default(),
3675            followers: Default::default(),
3676            _subscriptions: vec![
3677                client.add_request_handler(cx.weak_model(), Self::handle_follow),
3678                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3679                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3680            ],
3681            client,
3682        }
3683    }
3684
3685    pub fn update_followers(
3686        &self,
3687        project_id: Option<u64>,
3688        update: proto::update_followers::Variant,
3689        cx: &AppContext,
3690    ) -> Option<()> {
3691        if !cx.has_global::<Model<ActiveCall>>() {
3692            return None;
3693        }
3694
3695        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3696        let follower_ids: Vec<_> = self
3697            .followers
3698            .iter()
3699            .filter_map(|follower| {
3700                if follower.project_id == project_id || project_id.is_none() {
3701                    Some(follower.peer_id.into())
3702                } else {
3703                    None
3704                }
3705            })
3706            .collect();
3707        if follower_ids.is_empty() {
3708            return None;
3709        }
3710        self.client
3711            .send(proto::UpdateFollowers {
3712                room_id,
3713                project_id,
3714                follower_ids,
3715                variant: Some(update),
3716            })
3717            .log_err()
3718    }
3719
3720    pub async fn handle_follow(
3721        this: Model<Self>,
3722        envelope: TypedEnvelope<proto::Follow>,
3723        _: Arc<Client>,
3724        mut cx: AsyncAppContext,
3725    ) -> Result<proto::FollowResponse> {
3726        this.update(&mut cx, |this, cx| {
3727            let follower = Follower {
3728                project_id: envelope.payload.project_id,
3729                peer_id: envelope.original_sender_id()?,
3730            };
3731            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3732
3733            let mut response = proto::FollowResponse::default();
3734            this.workspaces.retain(|workspace| {
3735                workspace
3736                    .update(cx, |workspace, cx| {
3737                        let handler_response = workspace.handle_follow(follower.project_id, cx);
3738                        if response.views.is_empty() {
3739                            response.views = handler_response.views;
3740                        } else {
3741                            response.views.extend_from_slice(&handler_response.views);
3742                        }
3743
3744                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
3745                            if response.active_view_id.is_none()
3746                                || Some(workspace.project.downgrade()) == active_project
3747                            {
3748                                response.active_view_id = Some(active_view_id);
3749                            }
3750                        }
3751                    })
3752                    .is_ok()
3753            });
3754
3755            if let Err(ix) = this.followers.binary_search(&follower) {
3756                this.followers.insert(ix, follower);
3757            }
3758
3759            Ok(response)
3760        })?
3761    }
3762
3763    async fn handle_unfollow(
3764        model: Model<Self>,
3765        envelope: TypedEnvelope<proto::Unfollow>,
3766        _: Arc<Client>,
3767        mut cx: AsyncAppContext,
3768    ) -> Result<()> {
3769        model.update(&mut cx, |this, _| {
3770            let follower = Follower {
3771                project_id: envelope.payload.project_id,
3772                peer_id: envelope.original_sender_id()?,
3773            };
3774            if let Ok(ix) = this.followers.binary_search(&follower) {
3775                this.followers.remove(ix);
3776            }
3777            Ok(())
3778        })?
3779    }
3780
3781    async fn handle_update_followers(
3782        this: Model<Self>,
3783        envelope: TypedEnvelope<proto::UpdateFollowers>,
3784        _: Arc<Client>,
3785        mut cx: AsyncAppContext,
3786    ) -> Result<()> {
3787        let leader_id = envelope.original_sender_id()?;
3788        let update = envelope.payload;
3789
3790        this.update(&mut cx, |this, cx| {
3791            this.workspaces.retain(|workspace| {
3792                workspace
3793                    .update(cx, |workspace, cx| {
3794                        let project_id = workspace.project.read(cx).remote_id();
3795                        if update.project_id != project_id && update.project_id.is_some() {
3796                            return;
3797                        }
3798                        workspace.handle_update_followers(leader_id, update.clone(), cx);
3799                    })
3800                    .is_ok()
3801            });
3802            Ok(())
3803        })?
3804    }
3805}
3806
3807impl ViewId {
3808    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3809        Ok(Self {
3810            creator: message
3811                .creator
3812                .ok_or_else(|| anyhow!("creator is missing"))?,
3813            id: message.id,
3814        })
3815    }
3816
3817    pub(crate) fn to_proto(&self) -> proto::ViewId {
3818        proto::ViewId {
3819            creator: Some(self.creator),
3820            id: self.id,
3821        }
3822    }
3823}
3824
3825pub trait WorkspaceHandle {
3826    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3827}
3828
3829impl WorkspaceHandle for View<Workspace> {
3830    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3831        self.read(cx)
3832            .worktrees(cx)
3833            .flat_map(|worktree| {
3834                let worktree_id = worktree.read(cx).id();
3835                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3836                    worktree_id,
3837                    path: f.path.clone(),
3838                })
3839            })
3840            .collect::<Vec<_>>()
3841    }
3842}
3843
3844impl std::fmt::Debug for OpenPaths {
3845    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3846        f.debug_struct("OpenPaths")
3847            .field("paths", &self.paths)
3848            .finish()
3849    }
3850}
3851
3852pub fn activate_workspace_for_project(
3853    cx: &mut AppContext,
3854    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3855) -> Option<WindowHandle<Workspace>> {
3856    for window in cx.windows() {
3857        let Some(workspace) = window.downcast::<Workspace>() else {
3858            continue;
3859        };
3860
3861        let predicate = workspace
3862            .update(cx, |workspace, cx| {
3863                let project = workspace.project.read(cx);
3864                if predicate(project, cx) {
3865                    cx.activate_window();
3866                    true
3867                } else {
3868                    false
3869                }
3870            })
3871            .log_err()
3872            .unwrap_or(false);
3873
3874        if predicate {
3875            return Some(workspace);
3876        }
3877    }
3878
3879    None
3880}
3881
3882pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3883    DB.last_workspace().await.log_err().flatten()
3884}
3885
3886async fn join_channel_internal(
3887    channel_id: u64,
3888    app_state: &Arc<AppState>,
3889    requesting_window: Option<WindowHandle<Workspace>>,
3890    active_call: &Model<ActiveCall>,
3891    cx: &mut AsyncAppContext,
3892) -> Result<bool> {
3893    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
3894        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3895            return (false, None);
3896        };
3897
3898        let already_in_channel = room.channel_id() == Some(channel_id);
3899        let should_prompt = room.is_sharing_project()
3900            && room.remote_participants().len() > 0
3901            && !already_in_channel;
3902        let open_room = if already_in_channel {
3903            active_call.room().cloned()
3904        } else {
3905            None
3906        };
3907        (should_prompt, open_room)
3908    })?;
3909
3910    if let Some(room) = open_room {
3911        let task = room.update(cx, |room, cx| {
3912            if let Some((project, host)) = room.most_active_project(cx) {
3913                return Some(join_remote_project(project, host, app_state.clone(), cx));
3914            }
3915
3916            None
3917        })?;
3918        if let Some(task) = task {
3919            task.await?;
3920        }
3921        return anyhow::Ok(true);
3922    }
3923
3924    if should_prompt {
3925        if let Some(workspace) = requesting_window {
3926            let answer  = workspace.update(cx, |_, cx| {
3927                cx.prompt(
3928                    PromptLevel::Warning,
3929                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
3930                    &["Yes, Join Channel", "Cancel"],
3931                )
3932            })?.await;
3933
3934            if answer == Ok(1) {
3935                return Ok(false);
3936            }
3937        } else {
3938            return Ok(false); // unreachable!() hopefully
3939        }
3940    }
3941
3942    let client = cx.update(|cx| active_call.read(cx).client())?;
3943
3944    let mut client_status = client.status();
3945
3946    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
3947    'outer: loop {
3948        let Some(status) = client_status.recv().await else {
3949            return Err(anyhow!("error connecting"));
3950        };
3951
3952        match status {
3953            Status::Connecting
3954            | Status::Authenticating
3955            | Status::Reconnecting
3956            | Status::Reauthenticating => continue,
3957            Status::Connected { .. } => break 'outer,
3958            Status::SignedOut => return Err(anyhow!("not signed in")),
3959            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
3960            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
3961                return Err(anyhow!("zed is offline"))
3962            }
3963        }
3964    }
3965
3966    let room = active_call
3967        .update(cx, |active_call, cx| {
3968            active_call.join_channel(channel_id, cx)
3969        })?
3970        .await?;
3971
3972    let Some(room) = room else {
3973        return anyhow::Ok(true);
3974    };
3975
3976    room.update(cx, |room, _| room.room_update_completed())?
3977        .await;
3978
3979    let task = room.update(cx, |room, cx| {
3980        if let Some((project, host)) = room.most_active_project(cx) {
3981            return Some(join_remote_project(project, host, app_state.clone(), cx));
3982        }
3983
3984        None
3985    })?;
3986    if let Some(task) = task {
3987        task.await?;
3988        return anyhow::Ok(true);
3989    }
3990    anyhow::Ok(false)
3991}
3992
3993pub fn join_channel(
3994    channel_id: u64,
3995    app_state: Arc<AppState>,
3996    requesting_window: Option<WindowHandle<Workspace>>,
3997    cx: &mut AppContext,
3998) -> Task<Result<()>> {
3999    let active_call = ActiveCall::global(cx);
4000    cx.spawn(|mut cx| async move {
4001        let result = join_channel_internal(
4002            channel_id,
4003            &app_state,
4004            requesting_window,
4005            &active_call,
4006            &mut cx,
4007        )
4008        .await;
4009
4010        // join channel succeeded, and opened a window
4011        if matches!(result, Ok(true)) {
4012            return anyhow::Ok(());
4013        }
4014
4015        // find an existing workspace to focus and show call controls
4016        let mut active_window =
4017            requesting_window.or_else(|| activate_any_workspace_window(&mut cx));
4018        if active_window.is_none() {
4019            // no open workspaces, make one to show the error in (blergh)
4020            let (window_handle, _) = cx
4021                .update(|cx| {
4022                    Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)
4023                })?
4024                .await?;
4025
4026            active_window = Some(window_handle);
4027        }
4028
4029        if let Err(err) = result {
4030            log::error!("failed to join channel: {}", err);
4031            if let Some(active_window) = active_window {
4032                active_window
4033                    .update(&mut cx, |_, cx| {
4034                        cx.prompt(
4035                            PromptLevel::Critical,
4036                            &format!("Failed to join channel: {}", err),
4037                            &["Ok"],
4038                        )
4039                    })?
4040                    .await
4041                    .ok();
4042            }
4043        }
4044
4045        // return ok, we showed the error to the user.
4046        return anyhow::Ok(());
4047    })
4048}
4049
4050pub async fn get_any_active_workspace(
4051    app_state: Arc<AppState>,
4052    mut cx: AsyncAppContext,
4053) -> anyhow::Result<WindowHandle<Workspace>> {
4054    // find an existing workspace to focus and show call controls
4055    let active_window = activate_any_workspace_window(&mut cx);
4056    if active_window.is_none() {
4057        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
4058            .await?;
4059    }
4060    activate_any_workspace_window(&mut cx).context("could not open zed")
4061}
4062
4063fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandle<Workspace>> {
4064    cx.update(|cx| {
4065        for window in cx.windows() {
4066            if let Some(workspace_window) = window.downcast::<Workspace>() {
4067                workspace_window
4068                    .update(cx, |_, cx| cx.activate_window())
4069                    .ok();
4070                return Some(workspace_window);
4071            }
4072        }
4073        None
4074    })
4075    .ok()
4076    .flatten()
4077}
4078
4079#[allow(clippy::type_complexity)]
4080pub fn open_paths(
4081    abs_paths: &[PathBuf],
4082    app_state: &Arc<AppState>,
4083    requesting_window: Option<WindowHandle<Workspace>>,
4084    cx: &mut AppContext,
4085) -> Task<
4086    anyhow::Result<(
4087        WindowHandle<Workspace>,
4088        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4089    )>,
4090> {
4091    let app_state = app_state.clone();
4092    let abs_paths = abs_paths.to_vec();
4093    // Open paths in existing workspace if possible
4094    let existing = activate_workspace_for_project(cx, {
4095        let abs_paths = abs_paths.clone();
4096        move |project, cx| project.contains_paths(&abs_paths, cx)
4097    });
4098    cx.spawn(move |mut cx| async move {
4099        if let Some(existing) = existing {
4100            Ok((
4101                existing.clone(),
4102                existing
4103                    .update(&mut cx, |workspace, cx| {
4104                        workspace.open_paths(abs_paths, OpenVisible::All, None, cx)
4105                    })?
4106                    .await,
4107            ))
4108        } else {
4109            cx.update(move |cx| {
4110                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4111            })?
4112            .await
4113        }
4114    })
4115}
4116
4117pub fn open_new(
4118    app_state: &Arc<AppState>,
4119    cx: &mut AppContext,
4120    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4121) -> Task<()> {
4122    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4123    cx.spawn(|mut cx| async move {
4124        if let Some((workspace, opened_paths)) = task.await.log_err() {
4125            workspace
4126                .update(&mut cx, |workspace, cx| {
4127                    if opened_paths.is_empty() {
4128                        init(workspace, cx)
4129                    }
4130                })
4131                .log_err();
4132        }
4133    })
4134}
4135
4136pub fn create_and_open_local_file(
4137    path: &'static Path,
4138    cx: &mut ViewContext<Workspace>,
4139    default_content: impl 'static + Send + FnOnce() -> Rope,
4140) -> Task<Result<Box<dyn ItemHandle>>> {
4141    cx.spawn(|workspace, mut cx| async move {
4142        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4143        if !fs.is_file(path).await {
4144            fs.create_file(path, Default::default()).await?;
4145            fs.save(path, &default_content(), Default::default())
4146                .await?;
4147        }
4148
4149        let mut items = workspace
4150            .update(&mut cx, |workspace, cx| {
4151                workspace.with_local_workspace(cx, |workspace, cx| {
4152                    workspace.open_paths(vec![path.to_path_buf()], OpenVisible::None, None, cx)
4153                })
4154            })?
4155            .await?
4156            .await;
4157
4158        let item = items.pop().flatten();
4159        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4160    })
4161}
4162
4163pub fn join_remote_project(
4164    project_id: u64,
4165    follow_user_id: u64,
4166    app_state: Arc<AppState>,
4167    cx: &mut AppContext,
4168) -> Task<Result<()>> {
4169    let windows = cx.windows();
4170    cx.spawn(|mut cx| async move {
4171        let existing_workspace = windows.into_iter().find_map(|window| {
4172            window.downcast::<Workspace>().and_then(|window| {
4173                window
4174                    .update(&mut cx, |workspace, cx| {
4175                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4176                            Some(window)
4177                        } else {
4178                            None
4179                        }
4180                    })
4181                    .unwrap_or(None)
4182            })
4183        });
4184
4185        let workspace = if let Some(existing_workspace) = existing_workspace {
4186            existing_workspace
4187        } else {
4188            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4189            let room = active_call
4190                .read_with(&cx, |call, _| call.room().cloned())?
4191                .ok_or_else(|| anyhow!("not in a call"))?;
4192            let project = room
4193                .update(&mut cx, |room, cx| {
4194                    room.join_project(
4195                        project_id,
4196                        app_state.languages.clone(),
4197                        app_state.fs.clone(),
4198                        cx,
4199                    )
4200                })?
4201                .await?;
4202
4203            let window_bounds_override = window_bounds_env_override(&cx);
4204            cx.update(|cx| {
4205                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4206                cx.open_window(options, |cx| {
4207                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4208                })
4209            })?
4210        };
4211
4212        workspace.update(&mut cx, |workspace, cx| {
4213            cx.activate(true);
4214            cx.activate_window();
4215
4216            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4217                let follow_peer_id = room
4218                    .read(cx)
4219                    .remote_participants()
4220                    .iter()
4221                    .find(|(_, participant)| participant.user.id == follow_user_id)
4222                    .map(|(_, p)| p.peer_id)
4223                    .or_else(|| {
4224                        // If we couldn't follow the given user, follow the host instead.
4225                        let collaborator = workspace
4226                            .project()
4227                            .read(cx)
4228                            .collaborators()
4229                            .values()
4230                            .find(|collaborator| collaborator.replica_id == 0)?;
4231                        Some(collaborator.peer_id)
4232                    });
4233
4234                if let Some(follow_peer_id) = follow_peer_id {
4235                    workspace.follow(follow_peer_id, cx);
4236                }
4237            }
4238        })?;
4239
4240        anyhow::Ok(())
4241    })
4242}
4243
4244pub fn restart(_: &Restart, cx: &mut AppContext) {
4245    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4246    let mut workspace_windows = cx
4247        .windows()
4248        .into_iter()
4249        .filter_map(|window| window.downcast::<Workspace>())
4250        .collect::<Vec<_>>();
4251
4252    // If multiple windows have unsaved changes, and need a save prompt,
4253    // prompt in the active window before switching to a different window.
4254    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4255
4256    let mut prompt = None;
4257    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4258        prompt = window
4259            .update(cx, |_, cx| {
4260                cx.prompt(
4261                    PromptLevel::Info,
4262                    "Are you sure you want to restart?",
4263                    &["Restart", "Cancel"],
4264                )
4265            })
4266            .ok();
4267    }
4268
4269    cx.spawn(|mut cx| async move {
4270        if let Some(prompt) = prompt {
4271            let answer = prompt.await?;
4272            if answer != 0 {
4273                return Ok(());
4274            }
4275        }
4276
4277        // If the user cancels any save prompt, then keep the app open.
4278        for window in workspace_windows {
4279            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4280                workspace.prepare_to_close(true, cx)
4281            }) {
4282                if !should_close.await? {
4283                    return Ok(());
4284                }
4285            }
4286        }
4287
4288        cx.update(|cx| cx.restart())
4289    })
4290    .detach_and_log_err(cx);
4291}
4292
4293fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4294    let mut parts = value.split(',');
4295    let x: usize = parts.next()?.parse().ok()?;
4296    let y: usize = parts.next()?.parse().ok()?;
4297    Some(point((x as f64).into(), (y as f64).into()))
4298}
4299
4300fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4301    let mut parts = value.split(',');
4302    let width: usize = parts.next()?.parse().ok()?;
4303    let height: usize = parts.next()?.parse().ok()?;
4304    Some(size((width as f64).into(), (height as f64).into()))
4305}
4306
4307struct DisconnectedOverlay;
4308
4309impl Element for DisconnectedOverlay {
4310    type State = AnyElement;
4311
4312    fn request_layout(
4313        &mut self,
4314        _: Option<Self::State>,
4315        cx: &mut WindowContext,
4316    ) -> (LayoutId, Self::State) {
4317        let mut background = cx.theme().colors().elevated_surface_background;
4318        background.fade_out(0.2);
4319        let mut overlay = div()
4320            .bg(background)
4321            .absolute()
4322            .left_0()
4323            .top_0()
4324            .size_full()
4325            .flex()
4326            .items_center()
4327            .justify_center()
4328            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
4329            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
4330            .child(Label::new(
4331                "Your connection to the remote project has been lost.",
4332            ))
4333            .into_any();
4334        (overlay.request_layout(cx), overlay)
4335    }
4336
4337    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
4338        cx.with_z_index(u8::MAX, |cx| {
4339            cx.add_opaque_layer(bounds);
4340            overlay.paint(cx);
4341        })
4342    }
4343}
4344
4345impl IntoElement for DisconnectedOverlay {
4346    type Element = Self;
4347
4348    fn element_id(&self) -> Option<ui::prelude::ElementId> {
4349        None
4350    }
4351
4352    fn into_element(self) -> Self::Element {
4353        self
4354    }
4355}
4356
4357#[cfg(test)]
4358mod tests {
4359    use std::{cell::RefCell, rc::Rc};
4360
4361    use super::*;
4362    use crate::{
4363        dock::{test::TestPanel, PanelEvent},
4364        item::{
4365            test::{TestItem, TestProjectItem},
4366            ItemEvent,
4367        },
4368    };
4369    use fs::FakeFs;
4370    use gpui::{px, DismissEvent, TestAppContext, VisualTestContext};
4371    use project::{Project, ProjectEntryId};
4372    use serde_json::json;
4373    use settings::SettingsStore;
4374
4375    #[gpui::test]
4376    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4377        init_test(cx);
4378
4379        let fs = FakeFs::new(cx.executor());
4380        let project = Project::test(fs, [], cx).await;
4381        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4382
4383        // Adding an item with no ambiguity renders the tab without detail.
4384        let item1 = cx.new_view(|cx| {
4385            let mut item = TestItem::new(cx);
4386            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4387            item
4388        });
4389        workspace.update(cx, |workspace, cx| {
4390            workspace.add_item(Box::new(item1.clone()), cx);
4391        });
4392        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4393
4394        // Adding an item that creates ambiguity increases the level of detail on
4395        // both tabs.
4396        let item2 = cx.new_view(|cx| {
4397            let mut item = TestItem::new(cx);
4398            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4399            item
4400        });
4401        workspace.update(cx, |workspace, cx| {
4402            workspace.add_item(Box::new(item2.clone()), cx);
4403        });
4404        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4405        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4406
4407        // Adding an item that creates ambiguity increases the level of detail only
4408        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4409        // we stop at the highest detail available.
4410        let item3 = cx.new_view(|cx| {
4411            let mut item = TestItem::new(cx);
4412            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4413            item
4414        });
4415        workspace.update(cx, |workspace, cx| {
4416            workspace.add_item(Box::new(item3.clone()), cx);
4417        });
4418        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4419        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4420        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4421    }
4422
4423    #[gpui::test]
4424    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4425        init_test(cx);
4426
4427        let fs = FakeFs::new(cx.executor());
4428        fs.insert_tree(
4429            "/root1",
4430            json!({
4431                "one.txt": "",
4432                "two.txt": "",
4433            }),
4434        )
4435        .await;
4436        fs.insert_tree(
4437            "/root2",
4438            json!({
4439                "three.txt": "",
4440            }),
4441        )
4442        .await;
4443
4444        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4445        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4446        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4447        let worktree_id = project.update(cx, |project, cx| {
4448            project.worktrees().next().unwrap().read(cx).id()
4449        });
4450
4451        let item1 = cx.new_view(|cx| {
4452            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4453        });
4454        let item2 = cx.new_view(|cx| {
4455            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4456        });
4457
4458        // Add an item to an empty pane
4459        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4460        project.update(cx, |project, cx| {
4461            assert_eq!(
4462                project.active_entry(),
4463                project
4464                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4465                    .map(|e| e.id)
4466            );
4467        });
4468        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4469
4470        // Add a second item to a non-empty pane
4471        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4472        assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
4473        project.update(cx, |project, cx| {
4474            assert_eq!(
4475                project.active_entry(),
4476                project
4477                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4478                    .map(|e| e.id)
4479            );
4480        });
4481
4482        // Close the active item
4483        pane.update(cx, |pane, cx| {
4484            pane.close_active_item(&Default::default(), cx).unwrap()
4485        })
4486        .await
4487        .unwrap();
4488        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
4489        project.update(cx, |project, cx| {
4490            assert_eq!(
4491                project.active_entry(),
4492                project
4493                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4494                    .map(|e| e.id)
4495            );
4496        });
4497
4498        // Add a project folder
4499        project
4500            .update(cx, |project, cx| {
4501                project.find_or_create_local_worktree("/root2", true, cx)
4502            })
4503            .await
4504            .unwrap();
4505        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
4506
4507        // Remove a project folder
4508        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4509        assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
4510    }
4511
4512    #[gpui::test]
4513    async fn test_close_window(cx: &mut TestAppContext) {
4514        init_test(cx);
4515
4516        let fs = FakeFs::new(cx.executor());
4517        fs.insert_tree("/root", json!({ "one": "" })).await;
4518
4519        let project = Project::test(fs, ["root".as_ref()], cx).await;
4520        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4521
4522        // When there are no dirty items, there's nothing to do.
4523        let item1 = cx.new_view(|cx| TestItem::new(cx));
4524        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4525        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4526        assert!(task.await.unwrap());
4527
4528        // When there are dirty untitled items, prompt to save each one. If the user
4529        // cancels any prompt, then abort.
4530        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
4531        let item3 = cx.new_view(|cx| {
4532            TestItem::new(cx)
4533                .with_dirty(true)
4534                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4535        });
4536        workspace.update(cx, |w, cx| {
4537            w.add_item(Box::new(item2.clone()), cx);
4538            w.add_item(Box::new(item3.clone()), cx);
4539        });
4540        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4541        cx.executor().run_until_parked();
4542        cx.simulate_prompt_answer(2); // cancel save all
4543        cx.executor().run_until_parked();
4544        cx.simulate_prompt_answer(2); // cancel save all
4545        cx.executor().run_until_parked();
4546        assert!(!cx.has_pending_prompt());
4547        assert!(!task.await.unwrap());
4548    }
4549
4550    #[gpui::test]
4551    async fn test_close_pane_items(cx: &mut TestAppContext) {
4552        init_test(cx);
4553
4554        let fs = FakeFs::new(cx.executor());
4555
4556        let project = Project::test(fs, None, cx).await;
4557        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4558
4559        let item1 = cx.new_view(|cx| {
4560            TestItem::new(cx)
4561                .with_dirty(true)
4562                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4563        });
4564        let item2 = cx.new_view(|cx| {
4565            TestItem::new(cx)
4566                .with_dirty(true)
4567                .with_conflict(true)
4568                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4569        });
4570        let item3 = cx.new_view(|cx| {
4571            TestItem::new(cx)
4572                .with_dirty(true)
4573                .with_conflict(true)
4574                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4575        });
4576        let item4 = cx.new_view(|cx| {
4577            TestItem::new(cx)
4578                .with_dirty(true)
4579                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4580        });
4581        let pane = workspace.update(cx, |workspace, cx| {
4582            workspace.add_item(Box::new(item1.clone()), cx);
4583            workspace.add_item(Box::new(item2.clone()), cx);
4584            workspace.add_item(Box::new(item3.clone()), cx);
4585            workspace.add_item(Box::new(item4.clone()), cx);
4586            workspace.active_pane().clone()
4587        });
4588
4589        let close_items = pane.update(cx, |pane, cx| {
4590            pane.activate_item(1, true, true, cx);
4591            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4592            let item1_id = item1.item_id();
4593            let item3_id = item3.item_id();
4594            let item4_id = item4.item_id();
4595            pane.close_items(cx, SaveIntent::Close, move |id| {
4596                [item1_id, item3_id, item4_id].contains(&id)
4597            })
4598        });
4599        cx.executor().run_until_parked();
4600
4601        assert!(cx.has_pending_prompt());
4602        // Ignore "Save all" prompt
4603        cx.simulate_prompt_answer(2);
4604        cx.executor().run_until_parked();
4605        // There's a prompt to save item 1.
4606        pane.update(cx, |pane, _| {
4607            assert_eq!(pane.items_len(), 4);
4608            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4609        });
4610        // Confirm saving item 1.
4611        cx.simulate_prompt_answer(0);
4612        cx.executor().run_until_parked();
4613
4614        // Item 1 is saved. There's a prompt to save item 3.
4615        pane.update(cx, |pane, cx| {
4616            assert_eq!(item1.read(cx).save_count, 1);
4617            assert_eq!(item1.read(cx).save_as_count, 0);
4618            assert_eq!(item1.read(cx).reload_count, 0);
4619            assert_eq!(pane.items_len(), 3);
4620            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4621        });
4622        assert!(cx.has_pending_prompt());
4623
4624        // Cancel saving item 3.
4625        cx.simulate_prompt_answer(1);
4626        cx.executor().run_until_parked();
4627
4628        // Item 3 is reloaded. There's a prompt to save item 4.
4629        pane.update(cx, |pane, cx| {
4630            assert_eq!(item3.read(cx).save_count, 0);
4631            assert_eq!(item3.read(cx).save_as_count, 0);
4632            assert_eq!(item3.read(cx).reload_count, 1);
4633            assert_eq!(pane.items_len(), 2);
4634            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4635        });
4636        assert!(cx.has_pending_prompt());
4637
4638        // Confirm saving item 4.
4639        cx.simulate_prompt_answer(0);
4640        cx.executor().run_until_parked();
4641
4642        // There's a prompt for a path for item 4.
4643        cx.simulate_new_path_selection(|_| Some(Default::default()));
4644        close_items.await.unwrap();
4645
4646        // The requested items are closed.
4647        pane.update(cx, |pane, cx| {
4648            assert_eq!(item4.read(cx).save_count, 0);
4649            assert_eq!(item4.read(cx).save_as_count, 1);
4650            assert_eq!(item4.read(cx).reload_count, 0);
4651            assert_eq!(pane.items_len(), 1);
4652            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4653        });
4654    }
4655
4656    #[gpui::test]
4657    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4658        init_test(cx);
4659
4660        let fs = FakeFs::new(cx.executor());
4661        let project = Project::test(fs, [], cx).await;
4662        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4663
4664        // Create several workspace items with single project entries, and two
4665        // workspace items with multiple project entries.
4666        let single_entry_items = (0..=4)
4667            .map(|project_entry_id| {
4668                cx.new_view(|cx| {
4669                    TestItem::new(cx)
4670                        .with_dirty(true)
4671                        .with_project_items(&[TestProjectItem::new(
4672                            project_entry_id,
4673                            &format!("{project_entry_id}.txt"),
4674                            cx,
4675                        )])
4676                })
4677            })
4678            .collect::<Vec<_>>();
4679        let item_2_3 = cx.new_view(|cx| {
4680            TestItem::new(cx)
4681                .with_dirty(true)
4682                .with_singleton(false)
4683                .with_project_items(&[
4684                    single_entry_items[2].read(cx).project_items[0].clone(),
4685                    single_entry_items[3].read(cx).project_items[0].clone(),
4686                ])
4687        });
4688        let item_3_4 = cx.new_view(|cx| {
4689            TestItem::new(cx)
4690                .with_dirty(true)
4691                .with_singleton(false)
4692                .with_project_items(&[
4693                    single_entry_items[3].read(cx).project_items[0].clone(),
4694                    single_entry_items[4].read(cx).project_items[0].clone(),
4695                ])
4696        });
4697
4698        // Create two panes that contain the following project entries:
4699        //   left pane:
4700        //     multi-entry items:   (2, 3)
4701        //     single-entry items:  0, 1, 2, 3, 4
4702        //   right pane:
4703        //     single-entry items:  1
4704        //     multi-entry items:   (3, 4)
4705        let left_pane = workspace.update(cx, |workspace, cx| {
4706            let left_pane = workspace.active_pane().clone();
4707            workspace.add_item(Box::new(item_2_3.clone()), cx);
4708            for item in single_entry_items {
4709                workspace.add_item(Box::new(item), cx);
4710            }
4711            left_pane.update(cx, |pane, cx| {
4712                pane.activate_item(2, true, true, cx);
4713            });
4714
4715            let right_pane = workspace
4716                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4717                .unwrap();
4718
4719            right_pane.update(cx, |pane, cx| {
4720                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4721            });
4722
4723            left_pane
4724        });
4725
4726        cx.focus_view(&left_pane);
4727
4728        // When closing all of the items in the left pane, we should be prompted twice:
4729        // once for project entry 0, and once for project entry 2. Project entries 1,
4730        // 3, and 4 are all still open in the other paten. After those two
4731        // prompts, the task should complete.
4732
4733        let close = left_pane.update(cx, |pane, cx| {
4734            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4735        });
4736        cx.executor().run_until_parked();
4737
4738        // Discard "Save all" prompt
4739        cx.simulate_prompt_answer(2);
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(0)]
4746            );
4747        });
4748        cx.simulate_prompt_answer(0);
4749
4750        cx.executor().run_until_parked();
4751        left_pane.update(cx, |pane, cx| {
4752            assert_eq!(
4753                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4754                &[ProjectEntryId::from_proto(2)]
4755            );
4756        });
4757        cx.simulate_prompt_answer(0);
4758
4759        cx.executor().run_until_parked();
4760        close.await.unwrap();
4761        left_pane.update(cx, |pane, _| {
4762            assert_eq!(pane.items_len(), 0);
4763        });
4764    }
4765
4766    #[gpui::test]
4767    async fn test_autosave(cx: &mut gpui::TestAppContext) {
4768        init_test(cx);
4769
4770        let fs = FakeFs::new(cx.executor());
4771        let project = Project::test(fs, [], cx).await;
4772        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4773        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4774
4775        let item = cx.new_view(|cx| {
4776            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4777        });
4778        let item_id = item.entity_id();
4779        workspace.update(cx, |workspace, cx| {
4780            workspace.add_item(Box::new(item.clone()), cx);
4781        });
4782
4783        // Autosave on window change.
4784        item.update(cx, |item, cx| {
4785            cx.update_global(|settings: &mut SettingsStore, cx| {
4786                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4787                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4788                })
4789            });
4790            item.is_dirty = true;
4791        });
4792
4793        // Deactivating the window saves the file.
4794        cx.deactivate_window();
4795        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4796
4797        // Autosave on focus change.
4798        item.update(cx, |item, cx| {
4799            cx.focus_self();
4800            cx.update_global(|settings: &mut SettingsStore, cx| {
4801                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4802                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4803                })
4804            });
4805            item.is_dirty = true;
4806        });
4807
4808        // Blurring the item saves the file.
4809        item.update(cx, |_, cx| cx.blur());
4810        cx.executor().run_until_parked();
4811        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4812
4813        // Deactivating the window still saves the file.
4814        cx.update(|cx| cx.activate_window());
4815        item.update(cx, |item, cx| {
4816            cx.focus_self();
4817            item.is_dirty = true;
4818        });
4819        cx.deactivate_window();
4820
4821        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4822
4823        // Autosave after delay.
4824        item.update(cx, |item, cx| {
4825            cx.update_global(|settings: &mut SettingsStore, cx| {
4826                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4827                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4828                })
4829            });
4830            item.is_dirty = true;
4831            cx.emit(ItemEvent::Edit);
4832        });
4833
4834        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4835        cx.executor().advance_clock(Duration::from_millis(250));
4836        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4837
4838        // After delay expires, the file is saved.
4839        cx.executor().advance_clock(Duration::from_millis(250));
4840        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4841
4842        // Autosave on focus change, ensuring closing the tab counts as such.
4843        item.update(cx, |item, cx| {
4844            cx.update_global(|settings: &mut SettingsStore, cx| {
4845                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4846                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4847                })
4848            });
4849            item.is_dirty = true;
4850        });
4851
4852        pane.update(cx, |pane, cx| {
4853            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4854        })
4855        .await
4856        .unwrap();
4857        assert!(!cx.has_pending_prompt());
4858        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4859
4860        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4861        workspace.update(cx, |workspace, cx| {
4862            workspace.add_item(Box::new(item.clone()), cx);
4863        });
4864        item.update(cx, |item, cx| {
4865            item.project_items[0].update(cx, |item, _| {
4866                item.entry_id = None;
4867            });
4868            item.is_dirty = true;
4869            cx.blur();
4870        });
4871        cx.run_until_parked();
4872        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4873
4874        // Ensure autosave is prevented for deleted files also when closing the buffer.
4875        let _close_items = pane.update(cx, |pane, cx| {
4876            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4877        });
4878        cx.run_until_parked();
4879        assert!(cx.has_pending_prompt());
4880        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4881    }
4882
4883    #[gpui::test]
4884    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4885        init_test(cx);
4886
4887        let fs = FakeFs::new(cx.executor());
4888
4889        let project = Project::test(fs, [], cx).await;
4890        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4891
4892        let item = cx.new_view(|cx| {
4893            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4894        });
4895        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4896        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4897        let toolbar_notify_count = Rc::new(RefCell::new(0));
4898
4899        workspace.update(cx, |workspace, cx| {
4900            workspace.add_item(Box::new(item.clone()), cx);
4901            let toolbar_notification_count = toolbar_notify_count.clone();
4902            cx.observe(&toolbar, move |_, _, _| {
4903                *toolbar_notification_count.borrow_mut() += 1
4904            })
4905            .detach();
4906        });
4907
4908        pane.update(cx, |pane, _| {
4909            assert!(!pane.can_navigate_backward());
4910            assert!(!pane.can_navigate_forward());
4911        });
4912
4913        item.update(cx, |item, cx| {
4914            item.set_state("one".to_string(), cx);
4915        });
4916
4917        // Toolbar must be notified to re-render the navigation buttons
4918        assert_eq!(*toolbar_notify_count.borrow(), 1);
4919
4920        pane.update(cx, |pane, _| {
4921            assert!(pane.can_navigate_backward());
4922            assert!(!pane.can_navigate_forward());
4923        });
4924
4925        workspace
4926            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4927            .await
4928            .unwrap();
4929
4930        assert_eq!(*toolbar_notify_count.borrow(), 2);
4931        pane.update(cx, |pane, _| {
4932            assert!(!pane.can_navigate_backward());
4933            assert!(pane.can_navigate_forward());
4934        });
4935    }
4936
4937    #[gpui::test]
4938    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4939        init_test(cx);
4940        let fs = FakeFs::new(cx.executor());
4941
4942        let project = Project::test(fs, [], cx).await;
4943        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4944
4945        let panel = workspace.update(cx, |workspace, cx| {
4946            let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
4947            workspace.add_panel(panel.clone(), cx);
4948
4949            workspace
4950                .right_dock()
4951                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4952
4953            panel
4954        });
4955
4956        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4957        pane.update(cx, |pane, cx| {
4958            let item = cx.new_view(|cx| TestItem::new(cx));
4959            pane.add_item(Box::new(item), true, true, None, cx);
4960        });
4961
4962        // Transfer focus from center to panel
4963        workspace.update(cx, |workspace, cx| {
4964            workspace.toggle_panel_focus::<TestPanel>(cx);
4965        });
4966
4967        workspace.update(cx, |workspace, cx| {
4968            assert!(workspace.right_dock().read(cx).is_open());
4969            assert!(!panel.is_zoomed(cx));
4970            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
4971        });
4972
4973        // Transfer focus from panel to center
4974        workspace.update(cx, |workspace, cx| {
4975            workspace.toggle_panel_focus::<TestPanel>(cx);
4976        });
4977
4978        workspace.update(cx, |workspace, cx| {
4979            assert!(workspace.right_dock().read(cx).is_open());
4980            assert!(!panel.is_zoomed(cx));
4981            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4982        });
4983
4984        // Close the dock
4985        workspace.update(cx, |workspace, cx| {
4986            workspace.toggle_dock(DockPosition::Right, cx);
4987        });
4988
4989        workspace.update(cx, |workspace, cx| {
4990            assert!(!workspace.right_dock().read(cx).is_open());
4991            assert!(!panel.is_zoomed(cx));
4992            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
4993        });
4994
4995        // Open the dock
4996        workspace.update(cx, |workspace, cx| {
4997            workspace.toggle_dock(DockPosition::Right, cx);
4998        });
4999
5000        workspace.update(cx, |workspace, cx| {
5001            assert!(workspace.right_dock().read(cx).is_open());
5002            assert!(!panel.is_zoomed(cx));
5003            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5004        });
5005
5006        // Focus and zoom panel
5007        panel.update(cx, |panel, cx| {
5008            cx.focus_self();
5009            panel.set_zoomed(true, cx)
5010        });
5011
5012        workspace.update(cx, |workspace, cx| {
5013            assert!(workspace.right_dock().read(cx).is_open());
5014            assert!(panel.is_zoomed(cx));
5015            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5016        });
5017
5018        // Transfer focus to the center closes the dock
5019        workspace.update(cx, |workspace, cx| {
5020            workspace.toggle_panel_focus::<TestPanel>(cx);
5021        });
5022
5023        workspace.update(cx, |workspace, cx| {
5024            assert!(!workspace.right_dock().read(cx).is_open());
5025            assert!(panel.is_zoomed(cx));
5026            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5027        });
5028
5029        // Transferring focus back to the panel keeps it zoomed
5030        workspace.update(cx, |workspace, cx| {
5031            workspace.toggle_panel_focus::<TestPanel>(cx);
5032        });
5033
5034        workspace.update(cx, |workspace, cx| {
5035            assert!(workspace.right_dock().read(cx).is_open());
5036            assert!(panel.is_zoomed(cx));
5037            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5038        });
5039
5040        // Close the dock while it is zoomed
5041        workspace.update(cx, |workspace, cx| {
5042            workspace.toggle_dock(DockPosition::Right, cx)
5043        });
5044
5045        workspace.update(cx, |workspace, cx| {
5046            assert!(!workspace.right_dock().read(cx).is_open());
5047            assert!(panel.is_zoomed(cx));
5048            assert!(workspace.zoomed.is_none());
5049            assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
5050        });
5051
5052        // Opening the dock, when it's zoomed, retains focus
5053        workspace.update(cx, |workspace, cx| {
5054            workspace.toggle_dock(DockPosition::Right, cx)
5055        });
5056
5057        workspace.update(cx, |workspace, cx| {
5058            assert!(workspace.right_dock().read(cx).is_open());
5059            assert!(panel.is_zoomed(cx));
5060            assert!(workspace.zoomed.is_some());
5061            assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
5062        });
5063
5064        // Unzoom and close the panel, zoom the active pane.
5065        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5066        workspace.update(cx, |workspace, cx| {
5067            workspace.toggle_dock(DockPosition::Right, cx)
5068        });
5069        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5070
5071        // Opening a dock unzooms the pane.
5072        workspace.update(cx, |workspace, cx| {
5073            workspace.toggle_dock(DockPosition::Right, cx)
5074        });
5075        workspace.update(cx, |workspace, cx| {
5076            let pane = pane.read(cx);
5077            assert!(!pane.is_zoomed());
5078            assert!(!pane.focus_handle(cx).is_focused(cx));
5079            assert!(workspace.right_dock().read(cx).is_open());
5080            assert!(workspace.zoomed.is_none());
5081        });
5082    }
5083
5084    struct TestModal(FocusHandle);
5085
5086    impl TestModal {
5087        fn new(cx: &mut ViewContext<Self>) -> Self {
5088            Self(cx.focus_handle())
5089        }
5090    }
5091
5092    impl EventEmitter<DismissEvent> for TestModal {}
5093
5094    impl FocusableView for TestModal {
5095        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
5096            self.0.clone()
5097        }
5098    }
5099
5100    impl ModalView for TestModal {}
5101
5102    impl Render for TestModal {
5103        fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement {
5104            div().track_focus(&self.0)
5105        }
5106    }
5107
5108    #[gpui::test]
5109    async fn test_panels(cx: &mut gpui::TestAppContext) {
5110        init_test(cx);
5111        let fs = FakeFs::new(cx.executor());
5112
5113        let project = Project::test(fs, [], cx).await;
5114        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
5115
5116        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5117            let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
5118            workspace.add_panel(panel_1.clone(), cx);
5119            workspace
5120                .left_dock()
5121                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5122            let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx));
5123            workspace.add_panel(panel_2.clone(), cx);
5124            workspace
5125                .right_dock()
5126                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5127
5128            let left_dock = workspace.left_dock();
5129            assert_eq!(
5130                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5131                panel_1.panel_id()
5132            );
5133            assert_eq!(
5134                left_dock.read(cx).active_panel_size(cx).unwrap(),
5135                panel_1.size(cx)
5136            );
5137
5138            left_dock.update(cx, |left_dock, cx| {
5139                left_dock.resize_active_panel(Some(px(1337.)), cx)
5140            });
5141            assert_eq!(
5142                workspace
5143                    .right_dock()
5144                    .read(cx)
5145                    .visible_panel()
5146                    .unwrap()
5147                    .panel_id(),
5148                panel_2.panel_id(),
5149            );
5150
5151            (panel_1, panel_2)
5152        });
5153
5154        // Move panel_1 to the right
5155        panel_1.update(cx, |panel_1, cx| {
5156            panel_1.set_position(DockPosition::Right, cx)
5157        });
5158
5159        workspace.update(cx, |workspace, cx| {
5160            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5161            // Since it was the only panel on the left, the left dock should now be closed.
5162            assert!(!workspace.left_dock().read(cx).is_open());
5163            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5164            let right_dock = workspace.right_dock();
5165            assert_eq!(
5166                right_dock.read(cx).visible_panel().unwrap().panel_id(),
5167                panel_1.panel_id()
5168            );
5169            assert_eq!(
5170                right_dock.read(cx).active_panel_size(cx).unwrap(),
5171                px(1337.)
5172            );
5173
5174            // Now we move panel_2 to the left
5175            panel_2.set_position(DockPosition::Left, cx);
5176        });
5177
5178        workspace.update(cx, |workspace, cx| {
5179            // Since panel_2 was not visible on the right, we don't open the left dock.
5180            assert!(!workspace.left_dock().read(cx).is_open());
5181            // And the right dock is unaffected in it's displaying of panel_1
5182            assert!(workspace.right_dock().read(cx).is_open());
5183            assert_eq!(
5184                workspace
5185                    .right_dock()
5186                    .read(cx)
5187                    .visible_panel()
5188                    .unwrap()
5189                    .panel_id(),
5190                panel_1.panel_id(),
5191            );
5192        });
5193
5194        // Move panel_1 back to the left
5195        panel_1.update(cx, |panel_1, cx| {
5196            panel_1.set_position(DockPosition::Left, cx)
5197        });
5198
5199        workspace.update(cx, |workspace, cx| {
5200            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5201            let left_dock = workspace.left_dock();
5202            assert!(left_dock.read(cx).is_open());
5203            assert_eq!(
5204                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5205                panel_1.panel_id()
5206            );
5207            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.));
5208            // And the right dock should be closed as it no longer has any panels.
5209            assert!(!workspace.right_dock().read(cx).is_open());
5210
5211            // Now we move panel_1 to the bottom
5212            panel_1.set_position(DockPosition::Bottom, cx);
5213        });
5214
5215        workspace.update(cx, |workspace, cx| {
5216            // Since panel_1 was visible on the left, we close the left dock.
5217            assert!(!workspace.left_dock().read(cx).is_open());
5218            // The bottom dock is sized based on the panel's default size,
5219            // since the panel orientation changed from vertical to horizontal.
5220            let bottom_dock = workspace.bottom_dock();
5221            assert_eq!(
5222                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5223                panel_1.size(cx),
5224            );
5225            // Close bottom dock and move panel_1 back to the left.
5226            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5227            panel_1.set_position(DockPosition::Left, cx);
5228        });
5229
5230        // Emit activated event on panel 1
5231        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
5232
5233        // Now the left dock is open and panel_1 is active and focused.
5234        workspace.update(cx, |workspace, cx| {
5235            let left_dock = workspace.left_dock();
5236            assert!(left_dock.read(cx).is_open());
5237            assert_eq!(
5238                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5239                panel_1.panel_id(),
5240            );
5241            assert!(panel_1.focus_handle(cx).is_focused(cx));
5242        });
5243
5244        // Emit closed event on panel 2, which is not active
5245        panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5246
5247        // Wo don't close the left dock, because panel_2 wasn't the active panel
5248        workspace.update(cx, |workspace, cx| {
5249            let left_dock = workspace.left_dock();
5250            assert!(left_dock.read(cx).is_open());
5251            assert_eq!(
5252                left_dock.read(cx).visible_panel().unwrap().panel_id(),
5253                panel_1.panel_id(),
5254            );
5255        });
5256
5257        // Emitting a ZoomIn event shows the panel as zoomed.
5258        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
5259        workspace.update(cx, |workspace, _| {
5260            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5261            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5262        });
5263
5264        // Move panel to another dock while it is zoomed
5265        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5266        workspace.update(cx, |workspace, _| {
5267            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5268
5269            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5270        });
5271
5272        // This is a helper for getting a:
5273        // - valid focus on an element,
5274        // - that isn't a part of the panes and panels system of the Workspace,
5275        // - and doesn't trigger the 'on_focus_lost' API.
5276        let focus_other_view = {
5277            let workspace = workspace.clone();
5278            move |cx: &mut VisualTestContext| {
5279                workspace.update(cx, |workspace, cx| {
5280                    if let Some(_) = workspace.active_modal::<TestModal>(cx) {
5281                        workspace.toggle_modal(cx, TestModal::new);
5282                        workspace.toggle_modal(cx, TestModal::new);
5283                    } else {
5284                        workspace.toggle_modal(cx, TestModal::new);
5285                    }
5286                })
5287            }
5288        };
5289
5290        // If focus is transferred to another view that's not a panel or another pane, we still show
5291        // the panel as zoomed.
5292        focus_other_view(cx);
5293        workspace.update(cx, |workspace, _| {
5294            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5295            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5296        });
5297
5298        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5299        workspace.update(cx, |_, cx| cx.focus_self());
5300        workspace.update(cx, |workspace, _| {
5301            assert_eq!(workspace.zoomed, None);
5302            assert_eq!(workspace.zoomed_position, None);
5303        });
5304
5305        // If focus is transferred again to another view that's not a panel or a pane, we won't
5306        // show the panel as zoomed because it wasn't zoomed before.
5307        focus_other_view(cx);
5308        workspace.update(cx, |workspace, _| {
5309            assert_eq!(workspace.zoomed, None);
5310            assert_eq!(workspace.zoomed_position, None);
5311        });
5312
5313        // When the panel is activated, it is zoomed again.
5314        cx.dispatch_action(ToggleRightDock);
5315        workspace.update(cx, |workspace, _| {
5316            assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
5317            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5318        });
5319
5320        // Emitting a ZoomOut event unzooms the panel.
5321        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
5322        workspace.update(cx, |workspace, _| {
5323            assert_eq!(workspace.zoomed, None);
5324            assert_eq!(workspace.zoomed_position, None);
5325        });
5326
5327        // Emit closed event on panel 1, which is active
5328        panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close));
5329
5330        // Now the left dock is closed, because panel_1 was the active panel
5331        workspace.update(cx, |workspace, cx| {
5332            let right_dock = workspace.right_dock();
5333            assert!(!right_dock.read(cx).is_open());
5334        });
5335    }
5336
5337    pub fn init_test(cx: &mut TestAppContext) {
5338        cx.update(|cx| {
5339            let settings_store = SettingsStore::test(cx);
5340            cx.set_global(settings_store);
5341            theme::init(theme::LoadThemes::JustBase, cx);
5342            language::init(cx);
5343            crate::init_settings(cx);
5344            Project::init_settings(cx);
5345        });
5346    }
5347}