workspace.rs

   1pub mod dock;
   2pub mod item;
   3pub mod notifications;
   4pub mod pane;
   5pub mod pane_group;
   6mod persistence;
   7pub mod searchable;
   8pub mod shared_screen;
   9mod status_bar;
  10mod toolbar;
  11mod workspace_settings;
  12
  13use anyhow::{anyhow, Context, Result};
  14use call::ActiveCall;
  15use client::{
  16    proto::{self, PeerId},
  17    Client, TypedEnvelope, UserStore,
  18};
  19use collections::{hash_map, HashMap, HashSet};
  20use drag_and_drop::DragAndDrop;
  21use futures::{
  22    channel::{mpsc, oneshot},
  23    future::try_join_all,
  24    FutureExt, StreamExt,
  25};
  26use gpui::{
  27    actions,
  28    elements::*,
  29    geometry::{
  30        rect::RectF,
  31        vector::{vec2f, Vector2F},
  32    },
  33    impl_actions,
  34    platform::{
  35        CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
  36        WindowBounds, WindowOptions,
  37    },
  38    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  39    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  40    WeakViewHandle, WindowContext, WindowHandle,
  41};
  42use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  43use itertools::Itertools;
  44use language::{LanguageRegistry, Rope};
  45use std::{
  46    any::TypeId,
  47    borrow::Cow,
  48    cmp, env,
  49    future::Future,
  50    path::{Path, PathBuf},
  51    rc::Rc,
  52    str,
  53    sync::{atomic::AtomicUsize, Arc},
  54    time::Duration,
  55};
  56
  57use crate::{
  58    notifications::{simple_message_notification::MessageNotification, NotificationTracker},
  59    persistence::model::{
  60        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  61    },
  62};
  63use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  64use lazy_static::lazy_static;
  65use notifications::{NotificationHandle, NotifyResultExt};
  66pub use pane::*;
  67pub use pane_group::*;
  68use persistence::{model::SerializedItem, DB};
  69pub use persistence::{
  70    model::{ItemId, WorkspaceLocation},
  71    WorkspaceDb, DB as WORKSPACE_DB,
  72};
  73use postage::prelude::Stream;
  74use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  75use serde::Deserialize;
  76use shared_screen::SharedScreen;
  77use status_bar::StatusBar;
  78pub use status_bar::StatusItemView;
  79use theme::{Theme, ThemeSettings};
  80pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  81use util::ResultExt;
  82pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  83
  84lazy_static! {
  85    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  86        .ok()
  87        .as_deref()
  88        .and_then(parse_pixel_position_env_var);
  89    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  90        .ok()
  91        .as_deref()
  92        .and_then(parse_pixel_position_env_var);
  93}
  94
  95pub trait Modal: View {
  96    fn has_focus(&self) -> bool;
  97    fn dismiss_on_event(event: &Self::Event) -> bool;
  98}
  99
 100trait ModalHandle {
 101    fn as_any(&self) -> &AnyViewHandle;
 102    fn has_focus(&self, cx: &WindowContext) -> bool;
 103}
 104
 105impl<T: Modal> ModalHandle for ViewHandle<T> {
 106    fn as_any(&self) -> &AnyViewHandle {
 107        self
 108    }
 109
 110    fn has_focus(&self, cx: &WindowContext) -> bool {
 111        self.read(cx).has_focus()
 112    }
 113}
 114
 115#[derive(Clone, PartialEq)]
 116pub struct RemoveWorktreeFromProject(pub WorktreeId);
 117
 118actions!(
 119    workspace,
 120    [
 121        Open,
 122        NewFile,
 123        NewWindow,
 124        CloseWindow,
 125        CloseInactiveTabsAndPanes,
 126        AddFolderToProject,
 127        Unfollow,
 128        SaveAs,
 129        ReloadActiveItem,
 130        ActivatePreviousPane,
 131        ActivateNextPane,
 132        FollowNextCollaborator,
 133        NewTerminal,
 134        NewCenterTerminal,
 135        ToggleTerminalFocus,
 136        NewSearch,
 137        Feedback,
 138        Restart,
 139        Welcome,
 140        ToggleZoom,
 141        ToggleLeftDock,
 142        ToggleRightDock,
 143        ToggleBottomDock,
 144        CloseAllDocks,
 145    ]
 146);
 147
 148#[derive(Clone, PartialEq)]
 149pub struct OpenPaths {
 150    pub paths: Vec<PathBuf>,
 151}
 152
 153#[derive(Clone, Deserialize, PartialEq)]
 154pub struct ActivatePane(pub usize);
 155
 156#[derive(Clone, Deserialize, PartialEq)]
 157pub struct ActivatePaneInDirection(pub SplitDirection);
 158
 159#[derive(Clone, Deserialize, PartialEq)]
 160pub struct SwapPaneInDirection(pub SplitDirection);
 161
 162#[derive(Clone, Deserialize, PartialEq)]
 163pub struct NewFileInDirection(pub SplitDirection);
 164
 165#[derive(Clone, PartialEq, Debug, Deserialize)]
 166#[serde(rename_all = "camelCase")]
 167pub struct SaveAll {
 168    pub save_intent: Option<SaveIntent>,
 169}
 170
 171#[derive(Clone, PartialEq, Debug, Deserialize)]
 172#[serde(rename_all = "camelCase")]
 173pub struct Save {
 174    pub save_intent: Option<SaveIntent>,
 175}
 176
 177#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 178#[serde(rename_all = "camelCase")]
 179pub struct CloseAllItemsAndPanes {
 180    pub save_intent: Option<SaveIntent>,
 181}
 182
 183#[derive(Deserialize)]
 184pub struct Toast {
 185    id: usize,
 186    msg: Cow<'static, str>,
 187    #[serde(skip)]
 188    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 189}
 190
 191impl Toast {
 192    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 193        Toast {
 194            id,
 195            msg: msg.into(),
 196            on_click: None,
 197        }
 198    }
 199
 200    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 201    where
 202        M: Into<Cow<'static, str>>,
 203        F: Fn(&mut WindowContext) + 'static,
 204    {
 205        self.on_click = Some((message.into(), Arc::new(on_click)));
 206        self
 207    }
 208}
 209
 210impl PartialEq for Toast {
 211    fn eq(&self, other: &Self) -> bool {
 212        self.id == other.id
 213            && self.msg == other.msg
 214            && self.on_click.is_some() == other.on_click.is_some()
 215    }
 216}
 217
 218impl Clone for Toast {
 219    fn clone(&self) -> Self {
 220        Toast {
 221            id: self.id,
 222            msg: self.msg.to_owned(),
 223            on_click: self.on_click.clone(),
 224        }
 225    }
 226}
 227
 228#[derive(Clone, Deserialize, PartialEq)]
 229pub struct OpenTerminal {
 230    pub working_directory: PathBuf,
 231}
 232
 233impl_actions!(
 234    workspace,
 235    [
 236        ActivatePane,
 237        ActivatePaneInDirection,
 238        SwapPaneInDirection,
 239        NewFileInDirection,
 240        Toast,
 241        OpenTerminal,
 242        SaveAll,
 243        Save,
 244        CloseAllItemsAndPanes,
 245    ]
 246);
 247
 248pub type WorkspaceId = i64;
 249
 250pub fn init_settings(cx: &mut AppContext) {
 251    settings::register::<WorkspaceSettings>(cx);
 252    settings::register::<item::ItemSettings>(cx);
 253}
 254
 255pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 256    init_settings(cx);
 257    pane::init(cx);
 258    notifications::init(cx);
 259
 260    cx.add_global_action({
 261        let app_state = Arc::downgrade(&app_state);
 262        move |_: &Open, cx: &mut AppContext| {
 263            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 264                files: true,
 265                directories: true,
 266                multiple: true,
 267            });
 268
 269            if let Some(app_state) = app_state.upgrade() {
 270                cx.spawn(move |mut cx| async move {
 271                    if let Some(paths) = paths.recv().await.flatten() {
 272                        cx.update(|cx| {
 273                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 274                        });
 275                    }
 276                })
 277                .detach();
 278            }
 279        }
 280    });
 281    cx.add_async_action(Workspace::open);
 282
 283    cx.add_async_action(Workspace::follow_next_collaborator);
 284    cx.add_async_action(Workspace::close);
 285    cx.add_async_action(Workspace::close_inactive_items_and_panes);
 286    cx.add_async_action(Workspace::close_all_items_and_panes);
 287    cx.add_global_action(Workspace::close_global);
 288    cx.add_global_action(restart);
 289    cx.add_async_action(Workspace::save_all);
 290    cx.add_action(Workspace::add_folder_to_project);
 291    cx.add_action(
 292        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 293            let pane = workspace.active_pane().clone();
 294            workspace.unfollow(&pane, cx);
 295        },
 296    );
 297    cx.add_action(
 298        |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
 299            workspace
 300                .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
 301                .detach_and_log_err(cx);
 302        },
 303    );
 304    cx.add_action(
 305        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 306            workspace
 307                .save_active_item(SaveIntent::SaveAs, cx)
 308                .detach_and_log_err(cx);
 309        },
 310    );
 311    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 312        workspace.activate_previous_pane(cx)
 313    });
 314    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 315        workspace.activate_next_pane(cx)
 316    });
 317
 318    cx.add_action(
 319        |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
 320            workspace.activate_pane_in_direction(action.0, cx)
 321        },
 322    );
 323
 324    cx.add_action(
 325        |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
 326            workspace.swap_pane_in_direction(action.0, cx)
 327        },
 328    );
 329
 330    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 331        workspace.toggle_dock(DockPosition::Left, cx);
 332    });
 333    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 334        workspace.toggle_dock(DockPosition::Right, cx);
 335    });
 336    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 337        workspace.toggle_dock(DockPosition::Bottom, cx);
 338    });
 339    cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
 340        workspace.close_all_docks(cx);
 341    });
 342    cx.add_action(Workspace::activate_pane_at_index);
 343    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 344        workspace.reopen_closed_item(cx).detach();
 345    });
 346    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 347        workspace
 348            .go_back(workspace.active_pane().downgrade(), cx)
 349            .detach();
 350    });
 351    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 352        workspace
 353            .go_forward(workspace.active_pane().downgrade(), cx)
 354            .detach();
 355    });
 356
 357    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 358        cx.spawn(|workspace, mut cx| async move {
 359            let err = install_cli::install_cli(&cx)
 360                .await
 361                .context("Failed to create CLI symlink");
 362
 363            workspace.update(&mut cx, |workspace, cx| {
 364                if matches!(err, Err(_)) {
 365                    err.notify_err(workspace, cx);
 366                } else {
 367                    workspace.show_notification(1, cx, |cx| {
 368                        cx.add_view(|_| {
 369                            MessageNotification::new("Successfully installed the `zed` binary")
 370                        })
 371                    });
 372                }
 373            })
 374        })
 375        .detach();
 376    });
 377}
 378
 379type ProjectItemBuilders = HashMap<
 380    TypeId,
 381    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 382>;
 383pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 384    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 385        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 386            let item = model.downcast::<I::Item>().unwrap();
 387            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 388        });
 389    });
 390}
 391
 392type FollowableItemBuilder = fn(
 393    ViewHandle<Pane>,
 394    ViewHandle<Workspace>,
 395    ViewId,
 396    &mut Option<proto::view::Variant>,
 397    &mut AppContext,
 398) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 399type FollowableItemBuilders = HashMap<
 400    TypeId,
 401    (
 402        FollowableItemBuilder,
 403        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 404    ),
 405>;
 406pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 407    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 408        builders.insert(
 409            TypeId::of::<I>(),
 410            (
 411                |pane, workspace, id, state, cx| {
 412                    I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 413                        cx.foreground()
 414                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 415                    })
 416                },
 417                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 418            ),
 419        );
 420    });
 421}
 422
 423type ItemDeserializers = HashMap<
 424    Arc<str>,
 425    fn(
 426        ModelHandle<Project>,
 427        WeakViewHandle<Workspace>,
 428        WorkspaceId,
 429        ItemId,
 430        &mut ViewContext<Pane>,
 431    ) -> Task<Result<Box<dyn ItemHandle>>>,
 432>;
 433pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 434    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 435        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 436            deserializers.insert(
 437                Arc::from(serialized_item_kind),
 438                |project, workspace, workspace_id, item_id, cx| {
 439                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 440                    cx.foreground()
 441                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 442                },
 443            );
 444        }
 445    });
 446}
 447
 448pub struct AppState {
 449    pub languages: Arc<LanguageRegistry>,
 450    pub client: Arc<Client>,
 451    pub user_store: ModelHandle<UserStore>,
 452    pub workspace_store: ModelHandle<WorkspaceStore>,
 453    pub fs: Arc<dyn fs::Fs>,
 454    pub build_window_options:
 455        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 456    pub initialize_workspace:
 457        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 458    pub background_actions: BackgroundActions,
 459}
 460
 461pub struct WorkspaceStore {
 462    workspaces: HashSet<WeakViewHandle<Workspace>>,
 463    followers: Vec<Follower>,
 464    client: Arc<Client>,
 465    _subscriptions: Vec<client::Subscription>,
 466}
 467
 468#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 469struct Follower {
 470    project_id: Option<u64>,
 471    peer_id: PeerId,
 472}
 473
 474impl AppState {
 475    #[cfg(any(test, feature = "test-support"))]
 476    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 477        use settings::SettingsStore;
 478
 479        if !cx.has_global::<SettingsStore>() {
 480            cx.set_global(SettingsStore::test(cx));
 481        }
 482
 483        let fs = fs::FakeFs::new(cx.background().clone());
 484        let languages = Arc::new(LanguageRegistry::test());
 485        let http_client = util::http::FakeHttpClient::with_404_response();
 486        let client = Client::new(http_client.clone(), cx);
 487        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 488        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
 489
 490        theme::init((), cx);
 491        client::init(&client, cx);
 492        crate::init_settings(cx);
 493
 494        Arc::new(Self {
 495            client,
 496            fs,
 497            languages,
 498            user_store,
 499            // channel_store,
 500            workspace_store,
 501            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 502            build_window_options: |_, _, _| Default::default(),
 503            background_actions: || &[],
 504        })
 505    }
 506}
 507
 508struct DelayedDebouncedEditAction {
 509    task: Option<Task<()>>,
 510    cancel_channel: Option<oneshot::Sender<()>>,
 511}
 512
 513impl DelayedDebouncedEditAction {
 514    fn new() -> DelayedDebouncedEditAction {
 515        DelayedDebouncedEditAction {
 516            task: None,
 517            cancel_channel: None,
 518        }
 519    }
 520
 521    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 522    where
 523        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 524    {
 525        if let Some(channel) = self.cancel_channel.take() {
 526            _ = channel.send(());
 527        }
 528
 529        let (sender, mut receiver) = oneshot::channel::<()>();
 530        self.cancel_channel = Some(sender);
 531
 532        let previous_task = self.task.take();
 533        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 534            let mut timer = cx.background().timer(delay).fuse();
 535            if let Some(previous_task) = previous_task {
 536                previous_task.await;
 537            }
 538
 539            futures::select_biased! {
 540                _ = receiver => return,
 541                    _ = timer => {}
 542            }
 543
 544            if let Some(result) = workspace
 545                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 546                .log_err()
 547            {
 548                result.await.log_err();
 549            }
 550        }));
 551    }
 552}
 553
 554pub enum Event {
 555    PaneAdded(ViewHandle<Pane>),
 556    ContactRequestedJoin(u64),
 557}
 558
 559pub struct Workspace {
 560    weak_self: WeakViewHandle<Self>,
 561    modal: Option<ActiveModal>,
 562    zoomed: Option<AnyWeakViewHandle>,
 563    zoomed_position: Option<DockPosition>,
 564    center: PaneGroup,
 565    left_dock: ViewHandle<Dock>,
 566    bottom_dock: ViewHandle<Dock>,
 567    right_dock: ViewHandle<Dock>,
 568    panes: Vec<ViewHandle<Pane>>,
 569    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 570    active_pane: ViewHandle<Pane>,
 571    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 572    last_active_view_id: Option<proto::ViewId>,
 573    status_bar: ViewHandle<StatusBar>,
 574    titlebar_item: Option<AnyViewHandle>,
 575    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 576    project: ModelHandle<Project>,
 577    follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
 578    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 579    window_edited: bool,
 580    active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
 581    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 582    database_id: WorkspaceId,
 583    app_state: Arc<AppState>,
 584    subscriptions: Vec<Subscription>,
 585    _apply_leader_updates: Task<Result<()>>,
 586    _observe_current_user: Task<Result<()>>,
 587    _schedule_serialize: Option<Task<()>>,
 588    pane_history_timestamp: Arc<AtomicUsize>,
 589}
 590
 591struct ActiveModal {
 592    view: Box<dyn ModalHandle>,
 593    previously_focused_view_id: Option<usize>,
 594}
 595
 596#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 597pub struct ViewId {
 598    pub creator: PeerId,
 599    pub id: u64,
 600}
 601
 602#[derive(Default)]
 603struct FollowerState {
 604    leader_id: PeerId,
 605    active_view_id: Option<ViewId>,
 606    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 607}
 608
 609enum WorkspaceBounds {}
 610
 611impl Workspace {
 612    pub fn new(
 613        workspace_id: WorkspaceId,
 614        project: ModelHandle<Project>,
 615        app_state: Arc<AppState>,
 616        cx: &mut ViewContext<Self>,
 617    ) -> Self {
 618        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 619        cx.subscribe(&project, move |this, _, event, cx| {
 620            match event {
 621                project::Event::RemoteIdChanged(_) => {
 622                    this.update_window_title(cx);
 623                }
 624
 625                project::Event::CollaboratorLeft(peer_id) => {
 626                    this.collaborator_left(*peer_id, cx);
 627                }
 628
 629                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 630                    this.update_window_title(cx);
 631                    this.serialize_workspace(cx);
 632                }
 633
 634                project::Event::DisconnectedFromHost => {
 635                    this.update_window_edited(cx);
 636                    cx.blur();
 637                }
 638
 639                project::Event::Closed => {
 640                    cx.remove_window();
 641                }
 642
 643                project::Event::DeletedEntry(entry_id) => {
 644                    for pane in this.panes.iter() {
 645                        pane.update(cx, |pane, cx| {
 646                            pane.handle_deleted_project_item(*entry_id, cx)
 647                        });
 648                    }
 649                }
 650
 651                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 652                    cx.add_view(|_| MessageNotification::new(message.clone()))
 653                }),
 654
 655                _ => {}
 656            }
 657            cx.notify()
 658        })
 659        .detach();
 660
 661        let weak_handle = cx.weak_handle();
 662        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 663
 664        let center_pane = cx.add_view(|cx| {
 665            Pane::new(
 666                weak_handle.clone(),
 667                project.clone(),
 668                app_state.background_actions,
 669                pane_history_timestamp.clone(),
 670                cx,
 671            )
 672        });
 673        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 674        cx.focus(&center_pane);
 675        cx.emit(Event::PaneAdded(center_pane.clone()));
 676
 677        app_state.workspace_store.update(cx, |store, _| {
 678            store.workspaces.insert(weak_handle.clone());
 679        });
 680
 681        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 682        let mut connection_status = app_state.client.status();
 683        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 684            current_user.recv().await;
 685            connection_status.recv().await;
 686            let mut stream =
 687                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 688
 689            while stream.recv().await.is_some() {
 690                this.update(&mut cx, |_, cx| cx.notify())?;
 691            }
 692            anyhow::Ok(())
 693        });
 694
 695        // All leader updates are enqueued and then processed in a single task, so
 696        // that each asynchronous operation can be run in order.
 697        let (leader_updates_tx, mut leader_updates_rx) =
 698            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 699        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 700            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 701                Self::process_leader_update(&this, leader_id, update, &mut cx)
 702                    .await
 703                    .log_err();
 704            }
 705
 706            Ok(())
 707        });
 708
 709        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 710
 711        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 712        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 713        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 714        let left_dock_buttons =
 715            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 716        let bottom_dock_buttons =
 717            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 718        let right_dock_buttons =
 719            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 720        let status_bar = cx.add_view(|cx| {
 721            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 722            status_bar.add_left_item(left_dock_buttons, cx);
 723            status_bar.add_right_item(right_dock_buttons, cx);
 724            status_bar.add_right_item(bottom_dock_buttons, cx);
 725            status_bar
 726        });
 727
 728        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 729            drag_and_drop.register_container(weak_handle.clone());
 730        });
 731
 732        let mut active_call = None;
 733        if cx.has_global::<ModelHandle<ActiveCall>>() {
 734            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 735            let mut subscriptions = Vec::new();
 736            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 737            active_call = Some((call, subscriptions));
 738        }
 739
 740        let subscriptions = vec![
 741            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 742            cx.observe_window_activation(Self::on_window_activation_changed),
 743            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 744                // Transform fixed bounds to be stored in terms of the containing display
 745                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 746                    if let Some(screen) = cx.platform().screen_by_id(display) {
 747                        let screen_bounds = screen.bounds();
 748                        window_bounds
 749                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 750                        window_bounds
 751                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 752                        bounds = WindowBounds::Fixed(window_bounds);
 753                    }
 754                }
 755
 756                cx.background()
 757                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 758                    .detach_and_log_err(cx);
 759            }),
 760            cx.observe(&left_dock, |this, _, cx| {
 761                this.serialize_workspace(cx);
 762                cx.notify();
 763            }),
 764            cx.observe(&bottom_dock, |this, _, cx| {
 765                this.serialize_workspace(cx);
 766                cx.notify();
 767            }),
 768            cx.observe(&right_dock, |this, _, cx| {
 769                this.serialize_workspace(cx);
 770                cx.notify();
 771            }),
 772        ];
 773
 774        cx.defer(|this, cx| this.update_window_title(cx));
 775        Workspace {
 776            weak_self: weak_handle.clone(),
 777            modal: None,
 778            zoomed: None,
 779            zoomed_position: None,
 780            center: PaneGroup::new(center_pane.clone()),
 781            panes: vec![center_pane.clone()],
 782            panes_by_item: Default::default(),
 783            active_pane: center_pane.clone(),
 784            last_active_center_pane: Some(center_pane.downgrade()),
 785            last_active_view_id: None,
 786            status_bar,
 787            titlebar_item: None,
 788            notifications: Default::default(),
 789            left_dock,
 790            bottom_dock,
 791            right_dock,
 792            project: project.clone(),
 793            follower_states: Default::default(),
 794            last_leaders_by_pane: Default::default(),
 795            window_edited: false,
 796            active_call,
 797            database_id: workspace_id,
 798            app_state,
 799            _observe_current_user,
 800            _apply_leader_updates,
 801            _schedule_serialize: None,
 802            leader_updates_tx,
 803            subscriptions,
 804            pane_history_timestamp,
 805        }
 806    }
 807
 808    fn new_local(
 809        abs_paths: Vec<PathBuf>,
 810        app_state: Arc<AppState>,
 811        requesting_window: Option<WindowHandle<Workspace>>,
 812        cx: &mut AppContext,
 813    ) -> Task<(
 814        WeakViewHandle<Workspace>,
 815        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 816    )> {
 817        let project_handle = Project::local(
 818            app_state.client.clone(),
 819            app_state.user_store.clone(),
 820            app_state.languages.clone(),
 821            app_state.fs.clone(),
 822            cx,
 823        );
 824
 825        cx.spawn(|mut cx| async move {
 826            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 827
 828            let paths_to_open = Arc::new(abs_paths);
 829
 830            // Get project paths for all of the abs_paths
 831            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 832            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 833                Vec::with_capacity(paths_to_open.len());
 834            for path in paths_to_open.iter().cloned() {
 835                if let Some((worktree, project_entry)) = cx
 836                    .update(|cx| {
 837                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 838                    })
 839                    .await
 840                    .log_err()
 841                {
 842                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 843                    project_paths.push((path, Some(project_entry)));
 844                } else {
 845                    project_paths.push((path, None));
 846                }
 847            }
 848
 849            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 850                serialized_workspace.id
 851            } else {
 852                DB.next_id().await.unwrap_or(0)
 853            };
 854
 855            let window = if let Some(window) = requesting_window {
 856                window.replace_root(&mut cx, |cx| {
 857                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 858                });
 859                window
 860            } else {
 861                {
 862                    let window_bounds_override = window_bounds_env_override(&cx);
 863                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 864                        (Some(bounds), None)
 865                    } else {
 866                        serialized_workspace
 867                            .as_ref()
 868                            .and_then(|serialized_workspace| {
 869                                let display = serialized_workspace.display?;
 870                                let mut bounds = serialized_workspace.bounds?;
 871
 872                                // Stored bounds are relative to the containing display.
 873                                // So convert back to global coordinates if that screen still exists
 874                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 875                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 876                                        let screen_bounds = screen.bounds();
 877                                        window_bounds.set_origin_x(
 878                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 879                                        );
 880                                        window_bounds.set_origin_y(
 881                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 882                                        );
 883                                        bounds = WindowBounds::Fixed(window_bounds);
 884                                    } else {
 885                                        // Screen no longer exists. Return none here.
 886                                        return None;
 887                                    }
 888                                }
 889
 890                                Some((bounds, display))
 891                            })
 892                            .unzip()
 893                    };
 894
 895                    // Use the serialized workspace to construct the new window
 896                    cx.add_window(
 897                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 898                        |cx| {
 899                            Workspace::new(
 900                                workspace_id,
 901                                project_handle.clone(),
 902                                app_state.clone(),
 903                                cx,
 904                            )
 905                        },
 906                    )
 907                }
 908            };
 909
 910            // We haven't yielded the main thread since obtaining the window handle,
 911            // so the window exists.
 912            let workspace = window.root(&cx).unwrap();
 913
 914            (app_state.initialize_workspace)(
 915                workspace.downgrade(),
 916                serialized_workspace.is_some(),
 917                app_state.clone(),
 918                cx.clone(),
 919            )
 920            .await
 921            .log_err();
 922
 923            window.update(&mut cx, |cx| cx.activate_window());
 924
 925            let workspace = workspace.downgrade();
 926            notify_if_database_failed(&workspace, &mut cx);
 927            let opened_items = open_items(
 928                serialized_workspace,
 929                &workspace,
 930                project_paths,
 931                app_state,
 932                cx,
 933            )
 934            .await
 935            .unwrap_or_default();
 936
 937            (workspace, opened_items)
 938        })
 939    }
 940
 941    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 942        self.weak_self.clone()
 943    }
 944
 945    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 946        &self.left_dock
 947    }
 948
 949    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 950        &self.bottom_dock
 951    }
 952
 953    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 954        &self.right_dock
 955    }
 956
 957    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
 958    where
 959        T::Event: std::fmt::Debug,
 960    {
 961        self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
 962    }
 963
 964    pub fn add_panel_with_extra_event_handler<T: Panel, F>(
 965        &mut self,
 966        panel: ViewHandle<T>,
 967        cx: &mut ViewContext<Self>,
 968        handler: F,
 969    ) where
 970        T::Event: std::fmt::Debug,
 971        F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
 972    {
 973        let dock = match panel.position(cx) {
 974            DockPosition::Left => &self.left_dock,
 975            DockPosition::Bottom => &self.bottom_dock,
 976            DockPosition::Right => &self.right_dock,
 977        };
 978
 979        self.subscriptions.push(cx.subscribe(&panel, {
 980            let mut dock = dock.clone();
 981            let mut prev_position = panel.position(cx);
 982            move |this, panel, event, cx| {
 983                if T::should_change_position_on_event(event) {
 984                    let new_position = panel.read(cx).position(cx);
 985                    let mut was_visible = false;
 986                    dock.update(cx, |dock, cx| {
 987                        prev_position = new_position;
 988
 989                        was_visible = dock.is_open()
 990                            && dock
 991                                .visible_panel()
 992                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 993                        dock.remove_panel(&panel, cx);
 994                    });
 995
 996                    if panel.is_zoomed(cx) {
 997                        this.zoomed_position = Some(new_position);
 998                    }
 999
1000                    dock = match panel.read(cx).position(cx) {
1001                        DockPosition::Left => &this.left_dock,
1002                        DockPosition::Bottom => &this.bottom_dock,
1003                        DockPosition::Right => &this.right_dock,
1004                    }
1005                    .clone();
1006                    dock.update(cx, |dock, cx| {
1007                        dock.add_panel(panel.clone(), cx);
1008                        if was_visible {
1009                            dock.set_open(true, cx);
1010                            dock.activate_panel(dock.panels_len() - 1, cx);
1011                        }
1012                    });
1013                } else if T::should_zoom_in_on_event(event) {
1014                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
1015                    if !panel.has_focus(cx) {
1016                        cx.focus(&panel);
1017                    }
1018                    this.zoomed = Some(panel.downgrade().into_any());
1019                    this.zoomed_position = Some(panel.read(cx).position(cx));
1020                } else if T::should_zoom_out_on_event(event) {
1021                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
1022                    if this.zoomed_position == Some(prev_position) {
1023                        this.zoomed = None;
1024                        this.zoomed_position = None;
1025                    }
1026                    cx.notify();
1027                } else if T::is_focus_event(event) {
1028                    let position = panel.read(cx).position(cx);
1029                    this.dismiss_zoomed_items_to_reveal(Some(position), cx);
1030                    if panel.is_zoomed(cx) {
1031                        this.zoomed = Some(panel.downgrade().into_any());
1032                        this.zoomed_position = Some(position);
1033                    } else {
1034                        this.zoomed = None;
1035                        this.zoomed_position = None;
1036                    }
1037                    this.update_active_view_for_followers(cx);
1038                    cx.notify();
1039                } else {
1040                    handler(this, &panel, event, cx)
1041                }
1042            }
1043        }));
1044
1045        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
1046    }
1047
1048    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
1049        &self.status_bar
1050    }
1051
1052    pub fn app_state(&self) -> &Arc<AppState> {
1053        &self.app_state
1054    }
1055
1056    pub fn user_store(&self) -> &ModelHandle<UserStore> {
1057        &self.app_state.user_store
1058    }
1059
1060    pub fn project(&self) -> &ModelHandle<Project> {
1061        &self.project
1062    }
1063
1064    pub fn recent_navigation_history(
1065        &self,
1066        limit: Option<usize>,
1067        cx: &AppContext,
1068    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
1069        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
1070        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
1071        for pane in &self.panes {
1072            let pane = pane.read(cx);
1073            pane.nav_history()
1074                .for_each_entry(cx, |entry, (project_path, fs_path)| {
1075                    if let Some(fs_path) = &fs_path {
1076                        abs_paths_opened
1077                            .entry(fs_path.clone())
1078                            .or_default()
1079                            .insert(project_path.clone());
1080                    }
1081                    let timestamp = entry.timestamp;
1082                    match history.entry(project_path) {
1083                        hash_map::Entry::Occupied(mut entry) => {
1084                            let (_, old_timestamp) = entry.get();
1085                            if &timestamp > old_timestamp {
1086                                entry.insert((fs_path, timestamp));
1087                            }
1088                        }
1089                        hash_map::Entry::Vacant(entry) => {
1090                            entry.insert((fs_path, timestamp));
1091                        }
1092                    }
1093                });
1094        }
1095
1096        history
1097            .into_iter()
1098            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1099            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1100            .rev()
1101            .filter(|(history_path, abs_path)| {
1102                let latest_project_path_opened = abs_path
1103                    .as_ref()
1104                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1105                    .and_then(|project_paths| {
1106                        project_paths
1107                            .iter()
1108                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1109                    });
1110
1111                match latest_project_path_opened {
1112                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1113                    None => true,
1114                }
1115            })
1116            .take(limit.unwrap_or(usize::MAX))
1117            .collect()
1118    }
1119
1120    fn navigate_history(
1121        &mut self,
1122        pane: WeakViewHandle<Pane>,
1123        mode: NavigationMode,
1124        cx: &mut ViewContext<Workspace>,
1125    ) -> Task<Result<()>> {
1126        let to_load = if let Some(pane) = pane.upgrade(cx) {
1127            cx.focus(&pane);
1128
1129            pane.update(cx, |pane, cx| {
1130                loop {
1131                    // Retrieve the weak item handle from the history.
1132                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1133
1134                    // If the item is still present in this pane, then activate it.
1135                    if let Some(index) = entry
1136                        .item
1137                        .upgrade(cx)
1138                        .and_then(|v| pane.index_for_item(v.as_ref()))
1139                    {
1140                        let prev_active_item_index = pane.active_item_index();
1141                        pane.nav_history_mut().set_mode(mode);
1142                        pane.activate_item(index, true, true, cx);
1143                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1144
1145                        let mut navigated = prev_active_item_index != pane.active_item_index();
1146                        if let Some(data) = entry.data {
1147                            navigated |= pane.active_item()?.navigate(data, cx);
1148                        }
1149
1150                        if navigated {
1151                            break None;
1152                        }
1153                    }
1154                    // If the item is no longer present in this pane, then retrieve its
1155                    // project path in order to reopen it.
1156                    else {
1157                        break pane
1158                            .nav_history()
1159                            .path_for_item(entry.item.id())
1160                            .map(|(project_path, _)| (project_path, entry));
1161                    }
1162                }
1163            })
1164        } else {
1165            None
1166        };
1167
1168        if let Some((project_path, entry)) = to_load {
1169            // If the item was no longer present, then load it again from its previous path.
1170            let task = self.load_path(project_path, cx);
1171            cx.spawn(|workspace, mut cx| async move {
1172                let task = task.await;
1173                let mut navigated = false;
1174                if let Some((project_entry_id, build_item)) = task.log_err() {
1175                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1176                        pane.nav_history_mut().set_mode(mode);
1177                        pane.active_item().map(|p| p.id())
1178                    })?;
1179
1180                    pane.update(&mut cx, |pane, cx| {
1181                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1182                        navigated |= Some(item.id()) != prev_active_item_id;
1183                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1184                        if let Some(data) = entry.data {
1185                            navigated |= item.navigate(data, cx);
1186                        }
1187                    })?;
1188                }
1189
1190                if !navigated {
1191                    workspace
1192                        .update(&mut cx, |workspace, cx| {
1193                            Self::navigate_history(workspace, pane, mode, cx)
1194                        })?
1195                        .await?;
1196                }
1197
1198                Ok(())
1199            })
1200        } else {
1201            Task::ready(Ok(()))
1202        }
1203    }
1204
1205    pub fn go_back(
1206        &mut self,
1207        pane: WeakViewHandle<Pane>,
1208        cx: &mut ViewContext<Workspace>,
1209    ) -> Task<Result<()>> {
1210        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1211    }
1212
1213    pub fn go_forward(
1214        &mut self,
1215        pane: WeakViewHandle<Pane>,
1216        cx: &mut ViewContext<Workspace>,
1217    ) -> Task<Result<()>> {
1218        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1219    }
1220
1221    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1222        self.navigate_history(
1223            self.active_pane().downgrade(),
1224            NavigationMode::ReopeningClosedItem,
1225            cx,
1226        )
1227    }
1228
1229    pub fn client(&self) -> &Client {
1230        &self.app_state.client
1231    }
1232
1233    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1234        self.titlebar_item = Some(item);
1235        cx.notify();
1236    }
1237
1238    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1239        self.titlebar_item.clone()
1240    }
1241
1242    /// Call the given callback with a workspace whose project is local.
1243    ///
1244    /// If the given workspace has a local project, then it will be passed
1245    /// to the callback. Otherwise, a new empty window will be created.
1246    pub fn with_local_workspace<T, F>(
1247        &mut self,
1248        cx: &mut ViewContext<Self>,
1249        callback: F,
1250    ) -> Task<Result<T>>
1251    where
1252        T: 'static,
1253        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1254    {
1255        if self.project.read(cx).is_local() {
1256            Task::Ready(Some(Ok(callback(self, cx))))
1257        } else {
1258            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1259            cx.spawn(|_vh, mut cx| async move {
1260                let (workspace, _) = task.await;
1261                workspace.update(&mut cx, callback)
1262            })
1263        }
1264    }
1265
1266    pub fn worktrees<'a>(
1267        &self,
1268        cx: &'a AppContext,
1269    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1270        self.project.read(cx).worktrees(cx)
1271    }
1272
1273    pub fn visible_worktrees<'a>(
1274        &self,
1275        cx: &'a AppContext,
1276    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1277        self.project.read(cx).visible_worktrees(cx)
1278    }
1279
1280    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1281        let futures = self
1282            .worktrees(cx)
1283            .filter_map(|worktree| worktree.read(cx).as_local())
1284            .map(|worktree| worktree.scan_complete())
1285            .collect::<Vec<_>>();
1286        async move {
1287            for future in futures {
1288                future.await;
1289            }
1290        }
1291    }
1292
1293    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1294        cx.spawn(|mut cx| async move {
1295            let window = cx
1296                .windows()
1297                .into_iter()
1298                .find(|window| window.is_active(&cx).unwrap_or(false));
1299            if let Some(window) = window {
1300                //This can only get called when the window's project connection has been lost
1301                //so we don't need to prompt the user for anything and instead just close the window
1302                window.remove(&mut cx);
1303            }
1304        })
1305        .detach();
1306    }
1307
1308    pub fn close(
1309        &mut self,
1310        _: &CloseWindow,
1311        cx: &mut ViewContext<Self>,
1312    ) -> Option<Task<Result<()>>> {
1313        let window = cx.window();
1314        let prepare = self.prepare_to_close(false, cx);
1315        Some(cx.spawn(|_, mut cx| async move {
1316            if prepare.await? {
1317                window.remove(&mut cx);
1318            }
1319            Ok(())
1320        }))
1321    }
1322
1323    pub fn prepare_to_close(
1324        &mut self,
1325        quitting: bool,
1326        cx: &mut ViewContext<Self>,
1327    ) -> Task<Result<bool>> {
1328        let active_call = self.active_call().cloned();
1329        let window = cx.window();
1330
1331        cx.spawn(|this, mut cx| async move {
1332            let workspace_count = cx
1333                .windows()
1334                .into_iter()
1335                .filter(|window| window.root_is::<Workspace>())
1336                .count();
1337
1338            if let Some(active_call) = active_call {
1339                if !quitting
1340                    && workspace_count == 1
1341                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1342                {
1343                    let answer = window.prompt(
1344                        PromptLevel::Warning,
1345                        "Do you want to leave the current call?",
1346                        &["Close window and hang up", "Cancel"],
1347                        &mut cx,
1348                    );
1349
1350                    if let Some(mut answer) = answer {
1351                        if answer.next().await == Some(1) {
1352                            return anyhow::Ok(false);
1353                        } else {
1354                            active_call
1355                                .update(&mut cx, |call, cx| call.hang_up(cx))
1356                                .await
1357                                .log_err();
1358                        }
1359                    }
1360                }
1361            }
1362
1363            Ok(this
1364                .update(&mut cx, |this, cx| {
1365                    this.save_all_internal(SaveIntent::Close, cx)
1366                })?
1367                .await?)
1368        })
1369    }
1370
1371    fn save_all(
1372        &mut self,
1373        action: &SaveAll,
1374        cx: &mut ViewContext<Self>,
1375    ) -> Option<Task<Result<()>>> {
1376        let save_all =
1377            self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx);
1378        Some(cx.foreground().spawn(async move {
1379            save_all.await?;
1380            Ok(())
1381        }))
1382    }
1383
1384    fn save_all_internal(
1385        &mut self,
1386        mut save_intent: SaveIntent,
1387        cx: &mut ViewContext<Self>,
1388    ) -> Task<Result<bool>> {
1389        if self.project.read(cx).is_read_only() {
1390            return Task::ready(Ok(true));
1391        }
1392        let dirty_items = self
1393            .panes
1394            .iter()
1395            .flat_map(|pane| {
1396                pane.read(cx).items().filter_map(|item| {
1397                    if item.is_dirty(cx) {
1398                        Some((pane.downgrade(), item.boxed_clone()))
1399                    } else {
1400                        None
1401                    }
1402                })
1403            })
1404            .collect::<Vec<_>>();
1405
1406        let project = self.project.clone();
1407        cx.spawn(|workspace, mut cx| async move {
1408            // Override save mode and display "Save all files" prompt
1409            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1410                let mut answer = workspace.update(&mut cx, |_, cx| {
1411                    let prompt = Pane::file_names_for_prompt(
1412                        &mut dirty_items.iter().map(|(_, handle)| handle),
1413                        dirty_items.len(),
1414                        cx,
1415                    );
1416                    cx.prompt(
1417                        PromptLevel::Warning,
1418                        &prompt,
1419                        &["Save all", "Discard all", "Cancel"],
1420                    )
1421                })?;
1422                match answer.next().await {
1423                    Some(0) => save_intent = SaveIntent::SaveAll,
1424                    Some(1) => save_intent = SaveIntent::Skip,
1425                    _ => {}
1426                }
1427            }
1428            for (pane, item) in dirty_items {
1429                let (singleton, project_entry_ids) =
1430                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1431                if singleton || !project_entry_ids.is_empty() {
1432                    if let Some(ix) =
1433                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1434                    {
1435                        if !Pane::save_item(
1436                            project.clone(),
1437                            &pane,
1438                            ix,
1439                            &*item,
1440                            save_intent,
1441                            &mut cx,
1442                        )
1443                        .await?
1444                        {
1445                            return Ok(false);
1446                        }
1447                    }
1448                }
1449            }
1450            Ok(true)
1451        })
1452    }
1453
1454    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1455        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1456            files: true,
1457            directories: true,
1458            multiple: true,
1459        });
1460
1461        Some(cx.spawn(|this, mut cx| async move {
1462            if let Some(paths) = paths.recv().await.flatten() {
1463                if let Some(task) = this
1464                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1465                    .log_err()
1466                {
1467                    task.await?
1468                }
1469            }
1470            Ok(())
1471        }))
1472    }
1473
1474    pub fn open_workspace_for_paths(
1475        &mut self,
1476        paths: Vec<PathBuf>,
1477        cx: &mut ViewContext<Self>,
1478    ) -> Task<Result<()>> {
1479        let window = cx.window().downcast::<Self>();
1480        let is_remote = self.project.read(cx).is_remote();
1481        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1482        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1483        let close_task = if is_remote || has_worktree || has_dirty_items {
1484            None
1485        } else {
1486            Some(self.prepare_to_close(false, cx))
1487        };
1488        let app_state = self.app_state.clone();
1489
1490        cx.spawn(|_, mut cx| async move {
1491            let window_to_replace = if let Some(close_task) = close_task {
1492                if !close_task.await? {
1493                    return Ok(());
1494                }
1495                window
1496            } else {
1497                None
1498            };
1499            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
1500                .await?;
1501            Ok(())
1502        })
1503    }
1504
1505    #[allow(clippy::type_complexity)]
1506    pub fn open_paths(
1507        &mut self,
1508        mut abs_paths: Vec<PathBuf>,
1509        visible: bool,
1510        cx: &mut ViewContext<Self>,
1511    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1512        log::info!("open paths {:?}", abs_paths);
1513
1514        let fs = self.app_state.fs.clone();
1515
1516        // Sort the paths to ensure we add worktrees for parents before their children.
1517        abs_paths.sort_unstable();
1518        cx.spawn(|this, mut cx| async move {
1519            let mut tasks = Vec::with_capacity(abs_paths.len());
1520            for abs_path in &abs_paths {
1521                let project_path = match this
1522                    .update(&mut cx, |this, cx| {
1523                        Workspace::project_path_for_path(
1524                            this.project.clone(),
1525                            abs_path,
1526                            visible,
1527                            cx,
1528                        )
1529                    })
1530                    .log_err()
1531                {
1532                    Some(project_path) => project_path.await.log_err(),
1533                    None => None,
1534                };
1535
1536                let this = this.clone();
1537                let task = cx.spawn(|mut cx| {
1538                    let fs = fs.clone();
1539                    let abs_path = abs_path.clone();
1540                    async move {
1541                        let (worktree, project_path) = project_path?;
1542                        if fs.is_file(&abs_path).await {
1543                            Some(
1544                                this.update(&mut cx, |this, cx| {
1545                                    this.open_path(project_path, None, true, cx)
1546                                })
1547                                .log_err()?
1548                                .await,
1549                            )
1550                        } else {
1551                            this.update(&mut cx, |workspace, cx| {
1552                                let worktree = worktree.read(cx);
1553                                let worktree_abs_path = worktree.abs_path();
1554                                let entry_id = if abs_path == worktree_abs_path.as_ref() {
1555                                    worktree.root_entry()
1556                                } else {
1557                                    abs_path
1558                                        .strip_prefix(worktree_abs_path.as_ref())
1559                                        .ok()
1560                                        .and_then(|relative_path| {
1561                                            worktree.entry_for_path(relative_path)
1562                                        })
1563                                }
1564                                .map(|entry| entry.id);
1565                                if let Some(entry_id) = entry_id {
1566                                    workspace.project().update(cx, |_, cx| {
1567                                        cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
1568                                    })
1569                                }
1570                            })
1571                            .log_err()?;
1572                            None
1573                        }
1574                    }
1575                });
1576                tasks.push(task);
1577            }
1578
1579            futures::future::join_all(tasks).await
1580        })
1581    }
1582
1583    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1584        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1585            files: false,
1586            directories: true,
1587            multiple: true,
1588        });
1589        cx.spawn(|this, mut cx| async move {
1590            if let Some(paths) = paths.recv().await.flatten() {
1591                let results = this
1592                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1593                    .await;
1594                for result in results.into_iter().flatten() {
1595                    result.log_err();
1596                }
1597            }
1598            anyhow::Ok(())
1599        })
1600        .detach_and_log_err(cx);
1601    }
1602
1603    fn project_path_for_path(
1604        project: ModelHandle<Project>,
1605        abs_path: &Path,
1606        visible: bool,
1607        cx: &mut AppContext,
1608    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1609        let entry = project.update(cx, |project, cx| {
1610            project.find_or_create_local_worktree(abs_path, visible, cx)
1611        });
1612        cx.spawn(|cx| async move {
1613            let (worktree, path) = entry.await?;
1614            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1615            Ok((
1616                worktree,
1617                ProjectPath {
1618                    worktree_id,
1619                    path: path.into(),
1620                },
1621            ))
1622        })
1623    }
1624
1625    /// Returns the modal that was toggled closed if it was open.
1626    pub fn toggle_modal<V, F>(
1627        &mut self,
1628        cx: &mut ViewContext<Self>,
1629        add_view: F,
1630    ) -> Option<ViewHandle<V>>
1631    where
1632        V: 'static + Modal,
1633        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1634    {
1635        cx.notify();
1636        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1637        // it. Otherwise, create a new modal and set it as active.
1638        if let Some(already_open_modal) = self
1639            .dismiss_modal(cx)
1640            .and_then(|modal| modal.downcast::<V>())
1641        {
1642            cx.focus_self();
1643            Some(already_open_modal)
1644        } else {
1645            let modal = add_view(self, cx);
1646            cx.subscribe(&modal, |this, _, event, cx| {
1647                if V::dismiss_on_event(event) {
1648                    this.dismiss_modal(cx);
1649                }
1650            })
1651            .detach();
1652            let previously_focused_view_id = cx.focused_view_id();
1653            cx.focus(&modal);
1654            self.modal = Some(ActiveModal {
1655                view: Box::new(modal),
1656                previously_focused_view_id,
1657            });
1658            None
1659        }
1660    }
1661
1662    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1663        self.modal
1664            .as_ref()
1665            .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
1666    }
1667
1668    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
1669        if let Some(modal) = self.modal.take() {
1670            if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
1671                if modal.view.has_focus(cx) {
1672                    cx.window_context().focus(Some(previously_focused_view_id));
1673                }
1674            }
1675            cx.notify();
1676            Some(modal.view.as_any().clone())
1677        } else {
1678            None
1679        }
1680    }
1681
1682    pub fn items<'a>(
1683        &'a self,
1684        cx: &'a AppContext,
1685    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1686        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1687    }
1688
1689    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1690        self.items_of_type(cx).max_by_key(|item| item.id())
1691    }
1692
1693    pub fn items_of_type<'a, T: Item>(
1694        &'a self,
1695        cx: &'a AppContext,
1696    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1697        self.panes
1698            .iter()
1699            .flat_map(|pane| pane.read(cx).items_of_type())
1700    }
1701
1702    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1703        self.active_pane().read(cx).active_item()
1704    }
1705
1706    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1707        self.active_item(cx).and_then(|item| item.project_path(cx))
1708    }
1709
1710    pub fn save_active_item(
1711        &mut self,
1712        save_intent: SaveIntent,
1713        cx: &mut ViewContext<Self>,
1714    ) -> Task<Result<()>> {
1715        let project = self.project.clone();
1716        let pane = self.active_pane();
1717        let item_ix = pane.read(cx).active_item_index();
1718        let item = pane.read(cx).active_item();
1719        let pane = pane.downgrade();
1720
1721        cx.spawn(|_, mut cx| async move {
1722            if let Some(item) = item {
1723                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1724                    .await
1725                    .map(|_| ())
1726            } else {
1727                Ok(())
1728            }
1729        })
1730    }
1731
1732    pub fn close_inactive_items_and_panes(
1733        &mut self,
1734        _: &CloseInactiveTabsAndPanes,
1735        cx: &mut ViewContext<Self>,
1736    ) -> Option<Task<Result<()>>> {
1737        self.close_all_internal(true, SaveIntent::Close, cx)
1738    }
1739
1740    pub fn close_all_items_and_panes(
1741        &mut self,
1742        action: &CloseAllItemsAndPanes,
1743        cx: &mut ViewContext<Self>,
1744    ) -> Option<Task<Result<()>>> {
1745        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1746    }
1747
1748    fn close_all_internal(
1749        &mut self,
1750        retain_active_pane: bool,
1751        save_intent: SaveIntent,
1752        cx: &mut ViewContext<Self>,
1753    ) -> Option<Task<Result<()>>> {
1754        let current_pane = self.active_pane();
1755
1756        let mut tasks = Vec::new();
1757
1758        if retain_active_pane {
1759            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1760                pane.close_inactive_items(&CloseInactiveItems, cx)
1761            }) {
1762                tasks.push(current_pane_close);
1763            };
1764        }
1765
1766        for pane in self.panes() {
1767            if retain_active_pane && pane.id() == current_pane.id() {
1768                continue;
1769            }
1770
1771            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1772                pane.close_all_items(
1773                    &CloseAllItems {
1774                        save_intent: Some(save_intent),
1775                    },
1776                    cx,
1777                )
1778            }) {
1779                tasks.push(close_pane_items)
1780            }
1781        }
1782
1783        if tasks.is_empty() {
1784            None
1785        } else {
1786            Some(cx.spawn(|_, _| async move {
1787                for task in tasks {
1788                    task.await?
1789                }
1790                Ok(())
1791            }))
1792        }
1793    }
1794
1795    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1796        let dock = match dock_side {
1797            DockPosition::Left => &self.left_dock,
1798            DockPosition::Bottom => &self.bottom_dock,
1799            DockPosition::Right => &self.right_dock,
1800        };
1801        let mut focus_center = false;
1802        let mut reveal_dock = false;
1803        dock.update(cx, |dock, cx| {
1804            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1805            let was_visible = dock.is_open() && !other_is_zoomed;
1806            dock.set_open(!was_visible, cx);
1807
1808            if let Some(active_panel) = dock.active_panel() {
1809                if was_visible {
1810                    if active_panel.has_focus(cx) {
1811                        focus_center = true;
1812                    }
1813                } else {
1814                    cx.focus(active_panel.as_any());
1815                    reveal_dock = true;
1816                }
1817            }
1818        });
1819
1820        if reveal_dock {
1821            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1822        }
1823
1824        if focus_center {
1825            cx.focus_self();
1826        }
1827
1828        cx.notify();
1829        self.serialize_workspace(cx);
1830    }
1831
1832    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1833        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1834
1835        for dock in docks {
1836            dock.update(cx, |dock, cx| {
1837                dock.set_open(false, cx);
1838            });
1839        }
1840
1841        cx.focus_self();
1842        cx.notify();
1843        self.serialize_workspace(cx);
1844    }
1845
1846    /// Transfer focus to the panel of the given type.
1847    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1848        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1849            .as_any()
1850            .clone()
1851            .downcast()
1852    }
1853
1854    /// Focus the panel of the given type if it isn't already focused. If it is
1855    /// already focused, then transfer focus back to the workspace center.
1856    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1857        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1858    }
1859
1860    /// Focus or unfocus the given panel type, depending on the given callback.
1861    fn focus_or_unfocus_panel<T: Panel>(
1862        &mut self,
1863        cx: &mut ViewContext<Self>,
1864        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1865    ) -> Option<Rc<dyn PanelHandle>> {
1866        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1867            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1868                let mut focus_center = false;
1869                let mut reveal_dock = false;
1870                let panel = dock.update(cx, |dock, cx| {
1871                    dock.activate_panel(panel_index, cx);
1872
1873                    let panel = dock.active_panel().cloned();
1874                    if let Some(panel) = panel.as_ref() {
1875                        if should_focus(&**panel, cx) {
1876                            dock.set_open(true, cx);
1877                            cx.focus(panel.as_any());
1878                            reveal_dock = true;
1879                        } else {
1880                            // if panel.is_zoomed(cx) {
1881                            //     dock.set_open(false, cx);
1882                            // }
1883                            focus_center = true;
1884                        }
1885                    }
1886                    panel
1887                });
1888
1889                if focus_center {
1890                    cx.focus_self();
1891                }
1892
1893                self.serialize_workspace(cx);
1894                cx.notify();
1895                return panel;
1896            }
1897        }
1898        None
1899    }
1900
1901    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1902        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1903            let dock = dock.read(cx);
1904            if let Some(panel) = dock.panel::<T>() {
1905                return Some(panel);
1906            }
1907        }
1908        None
1909    }
1910
1911    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1912        for pane in &self.panes {
1913            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1914        }
1915
1916        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1917        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1918        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1919        self.zoomed = None;
1920        self.zoomed_position = None;
1921
1922        cx.notify();
1923    }
1924
1925    #[cfg(any(test, feature = "test-support"))]
1926    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1927        self.zoomed.and_then(|view| view.upgrade(cx))
1928    }
1929
1930    fn dismiss_zoomed_items_to_reveal(
1931        &mut self,
1932        dock_to_reveal: Option<DockPosition>,
1933        cx: &mut ViewContext<Self>,
1934    ) {
1935        // If a center pane is zoomed, unzoom it.
1936        for pane in &self.panes {
1937            if pane != &self.active_pane || dock_to_reveal.is_some() {
1938                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1939            }
1940        }
1941
1942        // If another dock is zoomed, hide it.
1943        let mut focus_center = false;
1944        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1945            dock.update(cx, |dock, cx| {
1946                if Some(dock.position()) != dock_to_reveal {
1947                    if let Some(panel) = dock.active_panel() {
1948                        if panel.is_zoomed(cx) {
1949                            focus_center |= panel.has_focus(cx);
1950                            dock.set_open(false, cx);
1951                        }
1952                    }
1953                }
1954            });
1955        }
1956
1957        if focus_center {
1958            cx.focus_self();
1959        }
1960
1961        if self.zoomed_position != dock_to_reveal {
1962            self.zoomed = None;
1963            self.zoomed_position = None;
1964        }
1965
1966        cx.notify();
1967    }
1968
1969    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1970        let pane = cx.add_view(|cx| {
1971            Pane::new(
1972                self.weak_handle(),
1973                self.project.clone(),
1974                self.app_state.background_actions,
1975                self.pane_history_timestamp.clone(),
1976                cx,
1977            )
1978        });
1979        cx.subscribe(&pane, Self::handle_pane_event).detach();
1980        self.panes.push(pane.clone());
1981        cx.focus(&pane);
1982        cx.emit(Event::PaneAdded(pane.clone()));
1983        pane
1984    }
1985
1986    pub fn add_item_to_center(
1987        &mut self,
1988        item: Box<dyn ItemHandle>,
1989        cx: &mut ViewContext<Self>,
1990    ) -> bool {
1991        if let Some(center_pane) = self.last_active_center_pane.clone() {
1992            if let Some(center_pane) = center_pane.upgrade(cx) {
1993                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1994                true
1995            } else {
1996                false
1997            }
1998        } else {
1999            false
2000        }
2001    }
2002
2003    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
2004        self.active_pane
2005            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
2006    }
2007
2008    pub fn split_item(
2009        &mut self,
2010        split_direction: SplitDirection,
2011        item: Box<dyn ItemHandle>,
2012        cx: &mut ViewContext<Self>,
2013    ) {
2014        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
2015        new_pane.update(cx, move |new_pane, cx| {
2016            new_pane.add_item(item, true, true, None, cx)
2017        })
2018    }
2019
2020    pub fn open_abs_path(
2021        &mut self,
2022        abs_path: PathBuf,
2023        visible: bool,
2024        cx: &mut ViewContext<Self>,
2025    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2026        cx.spawn(|workspace, mut cx| async move {
2027            let open_paths_task_result = workspace
2028                .update(&mut cx, |workspace, cx| {
2029                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
2030                })
2031                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
2032                .await;
2033            anyhow::ensure!(
2034                open_paths_task_result.len() == 1,
2035                "open abs path {abs_path:?} task returned incorrect number of results"
2036            );
2037            match open_paths_task_result
2038                .into_iter()
2039                .next()
2040                .expect("ensured single task result")
2041            {
2042                Some(open_result) => {
2043                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
2044                }
2045                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
2046            }
2047        })
2048    }
2049
2050    pub fn split_abs_path(
2051        &mut self,
2052        abs_path: PathBuf,
2053        visible: bool,
2054        cx: &mut ViewContext<Self>,
2055    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
2056        let project_path_task =
2057            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
2058        cx.spawn(|this, mut cx| async move {
2059            let (_, path) = project_path_task.await?;
2060            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
2061                .await
2062        })
2063    }
2064
2065    pub fn open_path(
2066        &mut self,
2067        path: impl Into<ProjectPath>,
2068        pane: Option<WeakViewHandle<Pane>>,
2069        focus_item: bool,
2070        cx: &mut ViewContext<Self>,
2071    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2072        let pane = pane.unwrap_or_else(|| {
2073            self.last_active_center_pane.clone().unwrap_or_else(|| {
2074                self.panes
2075                    .first()
2076                    .expect("There must be an active pane")
2077                    .downgrade()
2078            })
2079        });
2080
2081        let task = self.load_path(path.into(), cx);
2082        cx.spawn(|_, mut cx| async move {
2083            let (project_entry_id, build_item) = task.await?;
2084            pane.update(&mut cx, |pane, cx| {
2085                pane.open_item(project_entry_id, focus_item, cx, build_item)
2086            })
2087        })
2088    }
2089
2090    pub fn split_path(
2091        &mut self,
2092        path: impl Into<ProjectPath>,
2093        cx: &mut ViewContext<Self>,
2094    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
2095        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
2096            self.panes
2097                .first()
2098                .expect("There must be an active pane")
2099                .downgrade()
2100        });
2101
2102        if let Member::Pane(center_pane) = &self.center.root {
2103            if center_pane.read(cx).items_len() == 0 {
2104                return self.open_path(path, Some(pane), true, cx);
2105            }
2106        }
2107
2108        let task = self.load_path(path.into(), cx);
2109        cx.spawn(|this, mut cx| async move {
2110            let (project_entry_id, build_item) = task.await?;
2111            this.update(&mut cx, move |this, cx| -> Option<_> {
2112                let pane = pane.upgrade(cx)?;
2113                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
2114                new_pane.update(cx, |new_pane, cx| {
2115                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
2116                })
2117            })
2118            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
2119        })
2120    }
2121
2122    pub(crate) fn load_path(
2123        &mut self,
2124        path: ProjectPath,
2125        cx: &mut ViewContext<Self>,
2126    ) -> Task<
2127        Result<(
2128            ProjectEntryId,
2129            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
2130        )>,
2131    > {
2132        let project = self.project().clone();
2133        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
2134        cx.spawn(|_, mut cx| async move {
2135            let (project_entry_id, project_item) = project_item.await?;
2136            let build_item = cx.update(|cx| {
2137                cx.default_global::<ProjectItemBuilders>()
2138                    .get(&project_item.model_type())
2139                    .ok_or_else(|| anyhow!("no item builder for project item"))
2140                    .cloned()
2141            })?;
2142            let build_item =
2143                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
2144            Ok((project_entry_id, build_item))
2145        })
2146    }
2147
2148    pub fn open_project_item<T>(
2149        &mut self,
2150        project_item: ModelHandle<T::Item>,
2151        cx: &mut ViewContext<Self>,
2152    ) -> ViewHandle<T>
2153    where
2154        T: ProjectItem,
2155    {
2156        use project::Item as _;
2157
2158        let entry_id = project_item.read(cx).entry_id(cx);
2159        if let Some(item) = entry_id
2160            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2161            .and_then(|item| item.downcast())
2162        {
2163            self.activate_item(&item, cx);
2164            return item;
2165        }
2166
2167        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2168        self.add_item(Box::new(item.clone()), cx);
2169        item
2170    }
2171
2172    pub fn split_project_item<T>(
2173        &mut self,
2174        project_item: ModelHandle<T::Item>,
2175        cx: &mut ViewContext<Self>,
2176    ) -> ViewHandle<T>
2177    where
2178        T: ProjectItem,
2179    {
2180        use project::Item as _;
2181
2182        let entry_id = project_item.read(cx).entry_id(cx);
2183        if let Some(item) = entry_id
2184            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2185            .and_then(|item| item.downcast())
2186        {
2187            self.activate_item(&item, cx);
2188            return item;
2189        }
2190
2191        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2192        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2193        item
2194    }
2195
2196    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2197        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2198            self.active_pane.update(cx, |pane, cx| {
2199                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2200            });
2201        }
2202    }
2203
2204    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2205        let result = self.panes.iter().find_map(|pane| {
2206            pane.read(cx)
2207                .index_for_item(item)
2208                .map(|ix| (pane.clone(), ix))
2209        });
2210        if let Some((pane, ix)) = result {
2211            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2212            true
2213        } else {
2214            false
2215        }
2216    }
2217
2218    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2219        let panes = self.center.panes();
2220        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2221            cx.focus(&pane);
2222        } else {
2223            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2224        }
2225    }
2226
2227    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2228        let panes = self.center.panes();
2229        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2230            let next_ix = (ix + 1) % panes.len();
2231            let next_pane = panes[next_ix].clone();
2232            cx.focus(&next_pane);
2233        }
2234    }
2235
2236    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2237        let panes = self.center.panes();
2238        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2239            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2240            let prev_pane = panes[prev_ix].clone();
2241            cx.focus(&prev_pane);
2242        }
2243    }
2244
2245    pub fn activate_pane_in_direction(
2246        &mut self,
2247        direction: SplitDirection,
2248        cx: &mut ViewContext<Self>,
2249    ) {
2250        if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2251            cx.focus(pane);
2252        }
2253    }
2254
2255    pub fn swap_pane_in_direction(
2256        &mut self,
2257        direction: SplitDirection,
2258        cx: &mut ViewContext<Self>,
2259    ) {
2260        if let Some(to) = self
2261            .find_pane_in_direction(direction, cx)
2262            .map(|pane| pane.clone())
2263        {
2264            self.center.swap(&self.active_pane.clone(), &to);
2265            cx.notify();
2266        }
2267    }
2268
2269    fn find_pane_in_direction(
2270        &mut self,
2271        direction: SplitDirection,
2272        cx: &mut ViewContext<Self>,
2273    ) -> Option<&ViewHandle<Pane>> {
2274        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2275            return None;
2276        };
2277        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2278        let center = match cursor {
2279            Some(cursor) if bounding_box.contains_point(cursor) => cursor,
2280            _ => bounding_box.center(),
2281        };
2282
2283        let distance_to_next = theme::current(cx).workspace.pane_divider.width + 1.;
2284
2285        let target = match direction {
2286            SplitDirection::Left => vec2f(bounding_box.origin_x() - distance_to_next, center.y()),
2287            SplitDirection::Right => vec2f(bounding_box.max_x() + distance_to_next, center.y()),
2288            SplitDirection::Up => vec2f(center.x(), bounding_box.origin_y() - distance_to_next),
2289            SplitDirection::Down => vec2f(center.x(), bounding_box.max_y() + distance_to_next),
2290        };
2291        self.center.pane_at_pixel_position(target)
2292    }
2293
2294    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2295        if self.active_pane != pane {
2296            self.active_pane = pane.clone();
2297            self.status_bar.update(cx, |status_bar, cx| {
2298                status_bar.set_active_pane(&self.active_pane, cx);
2299            });
2300            self.active_item_path_changed(cx);
2301            self.last_active_center_pane = Some(pane.downgrade());
2302        }
2303
2304        self.dismiss_zoomed_items_to_reveal(None, cx);
2305        if pane.read(cx).is_zoomed() {
2306            self.zoomed = Some(pane.downgrade().into_any());
2307        } else {
2308            self.zoomed = None;
2309        }
2310        self.zoomed_position = None;
2311        self.update_active_view_for_followers(cx);
2312
2313        cx.notify();
2314    }
2315
2316    fn handle_pane_event(
2317        &mut self,
2318        pane: ViewHandle<Pane>,
2319        event: &pane::Event,
2320        cx: &mut ViewContext<Self>,
2321    ) {
2322        match event {
2323            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2324            pane::Event::Split(direction) => {
2325                self.split_and_clone(pane, *direction, cx);
2326            }
2327            pane::Event::Remove => self.remove_pane(pane, cx),
2328            pane::Event::ActivateItem { local } => {
2329                if *local {
2330                    self.unfollow(&pane, cx);
2331                }
2332                if &pane == self.active_pane() {
2333                    self.active_item_path_changed(cx);
2334                }
2335            }
2336            pane::Event::ChangeItemTitle => {
2337                if pane == self.active_pane {
2338                    self.active_item_path_changed(cx);
2339                }
2340                self.update_window_edited(cx);
2341            }
2342            pane::Event::RemoveItem { item_id } => {
2343                self.update_window_edited(cx);
2344                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2345                    if entry.get().id() == pane.id() {
2346                        entry.remove();
2347                    }
2348                }
2349            }
2350            pane::Event::Focus => {
2351                self.handle_pane_focused(pane.clone(), cx);
2352            }
2353            pane::Event::ZoomIn => {
2354                if pane == self.active_pane {
2355                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2356                    if pane.read(cx).has_focus() {
2357                        self.zoomed = Some(pane.downgrade().into_any());
2358                        self.zoomed_position = None;
2359                    }
2360                    cx.notify();
2361                }
2362            }
2363            pane::Event::ZoomOut => {
2364                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2365                if self.zoomed_position.is_none() {
2366                    self.zoomed = None;
2367                }
2368                cx.notify();
2369            }
2370        }
2371
2372        self.serialize_workspace(cx);
2373    }
2374
2375    pub fn split_pane(
2376        &mut self,
2377        pane_to_split: ViewHandle<Pane>,
2378        split_direction: SplitDirection,
2379        cx: &mut ViewContext<Self>,
2380    ) -> ViewHandle<Pane> {
2381        let new_pane = self.add_pane(cx);
2382        self.center
2383            .split(&pane_to_split, &new_pane, split_direction)
2384            .unwrap();
2385        cx.notify();
2386        new_pane
2387    }
2388
2389    pub fn split_and_clone(
2390        &mut self,
2391        pane: ViewHandle<Pane>,
2392        direction: SplitDirection,
2393        cx: &mut ViewContext<Self>,
2394    ) -> Option<ViewHandle<Pane>> {
2395        let item = pane.read(cx).active_item()?;
2396        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2397            let new_pane = self.add_pane(cx);
2398            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2399            self.center.split(&pane, &new_pane, direction).unwrap();
2400            Some(new_pane)
2401        } else {
2402            None
2403        };
2404        cx.notify();
2405        maybe_pane_handle
2406    }
2407
2408    pub fn split_pane_with_item(
2409        &mut self,
2410        pane_to_split: WeakViewHandle<Pane>,
2411        split_direction: SplitDirection,
2412        from: WeakViewHandle<Pane>,
2413        item_id_to_move: usize,
2414        cx: &mut ViewContext<Self>,
2415    ) {
2416        let Some(pane_to_split) = pane_to_split.upgrade(cx) else {
2417            return;
2418        };
2419        let Some(from) = from.upgrade(cx) else {
2420            return;
2421        };
2422
2423        let new_pane = self.add_pane(cx);
2424        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2425        self.center
2426            .split(&pane_to_split, &new_pane, split_direction)
2427            .unwrap();
2428        cx.notify();
2429    }
2430
2431    pub fn split_pane_with_project_entry(
2432        &mut self,
2433        pane_to_split: WeakViewHandle<Pane>,
2434        split_direction: SplitDirection,
2435        project_entry: ProjectEntryId,
2436        cx: &mut ViewContext<Self>,
2437    ) -> Option<Task<Result<()>>> {
2438        let pane_to_split = pane_to_split.upgrade(cx)?;
2439        let new_pane = self.add_pane(cx);
2440        self.center
2441            .split(&pane_to_split, &new_pane, split_direction)
2442            .unwrap();
2443
2444        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2445        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2446        Some(cx.foreground().spawn(async move {
2447            task.await?;
2448            Ok(())
2449        }))
2450    }
2451
2452    pub fn move_item(
2453        &mut self,
2454        source: ViewHandle<Pane>,
2455        destination: ViewHandle<Pane>,
2456        item_id_to_move: usize,
2457        destination_index: usize,
2458        cx: &mut ViewContext<Self>,
2459    ) {
2460        let item_to_move = source
2461            .read(cx)
2462            .items()
2463            .enumerate()
2464            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2465
2466        if item_to_move.is_none() {
2467            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2468            return;
2469        }
2470        let (item_ix, item_handle) = item_to_move.unwrap();
2471        let item_handle = item_handle.clone();
2472
2473        if source != destination {
2474            // Close item from previous pane
2475            source.update(cx, |source, cx| {
2476                source.remove_item(item_ix, false, cx);
2477            });
2478        }
2479
2480        // This automatically removes duplicate items in the pane
2481        destination.update(cx, |destination, cx| {
2482            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2483            cx.focus_self();
2484        });
2485    }
2486
2487    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2488        if self.center.remove(&pane).unwrap() {
2489            self.force_remove_pane(&pane, cx);
2490            self.unfollow(&pane, cx);
2491            self.last_leaders_by_pane.remove(&pane.downgrade());
2492            for removed_item in pane.read(cx).items() {
2493                self.panes_by_item.remove(&removed_item.id());
2494            }
2495
2496            cx.notify();
2497        } else {
2498            self.active_item_path_changed(cx);
2499        }
2500    }
2501
2502    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2503        &self.panes
2504    }
2505
2506    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2507        &self.active_pane
2508    }
2509
2510    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2511        self.follower_states.retain(|_, state| {
2512            if state.leader_id == peer_id {
2513                for item in state.items_by_leader_view_id.values() {
2514                    item.set_leader_peer_id(None, cx);
2515                }
2516                false
2517            } else {
2518                true
2519            }
2520        });
2521        cx.notify();
2522    }
2523
2524    fn start_following(
2525        &mut self,
2526        leader_id: PeerId,
2527        cx: &mut ViewContext<Self>,
2528    ) -> Option<Task<Result<()>>> {
2529        let pane = self.active_pane().clone();
2530
2531        self.last_leaders_by_pane
2532            .insert(pane.downgrade(), leader_id);
2533        self.unfollow(&pane, cx);
2534        self.follower_states.insert(
2535            pane.clone(),
2536            FollowerState {
2537                leader_id,
2538                active_view_id: None,
2539                items_by_leader_view_id: Default::default(),
2540            },
2541        );
2542        cx.notify();
2543
2544        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2545        let project_id = self.project.read(cx).remote_id();
2546        let request = self.app_state.client.request(proto::Follow {
2547            room_id,
2548            project_id,
2549            leader_id: Some(leader_id),
2550        });
2551
2552        Some(cx.spawn(|this, mut cx| async move {
2553            let response = request.await?;
2554            this.update(&mut cx, |this, _| {
2555                let state = this
2556                    .follower_states
2557                    .get_mut(&pane)
2558                    .ok_or_else(|| anyhow!("following interrupted"))?;
2559                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2560                    Some(ViewId::from_proto(active_view_id)?)
2561                } else {
2562                    None
2563                };
2564                Ok::<_, anyhow::Error>(())
2565            })??;
2566            Self::add_views_from_leader(
2567                this.clone(),
2568                leader_id,
2569                vec![pane],
2570                response.views,
2571                &mut cx,
2572            )
2573            .await?;
2574            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2575            Ok(())
2576        }))
2577    }
2578
2579    pub fn follow_next_collaborator(
2580        &mut self,
2581        _: &FollowNextCollaborator,
2582        cx: &mut ViewContext<Self>,
2583    ) -> Option<Task<Result<()>>> {
2584        let collaborators = self.project.read(cx).collaborators();
2585        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2586            let mut collaborators = collaborators.keys().copied();
2587            for peer_id in collaborators.by_ref() {
2588                if peer_id == leader_id {
2589                    break;
2590                }
2591            }
2592            collaborators.next()
2593        } else if let Some(last_leader_id) =
2594            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2595        {
2596            if collaborators.contains_key(last_leader_id) {
2597                Some(*last_leader_id)
2598            } else {
2599                None
2600            }
2601        } else {
2602            None
2603        };
2604
2605        let pane = self.active_pane.clone();
2606        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2607        else {
2608            return None;
2609        };
2610        if Some(leader_id) == self.unfollow(&pane, cx) {
2611            return None;
2612        }
2613        self.follow(leader_id, cx)
2614    }
2615
2616    pub fn follow(
2617        &mut self,
2618        leader_id: PeerId,
2619        cx: &mut ViewContext<Self>,
2620    ) -> Option<Task<Result<()>>> {
2621        let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2622        let project = self.project.read(cx);
2623
2624        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2625            return None;
2626        };
2627
2628        let other_project_id = match remote_participant.location {
2629            call::ParticipantLocation::External => None,
2630            call::ParticipantLocation::UnsharedProject => None,
2631            call::ParticipantLocation::SharedProject { project_id } => {
2632                if Some(project_id) == project.remote_id() {
2633                    None
2634                } else {
2635                    Some(project_id)
2636                }
2637            }
2638        };
2639
2640        // if they are active in another project, follow there.
2641        if let Some(project_id) = other_project_id {
2642            let app_state = self.app_state.clone();
2643            return Some(crate::join_remote_project(
2644                project_id,
2645                remote_participant.user.id,
2646                app_state,
2647                cx,
2648            ));
2649        }
2650
2651        // if you're already following, find the right pane and focus it.
2652        for (pane, state) in &self.follower_states {
2653            if leader_id == state.leader_id {
2654                cx.focus(pane);
2655                return None;
2656            }
2657        }
2658
2659        // Otherwise, follow.
2660        self.start_following(leader_id, cx)
2661    }
2662
2663    pub fn unfollow(
2664        &mut self,
2665        pane: &ViewHandle<Pane>,
2666        cx: &mut ViewContext<Self>,
2667    ) -> Option<PeerId> {
2668        let state = self.follower_states.remove(pane)?;
2669        let leader_id = state.leader_id;
2670        for (_, item) in state.items_by_leader_view_id {
2671            item.set_leader_peer_id(None, cx);
2672        }
2673
2674        if self
2675            .follower_states
2676            .values()
2677            .all(|state| state.leader_id != state.leader_id)
2678        {
2679            let project_id = self.project.read(cx).remote_id();
2680            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2681            self.app_state
2682                .client
2683                .send(proto::Unfollow {
2684                    room_id,
2685                    project_id,
2686                    leader_id: Some(leader_id),
2687                })
2688                .log_err();
2689        }
2690
2691        cx.notify();
2692        Some(leader_id)
2693    }
2694
2695    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2696        self.follower_states
2697            .values()
2698            .any(|state| state.leader_id == peer_id)
2699    }
2700
2701    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2702        // TODO: There should be a better system in place for this
2703        // (https://github.com/zed-industries/zed/issues/1290)
2704        let is_fullscreen = cx.window_is_fullscreen();
2705        let container_theme = if is_fullscreen {
2706            let mut container_theme = theme.titlebar.container;
2707            container_theme.padding.left = container_theme.padding.right;
2708            container_theme
2709        } else {
2710            theme.titlebar.container
2711        };
2712
2713        enum TitleBar {}
2714        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2715            Stack::new()
2716                .with_children(
2717                    self.titlebar_item
2718                        .as_ref()
2719                        .map(|item| ChildView::new(item, cx)),
2720                )
2721                .contained()
2722                .with_style(container_theme)
2723        })
2724        .on_click(MouseButton::Left, |event, _, cx| {
2725            if event.click_count == 2 {
2726                cx.zoom_window();
2727            }
2728        })
2729        .constrained()
2730        .with_height(theme.titlebar.height)
2731        .into_any_named("titlebar")
2732    }
2733
2734    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2735        let active_entry = self.active_project_path(cx);
2736        self.project
2737            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2738        self.update_window_title(cx);
2739    }
2740
2741    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2742        let project = self.project().read(cx);
2743        let mut title = String::new();
2744
2745        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2746            let filename = path
2747                .path
2748                .file_name()
2749                .map(|s| s.to_string_lossy())
2750                .or_else(|| {
2751                    Some(Cow::Borrowed(
2752                        project
2753                            .worktree_for_id(path.worktree_id, cx)?
2754                            .read(cx)
2755                            .root_name(),
2756                    ))
2757                });
2758
2759            if let Some(filename) = filename {
2760                title.push_str(filename.as_ref());
2761                title.push_str(" β€” ");
2762            }
2763        }
2764
2765        for (i, name) in project.worktree_root_names(cx).enumerate() {
2766            if i > 0 {
2767                title.push_str(", ");
2768            }
2769            title.push_str(name);
2770        }
2771
2772        if title.is_empty() {
2773            title = "empty project".to_string();
2774        }
2775
2776        if project.is_remote() {
2777            title.push_str(" ↙");
2778        } else if project.is_shared() {
2779            title.push_str(" β†—");
2780        }
2781
2782        cx.set_window_title(&title);
2783    }
2784
2785    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2786        let is_edited = !self.project.read(cx).is_read_only()
2787            && self
2788                .items(cx)
2789                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2790        if is_edited != self.window_edited {
2791            self.window_edited = is_edited;
2792            cx.set_window_edited(self.window_edited)
2793        }
2794    }
2795
2796    fn render_disconnected_overlay(
2797        &self,
2798        cx: &mut ViewContext<Workspace>,
2799    ) -> Option<AnyElement<Workspace>> {
2800        if self.project.read(cx).is_read_only() {
2801            enum DisconnectedOverlay {}
2802            Some(
2803                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2804                    let theme = &theme::current(cx);
2805                    Label::new(
2806                        "Your connection to the remote project has been lost.",
2807                        theme.workspace.disconnected_overlay.text.clone(),
2808                    )
2809                    .aligned()
2810                    .contained()
2811                    .with_style(theme.workspace.disconnected_overlay.container)
2812                })
2813                .with_cursor_style(CursorStyle::Arrow)
2814                .capture_all()
2815                .into_any_named("disconnected overlay"),
2816            )
2817        } else {
2818            None
2819        }
2820    }
2821
2822    fn render_notifications(
2823        &self,
2824        theme: &theme::Workspace,
2825        cx: &AppContext,
2826    ) -> Option<AnyElement<Workspace>> {
2827        if self.notifications.is_empty() {
2828            None
2829        } else {
2830            Some(
2831                Flex::column()
2832                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2833                        ChildView::new(notification.as_any(), cx)
2834                            .contained()
2835                            .with_style(theme.notification)
2836                    }))
2837                    .constrained()
2838                    .with_width(theme.notifications.width)
2839                    .contained()
2840                    .with_style(theme.notifications.container)
2841                    .aligned()
2842                    .bottom()
2843                    .right()
2844                    .into_any(),
2845            )
2846        }
2847    }
2848
2849    // RPC handlers
2850
2851    fn handle_follow(
2852        &mut self,
2853        follower_project_id: Option<u64>,
2854        cx: &mut ViewContext<Self>,
2855    ) -> proto::FollowResponse {
2856        let client = &self.app_state.client;
2857        let project_id = self.project.read(cx).remote_id();
2858
2859        let active_view_id = self.active_item(cx).and_then(|i| {
2860            Some(
2861                i.to_followable_item_handle(cx)?
2862                    .remote_id(client, cx)?
2863                    .to_proto(),
2864            )
2865        });
2866
2867        cx.notify();
2868
2869        self.last_active_view_id = active_view_id.clone();
2870        proto::FollowResponse {
2871            active_view_id,
2872            views: self
2873                .panes()
2874                .iter()
2875                .flat_map(|pane| {
2876                    let leader_id = self.leader_for_pane(pane);
2877                    pane.read(cx).items().filter_map({
2878                        let cx = &cx;
2879                        move |item| {
2880                            let item = item.to_followable_item_handle(cx)?;
2881                            if project_id.is_some()
2882                                && project_id != follower_project_id
2883                                && item.is_project_item(cx)
2884                            {
2885                                return None;
2886                            }
2887                            let id = item.remote_id(client, cx)?.to_proto();
2888                            let variant = item.to_state_proto(cx)?;
2889                            Some(proto::View {
2890                                id: Some(id),
2891                                leader_id,
2892                                variant: Some(variant),
2893                            })
2894                        }
2895                    })
2896                })
2897                .collect(),
2898        }
2899    }
2900
2901    fn handle_update_followers(
2902        &mut self,
2903        leader_id: PeerId,
2904        message: proto::UpdateFollowers,
2905        _cx: &mut ViewContext<Self>,
2906    ) {
2907        self.leader_updates_tx
2908            .unbounded_send((leader_id, message))
2909            .ok();
2910    }
2911
2912    async fn process_leader_update(
2913        this: &WeakViewHandle<Self>,
2914        leader_id: PeerId,
2915        update: proto::UpdateFollowers,
2916        cx: &mut AsyncAppContext,
2917    ) -> Result<()> {
2918        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2919            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2920                this.update(cx, |this, _| {
2921                    for (_, state) in &mut this.follower_states {
2922                        if state.leader_id == leader_id {
2923                            state.active_view_id =
2924                                if let Some(active_view_id) = update_active_view.id.clone() {
2925                                    Some(ViewId::from_proto(active_view_id)?)
2926                                } else {
2927                                    None
2928                                };
2929                        }
2930                    }
2931                    anyhow::Ok(())
2932                })??;
2933            }
2934            proto::update_followers::Variant::UpdateView(update_view) => {
2935                let variant = update_view
2936                    .variant
2937                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2938                let id = update_view
2939                    .id
2940                    .ok_or_else(|| anyhow!("missing update view id"))?;
2941                let mut tasks = Vec::new();
2942                this.update(cx, |this, cx| {
2943                    let project = this.project.clone();
2944                    for (_, state) in &mut this.follower_states {
2945                        if state.leader_id == leader_id {
2946                            let view_id = ViewId::from_proto(id.clone())?;
2947                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2948                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2949                            }
2950                        }
2951                    }
2952                    anyhow::Ok(())
2953                })??;
2954                try_join_all(tasks).await.log_err();
2955            }
2956            proto::update_followers::Variant::CreateView(view) => {
2957                let panes = this.read_with(cx, |this, _| {
2958                    this.follower_states
2959                        .iter()
2960                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2961                        .cloned()
2962                        .collect()
2963                })?;
2964                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2965            }
2966        }
2967        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2968        Ok(())
2969    }
2970
2971    async fn add_views_from_leader(
2972        this: WeakViewHandle<Self>,
2973        leader_id: PeerId,
2974        panes: Vec<ViewHandle<Pane>>,
2975        views: Vec<proto::View>,
2976        cx: &mut AsyncAppContext,
2977    ) -> Result<()> {
2978        let this = this
2979            .upgrade(cx)
2980            .ok_or_else(|| anyhow!("workspace dropped"))?;
2981
2982        let item_builders = cx.update(|cx| {
2983            cx.default_global::<FollowableItemBuilders>()
2984                .values()
2985                .map(|b| b.0)
2986                .collect::<Vec<_>>()
2987        });
2988
2989        let mut item_tasks_by_pane = HashMap::default();
2990        for pane in panes {
2991            let mut item_tasks = Vec::new();
2992            let mut leader_view_ids = Vec::new();
2993            for view in &views {
2994                let Some(id) = &view.id else { continue };
2995                let id = ViewId::from_proto(id.clone())?;
2996                let mut variant = view.variant.clone();
2997                if variant.is_none() {
2998                    Err(anyhow!("missing view variant"))?;
2999                }
3000                for build_item in &item_builders {
3001                    let task = cx
3002                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
3003                    if let Some(task) = task {
3004                        item_tasks.push(task);
3005                        leader_view_ids.push(id);
3006                        break;
3007                    } else {
3008                        assert!(variant.is_some());
3009                    }
3010                }
3011            }
3012
3013            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3014        }
3015
3016        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3017            let items = futures::future::try_join_all(item_tasks).await?;
3018            this.update(cx, |this, cx| {
3019                let state = this.follower_states.get_mut(&pane)?;
3020                for (id, item) in leader_view_ids.into_iter().zip(items) {
3021                    item.set_leader_peer_id(Some(leader_id), cx);
3022                    state.items_by_leader_view_id.insert(id, item);
3023                }
3024
3025                Some(())
3026            });
3027        }
3028        Ok(())
3029    }
3030
3031    fn update_active_view_for_followers(&mut self, cx: &AppContext) {
3032        let mut is_project_item = true;
3033        let mut update = proto::UpdateActiveView::default();
3034        if self.active_pane.read(cx).has_focus() {
3035            let item = self
3036                .active_item(cx)
3037                .and_then(|item| item.to_followable_item_handle(cx));
3038            if let Some(item) = item {
3039                is_project_item = item.is_project_item(cx);
3040                update = proto::UpdateActiveView {
3041                    id: item
3042                        .remote_id(&self.app_state.client, cx)
3043                        .map(|id| id.to_proto()),
3044                    leader_id: self.leader_for_pane(&self.active_pane),
3045                };
3046            }
3047        }
3048
3049        if update.id != self.last_active_view_id {
3050            self.last_active_view_id = update.id.clone();
3051            self.update_followers(
3052                is_project_item,
3053                proto::update_followers::Variant::UpdateActiveView(update),
3054                cx,
3055            );
3056        }
3057    }
3058
3059    fn update_followers(
3060        &self,
3061        project_only: bool,
3062        update: proto::update_followers::Variant,
3063        cx: &AppContext,
3064    ) -> Option<()> {
3065        let project_id = if project_only {
3066            self.project.read(cx).remote_id()
3067        } else {
3068            None
3069        };
3070        self.app_state().workspace_store.read_with(cx, |store, cx| {
3071            store.update_followers(project_id, update, cx)
3072        })
3073    }
3074
3075    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3076        self.follower_states.get(pane).map(|state| state.leader_id)
3077    }
3078
3079    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3080        cx.notify();
3081
3082        let call = self.active_call()?;
3083        let room = call.read(cx).room()?.read(cx);
3084        let participant = room.remote_participant_for_peer_id(leader_id)?;
3085        let mut items_to_activate = Vec::new();
3086
3087        let leader_in_this_app;
3088        let leader_in_this_project;
3089        match participant.location {
3090            call::ParticipantLocation::SharedProject { project_id } => {
3091                leader_in_this_app = true;
3092                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3093            }
3094            call::ParticipantLocation::UnsharedProject => {
3095                leader_in_this_app = true;
3096                leader_in_this_project = false;
3097            }
3098            call::ParticipantLocation::External => {
3099                leader_in_this_app = false;
3100                leader_in_this_project = false;
3101            }
3102        };
3103
3104        for (pane, state) in &self.follower_states {
3105            if state.leader_id != leader_id {
3106                continue;
3107            }
3108            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
3109                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
3110                    if leader_in_this_project || !item.is_project_item(cx) {
3111                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3112                    }
3113                } else {
3114                    log::warn!(
3115                        "unknown view id {:?} for leader {:?}",
3116                        active_view_id,
3117                        leader_id
3118                    );
3119                }
3120                continue;
3121            }
3122            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3123                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3124            }
3125        }
3126
3127        for (pane, item) in items_to_activate {
3128            let pane_was_focused = pane.read(cx).has_focus();
3129            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3130                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3131            } else {
3132                pane.update(cx, |pane, cx| {
3133                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3134                });
3135            }
3136
3137            if pane_was_focused {
3138                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3139            }
3140        }
3141
3142        None
3143    }
3144
3145    fn shared_screen_for_peer(
3146        &self,
3147        peer_id: PeerId,
3148        pane: &ViewHandle<Pane>,
3149        cx: &mut ViewContext<Self>,
3150    ) -> Option<ViewHandle<SharedScreen>> {
3151        let call = self.active_call()?;
3152        let room = call.read(cx).room()?.read(cx);
3153        let participant = room.remote_participant_for_peer_id(peer_id)?;
3154        let track = participant.video_tracks.values().next()?.clone();
3155        let user = participant.user.clone();
3156
3157        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3158            if item.read(cx).peer_id == peer_id {
3159                return Some(item);
3160            }
3161        }
3162
3163        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3164    }
3165
3166    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3167        if active {
3168            self.update_active_view_for_followers(cx);
3169            cx.background()
3170                .spawn(persistence::DB.update_timestamp(self.database_id()))
3171                .detach();
3172        } else {
3173            for pane in &self.panes {
3174                pane.update(cx, |pane, cx| {
3175                    if let Some(item) = pane.active_item() {
3176                        item.workspace_deactivated(cx);
3177                    }
3178                    if matches!(
3179                        settings::get::<WorkspaceSettings>(cx).autosave,
3180                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3181                    ) {
3182                        for item in pane.items() {
3183                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3184                                .detach_and_log_err(cx);
3185                        }
3186                    }
3187                });
3188            }
3189        }
3190    }
3191
3192    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3193        self.active_call.as_ref().map(|(call, _)| call)
3194    }
3195
3196    fn on_active_call_event(
3197        &mut self,
3198        _: ModelHandle<ActiveCall>,
3199        event: &call::room::Event,
3200        cx: &mut ViewContext<Self>,
3201    ) {
3202        match event {
3203            call::room::Event::ParticipantLocationChanged { participant_id }
3204            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3205                self.leader_updated(*participant_id, cx);
3206            }
3207            _ => {}
3208        }
3209    }
3210
3211    pub fn database_id(&self) -> WorkspaceId {
3212        self.database_id
3213    }
3214
3215    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3216        let project = self.project().read(cx);
3217
3218        if project.is_local() {
3219            Some(
3220                project
3221                    .visible_worktrees(cx)
3222                    .map(|worktree| worktree.read(cx).abs_path())
3223                    .collect::<Vec<_>>()
3224                    .into(),
3225            )
3226        } else {
3227            None
3228        }
3229    }
3230
3231    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3232        match member {
3233            Member::Axis(PaneAxis { members, .. }) => {
3234                for child in members.iter() {
3235                    self.remove_panes(child.clone(), cx)
3236                }
3237            }
3238            Member::Pane(pane) => {
3239                self.force_remove_pane(&pane, cx);
3240            }
3241        }
3242    }
3243
3244    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3245        self.panes.retain(|p| p != pane);
3246        cx.focus(self.panes.last().unwrap());
3247        if self.last_active_center_pane == Some(pane.downgrade()) {
3248            self.last_active_center_pane = None;
3249        }
3250        cx.notify();
3251    }
3252
3253    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3254        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3255            cx.background().timer(Duration::from_millis(100)).await;
3256            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3257                .ok();
3258        }));
3259    }
3260
3261    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3262        fn serialize_pane_handle(
3263            pane_handle: &ViewHandle<Pane>,
3264            cx: &AppContext,
3265        ) -> SerializedPane {
3266            let (items, active) = {
3267                let pane = pane_handle.read(cx);
3268                let active_item_id = pane.active_item().map(|item| item.id());
3269                (
3270                    pane.items()
3271                        .filter_map(|item_handle| {
3272                            Some(SerializedItem {
3273                                kind: Arc::from(item_handle.serialized_item_kind()?),
3274                                item_id: item_handle.id(),
3275                                active: Some(item_handle.id()) == active_item_id,
3276                            })
3277                        })
3278                        .collect::<Vec<_>>(),
3279                    pane.has_focus(),
3280                )
3281            };
3282
3283            SerializedPane::new(items, active)
3284        }
3285
3286        fn build_serialized_pane_group(
3287            pane_group: &Member,
3288            cx: &AppContext,
3289        ) -> SerializedPaneGroup {
3290            match pane_group {
3291                Member::Axis(PaneAxis {
3292                    axis,
3293                    members,
3294                    flexes,
3295                    bounding_boxes: _,
3296                }) => SerializedPaneGroup::Group {
3297                    axis: *axis,
3298                    children: members
3299                        .iter()
3300                        .map(|member| build_serialized_pane_group(member, cx))
3301                        .collect::<Vec<_>>(),
3302                    flexes: Some(flexes.borrow().clone()),
3303                },
3304                Member::Pane(pane_handle) => {
3305                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3306                }
3307            }
3308        }
3309
3310        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3311            let left_dock = this.left_dock.read(cx);
3312            let left_visible = left_dock.is_open();
3313            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3314                Some(
3315                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3316                        .to_string(),
3317                )
3318            });
3319            let left_dock_zoom = left_dock
3320                .visible_panel()
3321                .map(|panel| panel.is_zoomed(cx))
3322                .unwrap_or(false);
3323
3324            let right_dock = this.right_dock.read(cx);
3325            let right_visible = right_dock.is_open();
3326            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3327                Some(
3328                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3329                        .to_string(),
3330                )
3331            });
3332            let right_dock_zoom = right_dock
3333                .visible_panel()
3334                .map(|panel| panel.is_zoomed(cx))
3335                .unwrap_or(false);
3336
3337            let bottom_dock = this.bottom_dock.read(cx);
3338            let bottom_visible = bottom_dock.is_open();
3339            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3340                Some(
3341                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3342                        .to_string(),
3343                )
3344            });
3345            let bottom_dock_zoom = bottom_dock
3346                .visible_panel()
3347                .map(|panel| panel.is_zoomed(cx))
3348                .unwrap_or(false);
3349
3350            DockStructure {
3351                left: DockData {
3352                    visible: left_visible,
3353                    active_panel: left_active_panel,
3354                    zoom: left_dock_zoom,
3355                },
3356                right: DockData {
3357                    visible: right_visible,
3358                    active_panel: right_active_panel,
3359                    zoom: right_dock_zoom,
3360                },
3361                bottom: DockData {
3362                    visible: bottom_visible,
3363                    active_panel: bottom_active_panel,
3364                    zoom: bottom_dock_zoom,
3365                },
3366            }
3367        }
3368
3369        if let Some(location) = self.location(cx) {
3370            // Load bearing special case:
3371            //  - with_local_workspace() relies on this to not have other stuff open
3372            //    when you open your log
3373            if !location.paths().is_empty() {
3374                let center_group = build_serialized_pane_group(&self.center.root, cx);
3375                let docks = build_serialized_docks(self, cx);
3376
3377                let serialized_workspace = SerializedWorkspace {
3378                    id: self.database_id,
3379                    location,
3380                    center_group,
3381                    bounds: Default::default(),
3382                    display: Default::default(),
3383                    docks,
3384                };
3385
3386                cx.background()
3387                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3388                    .detach();
3389            }
3390        }
3391    }
3392
3393    pub(crate) fn load_workspace(
3394        workspace: WeakViewHandle<Workspace>,
3395        serialized_workspace: SerializedWorkspace,
3396        paths_to_open: Vec<Option<ProjectPath>>,
3397        cx: &mut AppContext,
3398    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3399        cx.spawn(|mut cx| async move {
3400            let (project, old_center_pane) = workspace.read_with(&cx, |workspace, _| {
3401                (
3402                    workspace.project().clone(),
3403                    workspace.last_active_center_pane.clone(),
3404                )
3405            })?;
3406
3407            let mut center_group = None;
3408            let mut center_items = None;
3409            // Traverse the splits tree and add to things
3410            if let Some((group, active_pane, items)) = serialized_workspace
3411                .center_group
3412                .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3413                .await
3414            {
3415                center_items = Some(items);
3416                center_group = Some((group, active_pane))
3417            }
3418
3419            let mut items_by_project_path = cx.read(|cx| {
3420                center_items
3421                    .unwrap_or_default()
3422                    .into_iter()
3423                    .filter_map(|item| {
3424                        let item = item?;
3425                        let project_path = item.project_path(cx)?;
3426                        Some((project_path, item))
3427                    })
3428                    .collect::<HashMap<_, _>>()
3429            });
3430
3431            let opened_items = paths_to_open
3432                .into_iter()
3433                .map(|path_to_open| {
3434                    path_to_open
3435                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3436                })
3437                .collect::<Vec<_>>();
3438
3439            // Remove old panes from workspace panes list
3440            workspace.update(&mut cx, |workspace, cx| {
3441                if let Some((center_group, active_pane)) = center_group {
3442                    workspace.remove_panes(workspace.center.root.clone(), cx);
3443
3444                    // Swap workspace center group
3445                    workspace.center = PaneGroup::with_root(center_group);
3446
3447                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
3448                    cx.focus_self();
3449
3450                    if let Some(active_pane) = active_pane {
3451                        cx.focus(&active_pane);
3452                    } else {
3453                        cx.focus(workspace.panes.last().unwrap());
3454                    }
3455                } else {
3456                    let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3457                    if let Some(old_center_handle) = old_center_handle {
3458                        cx.focus(&old_center_handle)
3459                    } else {
3460                        cx.focus_self()
3461                    }
3462                }
3463
3464                let docks = serialized_workspace.docks;
3465                workspace.left_dock.update(cx, |dock, cx| {
3466                    dock.set_open(docks.left.visible, cx);
3467                    if let Some(active_panel) = docks.left.active_panel {
3468                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3469                            dock.activate_panel(ix, cx);
3470                        }
3471                    }
3472                    dock.active_panel()
3473                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3474                    if docks.left.visible && docks.left.zoom {
3475                        cx.focus_self()
3476                    }
3477                });
3478                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3479                workspace.right_dock.update(cx, |dock, cx| {
3480                    dock.set_open(docks.right.visible, cx);
3481                    if let Some(active_panel) = docks.right.active_panel {
3482                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3483                            dock.activate_panel(ix, cx);
3484                        }
3485                    }
3486                    dock.active_panel()
3487                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3488
3489                    if docks.right.visible && docks.right.zoom {
3490                        cx.focus_self()
3491                    }
3492                });
3493                workspace.bottom_dock.update(cx, |dock, cx| {
3494                    dock.set_open(docks.bottom.visible, cx);
3495                    if let Some(active_panel) = docks.bottom.active_panel {
3496                        if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3497                            dock.activate_panel(ix, cx);
3498                        }
3499                    }
3500
3501                    dock.active_panel()
3502                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3503
3504                    if docks.bottom.visible && docks.bottom.zoom {
3505                        cx.focus_self()
3506                    }
3507                });
3508
3509                cx.notify();
3510            })?;
3511
3512            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3513            workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3514
3515            Ok(opened_items)
3516        })
3517    }
3518
3519    #[cfg(any(test, feature = "test-support"))]
3520    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3521        let client = project.read(cx).client();
3522        let user_store = project.read(cx).user_store();
3523
3524        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
3525        let app_state = Arc::new(AppState {
3526            languages: project.read(cx).languages().clone(),
3527            workspace_store,
3528            client,
3529            user_store,
3530            fs: project.read(cx).fs().clone(),
3531            build_window_options: |_, _, _| Default::default(),
3532            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3533            background_actions: || &[],
3534        });
3535        Self::new(0, project, app_state, cx)
3536    }
3537
3538    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3539        let dock = match position {
3540            DockPosition::Left => &self.left_dock,
3541            DockPosition::Right => &self.right_dock,
3542            DockPosition::Bottom => &self.bottom_dock,
3543        };
3544        let active_panel = dock.read(cx).visible_panel()?;
3545        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3546            dock.read(cx).render_placeholder(cx)
3547        } else {
3548            ChildView::new(dock, cx).into_any()
3549        };
3550
3551        Some(
3552            element
3553                .constrained()
3554                .dynamically(move |constraint, _, cx| match position {
3555                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3556                        Vector2F::new(20., constraint.min.y()),
3557                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3558                    ),
3559                    DockPosition::Bottom => SizeConstraint::new(
3560                        Vector2F::new(constraint.min.x(), 20.),
3561                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3562                    ),
3563                })
3564                .into_any(),
3565        )
3566    }
3567}
3568
3569fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3570    ZED_WINDOW_POSITION
3571        .zip(*ZED_WINDOW_SIZE)
3572        .map(|(position, size)| {
3573            WindowBounds::Fixed(RectF::new(
3574                cx.platform().screens()[0].bounds().origin() + position,
3575                size,
3576            ))
3577        })
3578}
3579
3580async fn open_items(
3581    serialized_workspace: Option<SerializedWorkspace>,
3582    workspace: &WeakViewHandle<Workspace>,
3583    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3584    app_state: Arc<AppState>,
3585    mut cx: AsyncAppContext,
3586) -> Result<Vec<Option<Result<Box<dyn ItemHandle>>>>> {
3587    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3588
3589    if let Some(serialized_workspace) = serialized_workspace {
3590        let workspace = workspace.clone();
3591        let restored_items = cx
3592            .update(|cx| {
3593                Workspace::load_workspace(
3594                    workspace,
3595                    serialized_workspace,
3596                    project_paths_to_open
3597                        .iter()
3598                        .map(|(_, project_path)| project_path)
3599                        .cloned()
3600                        .collect(),
3601                    cx,
3602                )
3603            })
3604            .await?;
3605
3606        let restored_project_paths = cx.read(|cx| {
3607            restored_items
3608                .iter()
3609                .filter_map(|item| item.as_ref()?.project_path(cx))
3610                .collect::<HashSet<_>>()
3611        });
3612
3613        for restored_item in restored_items {
3614            opened_items.push(restored_item.map(Ok));
3615        }
3616
3617        project_paths_to_open
3618            .iter_mut()
3619            .for_each(|(_, project_path)| {
3620                if let Some(project_path_to_open) = project_path {
3621                    if restored_project_paths.contains(project_path_to_open) {
3622                        *project_path = None;
3623                    }
3624                }
3625            });
3626    } else {
3627        for _ in 0..project_paths_to_open.len() {
3628            opened_items.push(None);
3629        }
3630    }
3631    assert!(opened_items.len() == project_paths_to_open.len());
3632
3633    let tasks =
3634        project_paths_to_open
3635            .into_iter()
3636            .enumerate()
3637            .map(|(i, (abs_path, project_path))| {
3638                let workspace = workspace.clone();
3639                cx.spawn(|mut cx| {
3640                    let fs = app_state.fs.clone();
3641                    async move {
3642                        let file_project_path = project_path?;
3643                        if fs.is_file(&abs_path).await {
3644                            Some((
3645                                i,
3646                                workspace
3647                                    .update(&mut cx, |workspace, cx| {
3648                                        workspace.open_path(file_project_path, None, true, cx)
3649                                    })
3650                                    .log_err()?
3651                                    .await,
3652                            ))
3653                        } else {
3654                            None
3655                        }
3656                    }
3657                })
3658            });
3659
3660    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3661        .await
3662        .into_iter()
3663    {
3664        if let Some((i, path_open_result)) = maybe_opened_path {
3665            opened_items[i] = Some(path_open_result);
3666        }
3667    }
3668
3669    Ok(opened_items)
3670}
3671
3672fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3673    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3674    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3675    const MESSAGE_ID: usize = 2;
3676
3677    if workspace
3678        .read_with(cx, |workspace, cx| {
3679            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3680        })
3681        .unwrap_or(false)
3682    {
3683        return;
3684    }
3685
3686    if db::kvp::KEY_VALUE_STORE
3687        .read_kvp(NEW_DOCK_HINT_KEY)
3688        .ok()
3689        .flatten()
3690        .is_some()
3691    {
3692        if !workspace
3693            .read_with(cx, |workspace, cx| {
3694                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3695            })
3696            .unwrap_or(false)
3697        {
3698            cx.update(|cx| {
3699                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3700                    let entry = tracker
3701                        .entry(TypeId::of::<MessageNotification>())
3702                        .or_default();
3703                    if !entry.contains(&MESSAGE_ID) {
3704                        entry.push(MESSAGE_ID);
3705                    }
3706                });
3707            });
3708        }
3709
3710        return;
3711    }
3712
3713    cx.spawn(|_| async move {
3714        db::kvp::KEY_VALUE_STORE
3715            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3716            .await
3717            .ok();
3718    })
3719    .detach();
3720
3721    workspace
3722        .update(cx, |workspace, cx| {
3723            workspace.show_notification_once(2, cx, |cx| {
3724                cx.add_view(|_| {
3725                    MessageNotification::new_element(|text, _| {
3726                        Text::new(
3727                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3728                            text,
3729                        )
3730                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3731                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3732                                .theme
3733                                .editor
3734                                .document_highlight_read_background;
3735
3736                            cx.scene().push_quad(gpui::Quad {
3737                                bounds,
3738                                background: Some(code_span_background_color),
3739                                border: Default::default(),
3740                                corner_radii: (2.0).into(),
3741                            })
3742                        })
3743                        .into_any()
3744                    })
3745                    .with_click_message("Read more about the new panel system")
3746                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3747                })
3748            })
3749        })
3750        .ok();
3751}
3752
3753fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3754    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3755
3756    workspace
3757        .update(cx, |workspace, cx| {
3758            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3759                workspace.show_notification_once(0, cx, |cx| {
3760                    cx.add_view(|_| {
3761                        MessageNotification::new("Failed to load the database file.")
3762                            .with_click_message("Click to let us know about this error")
3763                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3764                    })
3765                });
3766            }
3767        })
3768        .log_err();
3769}
3770
3771impl Entity for Workspace {
3772    type Event = Event;
3773
3774    fn release(&mut self, cx: &mut AppContext) {
3775        self.app_state.workspace_store.update(cx, |store, _| {
3776            store.workspaces.remove(&self.weak_self);
3777        })
3778    }
3779}
3780
3781impl View for Workspace {
3782    fn ui_name() -> &'static str {
3783        "Workspace"
3784    }
3785
3786    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3787        let theme = theme::current(cx).clone();
3788        Stack::new()
3789            .with_child(
3790                Flex::column()
3791                    .with_child(self.render_titlebar(&theme, cx))
3792                    .with_child(
3793                        Stack::new()
3794                            .with_child({
3795                                let project = self.project.clone();
3796                                Flex::row()
3797                                    .with_children(self.render_dock(DockPosition::Left, cx))
3798                                    .with_child(
3799                                        Flex::column()
3800                                            .with_child(
3801                                                FlexItem::new(
3802                                                    self.center.render(
3803                                                        &project,
3804                                                        &theme,
3805                                                        &self.follower_states,
3806                                                        self.active_call(),
3807                                                        self.active_pane(),
3808                                                        self.zoomed
3809                                                            .as_ref()
3810                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3811                                                            .as_ref(),
3812                                                        &self.app_state,
3813                                                        cx,
3814                                                    ),
3815                                                )
3816                                                .flex(1., true),
3817                                            )
3818                                            .with_children(
3819                                                self.render_dock(DockPosition::Bottom, cx),
3820                                            )
3821                                            .flex(1., true),
3822                                    )
3823                                    .with_children(self.render_dock(DockPosition::Right, cx))
3824                            })
3825                            .with_child(Overlay::new(
3826                                Stack::new()
3827                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3828                                        enum ZoomBackground {}
3829                                        let zoomed = zoomed.upgrade(cx)?;
3830
3831                                        let mut foreground_style =
3832                                            theme.workspace.zoomed_pane_foreground;
3833                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3834                                            foreground_style =
3835                                                theme.workspace.zoomed_panel_foreground;
3836                                            let margin = foreground_style.margin.top;
3837                                            let border = foreground_style.border.top;
3838
3839                                            // Only include a margin and border on the opposite side.
3840                                            foreground_style.margin.top = 0.;
3841                                            foreground_style.margin.left = 0.;
3842                                            foreground_style.margin.bottom = 0.;
3843                                            foreground_style.margin.right = 0.;
3844                                            foreground_style.border.top = false;
3845                                            foreground_style.border.left = false;
3846                                            foreground_style.border.bottom = false;
3847                                            foreground_style.border.right = false;
3848                                            match zoomed_dock_position {
3849                                                DockPosition::Left => {
3850                                                    foreground_style.margin.right = margin;
3851                                                    foreground_style.border.right = border;
3852                                                }
3853                                                DockPosition::Right => {
3854                                                    foreground_style.margin.left = margin;
3855                                                    foreground_style.border.left = border;
3856                                                }
3857                                                DockPosition::Bottom => {
3858                                                    foreground_style.margin.top = margin;
3859                                                    foreground_style.border.top = border;
3860                                                }
3861                                            }
3862                                        }
3863
3864                                        Some(
3865                                            ChildView::new(&zoomed, cx)
3866                                                .contained()
3867                                                .with_style(foreground_style)
3868                                                .aligned()
3869                                                .contained()
3870                                                .with_style(theme.workspace.zoomed_background)
3871                                                .mouse::<ZoomBackground>(0)
3872                                                .capture_all()
3873                                                .on_down(
3874                                                    MouseButton::Left,
3875                                                    |_, this: &mut Self, cx| {
3876                                                        this.zoom_out(cx);
3877                                                    },
3878                                                ),
3879                                        )
3880                                    }))
3881                                    .with_children(self.modal.as_ref().map(|modal| {
3882                                        // Prevent clicks within the modal from falling
3883                                        // through to the rest of the workspace.
3884                                        enum ModalBackground {}
3885                                        MouseEventHandler::new::<ModalBackground, _>(
3886                                            0,
3887                                            cx,
3888                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3889                                        )
3890                                        .on_click(MouseButton::Left, |_, _, _| {})
3891                                        .contained()
3892                                        .with_style(theme.workspace.modal)
3893                                        .aligned()
3894                                        .top()
3895                                    }))
3896                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3897                            ))
3898                            .provide_resize_bounds::<WorkspaceBounds>()
3899                            .flex(1.0, true),
3900                    )
3901                    .with_child(ChildView::new(&self.status_bar, cx))
3902                    .contained()
3903                    .with_background_color(theme.workspace.background),
3904            )
3905            .with_children(DragAndDrop::render(cx))
3906            .with_children(self.render_disconnected_overlay(cx))
3907            .into_any_named("workspace")
3908    }
3909
3910    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3911        if cx.is_self_focused() {
3912            cx.focus(&self.active_pane);
3913        }
3914    }
3915
3916    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3917        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3918    }
3919}
3920
3921impl WorkspaceStore {
3922    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3923        Self {
3924            workspaces: Default::default(),
3925            followers: Default::default(),
3926            _subscriptions: vec![
3927                client.add_request_handler(cx.handle(), Self::handle_follow),
3928                client.add_message_handler(cx.handle(), Self::handle_unfollow),
3929                client.add_message_handler(cx.handle(), Self::handle_update_followers),
3930            ],
3931            client,
3932        }
3933    }
3934
3935    pub fn update_followers(
3936        &self,
3937        project_id: Option<u64>,
3938        update: proto::update_followers::Variant,
3939        cx: &AppContext,
3940    ) -> Option<()> {
3941        if !cx.has_global::<ModelHandle<ActiveCall>>() {
3942            return None;
3943        }
3944
3945        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3946        let follower_ids: Vec<_> = self
3947            .followers
3948            .iter()
3949            .filter_map(|follower| {
3950                if follower.project_id == project_id || project_id.is_none() {
3951                    Some(follower.peer_id.into())
3952                } else {
3953                    None
3954                }
3955            })
3956            .collect();
3957        if follower_ids.is_empty() {
3958            return None;
3959        }
3960        self.client
3961            .send(proto::UpdateFollowers {
3962                room_id,
3963                project_id,
3964                follower_ids,
3965                variant: Some(update),
3966            })
3967            .log_err()
3968    }
3969
3970    async fn handle_follow(
3971        this: ModelHandle<Self>,
3972        envelope: TypedEnvelope<proto::Follow>,
3973        _: Arc<Client>,
3974        mut cx: AsyncAppContext,
3975    ) -> Result<proto::FollowResponse> {
3976        this.update(&mut cx, |this, cx| {
3977            let follower = Follower {
3978                project_id: envelope.payload.project_id,
3979                peer_id: envelope.original_sender_id()?,
3980            };
3981            let active_project = ActiveCall::global(cx)
3982                .read(cx)
3983                .location()
3984                .map(|project| project.id());
3985
3986            let mut response = proto::FollowResponse::default();
3987            for workspace in &this.workspaces {
3988                let Some(workspace) = workspace.upgrade(cx) else {
3989                    continue;
3990                };
3991
3992                workspace.update(cx.as_mut(), |workspace, cx| {
3993                    let handler_response = workspace.handle_follow(follower.project_id, cx);
3994                    if response.views.is_empty() {
3995                        response.views = handler_response.views;
3996                    } else {
3997                        response.views.extend_from_slice(&handler_response.views);
3998                    }
3999
4000                    if let Some(active_view_id) = handler_response.active_view_id.clone() {
4001                        if response.active_view_id.is_none()
4002                            || Some(workspace.project.id()) == active_project
4003                        {
4004                            response.active_view_id = Some(active_view_id);
4005                        }
4006                    }
4007                });
4008            }
4009
4010            if let Err(ix) = this.followers.binary_search(&follower) {
4011                this.followers.insert(ix, follower);
4012            }
4013
4014            Ok(response)
4015        })
4016    }
4017
4018    async fn handle_unfollow(
4019        this: ModelHandle<Self>,
4020        envelope: TypedEnvelope<proto::Unfollow>,
4021        _: Arc<Client>,
4022        mut cx: AsyncAppContext,
4023    ) -> Result<()> {
4024        this.update(&mut cx, |this, _| {
4025            let follower = Follower {
4026                project_id: envelope.payload.project_id,
4027                peer_id: envelope.original_sender_id()?,
4028            };
4029            if let Ok(ix) = this.followers.binary_search(&follower) {
4030                this.followers.remove(ix);
4031            }
4032            Ok(())
4033        })
4034    }
4035
4036    async fn handle_update_followers(
4037        this: ModelHandle<Self>,
4038        envelope: TypedEnvelope<proto::UpdateFollowers>,
4039        _: Arc<Client>,
4040        mut cx: AsyncAppContext,
4041    ) -> Result<()> {
4042        let leader_id = envelope.original_sender_id()?;
4043        let update = envelope.payload;
4044        this.update(&mut cx, |this, cx| {
4045            for workspace in &this.workspaces {
4046                let Some(workspace) = workspace.upgrade(cx) else {
4047                    continue;
4048                };
4049                workspace.update(cx.as_mut(), |workspace, cx| {
4050                    let project_id = workspace.project.read(cx).remote_id();
4051                    if update.project_id != project_id && update.project_id.is_some() {
4052                        return;
4053                    }
4054                    workspace.handle_update_followers(leader_id, update.clone(), cx);
4055                });
4056            }
4057            Ok(())
4058        })
4059    }
4060}
4061
4062impl Entity for WorkspaceStore {
4063    type Event = ();
4064}
4065
4066impl ViewId {
4067    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4068        Ok(Self {
4069            creator: message
4070                .creator
4071                .ok_or_else(|| anyhow!("creator is missing"))?,
4072            id: message.id,
4073        })
4074    }
4075
4076    pub(crate) fn to_proto(&self) -> proto::ViewId {
4077        proto::ViewId {
4078            creator: Some(self.creator),
4079            id: self.id,
4080        }
4081    }
4082}
4083
4084pub trait WorkspaceHandle {
4085    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4086}
4087
4088impl WorkspaceHandle for ViewHandle<Workspace> {
4089    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4090        self.read(cx)
4091            .worktrees(cx)
4092            .flat_map(|worktree| {
4093                let worktree_id = worktree.read(cx).id();
4094                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4095                    worktree_id,
4096                    path: f.path.clone(),
4097                })
4098            })
4099            .collect::<Vec<_>>()
4100    }
4101}
4102
4103impl std::fmt::Debug for OpenPaths {
4104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4105        f.debug_struct("OpenPaths")
4106            .field("paths", &self.paths)
4107            .finish()
4108    }
4109}
4110
4111pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
4112
4113pub fn activate_workspace_for_project(
4114    cx: &mut AsyncAppContext,
4115    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
4116) -> Option<WeakViewHandle<Workspace>> {
4117    for window in cx.windows() {
4118        let handle = window
4119            .update(cx, |cx| {
4120                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
4121                    let project = workspace_handle.read(cx).project.clone();
4122                    if project.update(cx, &predicate) {
4123                        cx.activate_window();
4124                        return Some(workspace_handle.clone());
4125                    }
4126                }
4127                None
4128            })
4129            .flatten();
4130
4131        if let Some(handle) = handle {
4132            return Some(handle.downgrade());
4133        }
4134    }
4135    None
4136}
4137
4138pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4139    DB.last_workspace().await.log_err().flatten()
4140}
4141
4142#[allow(clippy::type_complexity)]
4143pub fn open_paths(
4144    abs_paths: &[PathBuf],
4145    app_state: &Arc<AppState>,
4146    requesting_window: Option<WindowHandle<Workspace>>,
4147    cx: &mut AppContext,
4148) -> Task<
4149    Result<(
4150        WeakViewHandle<Workspace>,
4151        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4152    )>,
4153> {
4154    let app_state = app_state.clone();
4155    let abs_paths = abs_paths.to_vec();
4156    cx.spawn(|mut cx| async move {
4157        // Open paths in existing workspace if possible
4158        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4159            project.contains_paths(&abs_paths, cx)
4160        });
4161
4162        if let Some(existing) = existing {
4163            Ok((
4164                existing.clone(),
4165                existing
4166                    .update(&mut cx, |workspace, cx| {
4167                        workspace.open_paths(abs_paths, true, cx)
4168                    })?
4169                    .await,
4170            ))
4171        } else {
4172            Ok(cx
4173                .update(|cx| {
4174                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4175                })
4176                .await)
4177        }
4178    })
4179}
4180
4181pub fn open_new(
4182    app_state: &Arc<AppState>,
4183    cx: &mut AppContext,
4184    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4185) -> Task<()> {
4186    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4187    cx.spawn(|mut cx| async move {
4188        let (workspace, opened_paths) = task.await;
4189
4190        workspace
4191            .update(&mut cx, |workspace, cx| {
4192                if opened_paths.is_empty() {
4193                    init(workspace, cx)
4194                }
4195            })
4196            .log_err();
4197    })
4198}
4199
4200pub fn create_and_open_local_file(
4201    path: &'static Path,
4202    cx: &mut ViewContext<Workspace>,
4203    default_content: impl 'static + Send + FnOnce() -> Rope,
4204) -> Task<Result<Box<dyn ItemHandle>>> {
4205    cx.spawn(|workspace, mut cx| async move {
4206        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4207        if !fs.is_file(path).await {
4208            fs.create_file(path, Default::default()).await?;
4209            fs.save(path, &default_content(), Default::default())
4210                .await?;
4211        }
4212
4213        let mut items = workspace
4214            .update(&mut cx, |workspace, cx| {
4215                workspace.with_local_workspace(cx, |workspace, cx| {
4216                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4217                })
4218            })?
4219            .await?
4220            .await;
4221
4222        let item = items.pop().flatten();
4223        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4224    })
4225}
4226
4227pub fn join_remote_project(
4228    project_id: u64,
4229    follow_user_id: u64,
4230    app_state: Arc<AppState>,
4231    cx: &mut AppContext,
4232) -> Task<Result<()>> {
4233    cx.spawn(|mut cx| async move {
4234        let windows = cx.windows();
4235        let existing_workspace = windows.into_iter().find_map(|window| {
4236            window.downcast::<Workspace>().and_then(|window| {
4237                window
4238                    .read_root_with(&cx, |workspace, cx| {
4239                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4240                            Some(cx.handle().downgrade())
4241                        } else {
4242                            None
4243                        }
4244                    })
4245                    .unwrap_or(None)
4246            })
4247        });
4248
4249        let workspace = if let Some(existing_workspace) = existing_workspace {
4250            existing_workspace
4251        } else {
4252            let active_call = cx.read(ActiveCall::global);
4253            let room = active_call
4254                .read_with(&cx, |call, _| call.room().cloned())
4255                .ok_or_else(|| anyhow!("not in a call"))?;
4256            let project = room
4257                .update(&mut cx, |room, cx| {
4258                    room.join_project(
4259                        project_id,
4260                        app_state.languages.clone(),
4261                        app_state.fs.clone(),
4262                        cx,
4263                    )
4264                })
4265                .await?;
4266
4267            let window_bounds_override = window_bounds_env_override(&cx);
4268            let window = cx.add_window(
4269                (app_state.build_window_options)(
4270                    window_bounds_override,
4271                    None,
4272                    cx.platform().as_ref(),
4273                ),
4274                |cx| Workspace::new(0, project, app_state.clone(), cx),
4275            );
4276            let workspace = window.root(&cx).unwrap();
4277            (app_state.initialize_workspace)(
4278                workspace.downgrade(),
4279                false,
4280                app_state.clone(),
4281                cx.clone(),
4282            )
4283            .await
4284            .log_err();
4285
4286            workspace.downgrade()
4287        };
4288
4289        workspace.window().activate(&mut cx);
4290        cx.platform().activate(true);
4291
4292        workspace.update(&mut cx, |workspace, cx| {
4293            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4294                let follow_peer_id = room
4295                    .read(cx)
4296                    .remote_participants()
4297                    .iter()
4298                    .find(|(_, participant)| participant.user.id == follow_user_id)
4299                    .map(|(_, p)| p.peer_id)
4300                    .or_else(|| {
4301                        // If we couldn't follow the given user, follow the host instead.
4302                        let collaborator = workspace
4303                            .project()
4304                            .read(cx)
4305                            .collaborators()
4306                            .values()
4307                            .find(|collaborator| collaborator.replica_id == 0)?;
4308                        Some(collaborator.peer_id)
4309                    });
4310
4311                if let Some(follow_peer_id) = follow_peer_id {
4312                    workspace
4313                        .follow(follow_peer_id, cx)
4314                        .map(|follow| follow.detach_and_log_err(cx));
4315                }
4316            }
4317        })?;
4318
4319        anyhow::Ok(())
4320    })
4321}
4322
4323pub fn restart(_: &Restart, cx: &mut AppContext) {
4324    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4325    cx.spawn(|mut cx| async move {
4326        let mut workspace_windows = cx
4327            .windows()
4328            .into_iter()
4329            .filter_map(|window| window.downcast::<Workspace>())
4330            .collect::<Vec<_>>();
4331
4332        // If multiple windows have unsaved changes, and need a save prompt,
4333        // prompt in the active window before switching to a different window.
4334        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4335
4336        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4337            let answer = window.prompt(
4338                PromptLevel::Info,
4339                "Are you sure you want to restart?",
4340                &["Restart", "Cancel"],
4341                &mut cx,
4342            );
4343
4344            if let Some(mut answer) = answer {
4345                let answer = answer.next().await;
4346                if answer != Some(0) {
4347                    return Ok(());
4348                }
4349            }
4350        }
4351
4352        // If the user cancels any save prompt, then keep the app open.
4353        for window in workspace_windows {
4354            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4355                workspace.prepare_to_close(true, cx)
4356            }) {
4357                if !should_close.await? {
4358                    return Ok(());
4359                }
4360            }
4361        }
4362        cx.platform().restart();
4363        anyhow::Ok(())
4364    })
4365    .detach_and_log_err(cx);
4366}
4367
4368fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4369    let mut parts = value.split(',');
4370    let width: usize = parts.next()?.parse().ok()?;
4371    let height: usize = parts.next()?.parse().ok()?;
4372    Some(vec2f(width as f32, height as f32))
4373}
4374
4375#[cfg(test)]
4376mod tests {
4377    use super::*;
4378    use crate::{
4379        dock::test::{TestPanel, TestPanelEvent},
4380        item::test::{TestItem, TestItemEvent, TestProjectItem},
4381    };
4382    use fs::FakeFs;
4383    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4384    use project::{Project, ProjectEntryId};
4385    use serde_json::json;
4386    use settings::SettingsStore;
4387    use std::{cell::RefCell, rc::Rc};
4388
4389    #[gpui::test]
4390    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4391        init_test(cx);
4392
4393        let fs = FakeFs::new(cx.background());
4394        let project = Project::test(fs, [], cx).await;
4395        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4396        let workspace = window.root(cx);
4397
4398        // Adding an item with no ambiguity renders the tab without detail.
4399        let item1 = window.add_view(cx, |_| {
4400            let mut item = TestItem::new();
4401            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4402            item
4403        });
4404        workspace.update(cx, |workspace, cx| {
4405            workspace.add_item(Box::new(item1.clone()), cx);
4406        });
4407        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4408
4409        // Adding an item that creates ambiguity increases the level of detail on
4410        // both tabs.
4411        let item2 = window.add_view(cx, |_| {
4412            let mut item = TestItem::new();
4413            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4414            item
4415        });
4416        workspace.update(cx, |workspace, cx| {
4417            workspace.add_item(Box::new(item2.clone()), cx);
4418        });
4419        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4420        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4421
4422        // Adding an item that creates ambiguity increases the level of detail only
4423        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4424        // we stop at the highest detail available.
4425        let item3 = window.add_view(cx, |_| {
4426            let mut item = TestItem::new();
4427            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4428            item
4429        });
4430        workspace.update(cx, |workspace, cx| {
4431            workspace.add_item(Box::new(item3.clone()), cx);
4432        });
4433        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4434        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4435        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4436    }
4437
4438    #[gpui::test]
4439    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4440        init_test(cx);
4441
4442        let fs = FakeFs::new(cx.background());
4443        fs.insert_tree(
4444            "/root1",
4445            json!({
4446                "one.txt": "",
4447                "two.txt": "",
4448            }),
4449        )
4450        .await;
4451        fs.insert_tree(
4452            "/root2",
4453            json!({
4454                "three.txt": "",
4455            }),
4456        )
4457        .await;
4458
4459        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4460        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4461        let workspace = window.root(cx);
4462        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4463        let worktree_id = project.read_with(cx, |project, cx| {
4464            project.worktrees(cx).next().unwrap().read(cx).id()
4465        });
4466
4467        let item1 = window.add_view(cx, |cx| {
4468            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4469        });
4470        let item2 = window.add_view(cx, |cx| {
4471            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4472        });
4473
4474        // Add an item to an empty pane
4475        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4476        project.read_with(cx, |project, cx| {
4477            assert_eq!(
4478                project.active_entry(),
4479                project
4480                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4481                    .map(|e| e.id)
4482            );
4483        });
4484        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4485
4486        // Add a second item to a non-empty pane
4487        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4488        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4489        project.read_with(cx, |project, cx| {
4490            assert_eq!(
4491                project.active_entry(),
4492                project
4493                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4494                    .map(|e| e.id)
4495            );
4496        });
4497
4498        // Close the active item
4499        pane.update(cx, |pane, cx| {
4500            pane.close_active_item(&Default::default(), cx).unwrap()
4501        })
4502        .await
4503        .unwrap();
4504        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4505        project.read_with(cx, |project, cx| {
4506            assert_eq!(
4507                project.active_entry(),
4508                project
4509                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4510                    .map(|e| e.id)
4511            );
4512        });
4513
4514        // Add a project folder
4515        project
4516            .update(cx, |project, cx| {
4517                project.find_or_create_local_worktree("/root2", true, cx)
4518            })
4519            .await
4520            .unwrap();
4521        assert_eq!(
4522            window.current_title(cx).as_deref(),
4523            Some("one.txt β€” root1, root2")
4524        );
4525
4526        // Remove a project folder
4527        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4528        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4529    }
4530
4531    #[gpui::test]
4532    async fn test_close_window(cx: &mut TestAppContext) {
4533        init_test(cx);
4534
4535        let fs = FakeFs::new(cx.background());
4536        fs.insert_tree("/root", json!({ "one": "" })).await;
4537
4538        let project = Project::test(fs, ["root".as_ref()], cx).await;
4539        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4540        let workspace = window.root(cx);
4541
4542        // When there are no dirty items, there's nothing to do.
4543        let item1 = window.add_view(cx, |_| TestItem::new());
4544        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4545        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4546        assert!(task.await.unwrap());
4547
4548        // When there are dirty untitled items, prompt to save each one. If the user
4549        // cancels any prompt, then abort.
4550        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4551        let item3 = window.add_view(cx, |cx| {
4552            TestItem::new()
4553                .with_dirty(true)
4554                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4555        });
4556        workspace.update(cx, |w, cx| {
4557            w.add_item(Box::new(item2.clone()), cx);
4558            w.add_item(Box::new(item3.clone()), cx);
4559        });
4560        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4561        cx.foreground().run_until_parked();
4562        window.simulate_prompt_answer(2, cx); // cancel save all
4563        cx.foreground().run_until_parked();
4564        window.simulate_prompt_answer(2, cx); // cancel save all
4565        cx.foreground().run_until_parked();
4566        assert!(!window.has_pending_prompt(cx));
4567        assert!(!task.await.unwrap());
4568    }
4569
4570    #[gpui::test]
4571    async fn test_close_pane_items(cx: &mut TestAppContext) {
4572        init_test(cx);
4573
4574        let fs = FakeFs::new(cx.background());
4575
4576        let project = Project::test(fs, None, cx).await;
4577        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4578        let workspace = window.root(cx);
4579
4580        let item1 = window.add_view(cx, |cx| {
4581            TestItem::new()
4582                .with_dirty(true)
4583                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4584        });
4585        let item2 = window.add_view(cx, |cx| {
4586            TestItem::new()
4587                .with_dirty(true)
4588                .with_conflict(true)
4589                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4590        });
4591        let item3 = window.add_view(cx, |cx| {
4592            TestItem::new()
4593                .with_dirty(true)
4594                .with_conflict(true)
4595                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4596        });
4597        let item4 = window.add_view(cx, |cx| {
4598            TestItem::new()
4599                .with_dirty(true)
4600                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4601        });
4602        let pane = workspace.update(cx, |workspace, cx| {
4603            workspace.add_item(Box::new(item1.clone()), cx);
4604            workspace.add_item(Box::new(item2.clone()), cx);
4605            workspace.add_item(Box::new(item3.clone()), cx);
4606            workspace.add_item(Box::new(item4.clone()), cx);
4607            workspace.active_pane().clone()
4608        });
4609
4610        let close_items = pane.update(cx, |pane, cx| {
4611            pane.activate_item(1, true, true, cx);
4612            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4613            let item1_id = item1.id();
4614            let item3_id = item3.id();
4615            let item4_id = item4.id();
4616            pane.close_items(cx, SaveIntent::Close, move |id| {
4617                [item1_id, item3_id, item4_id].contains(&id)
4618            })
4619        });
4620        cx.foreground().run_until_parked();
4621
4622        assert!(window.has_pending_prompt(cx));
4623        // Ignore "Save all" prompt
4624        window.simulate_prompt_answer(2, cx);
4625        cx.foreground().run_until_parked();
4626        // There's a prompt to save item 1.
4627        pane.read_with(cx, |pane, _| {
4628            assert_eq!(pane.items_len(), 4);
4629            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4630        });
4631        // Confirm saving item 1.
4632        window.simulate_prompt_answer(0, cx);
4633        cx.foreground().run_until_parked();
4634
4635        // Item 1 is saved. There's a prompt to save item 3.
4636        pane.read_with(cx, |pane, cx| {
4637            assert_eq!(item1.read(cx).save_count, 1);
4638            assert_eq!(item1.read(cx).save_as_count, 0);
4639            assert_eq!(item1.read(cx).reload_count, 0);
4640            assert_eq!(pane.items_len(), 3);
4641            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4642        });
4643        assert!(window.has_pending_prompt(cx));
4644
4645        // Cancel saving item 3.
4646        window.simulate_prompt_answer(1, cx);
4647        cx.foreground().run_until_parked();
4648
4649        // Item 3 is reloaded. There's a prompt to save item 4.
4650        pane.read_with(cx, |pane, cx| {
4651            assert_eq!(item3.read(cx).save_count, 0);
4652            assert_eq!(item3.read(cx).save_as_count, 0);
4653            assert_eq!(item3.read(cx).reload_count, 1);
4654            assert_eq!(pane.items_len(), 2);
4655            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4656        });
4657        assert!(window.has_pending_prompt(cx));
4658
4659        // Confirm saving item 4.
4660        window.simulate_prompt_answer(0, cx);
4661        cx.foreground().run_until_parked();
4662
4663        // There's a prompt for a path for item 4.
4664        cx.simulate_new_path_selection(|_| Some(Default::default()));
4665        close_items.await.unwrap();
4666
4667        // The requested items are closed.
4668        pane.read_with(cx, |pane, cx| {
4669            assert_eq!(item4.read(cx).save_count, 0);
4670            assert_eq!(item4.read(cx).save_as_count, 1);
4671            assert_eq!(item4.read(cx).reload_count, 0);
4672            assert_eq!(pane.items_len(), 1);
4673            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4674        });
4675    }
4676
4677    #[gpui::test]
4678    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4679        init_test(cx);
4680
4681        let fs = FakeFs::new(cx.background());
4682
4683        let project = Project::test(fs, [], cx).await;
4684        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4685        let workspace = window.root(cx);
4686
4687        // Create several workspace items with single project entries, and two
4688        // workspace items with multiple project entries.
4689        let single_entry_items = (0..=4)
4690            .map(|project_entry_id| {
4691                window.add_view(cx, |cx| {
4692                    TestItem::new()
4693                        .with_dirty(true)
4694                        .with_project_items(&[TestProjectItem::new(
4695                            project_entry_id,
4696                            &format!("{project_entry_id}.txt"),
4697                            cx,
4698                        )])
4699                })
4700            })
4701            .collect::<Vec<_>>();
4702        let item_2_3 = window.add_view(cx, |cx| {
4703            TestItem::new()
4704                .with_dirty(true)
4705                .with_singleton(false)
4706                .with_project_items(&[
4707                    single_entry_items[2].read(cx).project_items[0].clone(),
4708                    single_entry_items[3].read(cx).project_items[0].clone(),
4709                ])
4710        });
4711        let item_3_4 = window.add_view(cx, |cx| {
4712            TestItem::new()
4713                .with_dirty(true)
4714                .with_singleton(false)
4715                .with_project_items(&[
4716                    single_entry_items[3].read(cx).project_items[0].clone(),
4717                    single_entry_items[4].read(cx).project_items[0].clone(),
4718                ])
4719        });
4720
4721        // Create two panes that contain the following project entries:
4722        //   left pane:
4723        //     multi-entry items:   (2, 3)
4724        //     single-entry items:  0, 1, 2, 3, 4
4725        //   right pane:
4726        //     single-entry items:  1
4727        //     multi-entry items:   (3, 4)
4728        let left_pane = workspace.update(cx, |workspace, cx| {
4729            let left_pane = workspace.active_pane().clone();
4730            workspace.add_item(Box::new(item_2_3.clone()), cx);
4731            for item in single_entry_items {
4732                workspace.add_item(Box::new(item), cx);
4733            }
4734            left_pane.update(cx, |pane, cx| {
4735                pane.activate_item(2, true, true, cx);
4736            });
4737
4738            workspace
4739                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4740                .unwrap();
4741
4742            left_pane
4743        });
4744
4745        //Need to cause an effect flush in order to respect new focus
4746        workspace.update(cx, |workspace, cx| {
4747            workspace.add_item(Box::new(item_3_4.clone()), cx);
4748            cx.focus(&left_pane);
4749        });
4750
4751        // When closing all of the items in the left pane, we should be prompted twice:
4752        // once for project entry 0, and once for project entry 2. After those two
4753        // prompts, the task should complete.
4754
4755        let close = left_pane.update(cx, |pane, cx| {
4756            pane.close_items(cx, SaveIntent::Close, move |_| true)
4757        });
4758        cx.foreground().run_until_parked();
4759        // Discard "Save all" prompt
4760        window.simulate_prompt_answer(2, cx);
4761
4762        cx.foreground().run_until_parked();
4763        left_pane.read_with(cx, |pane, cx| {
4764            assert_eq!(
4765                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4766                &[ProjectEntryId::from_proto(0)]
4767            );
4768        });
4769        window.simulate_prompt_answer(0, cx);
4770
4771        cx.foreground().run_until_parked();
4772        left_pane.read_with(cx, |pane, cx| {
4773            assert_eq!(
4774                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4775                &[ProjectEntryId::from_proto(2)]
4776            );
4777        });
4778        window.simulate_prompt_answer(0, cx);
4779
4780        cx.foreground().run_until_parked();
4781        close.await.unwrap();
4782        left_pane.read_with(cx, |pane, _| {
4783            assert_eq!(pane.items_len(), 0);
4784        });
4785    }
4786
4787    #[gpui::test]
4788    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4789        init_test(cx);
4790
4791        let fs = FakeFs::new(cx.background());
4792
4793        let project = Project::test(fs, [], cx).await;
4794        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4795        let workspace = window.root(cx);
4796        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4797
4798        let item = window.add_view(cx, |cx| {
4799            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4800        });
4801        let item_id = item.id();
4802        workspace.update(cx, |workspace, cx| {
4803            workspace.add_item(Box::new(item.clone()), cx);
4804        });
4805
4806        // Autosave on window change.
4807        item.update(cx, |item, cx| {
4808            cx.update_global(|settings: &mut SettingsStore, cx| {
4809                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4810                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4811                })
4812            });
4813            item.is_dirty = true;
4814        });
4815
4816        // Deactivating the window saves the file.
4817        window.simulate_deactivation(cx);
4818        deterministic.run_until_parked();
4819        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4820
4821        // Autosave on focus change.
4822        item.update(cx, |item, cx| {
4823            cx.focus_self();
4824            cx.update_global(|settings: &mut SettingsStore, cx| {
4825                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4826                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4827                })
4828            });
4829            item.is_dirty = true;
4830        });
4831
4832        // Blurring the item saves the file.
4833        item.update(cx, |_, cx| cx.blur());
4834        deterministic.run_until_parked();
4835        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4836
4837        // Deactivating the window still saves the file.
4838        window.simulate_activation(cx);
4839        item.update(cx, |item, cx| {
4840            cx.focus_self();
4841            item.is_dirty = true;
4842        });
4843        window.simulate_deactivation(cx);
4844
4845        deterministic.run_until_parked();
4846        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4847
4848        // Autosave after delay.
4849        item.update(cx, |item, cx| {
4850            cx.update_global(|settings: &mut SettingsStore, cx| {
4851                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4852                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4853                })
4854            });
4855            item.is_dirty = true;
4856            cx.emit(TestItemEvent::Edit);
4857        });
4858
4859        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4860        deterministic.advance_clock(Duration::from_millis(250));
4861        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4862
4863        // After delay expires, the file is saved.
4864        deterministic.advance_clock(Duration::from_millis(250));
4865        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4866
4867        // Autosave on focus change, ensuring closing the tab counts as such.
4868        item.update(cx, |item, cx| {
4869            cx.update_global(|settings: &mut SettingsStore, cx| {
4870                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4871                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4872                })
4873            });
4874            item.is_dirty = true;
4875        });
4876
4877        pane.update(cx, |pane, cx| {
4878            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4879        })
4880        .await
4881        .unwrap();
4882        assert!(!window.has_pending_prompt(cx));
4883        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4884
4885        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4886        workspace.update(cx, |workspace, cx| {
4887            workspace.add_item(Box::new(item.clone()), cx);
4888        });
4889        item.update(cx, |item, cx| {
4890            item.project_items[0].update(cx, |item, _| {
4891                item.entry_id = None;
4892            });
4893            item.is_dirty = true;
4894            cx.blur();
4895        });
4896        deterministic.run_until_parked();
4897        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4898
4899        // Ensure autosave is prevented for deleted files also when closing the buffer.
4900        let _close_items = pane.update(cx, |pane, cx| {
4901            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4902        });
4903        deterministic.run_until_parked();
4904        assert!(window.has_pending_prompt(cx));
4905        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4906    }
4907
4908    #[gpui::test]
4909    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4910        init_test(cx);
4911
4912        let fs = FakeFs::new(cx.background());
4913
4914        let project = Project::test(fs, [], cx).await;
4915        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4916        let workspace = window.root(cx);
4917
4918        let item = window.add_view(cx, |cx| {
4919            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4920        });
4921        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4922        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4923        let toolbar_notify_count = Rc::new(RefCell::new(0));
4924
4925        workspace.update(cx, |workspace, cx| {
4926            workspace.add_item(Box::new(item.clone()), cx);
4927            let toolbar_notification_count = toolbar_notify_count.clone();
4928            cx.observe(&toolbar, move |_, _, _| {
4929                *toolbar_notification_count.borrow_mut() += 1
4930            })
4931            .detach();
4932        });
4933
4934        pane.read_with(cx, |pane, _| {
4935            assert!(!pane.can_navigate_backward());
4936            assert!(!pane.can_navigate_forward());
4937        });
4938
4939        item.update(cx, |item, cx| {
4940            item.set_state("one".to_string(), cx);
4941        });
4942
4943        // Toolbar must be notified to re-render the navigation buttons
4944        assert_eq!(*toolbar_notify_count.borrow(), 1);
4945
4946        pane.read_with(cx, |pane, _| {
4947            assert!(pane.can_navigate_backward());
4948            assert!(!pane.can_navigate_forward());
4949        });
4950
4951        workspace
4952            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4953            .await
4954            .unwrap();
4955
4956        assert_eq!(*toolbar_notify_count.borrow(), 3);
4957        pane.read_with(cx, |pane, _| {
4958            assert!(!pane.can_navigate_backward());
4959            assert!(pane.can_navigate_forward());
4960        });
4961    }
4962
4963    #[gpui::test]
4964    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4965        init_test(cx);
4966        let fs = FakeFs::new(cx.background());
4967
4968        let project = Project::test(fs, [], cx).await;
4969        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4970        let workspace = window.root(cx);
4971
4972        let panel = workspace.update(cx, |workspace, cx| {
4973            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4974            workspace.add_panel(panel.clone(), cx);
4975
4976            workspace
4977                .right_dock()
4978                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4979
4980            panel
4981        });
4982
4983        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4984        pane.update(cx, |pane, cx| {
4985            let item = cx.add_view(|_| TestItem::new());
4986            pane.add_item(Box::new(item), true, true, None, cx);
4987        });
4988
4989        // Transfer focus from center to panel
4990        workspace.update(cx, |workspace, cx| {
4991            workspace.toggle_panel_focus::<TestPanel>(cx);
4992        });
4993
4994        workspace.read_with(cx, |workspace, cx| {
4995            assert!(workspace.right_dock().read(cx).is_open());
4996            assert!(!panel.is_zoomed(cx));
4997            assert!(panel.has_focus(cx));
4998        });
4999
5000        // Transfer focus from panel to center
5001        workspace.update(cx, |workspace, cx| {
5002            workspace.toggle_panel_focus::<TestPanel>(cx);
5003        });
5004
5005        workspace.read_with(cx, |workspace, cx| {
5006            assert!(workspace.right_dock().read(cx).is_open());
5007            assert!(!panel.is_zoomed(cx));
5008            assert!(!panel.has_focus(cx));
5009        });
5010
5011        // Close the dock
5012        workspace.update(cx, |workspace, cx| {
5013            workspace.toggle_dock(DockPosition::Right, cx);
5014        });
5015
5016        workspace.read_with(cx, |workspace, cx| {
5017            assert!(!workspace.right_dock().read(cx).is_open());
5018            assert!(!panel.is_zoomed(cx));
5019            assert!(!panel.has_focus(cx));
5020        });
5021
5022        // Open the dock
5023        workspace.update(cx, |workspace, cx| {
5024            workspace.toggle_dock(DockPosition::Right, cx);
5025        });
5026
5027        workspace.read_with(cx, |workspace, cx| {
5028            assert!(workspace.right_dock().read(cx).is_open());
5029            assert!(!panel.is_zoomed(cx));
5030            assert!(panel.has_focus(cx));
5031        });
5032
5033        // Focus and zoom panel
5034        panel.update(cx, |panel, cx| {
5035            cx.focus_self();
5036            panel.set_zoomed(true, cx)
5037        });
5038
5039        workspace.read_with(cx, |workspace, cx| {
5040            assert!(workspace.right_dock().read(cx).is_open());
5041            assert!(panel.is_zoomed(cx));
5042            assert!(panel.has_focus(cx));
5043        });
5044
5045        // Transfer focus to the center closes the dock
5046        workspace.update(cx, |workspace, cx| {
5047            workspace.toggle_panel_focus::<TestPanel>(cx);
5048        });
5049
5050        workspace.read_with(cx, |workspace, cx| {
5051            assert!(!workspace.right_dock().read(cx).is_open());
5052            assert!(panel.is_zoomed(cx));
5053            assert!(!panel.has_focus(cx));
5054        });
5055
5056        // Transferring focus back to the panel keeps it zoomed
5057        workspace.update(cx, |workspace, cx| {
5058            workspace.toggle_panel_focus::<TestPanel>(cx);
5059        });
5060
5061        workspace.read_with(cx, |workspace, cx| {
5062            assert!(workspace.right_dock().read(cx).is_open());
5063            assert!(panel.is_zoomed(cx));
5064            assert!(panel.has_focus(cx));
5065        });
5066
5067        // Close the dock while it is zoomed
5068        workspace.update(cx, |workspace, cx| {
5069            workspace.toggle_dock(DockPosition::Right, cx)
5070        });
5071
5072        workspace.read_with(cx, |workspace, cx| {
5073            assert!(!workspace.right_dock().read(cx).is_open());
5074            assert!(panel.is_zoomed(cx));
5075            assert!(workspace.zoomed.is_none());
5076            assert!(!panel.has_focus(cx));
5077        });
5078
5079        // Opening the dock, when it's zoomed, retains focus
5080        workspace.update(cx, |workspace, cx| {
5081            workspace.toggle_dock(DockPosition::Right, cx)
5082        });
5083
5084        workspace.read_with(cx, |workspace, cx| {
5085            assert!(workspace.right_dock().read(cx).is_open());
5086            assert!(panel.is_zoomed(cx));
5087            assert!(workspace.zoomed.is_some());
5088            assert!(panel.has_focus(cx));
5089        });
5090
5091        // Unzoom and close the panel, zoom the active pane.
5092        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5093        workspace.update(cx, |workspace, cx| {
5094            workspace.toggle_dock(DockPosition::Right, cx)
5095        });
5096        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5097
5098        // Opening a dock unzooms the pane.
5099        workspace.update(cx, |workspace, cx| {
5100            workspace.toggle_dock(DockPosition::Right, cx)
5101        });
5102        workspace.read_with(cx, |workspace, cx| {
5103            let pane = pane.read(cx);
5104            assert!(!pane.is_zoomed());
5105            assert!(!pane.has_focus());
5106            assert!(workspace.right_dock().read(cx).is_open());
5107            assert!(workspace.zoomed.is_none());
5108        });
5109    }
5110
5111    #[gpui::test]
5112    async fn test_panels(cx: &mut gpui::TestAppContext) {
5113        init_test(cx);
5114        let fs = FakeFs::new(cx.background());
5115
5116        let project = Project::test(fs, [], cx).await;
5117        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5118        let workspace = window.root(cx);
5119
5120        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5121            // Add panel_1 on the left, panel_2 on the right.
5122            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5123            workspace.add_panel(panel_1.clone(), cx);
5124            workspace
5125                .left_dock()
5126                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5127            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5128            workspace.add_panel(panel_2.clone(), cx);
5129            workspace
5130                .right_dock()
5131                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5132
5133            let left_dock = workspace.left_dock();
5134            assert_eq!(
5135                left_dock.read(cx).visible_panel().unwrap().id(),
5136                panel_1.id()
5137            );
5138            assert_eq!(
5139                left_dock.read(cx).active_panel_size(cx).unwrap(),
5140                panel_1.size(cx)
5141            );
5142
5143            left_dock.update(cx, |left_dock, cx| {
5144                left_dock.resize_active_panel(Some(1337.), cx)
5145            });
5146            assert_eq!(
5147                workspace
5148                    .right_dock()
5149                    .read(cx)
5150                    .visible_panel()
5151                    .unwrap()
5152                    .id(),
5153                panel_2.id()
5154            );
5155
5156            (panel_1, panel_2)
5157        });
5158
5159        // Move panel_1 to the right
5160        panel_1.update(cx, |panel_1, cx| {
5161            panel_1.set_position(DockPosition::Right, cx)
5162        });
5163
5164        workspace.update(cx, |workspace, cx| {
5165            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5166            // Since it was the only panel on the left, the left dock should now be closed.
5167            assert!(!workspace.left_dock().read(cx).is_open());
5168            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5169            let right_dock = workspace.right_dock();
5170            assert_eq!(
5171                right_dock.read(cx).visible_panel().unwrap().id(),
5172                panel_1.id()
5173            );
5174            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5175
5176            // Now we move panel_2Β to the left
5177            panel_2.set_position(DockPosition::Left, cx);
5178        });
5179
5180        workspace.update(cx, |workspace, cx| {
5181            // Since panel_2 was not visible on the right, we don't open the left dock.
5182            assert!(!workspace.left_dock().read(cx).is_open());
5183            // And the right dock is unaffected in it's displaying of panel_1
5184            assert!(workspace.right_dock().read(cx).is_open());
5185            assert_eq!(
5186                workspace
5187                    .right_dock()
5188                    .read(cx)
5189                    .visible_panel()
5190                    .unwrap()
5191                    .id(),
5192                panel_1.id()
5193            );
5194        });
5195
5196        // Move panel_1 back to the left
5197        panel_1.update(cx, |panel_1, cx| {
5198            panel_1.set_position(DockPosition::Left, cx)
5199        });
5200
5201        workspace.update(cx, |workspace, cx| {
5202            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5203            let left_dock = workspace.left_dock();
5204            assert!(left_dock.read(cx).is_open());
5205            assert_eq!(
5206                left_dock.read(cx).visible_panel().unwrap().id(),
5207                panel_1.id()
5208            );
5209            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5210            // And right the dock should be closed as it no longer has any panels.
5211            assert!(!workspace.right_dock().read(cx).is_open());
5212
5213            // Now we move panel_1 to the bottom
5214            panel_1.set_position(DockPosition::Bottom, cx);
5215        });
5216
5217        workspace.update(cx, |workspace, cx| {
5218            // Since panel_1 was visible on the left, we close the left dock.
5219            assert!(!workspace.left_dock().read(cx).is_open());
5220            // The bottom dock is sized based on the panel's default size,
5221            // since the panel orientation changed from vertical to horizontal.
5222            let bottom_dock = workspace.bottom_dock();
5223            assert_eq!(
5224                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5225                panel_1.size(cx),
5226            );
5227            // Close bottom dock and move panel_1 back to the left.
5228            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5229            panel_1.set_position(DockPosition::Left, cx);
5230        });
5231
5232        // Emit activated event on panel 1
5233        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5234
5235        // Now the left dock is open and panel_1 is active and focused.
5236        workspace.read_with(cx, |workspace, cx| {
5237            let left_dock = workspace.left_dock();
5238            assert!(left_dock.read(cx).is_open());
5239            assert_eq!(
5240                left_dock.read(cx).visible_panel().unwrap().id(),
5241                panel_1.id()
5242            );
5243            assert!(panel_1.is_focused(cx));
5244        });
5245
5246        // Emit closed event on panel 2, which is not active
5247        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5248
5249        // Wo don't close the left dock, because panel_2 wasn't the active panel
5250        workspace.read_with(cx, |workspace, cx| {
5251            let left_dock = workspace.left_dock();
5252            assert!(left_dock.read(cx).is_open());
5253            assert_eq!(
5254                left_dock.read(cx).visible_panel().unwrap().id(),
5255                panel_1.id()
5256            );
5257        });
5258
5259        // Emitting a ZoomIn event shows the panel as zoomed.
5260        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5261        workspace.read_with(cx, |workspace, _| {
5262            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5263            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5264        });
5265
5266        // Move panel to another dock while it is zoomed
5267        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5268        workspace.read_with(cx, |workspace, _| {
5269            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5270            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5271        });
5272
5273        // If focus is transferred to another view that's not a panel or another pane, we still show
5274        // the panel as zoomed.
5275        let focus_receiver = window.add_view(cx, |_| EmptyView);
5276        focus_receiver.update(cx, |_, cx| cx.focus_self());
5277        workspace.read_with(cx, |workspace, _| {
5278            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5279            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5280        });
5281
5282        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5283        workspace.update(cx, |_, cx| cx.focus_self());
5284        workspace.read_with(cx, |workspace, _| {
5285            assert_eq!(workspace.zoomed, None);
5286            assert_eq!(workspace.zoomed_position, None);
5287        });
5288
5289        // If focus is transferred again to another view that's not a panel or a pane, we won't
5290        // show the panel as zoomed because it wasn't zoomed before.
5291        focus_receiver.update(cx, |_, cx| cx.focus_self());
5292        workspace.read_with(cx, |workspace, _| {
5293            assert_eq!(workspace.zoomed, None);
5294            assert_eq!(workspace.zoomed_position, None);
5295        });
5296
5297        // When focus is transferred back to the panel, it is zoomed again.
5298        panel_1.update(cx, |_, cx| cx.focus_self());
5299        workspace.read_with(cx, |workspace, _| {
5300            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5301            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5302        });
5303
5304        // Emitting a ZoomOut event unzooms the panel.
5305        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5306        workspace.read_with(cx, |workspace, _| {
5307            assert_eq!(workspace.zoomed, None);
5308            assert_eq!(workspace.zoomed_position, None);
5309        });
5310
5311        // Emit closed event on panel 1, which is active
5312        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5313
5314        // Now the left dock is closed, because panel_1 was the active panel
5315        workspace.read_with(cx, |workspace, cx| {
5316            let right_dock = workspace.right_dock();
5317            assert!(!right_dock.read(cx).is_open());
5318        });
5319    }
5320
5321    pub fn init_test(cx: &mut TestAppContext) {
5322        cx.foreground().forbid_parking();
5323        cx.update(|cx| {
5324            cx.set_global(SettingsStore::test(cx));
5325            theme::init((), cx);
5326            language::init(cx);
5327            crate::init_settings(cx);
5328            Project::init_settings(cx);
5329        });
5330    }
5331}