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