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