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