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