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