workspace.rs

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