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