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