workspace.rs

   1pub mod dock;
   2pub mod item;
   3pub mod notifications;
   4pub mod pane;
   5pub mod pane_group;
   6mod persistence;
   7pub mod searchable;
   8pub mod shared_screen;
   9mod status_bar;
  10mod toolbar;
  11mod workspace_settings;
  12
  13use anyhow::{anyhow, Context, Result};
  14use call::ActiveCall;
  15use channel::ChannelStore;
  16use client::{
  17    proto::{self, PeerId},
  18    Client, TypedEnvelope, UserStore,
  19};
  20use collections::{hash_map, HashMap, HashSet};
  21use drag_and_drop::DragAndDrop;
  22use futures::{
  23    channel::{mpsc, oneshot},
  24    future::try_join_all,
  25    FutureExt, StreamExt,
  26};
  27use gpui::{
  28    actions,
  29    elements::*,
  30    geometry::{
  31        rect::RectF,
  32        vector::{vec2f, Vector2F},
  33    },
  34    impl_actions,
  35    platform::{
  36        CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
  37        WindowBounds, WindowOptions,
  38    },
  39    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  40    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  41    WeakViewHandle, WindowContext, WindowHandle,
  42};
  43use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  44use itertools::Itertools;
  45use language::{LanguageRegistry, Rope};
  46use std::{
  47    any::TypeId,
  48    borrow::Cow,
  49    cmp, env,
  50    future::Future,
  51    path::{Path, PathBuf},
  52    rc::Rc,
  53    str,
  54    sync::{atomic::AtomicUsize, Arc},
  55    time::Duration,
  56};
  57
  58use crate::{
  59    notifications::{simple_message_notification::MessageNotification, NotificationTracker},
  60    persistence::model::{
  61        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  62    },
  63};
  64use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  65use lazy_static::lazy_static;
  66use notifications::{NotificationHandle, NotifyResultExt};
  67pub use pane::*;
  68pub use pane_group::*;
  69use persistence::{model::SerializedItem, DB};
  70pub use persistence::{
  71    model::{ItemId, WorkspaceLocation},
  72    WorkspaceDb, DB as WORKSPACE_DB,
  73};
  74use postage::prelude::Stream;
  75use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  76use serde::Deserialize;
  77use shared_screen::SharedScreen;
  78use status_bar::StatusBar;
  79pub use status_bar::StatusItemView;
  80use theme::{Theme, ThemeSettings};
  81pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  82use util::{async_iife, ResultExt};
  83pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  84
  85lazy_static! {
  86    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  87        .ok()
  88        .as_deref()
  89        .and_then(parse_pixel_position_env_var);
  90    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  91        .ok()
  92        .as_deref()
  93        .and_then(parse_pixel_position_env_var);
  94}
  95
  96pub trait Modal: View {
  97    fn has_focus(&self) -> bool;
  98    fn dismiss_on_event(event: &Self::Event) -> bool;
  99}
 100
 101trait ModalHandle {
 102    fn as_any(&self) -> &AnyViewHandle;
 103    fn has_focus(&self, cx: &WindowContext) -> bool;
 104}
 105
 106impl<T: Modal> ModalHandle for ViewHandle<T> {
 107    fn as_any(&self) -> &AnyViewHandle {
 108        self
 109    }
 110
 111    fn has_focus(&self, cx: &WindowContext) -> bool {
 112        self.read(cx).has_focus()
 113    }
 114}
 115
 116#[derive(Clone, PartialEq)]
 117pub struct RemoveWorktreeFromProject(pub WorktreeId);
 118
 119actions!(
 120    workspace,
 121    [
 122        Open,
 123        NewFile,
 124        NewWindow,
 125        CloseWindow,
 126        CloseInactiveTabsAndPanes,
 127        AddFolderToProject,
 128        Unfollow,
 129        SaveAs,
 130        ReloadActiveItem,
 131        ActivatePreviousPane,
 132        ActivateNextPane,
 133        FollowNextCollaborator,
 134        NewTerminal,
 135        NewCenterTerminal,
 136        ToggleTerminalFocus,
 137        NewSearch,
 138        Feedback,
 139        Restart,
 140        Welcome,
 141        ToggleZoom,
 142        ToggleLeftDock,
 143        ToggleRightDock,
 144        ToggleBottomDock,
 145        CloseAllDocks,
 146    ]
 147);
 148
 149#[derive(Clone, PartialEq)]
 150pub struct OpenPaths {
 151    pub paths: Vec<PathBuf>,
 152}
 153
 154#[derive(Clone, Deserialize, PartialEq)]
 155pub struct ActivatePane(pub usize);
 156
 157#[derive(Clone, Deserialize, PartialEq)]
 158pub struct ActivatePaneInDirection(pub SplitDirection);
 159
 160#[derive(Clone, Deserialize, PartialEq)]
 161pub struct SwapPaneInDirection(pub SplitDirection);
 162
 163#[derive(Clone, Deserialize, PartialEq)]
 164pub struct NewFileInDirection(pub SplitDirection);
 165
 166#[derive(Clone, PartialEq, Debug, Deserialize)]
 167#[serde(rename_all = "camelCase")]
 168pub struct SaveAll {
 169    pub save_intent: Option<SaveIntent>,
 170}
 171
 172#[derive(Clone, PartialEq, Debug, Deserialize)]
 173#[serde(rename_all = "camelCase")]
 174pub struct Save {
 175    pub save_intent: Option<SaveIntent>,
 176}
 177
 178#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 179#[serde(rename_all = "camelCase")]
 180pub struct CloseAllItemsAndPanes {
 181    pub save_intent: Option<SaveIntent>,
 182}
 183
 184#[derive(Deserialize)]
 185pub struct Toast {
 186    id: usize,
 187    msg: Cow<'static, str>,
 188    #[serde(skip)]
 189    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 190}
 191
 192impl Toast {
 193    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 194        Toast {
 195            id,
 196            msg: msg.into(),
 197            on_click: None,
 198        }
 199    }
 200
 201    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 202    where
 203        M: Into<Cow<'static, str>>,
 204        F: Fn(&mut WindowContext) + 'static,
 205    {
 206        self.on_click = Some((message.into(), Arc::new(on_click)));
 207        self
 208    }
 209}
 210
 211impl PartialEq for Toast {
 212    fn eq(&self, other: &Self) -> bool {
 213        self.id == other.id
 214            && self.msg == other.msg
 215            && self.on_click.is_some() == other.on_click.is_some()
 216    }
 217}
 218
 219impl Clone for Toast {
 220    fn clone(&self) -> Self {
 221        Toast {
 222            id: self.id,
 223            msg: self.msg.to_owned(),
 224            on_click: self.on_click.clone(),
 225        }
 226    }
 227}
 228
 229#[derive(Clone, Deserialize, PartialEq)]
 230pub struct OpenTerminal {
 231    pub working_directory: PathBuf,
 232}
 233
 234impl_actions!(
 235    workspace,
 236    [
 237        ActivatePane,
 238        ActivatePaneInDirection,
 239        SwapPaneInDirection,
 240        NewFileInDirection,
 241        Toast,
 242        OpenTerminal,
 243        SaveAll,
 244        Save,
 245        CloseAllItemsAndPanes,
 246    ]
 247);
 248
 249pub type WorkspaceId = i64;
 250
 251pub fn init_settings(cx: &mut AppContext) {
 252    settings::register::<WorkspaceSettings>(cx);
 253    settings::register::<item::ItemSettings>(cx);
 254}
 255
 256pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 257    init_settings(cx);
 258    pane::init(cx);
 259    notifications::init(cx);
 260
 261    cx.add_global_action({
 262        let app_state = Arc::downgrade(&app_state);
 263        move |_: &Open, cx: &mut AppContext| {
 264            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 265                files: true,
 266                directories: true,
 267                multiple: true,
 268            });
 269
 270            if let Some(app_state) = app_state.upgrade() {
 271                cx.spawn(move |mut cx| async move {
 272                    if let Some(paths) = paths.recv().await.flatten() {
 273                        cx.update(|cx| {
 274                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 275                        });
 276                    }
 277                })
 278                .detach();
 279            }
 280        }
 281    });
 282    cx.add_async_action(Workspace::open);
 283
 284    cx.add_async_action(Workspace::follow_next_collaborator);
 285    cx.add_async_action(Workspace::close);
 286    cx.add_async_action(Workspace::close_inactive_items_and_panes);
 287    cx.add_async_action(Workspace::close_all_items_and_panes);
 288    cx.add_global_action(Workspace::close_global);
 289    cx.add_global_action(restart);
 290    cx.add_async_action(Workspace::save_all);
 291    cx.add_action(Workspace::add_folder_to_project);
 292    cx.add_action(
 293        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 294            let pane = workspace.active_pane().clone();
 295            workspace.unfollow(&pane, cx);
 296        },
 297    );
 298    cx.add_action(
 299        |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
 300            workspace
 301                .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
 302                .detach_and_log_err(cx);
 303        },
 304    );
 305    cx.add_action(
 306        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 307            workspace
 308                .save_active_item(SaveIntent::SaveAs, cx)
 309                .detach_and_log_err(cx);
 310        },
 311    );
 312    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 313        workspace.activate_previous_pane(cx)
 314    });
 315    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 316        workspace.activate_next_pane(cx)
 317    });
 318
 319    cx.add_action(
 320        |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
 321            workspace.activate_pane_in_direction(action.0, cx)
 322        },
 323    );
 324
 325    cx.add_action(
 326        |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
 327            workspace.swap_pane_in_direction(action.0, cx)
 328        },
 329    );
 330
 331    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 332        workspace.toggle_dock(DockPosition::Left, cx);
 333    });
 334    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 335        workspace.toggle_dock(DockPosition::Right, cx);
 336    });
 337    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 338        workspace.toggle_dock(DockPosition::Bottom, cx);
 339    });
 340    cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
 341        workspace.close_all_docks(cx);
 342    });
 343    cx.add_action(Workspace::activate_pane_at_index);
 344    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 345        workspace.reopen_closed_item(cx).detach();
 346    });
 347    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 348        workspace
 349            .go_back(workspace.active_pane().downgrade(), cx)
 350            .detach();
 351    });
 352    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 353        workspace
 354            .go_forward(workspace.active_pane().downgrade(), cx)
 355            .detach();
 356    });
 357
 358    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 359        cx.spawn(|workspace, mut cx| async move {
 360            let err = install_cli::install_cli(&cx)
 361                .await
 362                .context("Failed to create CLI symlink");
 363
 364            workspace.update(&mut cx, |workspace, cx| {
 365                if matches!(err, Err(_)) {
 366                    err.notify_err(workspace, cx);
 367                } else {
 368                    workspace.show_notification(1, cx, |cx| {
 369                        cx.add_view(|_| {
 370                            MessageNotification::new("Successfully installed the `zed` binary")
 371                        })
 372                    });
 373                }
 374            })
 375        })
 376        .detach();
 377    });
 378}
 379
 380type ProjectItemBuilders = HashMap<
 381    TypeId,
 382    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 383>;
 384pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 385    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 386        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 387            let item = model.downcast::<I::Item>().unwrap();
 388            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 389        });
 390    });
 391}
 392
 393type FollowableItemBuilder = fn(
 394    ViewHandle<Pane>,
 395    ViewHandle<Workspace>,
 396    ViewId,
 397    &mut Option<proto::view::Variant>,
 398    &mut AppContext,
 399) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 400type FollowableItemBuilders = HashMap<
 401    TypeId,
 402    (
 403        FollowableItemBuilder,
 404        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 405    ),
 406>;
 407pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 408    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 409        builders.insert(
 410            TypeId::of::<I>(),
 411            (
 412                |pane, workspace, id, state, cx| {
 413                    I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
 414                        cx.foreground()
 415                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 416                    })
 417                },
 418                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 419            ),
 420        );
 421    });
 422}
 423
 424type ItemDeserializers = HashMap<
 425    Arc<str>,
 426    fn(
 427        ModelHandle<Project>,
 428        WeakViewHandle<Workspace>,
 429        WorkspaceId,
 430        ItemId,
 431        &mut ViewContext<Pane>,
 432    ) -> Task<Result<Box<dyn ItemHandle>>>,
 433>;
 434pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 435    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 436        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 437            deserializers.insert(
 438                Arc::from(serialized_item_kind),
 439                |project, workspace, workspace_id, item_id, cx| {
 440                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 441                    cx.foreground()
 442                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 443                },
 444            );
 445        }
 446    });
 447}
 448
 449pub struct AppState {
 450    pub languages: Arc<LanguageRegistry>,
 451    pub client: Arc<Client>,
 452    pub user_store: ModelHandle<UserStore>,
 453    pub channel_store: ModelHandle<ChannelStore>,
 454    pub workspace_store: ModelHandle<WorkspaceStore>,
 455    pub fs: Arc<dyn fs::Fs>,
 456    pub build_window_options:
 457        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 458    pub initialize_workspace:
 459        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 460    pub background_actions: BackgroundActions,
 461}
 462
 463pub struct WorkspaceStore {
 464    workspaces: HashSet<WeakViewHandle<Workspace>>,
 465    followers: Vec<Follower>,
 466    client: Arc<Client>,
 467    _subscriptions: Vec<client::Subscription>,
 468}
 469
 470#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
 471struct Follower {
 472    project_id: Option<u64>,
 473    peer_id: PeerId,
 474}
 475
 476impl AppState {
 477    #[cfg(any(test, feature = "test-support"))]
 478    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 479        use settings::SettingsStore;
 480
 481        if !cx.has_global::<SettingsStore>() {
 482            cx.set_global(SettingsStore::test(cx));
 483        }
 484
 485        let fs = fs::FakeFs::new(cx.background().clone());
 486        let languages = Arc::new(LanguageRegistry::test());
 487        let http_client = util::http::FakeHttpClient::with_404_response();
 488        let client = Client::new(http_client.clone(), cx);
 489        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 490        let channel_store =
 491            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
 492        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
 493
 494        theme::init((), cx);
 495        client::init(&client, cx);
 496        crate::init_settings(cx);
 497
 498        Arc::new(Self {
 499            client,
 500            fs,
 501            languages,
 502            user_store,
 503            channel_store,
 504            workspace_store,
 505            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 506            build_window_options: |_, _, _| Default::default(),
 507            background_actions: || &[],
 508        })
 509    }
 510}
 511
 512struct DelayedDebouncedEditAction {
 513    task: Option<Task<()>>,
 514    cancel_channel: Option<oneshot::Sender<()>>,
 515}
 516
 517impl DelayedDebouncedEditAction {
 518    fn new() -> DelayedDebouncedEditAction {
 519        DelayedDebouncedEditAction {
 520            task: None,
 521            cancel_channel: None,
 522        }
 523    }
 524
 525    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 526    where
 527        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 528    {
 529        if let Some(channel) = self.cancel_channel.take() {
 530            _ = channel.send(());
 531        }
 532
 533        let (sender, mut receiver) = oneshot::channel::<()>();
 534        self.cancel_channel = Some(sender);
 535
 536        let previous_task = self.task.take();
 537        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 538            let mut timer = cx.background().timer(delay).fuse();
 539            if let Some(previous_task) = previous_task {
 540                previous_task.await;
 541            }
 542
 543            futures::select_biased! {
 544                _ = receiver => return,
 545                    _ = timer => {}
 546            }
 547
 548            if let Some(result) = workspace
 549                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 550                .log_err()
 551            {
 552                result.await.log_err();
 553            }
 554        }));
 555    }
 556}
 557
 558pub enum Event {
 559    PaneAdded(ViewHandle<Pane>),
 560    ContactRequestedJoin(u64),
 561}
 562
 563pub struct Workspace {
 564    weak_self: WeakViewHandle<Self>,
 565    modal: Option<ActiveModal>,
 566    zoomed: Option<AnyWeakViewHandle>,
 567    zoomed_position: Option<DockPosition>,
 568    center: PaneGroup,
 569    left_dock: ViewHandle<Dock>,
 570    bottom_dock: ViewHandle<Dock>,
 571    right_dock: ViewHandle<Dock>,
 572    panes: Vec<ViewHandle<Pane>>,
 573    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 574    active_pane: ViewHandle<Pane>,
 575    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 576    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_by_leader: FollowerStatesByLeader,
 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
 606type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 607
 608#[derive(Default)]
 609struct FollowerState {
 610    active_view_id: Option<ViewId>,
 611    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 612}
 613
 614enum WorkspaceBounds {}
 615
 616impl Workspace {
 617    pub fn new(
 618        workspace_id: WorkspaceId,
 619        project: ModelHandle<Project>,
 620        app_state: Arc<AppState>,
 621        cx: &mut ViewContext<Self>,
 622    ) -> Self {
 623        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 624        cx.subscribe(&project, move |this, _, event, cx| {
 625            match event {
 626                project::Event::RemoteIdChanged(_) => {
 627                    this.update_window_title(cx);
 628                }
 629
 630                project::Event::CollaboratorLeft(peer_id) => {
 631                    this.collaborator_left(*peer_id, cx);
 632                }
 633
 634                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 635                    this.update_window_title(cx);
 636                    this.serialize_workspace(cx);
 637                }
 638
 639                project::Event::DisconnectedFromHost => {
 640                    this.update_window_edited(cx);
 641                    cx.blur();
 642                }
 643
 644                project::Event::Closed => {
 645                    cx.remove_window();
 646                }
 647
 648                project::Event::DeletedEntry(entry_id) => {
 649                    for pane in this.panes.iter() {
 650                        pane.update(cx, |pane, cx| {
 651                            pane.handle_deleted_project_item(*entry_id, cx)
 652                        });
 653                    }
 654                }
 655
 656                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 657                    cx.add_view(|_| MessageNotification::new(message.clone()))
 658                }),
 659
 660                _ => {}
 661            }
 662            cx.notify()
 663        })
 664        .detach();
 665
 666        let weak_handle = cx.weak_handle();
 667        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 668
 669        let center_pane = cx.add_view(|cx| {
 670            Pane::new(
 671                weak_handle.clone(),
 672                project.clone(),
 673                app_state.background_actions,
 674                pane_history_timestamp.clone(),
 675                cx,
 676            )
 677        });
 678        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 679        cx.focus(&center_pane);
 680        cx.emit(Event::PaneAdded(center_pane.clone()));
 681
 682        app_state.workspace_store.update(cx, |store, _| {
 683            store.workspaces.insert(weak_handle.clone());
 684        });
 685
 686        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 687        let mut connection_status = app_state.client.status();
 688        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 689            current_user.recv().await;
 690            connection_status.recv().await;
 691            let mut stream =
 692                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 693
 694            while stream.recv().await.is_some() {
 695                this.update(&mut cx, |_, cx| cx.notify())?;
 696            }
 697            anyhow::Ok(())
 698        });
 699
 700        // All leader updates are enqueued and then processed in a single task, so
 701        // that each asynchronous operation can be run in order.
 702        let (leader_updates_tx, mut leader_updates_rx) =
 703            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 704        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 705            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 706                Self::process_leader_update(&this, leader_id, update, &mut cx)
 707                    .await
 708                    .log_err();
 709            }
 710
 711            Ok(())
 712        });
 713
 714        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 715
 716        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 717        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 718        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 719        let left_dock_buttons =
 720            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 721        let bottom_dock_buttons =
 722            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 723        let right_dock_buttons =
 724            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 725        let status_bar = cx.add_view(|cx| {
 726            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 727            status_bar.add_left_item(left_dock_buttons, cx);
 728            status_bar.add_right_item(right_dock_buttons, cx);
 729            status_bar.add_right_item(bottom_dock_buttons, cx);
 730            status_bar
 731        });
 732
 733        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 734            drag_and_drop.register_container(weak_handle.clone());
 735        });
 736
 737        let mut active_call = None;
 738        if cx.has_global::<ModelHandle<ActiveCall>>() {
 739            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 740            let mut subscriptions = Vec::new();
 741            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 742            active_call = Some((call, subscriptions));
 743        }
 744
 745        let subscriptions = vec![
 746            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 747            cx.observe_window_activation(Self::on_window_activation_changed),
 748            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 749                // Transform fixed bounds to be stored in terms of the containing display
 750                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 751                    if let Some(screen) = cx.platform().screen_by_id(display) {
 752                        let screen_bounds = screen.bounds();
 753                        window_bounds
 754                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 755                        window_bounds
 756                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 757                        bounds = WindowBounds::Fixed(window_bounds);
 758                    }
 759                }
 760
 761                cx.background()
 762                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 763                    .detach_and_log_err(cx);
 764            }),
 765            cx.observe(&left_dock, |this, _, cx| {
 766                this.serialize_workspace(cx);
 767                cx.notify();
 768            }),
 769            cx.observe(&bottom_dock, |this, _, cx| {
 770                this.serialize_workspace(cx);
 771                cx.notify();
 772            }),
 773            cx.observe(&right_dock, |this, _, cx| {
 774                this.serialize_workspace(cx);
 775                cx.notify();
 776            }),
 777        ];
 778
 779        cx.defer(|this, cx| this.update_window_title(cx));
 780        Workspace {
 781            weak_self: weak_handle.clone(),
 782            modal: None,
 783            zoomed: None,
 784            zoomed_position: None,
 785            center: PaneGroup::new(center_pane.clone()),
 786            panes: vec![center_pane.clone()],
 787            panes_by_item: Default::default(),
 788            active_pane: center_pane.clone(),
 789            last_active_center_pane: Some(center_pane.downgrade()),
 790            last_active_view_id: None,
 791            status_bar,
 792            titlebar_item: None,
 793            notifications: Default::default(),
 794            left_dock,
 795            bottom_dock,
 796            right_dock,
 797            project: project.clone(),
 798            follower_states_by_leader: Default::default(),
 799            last_leaders_by_pane: Default::default(),
 800            window_edited: false,
 801            active_call,
 802            database_id: workspace_id,
 803            app_state,
 804            _observe_current_user,
 805            _apply_leader_updates,
 806            _schedule_serialize: None,
 807            leader_updates_tx,
 808            subscriptions,
 809            pane_history_timestamp,
 810        }
 811    }
 812
 813    fn new_local(
 814        abs_paths: Vec<PathBuf>,
 815        app_state: Arc<AppState>,
 816        requesting_window: Option<WindowHandle<Workspace>>,
 817        cx: &mut AppContext,
 818    ) -> Task<(
 819        WeakViewHandle<Workspace>,
 820        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 821    )> {
 822        let project_handle = Project::local(
 823            app_state.client.clone(),
 824            app_state.user_store.clone(),
 825            app_state.languages.clone(),
 826            app_state.fs.clone(),
 827            cx,
 828        );
 829
 830        cx.spawn(|mut cx| async move {
 831            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 832
 833            let paths_to_open = Arc::new(abs_paths);
 834
 835            // Get project paths for all of the abs_paths
 836            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 837            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 838                Vec::with_capacity(paths_to_open.len());
 839            for path in paths_to_open.iter().cloned() {
 840                if let Some((worktree, project_entry)) = cx
 841                    .update(|cx| {
 842                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 843                    })
 844                    .await
 845                    .log_err()
 846                {
 847                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 848                    project_paths.push((path, Some(project_entry)));
 849                } else {
 850                    project_paths.push((path, None));
 851                }
 852            }
 853
 854            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 855                serialized_workspace.id
 856            } else {
 857                DB.next_id().await.unwrap_or(0)
 858            };
 859
 860            let window = if let Some(window) = requesting_window {
 861                window.replace_root(&mut cx, |cx| {
 862                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 863                });
 864                window
 865            } else {
 866                {
 867                    let window_bounds_override = window_bounds_env_override(&cx);
 868                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 869                        (Some(bounds), None)
 870                    } else {
 871                        serialized_workspace
 872                            .as_ref()
 873                            .and_then(|serialized_workspace| {
 874                                let display = serialized_workspace.display?;
 875                                let mut bounds = serialized_workspace.bounds?;
 876
 877                                // Stored bounds are relative to the containing display.
 878                                // So convert back to global coordinates if that screen still exists
 879                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 880                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 881                                        let screen_bounds = screen.bounds();
 882                                        window_bounds.set_origin_x(
 883                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 884                                        );
 885                                        window_bounds.set_origin_y(
 886                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 887                                        );
 888                                        bounds = WindowBounds::Fixed(window_bounds);
 889                                    } else {
 890                                        // Screen no longer exists. Return none here.
 891                                        return None;
 892                                    }
 893                                }
 894
 895                                Some((bounds, display))
 896                            })
 897                            .unzip()
 898                    };
 899
 900                    // Use the serialized workspace to construct the new window
 901                    cx.add_window(
 902                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 903                        |cx| {
 904                            Workspace::new(
 905                                workspace_id,
 906                                project_handle.clone(),
 907                                app_state.clone(),
 908                                cx,
 909                            )
 910                        },
 911                    )
 912                }
 913            };
 914
 915            // We haven't yielded the main thread since obtaining the window handle,
 916            // so the window exists.
 917            let workspace = window.root(&cx).unwrap();
 918
 919            (app_state.initialize_workspace)(
 920                workspace.downgrade(),
 921                serialized_workspace.is_some(),
 922                app_state.clone(),
 923                cx.clone(),
 924            )
 925            .await
 926            .log_err();
 927
 928            window.update(&mut cx, |cx| cx.activate_window());
 929
 930            let workspace = workspace.downgrade();
 931            notify_if_database_failed(&workspace, &mut cx);
 932            let opened_items = open_items(
 933                serialized_workspace,
 934                &workspace,
 935                project_paths,
 936                app_state,
 937                cx,
 938            )
 939            .await;
 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        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2516            for state in states_by_pane.into_values() {
2517                for item in state.items_by_leader_view_id.into_values() {
2518                    item.set_leader_peer_id(None, cx);
2519                }
2520            }
2521        }
2522        cx.notify();
2523    }
2524
2525    fn start_following(
2526        &mut self,
2527        leader_id: PeerId,
2528        cx: &mut ViewContext<Self>,
2529    ) -> Option<Task<Result<()>>> {
2530        let pane = self.active_pane().clone();
2531
2532        self.last_leaders_by_pane
2533            .insert(pane.downgrade(), leader_id);
2534        self.follower_states_by_leader
2535            .entry(leader_id)
2536            .or_default()
2537            .insert(pane.clone(), Default::default());
2538        cx.notify();
2539
2540        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2541        let project_id = self.project.read(cx).remote_id();
2542        let request = self.app_state.client.request(proto::Follow {
2543            room_id,
2544            project_id,
2545            leader_id: Some(leader_id),
2546        });
2547
2548        Some(cx.spawn(|this, mut cx| async move {
2549            let response = request.await?;
2550            this.update(&mut cx, |this, _| {
2551                let state = this
2552                    .follower_states_by_leader
2553                    .get_mut(&leader_id)
2554                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2555                    .ok_or_else(|| anyhow!("following interrupted"))?;
2556                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2557                    Some(ViewId::from_proto(active_view_id)?)
2558                } else {
2559                    None
2560                };
2561                Ok::<_, anyhow::Error>(())
2562            })??;
2563            Self::add_views_from_leader(
2564                this.clone(),
2565                leader_id,
2566                vec![pane],
2567                response.views,
2568                &mut cx,
2569            )
2570            .await?;
2571            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2572            Ok(())
2573        }))
2574    }
2575
2576    pub fn follow_next_collaborator(
2577        &mut self,
2578        _: &FollowNextCollaborator,
2579        cx: &mut ViewContext<Self>,
2580    ) -> Option<Task<Result<()>>> {
2581        let collaborators = self.project.read(cx).collaborators();
2582        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2583            let mut collaborators = collaborators.keys().copied();
2584            for peer_id in collaborators.by_ref() {
2585                if peer_id == leader_id {
2586                    break;
2587                }
2588            }
2589            collaborators.next()
2590        } else if let Some(last_leader_id) =
2591            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2592        {
2593            if collaborators.contains_key(last_leader_id) {
2594                Some(*last_leader_id)
2595            } else {
2596                None
2597            }
2598        } else {
2599            None
2600        };
2601
2602        let pane = self.active_pane.clone();
2603        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2604        else {
2605            return None;
2606        };
2607        if Some(leader_id) == self.unfollow(&pane, cx) {
2608            return None;
2609        }
2610        self.follow(leader_id, cx)
2611    }
2612
2613    pub fn follow(
2614        &mut self,
2615        leader_id: PeerId,
2616        cx: &mut ViewContext<Self>,
2617    ) -> Option<Task<Result<()>>> {
2618        let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2619        let project = self.project.read(cx);
2620
2621        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2622            return None;
2623        };
2624
2625        let other_project_id = match remote_participant.location {
2626            call::ParticipantLocation::External => None,
2627            call::ParticipantLocation::UnsharedProject => None,
2628            call::ParticipantLocation::SharedProject { project_id } => {
2629                if Some(project_id) == project.remote_id() {
2630                    None
2631                } else {
2632                    Some(project_id)
2633                }
2634            }
2635        };
2636
2637        // if they are active in another project, follow there.
2638        if let Some(project_id) = other_project_id {
2639            let app_state = self.app_state.clone();
2640            return Some(crate::join_remote_project(
2641                project_id,
2642                remote_participant.user.id,
2643                app_state,
2644                cx,
2645            ));
2646        }
2647
2648        // if you're already following, find the right pane and focus it.
2649        for (existing_leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2650            if leader_id == *existing_leader_id {
2651                for (pane, _) in states_by_pane {
2652                    cx.focus(pane);
2653                    return None;
2654                }
2655            }
2656        }
2657
2658        // Otherwise, follow.
2659        self.start_following(leader_id, cx)
2660    }
2661
2662    pub fn unfollow(
2663        &mut self,
2664        pane: &ViewHandle<Pane>,
2665        cx: &mut ViewContext<Self>,
2666    ) -> Option<PeerId> {
2667        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2668            let leader_id = *leader_id;
2669            if let Some(state) = states_by_pane.remove(pane) {
2670                for (_, item) in state.items_by_leader_view_id {
2671                    item.set_leader_peer_id(None, cx);
2672                }
2673
2674                if states_by_pane.is_empty() {
2675                    self.follower_states_by_leader.remove(&leader_id);
2676                    let project_id = self.project.read(cx).remote_id();
2677                    let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2678                    self.app_state
2679                        .client
2680                        .send(proto::Unfollow {
2681                            room_id,
2682                            project_id,
2683                            leader_id: Some(leader_id),
2684                        })
2685                        .log_err();
2686                }
2687
2688                cx.notify();
2689                return Some(leader_id);
2690            }
2691        }
2692        None
2693    }
2694
2695    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2696        self.follower_states_by_leader.contains_key(&peer_id)
2697    }
2698
2699    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2700        // TODO: There should be a better system in place for this
2701        // (https://github.com/zed-industries/zed/issues/1290)
2702        let is_fullscreen = cx.window_is_fullscreen();
2703        let container_theme = if is_fullscreen {
2704            let mut container_theme = theme.titlebar.container;
2705            container_theme.padding.left = container_theme.padding.right;
2706            container_theme
2707        } else {
2708            theme.titlebar.container
2709        };
2710
2711        enum TitleBar {}
2712        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
2713            Stack::new()
2714                .with_children(
2715                    self.titlebar_item
2716                        .as_ref()
2717                        .map(|item| ChildView::new(item, cx)),
2718                )
2719                .contained()
2720                .with_style(container_theme)
2721        })
2722        .on_click(MouseButton::Left, |event, _, cx| {
2723            if event.click_count == 2 {
2724                cx.zoom_window();
2725            }
2726        })
2727        .constrained()
2728        .with_height(theme.titlebar.height)
2729        .into_any_named("titlebar")
2730    }
2731
2732    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2733        let active_entry = self.active_project_path(cx);
2734        self.project
2735            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2736        self.update_window_title(cx);
2737    }
2738
2739    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2740        let project = self.project().read(cx);
2741        let mut title = String::new();
2742
2743        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2744            let filename = path
2745                .path
2746                .file_name()
2747                .map(|s| s.to_string_lossy())
2748                .or_else(|| {
2749                    Some(Cow::Borrowed(
2750                        project
2751                            .worktree_for_id(path.worktree_id, cx)?
2752                            .read(cx)
2753                            .root_name(),
2754                    ))
2755                });
2756
2757            if let Some(filename) = filename {
2758                title.push_str(filename.as_ref());
2759                title.push_str(" β€” ");
2760            }
2761        }
2762
2763        for (i, name) in project.worktree_root_names(cx).enumerate() {
2764            if i > 0 {
2765                title.push_str(", ");
2766            }
2767            title.push_str(name);
2768        }
2769
2770        if title.is_empty() {
2771            title = "empty project".to_string();
2772        }
2773
2774        if project.is_remote() {
2775            title.push_str(" ↙");
2776        } else if project.is_shared() {
2777            title.push_str(" β†—");
2778        }
2779
2780        cx.set_window_title(&title);
2781    }
2782
2783    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2784        let is_edited = !self.project.read(cx).is_read_only()
2785            && self
2786                .items(cx)
2787                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2788        if is_edited != self.window_edited {
2789            self.window_edited = is_edited;
2790            cx.set_window_edited(self.window_edited)
2791        }
2792    }
2793
2794    fn render_disconnected_overlay(
2795        &self,
2796        cx: &mut ViewContext<Workspace>,
2797    ) -> Option<AnyElement<Workspace>> {
2798        if self.project.read(cx).is_read_only() {
2799            enum DisconnectedOverlay {}
2800            Some(
2801                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2802                    let theme = &theme::current(cx);
2803                    Label::new(
2804                        "Your connection to the remote project has been lost.",
2805                        theme.workspace.disconnected_overlay.text.clone(),
2806                    )
2807                    .aligned()
2808                    .contained()
2809                    .with_style(theme.workspace.disconnected_overlay.container)
2810                })
2811                .with_cursor_style(CursorStyle::Arrow)
2812                .capture_all()
2813                .into_any_named("disconnected overlay"),
2814            )
2815        } else {
2816            None
2817        }
2818    }
2819
2820    fn render_notifications(
2821        &self,
2822        theme: &theme::Workspace,
2823        cx: &AppContext,
2824    ) -> Option<AnyElement<Workspace>> {
2825        if self.notifications.is_empty() {
2826            None
2827        } else {
2828            Some(
2829                Flex::column()
2830                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2831                        ChildView::new(notification.as_any(), cx)
2832                            .contained()
2833                            .with_style(theme.notification)
2834                    }))
2835                    .constrained()
2836                    .with_width(theme.notifications.width)
2837                    .contained()
2838                    .with_style(theme.notifications.container)
2839                    .aligned()
2840                    .bottom()
2841                    .right()
2842                    .into_any(),
2843            )
2844        }
2845    }
2846
2847    // RPC handlers
2848
2849    fn handle_follow(
2850        &mut self,
2851        follower_project_id: Option<u64>,
2852        cx: &mut ViewContext<Self>,
2853    ) -> proto::FollowResponse {
2854        let client = &self.app_state.client;
2855        let project_id = self.project.read(cx).remote_id();
2856
2857        let active_view_id = self.active_item(cx).and_then(|i| {
2858            Some(
2859                i.to_followable_item_handle(cx)?
2860                    .remote_id(client, cx)?
2861                    .to_proto(),
2862            )
2863        });
2864
2865        cx.notify();
2866
2867        self.last_active_view_id = active_view_id.clone();
2868        proto::FollowResponse {
2869            active_view_id,
2870            views: self
2871                .panes()
2872                .iter()
2873                .flat_map(|pane| {
2874                    let leader_id = self.leader_for_pane(pane);
2875                    pane.read(cx).items().filter_map({
2876                        let cx = &cx;
2877                        move |item| {
2878                            let item = item.to_followable_item_handle(cx)?;
2879                            if project_id.is_some()
2880                                && project_id != follower_project_id
2881                                && item.is_project_item(cx)
2882                            {
2883                                return None;
2884                            }
2885                            let id = item.remote_id(client, cx)?.to_proto();
2886                            let variant = item.to_state_proto(cx)?;
2887                            Some(proto::View {
2888                                id: Some(id),
2889                                leader_id,
2890                                variant: Some(variant),
2891                            })
2892                        }
2893                    })
2894                })
2895                .collect(),
2896        }
2897    }
2898
2899    fn handle_update_followers(
2900        &mut self,
2901        leader_id: PeerId,
2902        message: proto::UpdateFollowers,
2903        _cx: &mut ViewContext<Self>,
2904    ) {
2905        self.leader_updates_tx
2906            .unbounded_send((leader_id, message))
2907            .ok();
2908    }
2909
2910    async fn process_leader_update(
2911        this: &WeakViewHandle<Self>,
2912        leader_id: PeerId,
2913        update: proto::UpdateFollowers,
2914        cx: &mut AsyncAppContext,
2915    ) -> Result<()> {
2916        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2917            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2918                this.update(cx, |this, _| {
2919                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2920                        for state in state.values_mut() {
2921                            state.active_view_id =
2922                                if let Some(active_view_id) = update_active_view.id.clone() {
2923                                    Some(ViewId::from_proto(active_view_id)?)
2924                                } else {
2925                                    None
2926                                };
2927                        }
2928                    }
2929                    anyhow::Ok(())
2930                })??;
2931            }
2932            proto::update_followers::Variant::UpdateView(update_view) => {
2933                let variant = update_view
2934                    .variant
2935                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2936                let id = update_view
2937                    .id
2938                    .ok_or_else(|| anyhow!("missing update view id"))?;
2939                let mut tasks = Vec::new();
2940                this.update(cx, |this, cx| {
2941                    let project = this.project.clone();
2942                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2943                        for state in state.values_mut() {
2944                            let view_id = ViewId::from_proto(id.clone())?;
2945                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2946                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2947                            }
2948                        }
2949                    }
2950                    anyhow::Ok(())
2951                })??;
2952                try_join_all(tasks).await.log_err();
2953            }
2954            proto::update_followers::Variant::CreateView(view) => {
2955                let panes = this.read_with(cx, |this, _| {
2956                    this.follower_states_by_leader
2957                        .get(&leader_id)
2958                        .into_iter()
2959                        .flat_map(|states_by_pane| states_by_pane.keys())
2960                        .cloned()
2961                        .collect()
2962                })?;
2963                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2964            }
2965        }
2966        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2967        Ok(())
2968    }
2969
2970    async fn add_views_from_leader(
2971        this: WeakViewHandle<Self>,
2972        leader_id: PeerId,
2973        panes: Vec<ViewHandle<Pane>>,
2974        views: Vec<proto::View>,
2975        cx: &mut AsyncAppContext,
2976    ) -> Result<()> {
2977        let this = this
2978            .upgrade(cx)
2979            .ok_or_else(|| anyhow!("workspace dropped"))?;
2980
2981        let item_builders = cx.update(|cx| {
2982            cx.default_global::<FollowableItemBuilders>()
2983                .values()
2984                .map(|b| b.0)
2985                .collect::<Vec<_>>()
2986        });
2987
2988        let mut item_tasks_by_pane = HashMap::default();
2989        for pane in panes {
2990            let mut item_tasks = Vec::new();
2991            let mut leader_view_ids = Vec::new();
2992            for view in &views {
2993                let Some(id) = &view.id else { continue };
2994                let id = ViewId::from_proto(id.clone())?;
2995                let mut variant = view.variant.clone();
2996                if variant.is_none() {
2997                    Err(anyhow!("missing view variant"))?;
2998                }
2999                for build_item in &item_builders {
3000                    let task = cx
3001                        .update(|cx| build_item(pane.clone(), this.clone(), id, &mut variant, cx));
3002                    if let Some(task) = task {
3003                        item_tasks.push(task);
3004                        leader_view_ids.push(id);
3005                        break;
3006                    } else {
3007                        assert!(variant.is_some());
3008                    }
3009                }
3010            }
3011
3012            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
3013        }
3014
3015        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
3016            let items = futures::future::try_join_all(item_tasks).await?;
3017            this.update(cx, |this, cx| {
3018                let state = this
3019                    .follower_states_by_leader
3020                    .get_mut(&leader_id)?
3021                    .get_mut(&pane)?;
3022
3023                for (id, item) in leader_view_ids.into_iter().zip(items) {
3024                    item.set_leader_peer_id(Some(leader_id), cx);
3025                    state.items_by_leader_view_id.insert(id, item);
3026                }
3027
3028                Some(())
3029            });
3030        }
3031        Ok(())
3032    }
3033
3034    fn update_active_view_for_followers(&mut self, cx: &AppContext) {
3035        let mut is_project_item = true;
3036        let mut update = proto::UpdateActiveView::default();
3037        if self.active_pane.read(cx).has_focus() {
3038            let item = self
3039                .active_item(cx)
3040                .and_then(|item| item.to_followable_item_handle(cx));
3041            if let Some(item) = item {
3042                is_project_item = item.is_project_item(cx);
3043                update = proto::UpdateActiveView {
3044                    id: item
3045                        .remote_id(&self.app_state.client, cx)
3046                        .map(|id| id.to_proto()),
3047                    leader_id: self.leader_for_pane(&self.active_pane),
3048                };
3049            }
3050        }
3051
3052        if update.id != self.last_active_view_id {
3053            self.last_active_view_id = update.id.clone();
3054            self.update_followers(
3055                is_project_item,
3056                proto::update_followers::Variant::UpdateActiveView(update),
3057                cx,
3058            );
3059        }
3060    }
3061
3062    fn update_followers(
3063        &self,
3064        project_only: bool,
3065        update: proto::update_followers::Variant,
3066        cx: &AppContext,
3067    ) -> Option<()> {
3068        let project_id = if project_only {
3069            self.project.read(cx).remote_id()
3070        } else {
3071            None
3072        };
3073        self.app_state().workspace_store.read_with(cx, |store, cx| {
3074            store.update_followers(project_id, update, cx)
3075        })
3076    }
3077
3078    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
3079        self.follower_states_by_leader
3080            .iter()
3081            .find_map(|(leader_id, state)| {
3082                if state.contains_key(pane) {
3083                    Some(*leader_id)
3084                } else {
3085                    None
3086                }
3087            })
3088    }
3089
3090    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
3091        cx.notify();
3092
3093        let call = self.active_call()?;
3094        let room = call.read(cx).room()?.read(cx);
3095        let participant = room.remote_participant_for_peer_id(leader_id)?;
3096        let mut items_to_activate = Vec::new();
3097
3098        let leader_in_this_app;
3099        let leader_in_this_project;
3100        match participant.location {
3101            call::ParticipantLocation::SharedProject { project_id } => {
3102                leader_in_this_app = true;
3103                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
3104            }
3105            call::ParticipantLocation::UnsharedProject => {
3106                leader_in_this_app = true;
3107                leader_in_this_project = false;
3108            }
3109            call::ParticipantLocation::External => {
3110                leader_in_this_app = false;
3111                leader_in_this_project = false;
3112            }
3113        };
3114
3115        for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
3116            if leader_in_this_app {
3117                let item = state
3118                    .active_view_id
3119                    .and_then(|id| state.items_by_leader_view_id.get(&id));
3120                if let Some(item) = item {
3121                    if leader_in_this_project || !item.is_project_item(cx) {
3122                        items_to_activate.push((pane.clone(), item.boxed_clone()));
3123                    }
3124                    continue;
3125                }
3126            }
3127            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
3128                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
3129            }
3130        }
3131
3132        for (pane, item) in items_to_activate {
3133            let pane_was_focused = pane.read(cx).has_focus();
3134            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
3135                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
3136            } else {
3137                pane.update(cx, |pane, cx| {
3138                    pane.add_item(item.boxed_clone(), false, false, None, cx)
3139                });
3140            }
3141
3142            if pane_was_focused {
3143                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
3144            }
3145        }
3146
3147        None
3148    }
3149
3150    fn shared_screen_for_peer(
3151        &self,
3152        peer_id: PeerId,
3153        pane: &ViewHandle<Pane>,
3154        cx: &mut ViewContext<Self>,
3155    ) -> Option<ViewHandle<SharedScreen>> {
3156        let call = self.active_call()?;
3157        let room = call.read(cx).room()?.read(cx);
3158        let participant = room.remote_participant_for_peer_id(peer_id)?;
3159        let track = participant.video_tracks.values().next()?.clone();
3160        let user = participant.user.clone();
3161
3162        for item in pane.read(cx).items_of_type::<SharedScreen>() {
3163            if item.read(cx).peer_id == peer_id {
3164                return Some(item);
3165            }
3166        }
3167
3168        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
3169    }
3170
3171    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
3172        if active {
3173            self.update_active_view_for_followers(cx);
3174            cx.background()
3175                .spawn(persistence::DB.update_timestamp(self.database_id()))
3176                .detach();
3177        } else {
3178            for pane in &self.panes {
3179                pane.update(cx, |pane, cx| {
3180                    if let Some(item) = pane.active_item() {
3181                        item.workspace_deactivated(cx);
3182                    }
3183                    if matches!(
3184                        settings::get::<WorkspaceSettings>(cx).autosave,
3185                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
3186                    ) {
3187                        for item in pane.items() {
3188                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
3189                                .detach_and_log_err(cx);
3190                        }
3191                    }
3192                });
3193            }
3194        }
3195    }
3196
3197    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
3198        self.active_call.as_ref().map(|(call, _)| call)
3199    }
3200
3201    fn on_active_call_event(
3202        &mut self,
3203        _: ModelHandle<ActiveCall>,
3204        event: &call::room::Event,
3205        cx: &mut ViewContext<Self>,
3206    ) {
3207        match event {
3208            call::room::Event::ParticipantLocationChanged { participant_id }
3209            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
3210                self.leader_updated(*participant_id, cx);
3211            }
3212            _ => {}
3213        }
3214    }
3215
3216    pub fn database_id(&self) -> WorkspaceId {
3217        self.database_id
3218    }
3219
3220    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
3221        let project = self.project().read(cx);
3222
3223        if project.is_local() {
3224            Some(
3225                project
3226                    .visible_worktrees(cx)
3227                    .map(|worktree| worktree.read(cx).abs_path())
3228                    .collect::<Vec<_>>()
3229                    .into(),
3230            )
3231        } else {
3232            None
3233        }
3234    }
3235
3236    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3237        match member {
3238            Member::Axis(PaneAxis { members, .. }) => {
3239                for child in members.iter() {
3240                    self.remove_panes(child.clone(), cx)
3241                }
3242            }
3243            Member::Pane(pane) => {
3244                self.force_remove_pane(&pane, cx);
3245            }
3246        }
3247    }
3248
3249    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
3250        self.panes.retain(|p| p != pane);
3251        cx.focus(self.panes.last().unwrap());
3252        if self.last_active_center_pane == Some(pane.downgrade()) {
3253            self.last_active_center_pane = None;
3254        }
3255        cx.notify();
3256    }
3257
3258    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3259        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
3260            cx.background().timer(Duration::from_millis(100)).await;
3261            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
3262                .ok();
3263        }));
3264    }
3265
3266    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3267        fn serialize_pane_handle(
3268            pane_handle: &ViewHandle<Pane>,
3269            cx: &AppContext,
3270        ) -> SerializedPane {
3271            let (items, active) = {
3272                let pane = pane_handle.read(cx);
3273                let active_item_id = pane.active_item().map(|item| item.id());
3274                (
3275                    pane.items()
3276                        .filter_map(|item_handle| {
3277                            Some(SerializedItem {
3278                                kind: Arc::from(item_handle.serialized_item_kind()?),
3279                                item_id: item_handle.id(),
3280                                active: Some(item_handle.id()) == active_item_id,
3281                            })
3282                        })
3283                        .collect::<Vec<_>>(),
3284                    pane.has_focus(),
3285                )
3286            };
3287
3288            SerializedPane::new(items, active)
3289        }
3290
3291        fn build_serialized_pane_group(
3292            pane_group: &Member,
3293            cx: &AppContext,
3294        ) -> SerializedPaneGroup {
3295            match pane_group {
3296                Member::Axis(PaneAxis {
3297                    axis,
3298                    members,
3299                    flexes,
3300                    bounding_boxes: _,
3301                }) => SerializedPaneGroup::Group {
3302                    axis: *axis,
3303                    children: members
3304                        .iter()
3305                        .map(|member| build_serialized_pane_group(member, cx))
3306                        .collect::<Vec<_>>(),
3307                    flexes: Some(flexes.borrow().clone()),
3308                },
3309                Member::Pane(pane_handle) => {
3310                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3311                }
3312            }
3313        }
3314
3315        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3316            let left_dock = this.left_dock.read(cx);
3317            let left_visible = left_dock.is_open();
3318            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3319                Some(
3320                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3321                        .to_string(),
3322                )
3323            });
3324            let left_dock_zoom = left_dock
3325                .visible_panel()
3326                .map(|panel| panel.is_zoomed(cx))
3327                .unwrap_or(false);
3328
3329            let right_dock = this.right_dock.read(cx);
3330            let right_visible = right_dock.is_open();
3331            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3332                Some(
3333                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3334                        .to_string(),
3335                )
3336            });
3337            let right_dock_zoom = right_dock
3338                .visible_panel()
3339                .map(|panel| panel.is_zoomed(cx))
3340                .unwrap_or(false);
3341
3342            let bottom_dock = this.bottom_dock.read(cx);
3343            let bottom_visible = bottom_dock.is_open();
3344            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3345                Some(
3346                    cx.view_ui_name(panel.as_any().window(), panel.id())?
3347                        .to_string(),
3348                )
3349            });
3350            let bottom_dock_zoom = bottom_dock
3351                .visible_panel()
3352                .map(|panel| panel.is_zoomed(cx))
3353                .unwrap_or(false);
3354
3355            DockStructure {
3356                left: DockData {
3357                    visible: left_visible,
3358                    active_panel: left_active_panel,
3359                    zoom: left_dock_zoom,
3360                },
3361                right: DockData {
3362                    visible: right_visible,
3363                    active_panel: right_active_panel,
3364                    zoom: right_dock_zoom,
3365                },
3366                bottom: DockData {
3367                    visible: bottom_visible,
3368                    active_panel: bottom_active_panel,
3369                    zoom: bottom_dock_zoom,
3370                },
3371            }
3372        }
3373
3374        if let Some(location) = self.location(cx) {
3375            // Load bearing special case:
3376            //  - with_local_workspace() relies on this to not have other stuff open
3377            //    when you open your log
3378            if !location.paths().is_empty() {
3379                let center_group = build_serialized_pane_group(&self.center.root, cx);
3380                let docks = build_serialized_docks(self, cx);
3381
3382                let serialized_workspace = SerializedWorkspace {
3383                    id: self.database_id,
3384                    location,
3385                    center_group,
3386                    bounds: Default::default(),
3387                    display: Default::default(),
3388                    docks,
3389                };
3390
3391                cx.background()
3392                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3393                    .detach();
3394            }
3395        }
3396    }
3397
3398    pub(crate) fn load_workspace(
3399        workspace: WeakViewHandle<Workspace>,
3400        serialized_workspace: SerializedWorkspace,
3401        paths_to_open: Vec<Option<ProjectPath>>,
3402        cx: &mut AppContext,
3403    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3404        cx.spawn(|mut cx| async move {
3405            let result = async_iife! {{
3406                let (project, old_center_pane) =
3407                workspace.read_with(&cx, |workspace, _| {
3408                    (
3409                        workspace.project().clone(),
3410                        workspace.last_active_center_pane.clone(),
3411                    )
3412                })?;
3413
3414                let mut center_items = None;
3415                let mut center_group = None;
3416                // Traverse the splits tree and add to things
3417                if let Some((group, active_pane, items)) = serialized_workspace
3418                        .center_group
3419                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3420                        .await {
3421                    center_items = Some(items);
3422                    center_group = Some((group, active_pane))
3423                }
3424
3425                let resulting_list = cx.read(|cx| {
3426                    let mut opened_items = center_items
3427                        .unwrap_or_default()
3428                        .into_iter()
3429                        .filter_map(|item| {
3430                            let item = item?;
3431                            let project_path = item.project_path(cx)?;
3432                            Some((project_path, item))
3433                        })
3434                        .collect::<HashMap<_, _>>();
3435
3436                    paths_to_open
3437                        .into_iter()
3438                        .map(|path_to_open| {
3439                            path_to_open.map(|path_to_open| {
3440                                Ok(opened_items.remove(&path_to_open))
3441                            })
3442                            .transpose()
3443                            .map(|item| item.flatten())
3444                            .transpose()
3445                        })
3446                        .collect::<Vec<_>>()
3447                });
3448
3449                // Remove old panes from workspace panes list
3450                workspace.update(&mut cx, |workspace, cx| {
3451                    if let Some((center_group, active_pane)) = center_group {
3452                        workspace.remove_panes(workspace.center.root.clone(), cx);
3453
3454                        // Swap workspace center group
3455                        workspace.center = PaneGroup::with_root(center_group);
3456
3457                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3458                        cx.focus_self();
3459
3460                        if let Some(active_pane) = active_pane {
3461                            cx.focus(&active_pane);
3462                        } else {
3463                            cx.focus(workspace.panes.last().unwrap());
3464                        }
3465                    } else {
3466                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3467                        if let Some(old_center_handle) = old_center_handle {
3468                            cx.focus(&old_center_handle)
3469                        } else {
3470                            cx.focus_self()
3471                        }
3472                    }
3473
3474                    let docks = serialized_workspace.docks;
3475                    workspace.left_dock.update(cx, |dock, cx| {
3476                        dock.set_open(docks.left.visible, cx);
3477                        if let Some(active_panel) = docks.left.active_panel {
3478                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3479                                dock.activate_panel(ix, cx);
3480                            }
3481                        }
3482                                dock.active_panel()
3483                                    .map(|panel| {
3484                                        panel.set_zoomed(docks.left.zoom, cx)
3485                                    });
3486                                if docks.left.visible && docks.left.zoom {
3487                                    cx.focus_self()
3488                                }
3489                    });
3490                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3491                    workspace.right_dock.update(cx, |dock, cx| {
3492                        dock.set_open(docks.right.visible, cx);
3493                        if let Some(active_panel) = docks.right.active_panel {
3494                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3495                                dock.activate_panel(ix, cx);
3496
3497                            }
3498                        }
3499                                dock.active_panel()
3500                                    .map(|panel| {
3501                                        panel.set_zoomed(docks.right.zoom, cx)
3502                                    });
3503
3504                                if docks.right.visible && docks.right.zoom {
3505                                    cx.focus_self()
3506                                }
3507                    });
3508                    workspace.bottom_dock.update(cx, |dock, cx| {
3509                        dock.set_open(docks.bottom.visible, cx);
3510                        if let Some(active_panel) = docks.bottom.active_panel {
3511                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3512                                dock.activate_panel(ix, cx);
3513                            }
3514                        }
3515
3516                        dock.active_panel()
3517                            .map(|panel| {
3518                                panel.set_zoomed(docks.bottom.zoom, cx)
3519                            });
3520
3521                        if docks.bottom.visible && docks.bottom.zoom {
3522                            cx.focus_self()
3523                        }
3524                    });
3525
3526
3527                    cx.notify();
3528                })?;
3529
3530                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3531                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3532
3533                Ok::<_, anyhow::Error>(resulting_list)
3534            }};
3535
3536            result.await.unwrap_or_default()
3537        })
3538    }
3539
3540    #[cfg(any(test, feature = "test-support"))]
3541    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3542        let client = project.read(cx).client();
3543        let user_store = project.read(cx).user_store();
3544
3545        let channel_store =
3546            cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
3547        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
3548        let app_state = Arc::new(AppState {
3549            languages: project.read(cx).languages().clone(),
3550            workspace_store,
3551            client,
3552            user_store,
3553            channel_store,
3554            fs: project.read(cx).fs().clone(),
3555            build_window_options: |_, _, _| Default::default(),
3556            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3557            background_actions: || &[],
3558        });
3559        Self::new(0, project, app_state, cx)
3560    }
3561
3562    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3563        let dock = match position {
3564            DockPosition::Left => &self.left_dock,
3565            DockPosition::Right => &self.right_dock,
3566            DockPosition::Bottom => &self.bottom_dock,
3567        };
3568        let active_panel = dock.read(cx).visible_panel()?;
3569        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3570            dock.read(cx).render_placeholder(cx)
3571        } else {
3572            ChildView::new(dock, cx).into_any()
3573        };
3574
3575        Some(
3576            element
3577                .constrained()
3578                .dynamically(move |constraint, _, cx| match position {
3579                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3580                        Vector2F::new(20., constraint.min.y()),
3581                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3582                    ),
3583                    DockPosition::Bottom => SizeConstraint::new(
3584                        Vector2F::new(constraint.min.x(), 20.),
3585                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3586                    ),
3587                })
3588                .into_any(),
3589        )
3590    }
3591}
3592
3593fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3594    ZED_WINDOW_POSITION
3595        .zip(*ZED_WINDOW_SIZE)
3596        .map(|(position, size)| {
3597            WindowBounds::Fixed(RectF::new(
3598                cx.platform().screens()[0].bounds().origin() + position,
3599                size,
3600            ))
3601        })
3602}
3603
3604async fn open_items(
3605    serialized_workspace: Option<SerializedWorkspace>,
3606    workspace: &WeakViewHandle<Workspace>,
3607    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3608    app_state: Arc<AppState>,
3609    mut cx: AsyncAppContext,
3610) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3611    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3612
3613    if let Some(serialized_workspace) = serialized_workspace {
3614        let workspace = workspace.clone();
3615        let restored_items = cx
3616            .update(|cx| {
3617                Workspace::load_workspace(
3618                    workspace,
3619                    serialized_workspace,
3620                    project_paths_to_open
3621                        .iter()
3622                        .map(|(_, project_path)| project_path)
3623                        .cloned()
3624                        .collect(),
3625                    cx,
3626                )
3627            })
3628            .await;
3629
3630        let restored_project_paths = cx.read(|cx| {
3631            restored_items
3632                .iter()
3633                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3634                .collect::<HashSet<_>>()
3635        });
3636
3637        opened_items = restored_items;
3638        project_paths_to_open
3639            .iter_mut()
3640            .for_each(|(_, project_path)| {
3641                if let Some(project_path_to_open) = project_path {
3642                    if restored_project_paths.contains(project_path_to_open) {
3643                        *project_path = None;
3644                    }
3645                }
3646            });
3647    } else {
3648        for _ in 0..project_paths_to_open.len() {
3649            opened_items.push(None);
3650        }
3651    }
3652    assert!(opened_items.len() == project_paths_to_open.len());
3653
3654    let tasks =
3655        project_paths_to_open
3656            .into_iter()
3657            .enumerate()
3658            .map(|(i, (abs_path, project_path))| {
3659                let workspace = workspace.clone();
3660                cx.spawn(|mut cx| {
3661                    let fs = app_state.fs.clone();
3662                    async move {
3663                        let file_project_path = project_path?;
3664                        if fs.is_file(&abs_path).await {
3665                            Some((
3666                                i,
3667                                workspace
3668                                    .update(&mut cx, |workspace, cx| {
3669                                        workspace.open_path(file_project_path, None, true, cx)
3670                                    })
3671                                    .log_err()?
3672                                    .await,
3673                            ))
3674                        } else {
3675                            None
3676                        }
3677                    }
3678                })
3679            });
3680
3681    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3682        .await
3683        .into_iter()
3684    {
3685        if let Some((i, path_open_result)) = maybe_opened_path {
3686            opened_items[i] = Some(path_open_result);
3687        }
3688    }
3689
3690    opened_items
3691}
3692
3693fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3694    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3695    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3696    const MESSAGE_ID: usize = 2;
3697
3698    if workspace
3699        .read_with(cx, |workspace, cx| {
3700            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3701        })
3702        .unwrap_or(false)
3703    {
3704        return;
3705    }
3706
3707    if db::kvp::KEY_VALUE_STORE
3708        .read_kvp(NEW_DOCK_HINT_KEY)
3709        .ok()
3710        .flatten()
3711        .is_some()
3712    {
3713        if !workspace
3714            .read_with(cx, |workspace, cx| {
3715                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3716            })
3717            .unwrap_or(false)
3718        {
3719            cx.update(|cx| {
3720                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3721                    let entry = tracker
3722                        .entry(TypeId::of::<MessageNotification>())
3723                        .or_default();
3724                    if !entry.contains(&MESSAGE_ID) {
3725                        entry.push(MESSAGE_ID);
3726                    }
3727                });
3728            });
3729        }
3730
3731        return;
3732    }
3733
3734    cx.spawn(|_| async move {
3735        db::kvp::KEY_VALUE_STORE
3736            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3737            .await
3738            .ok();
3739    })
3740    .detach();
3741
3742    workspace
3743        .update(cx, |workspace, cx| {
3744            workspace.show_notification_once(2, cx, |cx| {
3745                cx.add_view(|_| {
3746                    MessageNotification::new_element(|text, _| {
3747                        Text::new(
3748                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3749                            text,
3750                        )
3751                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3752                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3753                                .theme
3754                                .editor
3755                                .document_highlight_read_background;
3756
3757                            cx.scene().push_quad(gpui::Quad {
3758                                bounds,
3759                                background: Some(code_span_background_color),
3760                                border: Default::default(),
3761                                corner_radii: (2.0).into(),
3762                            })
3763                        })
3764                        .into_any()
3765                    })
3766                    .with_click_message("Read more about the new panel system")
3767                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3768                })
3769            })
3770        })
3771        .ok();
3772}
3773
3774fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3775    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3776
3777    workspace
3778        .update(cx, |workspace, cx| {
3779            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3780                workspace.show_notification_once(0, cx, |cx| {
3781                    cx.add_view(|_| {
3782                        MessageNotification::new("Failed to load the database file.")
3783                            .with_click_message("Click to let us know about this error")
3784                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3785                    })
3786                });
3787            }
3788        })
3789        .log_err();
3790}
3791
3792impl Entity for Workspace {
3793    type Event = Event;
3794
3795    fn release(&mut self, cx: &mut AppContext) {
3796        self.app_state.workspace_store.update(cx, |store, _| {
3797            store.workspaces.remove(&self.weak_self);
3798        })
3799    }
3800}
3801
3802impl View for Workspace {
3803    fn ui_name() -> &'static str {
3804        "Workspace"
3805    }
3806
3807    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3808        let theme = theme::current(cx).clone();
3809        Stack::new()
3810            .with_child(
3811                Flex::column()
3812                    .with_child(self.render_titlebar(&theme, cx))
3813                    .with_child(
3814                        Stack::new()
3815                            .with_child({
3816                                let project = self.project.clone();
3817                                Flex::row()
3818                                    .with_children(self.render_dock(DockPosition::Left, cx))
3819                                    .with_child(
3820                                        Flex::column()
3821                                            .with_child(
3822                                                FlexItem::new(
3823                                                    self.center.render(
3824                                                        &project,
3825                                                        &theme,
3826                                                        &self.follower_states_by_leader,
3827                                                        self.active_call(),
3828                                                        self.active_pane(),
3829                                                        self.zoomed
3830                                                            .as_ref()
3831                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3832                                                            .as_ref(),
3833                                                        &self.app_state,
3834                                                        cx,
3835                                                    ),
3836                                                )
3837                                                .flex(1., true),
3838                                            )
3839                                            .with_children(
3840                                                self.render_dock(DockPosition::Bottom, cx),
3841                                            )
3842                                            .flex(1., true),
3843                                    )
3844                                    .with_children(self.render_dock(DockPosition::Right, cx))
3845                            })
3846                            .with_child(Overlay::new(
3847                                Stack::new()
3848                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3849                                        enum ZoomBackground {}
3850                                        let zoomed = zoomed.upgrade(cx)?;
3851
3852                                        let mut foreground_style =
3853                                            theme.workspace.zoomed_pane_foreground;
3854                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3855                                            foreground_style =
3856                                                theme.workspace.zoomed_panel_foreground;
3857                                            let margin = foreground_style.margin.top;
3858                                            let border = foreground_style.border.top;
3859
3860                                            // Only include a margin and border on the opposite side.
3861                                            foreground_style.margin.top = 0.;
3862                                            foreground_style.margin.left = 0.;
3863                                            foreground_style.margin.bottom = 0.;
3864                                            foreground_style.margin.right = 0.;
3865                                            foreground_style.border.top = false;
3866                                            foreground_style.border.left = false;
3867                                            foreground_style.border.bottom = false;
3868                                            foreground_style.border.right = false;
3869                                            match zoomed_dock_position {
3870                                                DockPosition::Left => {
3871                                                    foreground_style.margin.right = margin;
3872                                                    foreground_style.border.right = border;
3873                                                }
3874                                                DockPosition::Right => {
3875                                                    foreground_style.margin.left = margin;
3876                                                    foreground_style.border.left = border;
3877                                                }
3878                                                DockPosition::Bottom => {
3879                                                    foreground_style.margin.top = margin;
3880                                                    foreground_style.border.top = border;
3881                                                }
3882                                            }
3883                                        }
3884
3885                                        Some(
3886                                            ChildView::new(&zoomed, cx)
3887                                                .contained()
3888                                                .with_style(foreground_style)
3889                                                .aligned()
3890                                                .contained()
3891                                                .with_style(theme.workspace.zoomed_background)
3892                                                .mouse::<ZoomBackground>(0)
3893                                                .capture_all()
3894                                                .on_down(
3895                                                    MouseButton::Left,
3896                                                    |_, this: &mut Self, cx| {
3897                                                        this.zoom_out(cx);
3898                                                    },
3899                                                ),
3900                                        )
3901                                    }))
3902                                    .with_children(self.modal.as_ref().map(|modal| {
3903                                        // Prevent clicks within the modal from falling
3904                                        // through to the rest of the workspace.
3905                                        enum ModalBackground {}
3906                                        MouseEventHandler::new::<ModalBackground, _>(
3907                                            0,
3908                                            cx,
3909                                            |_, cx| ChildView::new(modal.view.as_any(), cx),
3910                                        )
3911                                        .on_click(MouseButton::Left, |_, _, _| {})
3912                                        .contained()
3913                                        .with_style(theme.workspace.modal)
3914                                        .aligned()
3915                                        .top()
3916                                    }))
3917                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3918                            ))
3919                            .provide_resize_bounds::<WorkspaceBounds>()
3920                            .flex(1.0, true),
3921                    )
3922                    .with_child(ChildView::new(&self.status_bar, cx))
3923                    .contained()
3924                    .with_background_color(theme.workspace.background),
3925            )
3926            .with_children(DragAndDrop::render(cx))
3927            .with_children(self.render_disconnected_overlay(cx))
3928            .into_any_named("workspace")
3929    }
3930
3931    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3932        if cx.is_self_focused() {
3933            cx.focus(&self.active_pane);
3934        }
3935    }
3936
3937    fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3938        DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3939    }
3940}
3941
3942impl WorkspaceStore {
3943    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3944        Self {
3945            workspaces: Default::default(),
3946            followers: Default::default(),
3947            _subscriptions: vec![
3948                client.add_request_handler(cx.handle(), Self::handle_follow),
3949                client.add_message_handler(cx.handle(), Self::handle_unfollow),
3950                client.add_message_handler(cx.handle(), Self::handle_update_followers),
3951            ],
3952            client,
3953        }
3954    }
3955
3956    pub fn update_followers(
3957        &self,
3958        project_id: Option<u64>,
3959        update: proto::update_followers::Variant,
3960        cx: &AppContext,
3961    ) -> Option<()> {
3962        if !cx.has_global::<ModelHandle<ActiveCall>>() {
3963            return None;
3964        }
3965
3966        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3967        let follower_ids: Vec<_> = self
3968            .followers
3969            .iter()
3970            .filter_map(|follower| {
3971                if follower.project_id == project_id || project_id.is_none() {
3972                    Some(follower.peer_id.into())
3973                } else {
3974                    None
3975                }
3976            })
3977            .collect();
3978        if follower_ids.is_empty() {
3979            return None;
3980        }
3981        self.client
3982            .send(proto::UpdateFollowers {
3983                room_id,
3984                project_id,
3985                follower_ids,
3986                variant: Some(update),
3987            })
3988            .log_err()
3989    }
3990
3991    async fn handle_follow(
3992        this: ModelHandle<Self>,
3993        envelope: TypedEnvelope<proto::Follow>,
3994        _: Arc<Client>,
3995        mut cx: AsyncAppContext,
3996    ) -> Result<proto::FollowResponse> {
3997        this.update(&mut cx, |this, cx| {
3998            let follower = Follower {
3999                project_id: envelope.payload.project_id,
4000                peer_id: envelope.original_sender_id()?,
4001            };
4002            let active_project = ActiveCall::global(cx)
4003                .read(cx)
4004                .location()
4005                .map(|project| project.id());
4006
4007            let mut response = proto::FollowResponse::default();
4008            for workspace in &this.workspaces {
4009                let Some(workspace) = workspace.upgrade(cx) else {
4010                    continue;
4011                };
4012
4013                workspace.update(cx.as_mut(), |workspace, cx| {
4014                    let handler_response = workspace.handle_follow(follower.project_id, cx);
4015                    if response.views.is_empty() {
4016                        response.views = handler_response.views;
4017                    } else {
4018                        response.views.extend_from_slice(&handler_response.views);
4019                    }
4020
4021                    if let Some(active_view_id) = handler_response.active_view_id.clone() {
4022                        if response.active_view_id.is_none()
4023                            || Some(workspace.project.id()) == active_project
4024                        {
4025                            response.active_view_id = Some(active_view_id);
4026                        }
4027                    }
4028                });
4029            }
4030
4031            if let Err(ix) = this.followers.binary_search(&follower) {
4032                this.followers.insert(ix, follower);
4033            }
4034
4035            Ok(response)
4036        })
4037    }
4038
4039    async fn handle_unfollow(
4040        this: ModelHandle<Self>,
4041        envelope: TypedEnvelope<proto::Unfollow>,
4042        _: Arc<Client>,
4043        mut cx: AsyncAppContext,
4044    ) -> Result<()> {
4045        this.update(&mut cx, |this, _| {
4046            let follower = Follower {
4047                project_id: envelope.payload.project_id,
4048                peer_id: envelope.original_sender_id()?,
4049            };
4050            if let Ok(ix) = this.followers.binary_search(&follower) {
4051                this.followers.remove(ix);
4052            }
4053            Ok(())
4054        })
4055    }
4056
4057    async fn handle_update_followers(
4058        this: ModelHandle<Self>,
4059        envelope: TypedEnvelope<proto::UpdateFollowers>,
4060        _: Arc<Client>,
4061        mut cx: AsyncAppContext,
4062    ) -> Result<()> {
4063        let leader_id = envelope.original_sender_id()?;
4064        let update = envelope.payload;
4065        this.update(&mut cx, |this, cx| {
4066            for workspace in &this.workspaces {
4067                let Some(workspace) = workspace.upgrade(cx) else {
4068                    continue;
4069                };
4070                workspace.update(cx.as_mut(), |workspace, cx| {
4071                    let project_id = workspace.project.read(cx).remote_id();
4072                    if update.project_id != project_id && update.project_id.is_some() {
4073                        return;
4074                    }
4075                    workspace.handle_update_followers(leader_id, update.clone(), cx);
4076                });
4077            }
4078            Ok(())
4079        })
4080    }
4081}
4082
4083impl Entity for WorkspaceStore {
4084    type Event = ();
4085}
4086
4087impl ViewId {
4088    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4089        Ok(Self {
4090            creator: message
4091                .creator
4092                .ok_or_else(|| anyhow!("creator is missing"))?,
4093            id: message.id,
4094        })
4095    }
4096
4097    pub(crate) fn to_proto(&self) -> proto::ViewId {
4098        proto::ViewId {
4099            creator: Some(self.creator),
4100            id: self.id,
4101        }
4102    }
4103}
4104
4105pub trait WorkspaceHandle {
4106    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4107}
4108
4109impl WorkspaceHandle for ViewHandle<Workspace> {
4110    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4111        self.read(cx)
4112            .worktrees(cx)
4113            .flat_map(|worktree| {
4114                let worktree_id = worktree.read(cx).id();
4115                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4116                    worktree_id,
4117                    path: f.path.clone(),
4118                })
4119            })
4120            .collect::<Vec<_>>()
4121    }
4122}
4123
4124impl std::fmt::Debug for OpenPaths {
4125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4126        f.debug_struct("OpenPaths")
4127            .field("paths", &self.paths)
4128            .finish()
4129    }
4130}
4131
4132pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
4133
4134pub fn activate_workspace_for_project(
4135    cx: &mut AsyncAppContext,
4136    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
4137) -> Option<WeakViewHandle<Workspace>> {
4138    for window in cx.windows() {
4139        let handle = window
4140            .update(cx, |cx| {
4141                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
4142                    let project = workspace_handle.read(cx).project.clone();
4143                    if project.update(cx, &predicate) {
4144                        cx.activate_window();
4145                        return Some(workspace_handle.clone());
4146                    }
4147                }
4148                None
4149            })
4150            .flatten();
4151
4152        if let Some(handle) = handle {
4153            return Some(handle.downgrade());
4154        }
4155    }
4156    None
4157}
4158
4159pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4160    DB.last_workspace().await.log_err().flatten()
4161}
4162
4163#[allow(clippy::type_complexity)]
4164pub fn open_paths(
4165    abs_paths: &[PathBuf],
4166    app_state: &Arc<AppState>,
4167    requesting_window: Option<WindowHandle<Workspace>>,
4168    cx: &mut AppContext,
4169) -> Task<
4170    Result<(
4171        WeakViewHandle<Workspace>,
4172        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4173    )>,
4174> {
4175    let app_state = app_state.clone();
4176    let abs_paths = abs_paths.to_vec();
4177    cx.spawn(|mut cx| async move {
4178        // Open paths in existing workspace if possible
4179        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
4180            project.contains_paths(&abs_paths, cx)
4181        });
4182
4183        if let Some(existing) = existing {
4184            Ok((
4185                existing.clone(),
4186                existing
4187                    .update(&mut cx, |workspace, cx| {
4188                        workspace.open_paths(abs_paths, true, cx)
4189                    })?
4190                    .await,
4191            ))
4192        } else {
4193            Ok(cx
4194                .update(|cx| {
4195                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4196                })
4197                .await)
4198        }
4199    })
4200}
4201
4202pub fn open_new(
4203    app_state: &Arc<AppState>,
4204    cx: &mut AppContext,
4205    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
4206) -> Task<()> {
4207    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4208    cx.spawn(|mut cx| async move {
4209        let (workspace, opened_paths) = task.await;
4210
4211        workspace
4212            .update(&mut cx, |workspace, cx| {
4213                if opened_paths.is_empty() {
4214                    init(workspace, cx)
4215                }
4216            })
4217            .log_err();
4218    })
4219}
4220
4221pub fn create_and_open_local_file(
4222    path: &'static Path,
4223    cx: &mut ViewContext<Workspace>,
4224    default_content: impl 'static + Send + FnOnce() -> Rope,
4225) -> Task<Result<Box<dyn ItemHandle>>> {
4226    cx.spawn(|workspace, mut cx| async move {
4227        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
4228        if !fs.is_file(path).await {
4229            fs.create_file(path, Default::default()).await?;
4230            fs.save(path, &default_content(), Default::default())
4231                .await?;
4232        }
4233
4234        let mut items = workspace
4235            .update(&mut cx, |workspace, cx| {
4236                workspace.with_local_workspace(cx, |workspace, cx| {
4237                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
4238                })
4239            })?
4240            .await?
4241            .await;
4242
4243        let item = items.pop().flatten();
4244        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4245    })
4246}
4247
4248pub fn join_remote_project(
4249    project_id: u64,
4250    follow_user_id: u64,
4251    app_state: Arc<AppState>,
4252    cx: &mut AppContext,
4253) -> Task<Result<()>> {
4254    cx.spawn(|mut cx| async move {
4255        let windows = cx.windows();
4256        let existing_workspace = windows.into_iter().find_map(|window| {
4257            window.downcast::<Workspace>().and_then(|window| {
4258                window
4259                    .read_root_with(&cx, |workspace, cx| {
4260                        if workspace.project().read(cx).remote_id() == Some(project_id) {
4261                            Some(cx.handle().downgrade())
4262                        } else {
4263                            None
4264                        }
4265                    })
4266                    .unwrap_or(None)
4267            })
4268        });
4269
4270        let workspace = if let Some(existing_workspace) = existing_workspace {
4271            existing_workspace
4272        } else {
4273            let active_call = cx.read(ActiveCall::global);
4274            let room = active_call
4275                .read_with(&cx, |call, _| call.room().cloned())
4276                .ok_or_else(|| anyhow!("not in a call"))?;
4277            let project = room
4278                .update(&mut cx, |room, cx| {
4279                    room.join_project(
4280                        project_id,
4281                        app_state.languages.clone(),
4282                        app_state.fs.clone(),
4283                        cx,
4284                    )
4285                })
4286                .await?;
4287
4288            let window_bounds_override = window_bounds_env_override(&cx);
4289            let window = cx.add_window(
4290                (app_state.build_window_options)(
4291                    window_bounds_override,
4292                    None,
4293                    cx.platform().as_ref(),
4294                ),
4295                |cx| Workspace::new(0, project, app_state.clone(), cx),
4296            );
4297            let workspace = window.root(&cx).unwrap();
4298            (app_state.initialize_workspace)(
4299                workspace.downgrade(),
4300                false,
4301                app_state.clone(),
4302                cx.clone(),
4303            )
4304            .await
4305            .log_err();
4306
4307            workspace.downgrade()
4308        };
4309
4310        workspace.window().activate(&mut cx);
4311        cx.platform().activate(true);
4312
4313        workspace.update(&mut cx, |workspace, cx| {
4314            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4315                let follow_peer_id = room
4316                    .read(cx)
4317                    .remote_participants()
4318                    .iter()
4319                    .find(|(_, participant)| participant.user.id == follow_user_id)
4320                    .map(|(_, p)| p.peer_id)
4321                    .or_else(|| {
4322                        // If we couldn't follow the given user, follow the host instead.
4323                        let collaborator = workspace
4324                            .project()
4325                            .read(cx)
4326                            .collaborators()
4327                            .values()
4328                            .find(|collaborator| collaborator.replica_id == 0)?;
4329                        Some(collaborator.peer_id)
4330                    });
4331
4332                if let Some(follow_peer_id) = follow_peer_id {
4333                    workspace
4334                        .follow(follow_peer_id, cx)
4335                        .map(|follow| follow.detach_and_log_err(cx));
4336                }
4337            }
4338        })?;
4339
4340        anyhow::Ok(())
4341    })
4342}
4343
4344pub fn restart(_: &Restart, cx: &mut AppContext) {
4345    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4346    cx.spawn(|mut cx| async move {
4347        let mut workspace_windows = cx
4348            .windows()
4349            .into_iter()
4350            .filter_map(|window| window.downcast::<Workspace>())
4351            .collect::<Vec<_>>();
4352
4353        // If multiple windows have unsaved changes, and need a save prompt,
4354        // prompt in the active window before switching to a different window.
4355        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4356
4357        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4358            let answer = window.prompt(
4359                PromptLevel::Info,
4360                "Are you sure you want to restart?",
4361                &["Restart", "Cancel"],
4362                &mut cx,
4363            );
4364
4365            if let Some(mut answer) = answer {
4366                let answer = answer.next().await;
4367                if answer != Some(0) {
4368                    return Ok(());
4369                }
4370            }
4371        }
4372
4373        // If the user cancels any save prompt, then keep the app open.
4374        for window in workspace_windows {
4375            if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4376                workspace.prepare_to_close(true, cx)
4377            }) {
4378                if !should_close.await? {
4379                    return Ok(());
4380                }
4381            }
4382        }
4383        cx.platform().restart();
4384        anyhow::Ok(())
4385    })
4386    .detach_and_log_err(cx);
4387}
4388
4389fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
4390    let mut parts = value.split(',');
4391    let width: usize = parts.next()?.parse().ok()?;
4392    let height: usize = parts.next()?.parse().ok()?;
4393    Some(vec2f(width as f32, height as f32))
4394}
4395
4396#[cfg(test)]
4397mod tests {
4398    use super::*;
4399    use crate::{
4400        dock::test::{TestPanel, TestPanelEvent},
4401        item::test::{TestItem, TestItemEvent, TestProjectItem},
4402    };
4403    use fs::FakeFs;
4404    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4405    use project::{Project, ProjectEntryId};
4406    use serde_json::json;
4407    use settings::SettingsStore;
4408    use std::{cell::RefCell, rc::Rc};
4409
4410    #[gpui::test]
4411    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4412        init_test(cx);
4413
4414        let fs = FakeFs::new(cx.background());
4415        let project = Project::test(fs, [], cx).await;
4416        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4417        let workspace = window.root(cx);
4418
4419        // Adding an item with no ambiguity renders the tab without detail.
4420        let item1 = window.add_view(cx, |_| {
4421            let mut item = TestItem::new();
4422            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4423            item
4424        });
4425        workspace.update(cx, |workspace, cx| {
4426            workspace.add_item(Box::new(item1.clone()), cx);
4427        });
4428        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4429
4430        // Adding an item that creates ambiguity increases the level of detail on
4431        // both tabs.
4432        let item2 = window.add_view(cx, |_| {
4433            let mut item = TestItem::new();
4434            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4435            item
4436        });
4437        workspace.update(cx, |workspace, cx| {
4438            workspace.add_item(Box::new(item2.clone()), cx);
4439        });
4440        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4441        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4442
4443        // Adding an item that creates ambiguity increases the level of detail only
4444        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4445        // we stop at the highest detail available.
4446        let item3 = window.add_view(cx, |_| {
4447            let mut item = TestItem::new();
4448            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4449            item
4450        });
4451        workspace.update(cx, |workspace, cx| {
4452            workspace.add_item(Box::new(item3.clone()), cx);
4453        });
4454        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4455        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4456        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4457    }
4458
4459    #[gpui::test]
4460    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4461        init_test(cx);
4462
4463        let fs = FakeFs::new(cx.background());
4464        fs.insert_tree(
4465            "/root1",
4466            json!({
4467                "one.txt": "",
4468                "two.txt": "",
4469            }),
4470        )
4471        .await;
4472        fs.insert_tree(
4473            "/root2",
4474            json!({
4475                "three.txt": "",
4476            }),
4477        )
4478        .await;
4479
4480        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4481        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4482        let workspace = window.root(cx);
4483        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4484        let worktree_id = project.read_with(cx, |project, cx| {
4485            project.worktrees(cx).next().unwrap().read(cx).id()
4486        });
4487
4488        let item1 = window.add_view(cx, |cx| {
4489            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4490        });
4491        let item2 = window.add_view(cx, |cx| {
4492            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4493        });
4494
4495        // Add an item to an empty pane
4496        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4497        project.read_with(cx, |project, cx| {
4498            assert_eq!(
4499                project.active_entry(),
4500                project
4501                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4502                    .map(|e| e.id)
4503            );
4504        });
4505        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4506
4507        // Add a second item to a non-empty pane
4508        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4509        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
4510        project.read_with(cx, |project, cx| {
4511            assert_eq!(
4512                project.active_entry(),
4513                project
4514                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4515                    .map(|e| e.id)
4516            );
4517        });
4518
4519        // Close the active item
4520        pane.update(cx, |pane, cx| {
4521            pane.close_active_item(&Default::default(), cx).unwrap()
4522        })
4523        .await
4524        .unwrap();
4525        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
4526        project.read_with(cx, |project, cx| {
4527            assert_eq!(
4528                project.active_entry(),
4529                project
4530                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4531                    .map(|e| e.id)
4532            );
4533        });
4534
4535        // Add a project folder
4536        project
4537            .update(cx, |project, cx| {
4538                project.find_or_create_local_worktree("/root2", true, cx)
4539            })
4540            .await
4541            .unwrap();
4542        assert_eq!(
4543            window.current_title(cx).as_deref(),
4544            Some("one.txt β€” root1, root2")
4545        );
4546
4547        // Remove a project folder
4548        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4549        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
4550    }
4551
4552    #[gpui::test]
4553    async fn test_close_window(cx: &mut TestAppContext) {
4554        init_test(cx);
4555
4556        let fs = FakeFs::new(cx.background());
4557        fs.insert_tree("/root", json!({ "one": "" })).await;
4558
4559        let project = Project::test(fs, ["root".as_ref()], cx).await;
4560        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4561        let workspace = window.root(cx);
4562
4563        // When there are no dirty items, there's nothing to do.
4564        let item1 = window.add_view(cx, |_| TestItem::new());
4565        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4566        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4567        assert!(task.await.unwrap());
4568
4569        // When there are dirty untitled items, prompt to save each one. If the user
4570        // cancels any prompt, then abort.
4571        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
4572        let item3 = window.add_view(cx, |cx| {
4573            TestItem::new()
4574                .with_dirty(true)
4575                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4576        });
4577        workspace.update(cx, |w, cx| {
4578            w.add_item(Box::new(item2.clone()), cx);
4579            w.add_item(Box::new(item3.clone()), cx);
4580        });
4581        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4582        cx.foreground().run_until_parked();
4583        window.simulate_prompt_answer(2, cx); // cancel save all
4584        cx.foreground().run_until_parked();
4585        window.simulate_prompt_answer(2, cx); // cancel save all
4586        cx.foreground().run_until_parked();
4587        assert!(!window.has_pending_prompt(cx));
4588        assert!(!task.await.unwrap());
4589    }
4590
4591    #[gpui::test]
4592    async fn test_close_pane_items(cx: &mut TestAppContext) {
4593        init_test(cx);
4594
4595        let fs = FakeFs::new(cx.background());
4596
4597        let project = Project::test(fs, None, cx).await;
4598        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4599        let workspace = window.root(cx);
4600
4601        let item1 = window.add_view(cx, |cx| {
4602            TestItem::new()
4603                .with_dirty(true)
4604                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4605        });
4606        let item2 = window.add_view(cx, |cx| {
4607            TestItem::new()
4608                .with_dirty(true)
4609                .with_conflict(true)
4610                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4611        });
4612        let item3 = window.add_view(cx, |cx| {
4613            TestItem::new()
4614                .with_dirty(true)
4615                .with_conflict(true)
4616                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4617        });
4618        let item4 = window.add_view(cx, |cx| {
4619            TestItem::new()
4620                .with_dirty(true)
4621                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4622        });
4623        let pane = workspace.update(cx, |workspace, cx| {
4624            workspace.add_item(Box::new(item1.clone()), cx);
4625            workspace.add_item(Box::new(item2.clone()), cx);
4626            workspace.add_item(Box::new(item3.clone()), cx);
4627            workspace.add_item(Box::new(item4.clone()), cx);
4628            workspace.active_pane().clone()
4629        });
4630
4631        let close_items = pane.update(cx, |pane, cx| {
4632            pane.activate_item(1, true, true, cx);
4633            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4634            let item1_id = item1.id();
4635            let item3_id = item3.id();
4636            let item4_id = item4.id();
4637            pane.close_items(cx, SaveIntent::Close, move |id| {
4638                [item1_id, item3_id, item4_id].contains(&id)
4639            })
4640        });
4641        cx.foreground().run_until_parked();
4642
4643        assert!(window.has_pending_prompt(cx));
4644        // Ignore "Save all" prompt
4645        window.simulate_prompt_answer(2, cx);
4646        cx.foreground().run_until_parked();
4647        // There's a prompt to save item 1.
4648        pane.read_with(cx, |pane, _| {
4649            assert_eq!(pane.items_len(), 4);
4650            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4651        });
4652        // Confirm saving item 1.
4653        window.simulate_prompt_answer(0, cx);
4654        cx.foreground().run_until_parked();
4655
4656        // Item 1 is saved. There's a prompt to save item 3.
4657        pane.read_with(cx, |pane, cx| {
4658            assert_eq!(item1.read(cx).save_count, 1);
4659            assert_eq!(item1.read(cx).save_as_count, 0);
4660            assert_eq!(item1.read(cx).reload_count, 0);
4661            assert_eq!(pane.items_len(), 3);
4662            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4663        });
4664        assert!(window.has_pending_prompt(cx));
4665
4666        // Cancel saving item 3.
4667        window.simulate_prompt_answer(1, cx);
4668        cx.foreground().run_until_parked();
4669
4670        // Item 3 is reloaded. There's a prompt to save item 4.
4671        pane.read_with(cx, |pane, cx| {
4672            assert_eq!(item3.read(cx).save_count, 0);
4673            assert_eq!(item3.read(cx).save_as_count, 0);
4674            assert_eq!(item3.read(cx).reload_count, 1);
4675            assert_eq!(pane.items_len(), 2);
4676            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4677        });
4678        assert!(window.has_pending_prompt(cx));
4679
4680        // Confirm saving item 4.
4681        window.simulate_prompt_answer(0, cx);
4682        cx.foreground().run_until_parked();
4683
4684        // There's a prompt for a path for item 4.
4685        cx.simulate_new_path_selection(|_| Some(Default::default()));
4686        close_items.await.unwrap();
4687
4688        // The requested items are closed.
4689        pane.read_with(cx, |pane, cx| {
4690            assert_eq!(item4.read(cx).save_count, 0);
4691            assert_eq!(item4.read(cx).save_as_count, 1);
4692            assert_eq!(item4.read(cx).reload_count, 0);
4693            assert_eq!(pane.items_len(), 1);
4694            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4695        });
4696    }
4697
4698    #[gpui::test]
4699    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4700        init_test(cx);
4701
4702        let fs = FakeFs::new(cx.background());
4703
4704        let project = Project::test(fs, [], cx).await;
4705        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4706        let workspace = window.root(cx);
4707
4708        // Create several workspace items with single project entries, and two
4709        // workspace items with multiple project entries.
4710        let single_entry_items = (0..=4)
4711            .map(|project_entry_id| {
4712                window.add_view(cx, |cx| {
4713                    TestItem::new()
4714                        .with_dirty(true)
4715                        .with_project_items(&[TestProjectItem::new(
4716                            project_entry_id,
4717                            &format!("{project_entry_id}.txt"),
4718                            cx,
4719                        )])
4720                })
4721            })
4722            .collect::<Vec<_>>();
4723        let item_2_3 = window.add_view(cx, |cx| {
4724            TestItem::new()
4725                .with_dirty(true)
4726                .with_singleton(false)
4727                .with_project_items(&[
4728                    single_entry_items[2].read(cx).project_items[0].clone(),
4729                    single_entry_items[3].read(cx).project_items[0].clone(),
4730                ])
4731        });
4732        let item_3_4 = window.add_view(cx, |cx| {
4733            TestItem::new()
4734                .with_dirty(true)
4735                .with_singleton(false)
4736                .with_project_items(&[
4737                    single_entry_items[3].read(cx).project_items[0].clone(),
4738                    single_entry_items[4].read(cx).project_items[0].clone(),
4739                ])
4740        });
4741
4742        // Create two panes that contain the following project entries:
4743        //   left pane:
4744        //     multi-entry items:   (2, 3)
4745        //     single-entry items:  0, 1, 2, 3, 4
4746        //   right pane:
4747        //     single-entry items:  1
4748        //     multi-entry items:   (3, 4)
4749        let left_pane = workspace.update(cx, |workspace, cx| {
4750            let left_pane = workspace.active_pane().clone();
4751            workspace.add_item(Box::new(item_2_3.clone()), cx);
4752            for item in single_entry_items {
4753                workspace.add_item(Box::new(item), cx);
4754            }
4755            left_pane.update(cx, |pane, cx| {
4756                pane.activate_item(2, true, true, cx);
4757            });
4758
4759            workspace
4760                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4761                .unwrap();
4762
4763            left_pane
4764        });
4765
4766        //Need to cause an effect flush in order to respect new focus
4767        workspace.update(cx, |workspace, cx| {
4768            workspace.add_item(Box::new(item_3_4.clone()), cx);
4769            cx.focus(&left_pane);
4770        });
4771
4772        // When closing all of the items in the left pane, we should be prompted twice:
4773        // once for project entry 0, and once for project entry 2. After those two
4774        // prompts, the task should complete.
4775
4776        let close = left_pane.update(cx, |pane, cx| {
4777            pane.close_items(cx, SaveIntent::Close, move |_| true)
4778        });
4779        cx.foreground().run_until_parked();
4780        // Discard "Save all" prompt
4781        window.simulate_prompt_answer(2, cx);
4782
4783        cx.foreground().run_until_parked();
4784        left_pane.read_with(cx, |pane, cx| {
4785            assert_eq!(
4786                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4787                &[ProjectEntryId::from_proto(0)]
4788            );
4789        });
4790        window.simulate_prompt_answer(0, cx);
4791
4792        cx.foreground().run_until_parked();
4793        left_pane.read_with(cx, |pane, cx| {
4794            assert_eq!(
4795                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4796                &[ProjectEntryId::from_proto(2)]
4797            );
4798        });
4799        window.simulate_prompt_answer(0, cx);
4800
4801        cx.foreground().run_until_parked();
4802        close.await.unwrap();
4803        left_pane.read_with(cx, |pane, _| {
4804            assert_eq!(pane.items_len(), 0);
4805        });
4806    }
4807
4808    #[gpui::test]
4809    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4810        init_test(cx);
4811
4812        let fs = FakeFs::new(cx.background());
4813
4814        let project = Project::test(fs, [], cx).await;
4815        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4816        let workspace = window.root(cx);
4817        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4818
4819        let item = window.add_view(cx, |cx| {
4820            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4821        });
4822        let item_id = item.id();
4823        workspace.update(cx, |workspace, cx| {
4824            workspace.add_item(Box::new(item.clone()), cx);
4825        });
4826
4827        // Autosave on window change.
4828        item.update(cx, |item, cx| {
4829            cx.update_global(|settings: &mut SettingsStore, cx| {
4830                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4831                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4832                })
4833            });
4834            item.is_dirty = true;
4835        });
4836
4837        // Deactivating the window saves the file.
4838        window.simulate_deactivation(cx);
4839        deterministic.run_until_parked();
4840        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4841
4842        // Autosave on focus change.
4843        item.update(cx, |item, cx| {
4844            cx.focus_self();
4845            cx.update_global(|settings: &mut SettingsStore, cx| {
4846                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4847                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4848                })
4849            });
4850            item.is_dirty = true;
4851        });
4852
4853        // Blurring the item saves the file.
4854        item.update(cx, |_, cx| cx.blur());
4855        deterministic.run_until_parked();
4856        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4857
4858        // Deactivating the window still saves the file.
4859        window.simulate_activation(cx);
4860        item.update(cx, |item, cx| {
4861            cx.focus_self();
4862            item.is_dirty = true;
4863        });
4864        window.simulate_deactivation(cx);
4865
4866        deterministic.run_until_parked();
4867        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4868
4869        // Autosave after delay.
4870        item.update(cx, |item, cx| {
4871            cx.update_global(|settings: &mut SettingsStore, cx| {
4872                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4873                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4874                })
4875            });
4876            item.is_dirty = true;
4877            cx.emit(TestItemEvent::Edit);
4878        });
4879
4880        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4881        deterministic.advance_clock(Duration::from_millis(250));
4882        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4883
4884        // After delay expires, the file is saved.
4885        deterministic.advance_clock(Duration::from_millis(250));
4886        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4887
4888        // Autosave on focus change, ensuring closing the tab counts as such.
4889        item.update(cx, |item, cx| {
4890            cx.update_global(|settings: &mut SettingsStore, cx| {
4891                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4892                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4893                })
4894            });
4895            item.is_dirty = true;
4896        });
4897
4898        pane.update(cx, |pane, cx| {
4899            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4900        })
4901        .await
4902        .unwrap();
4903        assert!(!window.has_pending_prompt(cx));
4904        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4905
4906        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4907        workspace.update(cx, |workspace, cx| {
4908            workspace.add_item(Box::new(item.clone()), cx);
4909        });
4910        item.update(cx, |item, cx| {
4911            item.project_items[0].update(cx, |item, _| {
4912                item.entry_id = None;
4913            });
4914            item.is_dirty = true;
4915            cx.blur();
4916        });
4917        deterministic.run_until_parked();
4918        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4919
4920        // Ensure autosave is prevented for deleted files also when closing the buffer.
4921        let _close_items = pane.update(cx, |pane, cx| {
4922            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4923        });
4924        deterministic.run_until_parked();
4925        assert!(window.has_pending_prompt(cx));
4926        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4927    }
4928
4929    #[gpui::test]
4930    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4931        init_test(cx);
4932
4933        let fs = FakeFs::new(cx.background());
4934
4935        let project = Project::test(fs, [], cx).await;
4936        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4937        let workspace = window.root(cx);
4938
4939        let item = window.add_view(cx, |cx| {
4940            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4941        });
4942        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4943        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4944        let toolbar_notify_count = Rc::new(RefCell::new(0));
4945
4946        workspace.update(cx, |workspace, cx| {
4947            workspace.add_item(Box::new(item.clone()), cx);
4948            let toolbar_notification_count = toolbar_notify_count.clone();
4949            cx.observe(&toolbar, move |_, _, _| {
4950                *toolbar_notification_count.borrow_mut() += 1
4951            })
4952            .detach();
4953        });
4954
4955        pane.read_with(cx, |pane, _| {
4956            assert!(!pane.can_navigate_backward());
4957            assert!(!pane.can_navigate_forward());
4958        });
4959
4960        item.update(cx, |item, cx| {
4961            item.set_state("one".to_string(), cx);
4962        });
4963
4964        // Toolbar must be notified to re-render the navigation buttons
4965        assert_eq!(*toolbar_notify_count.borrow(), 1);
4966
4967        pane.read_with(cx, |pane, _| {
4968            assert!(pane.can_navigate_backward());
4969            assert!(!pane.can_navigate_forward());
4970        });
4971
4972        workspace
4973            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4974            .await
4975            .unwrap();
4976
4977        assert_eq!(*toolbar_notify_count.borrow(), 3);
4978        pane.read_with(cx, |pane, _| {
4979            assert!(!pane.can_navigate_backward());
4980            assert!(pane.can_navigate_forward());
4981        });
4982    }
4983
4984    #[gpui::test]
4985    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4986        init_test(cx);
4987        let fs = FakeFs::new(cx.background());
4988
4989        let project = Project::test(fs, [], cx).await;
4990        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4991        let workspace = window.root(cx);
4992
4993        let panel = workspace.update(cx, |workspace, cx| {
4994            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4995            workspace.add_panel(panel.clone(), cx);
4996
4997            workspace
4998                .right_dock()
4999                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5000
5001            panel
5002        });
5003
5004        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5005        pane.update(cx, |pane, cx| {
5006            let item = cx.add_view(|_| TestItem::new());
5007            pane.add_item(Box::new(item), true, true, None, cx);
5008        });
5009
5010        // Transfer focus from center to panel
5011        workspace.update(cx, |workspace, cx| {
5012            workspace.toggle_panel_focus::<TestPanel>(cx);
5013        });
5014
5015        workspace.read_with(cx, |workspace, cx| {
5016            assert!(workspace.right_dock().read(cx).is_open());
5017            assert!(!panel.is_zoomed(cx));
5018            assert!(panel.has_focus(cx));
5019        });
5020
5021        // Transfer focus from panel to center
5022        workspace.update(cx, |workspace, cx| {
5023            workspace.toggle_panel_focus::<TestPanel>(cx);
5024        });
5025
5026        workspace.read_with(cx, |workspace, cx| {
5027            assert!(workspace.right_dock().read(cx).is_open());
5028            assert!(!panel.is_zoomed(cx));
5029            assert!(!panel.has_focus(cx));
5030        });
5031
5032        // Close the dock
5033        workspace.update(cx, |workspace, cx| {
5034            workspace.toggle_dock(DockPosition::Right, cx);
5035        });
5036
5037        workspace.read_with(cx, |workspace, cx| {
5038            assert!(!workspace.right_dock().read(cx).is_open());
5039            assert!(!panel.is_zoomed(cx));
5040            assert!(!panel.has_focus(cx));
5041        });
5042
5043        // Open the dock
5044        workspace.update(cx, |workspace, cx| {
5045            workspace.toggle_dock(DockPosition::Right, cx);
5046        });
5047
5048        workspace.read_with(cx, |workspace, cx| {
5049            assert!(workspace.right_dock().read(cx).is_open());
5050            assert!(!panel.is_zoomed(cx));
5051            assert!(panel.has_focus(cx));
5052        });
5053
5054        // Focus and zoom panel
5055        panel.update(cx, |panel, cx| {
5056            cx.focus_self();
5057            panel.set_zoomed(true, cx)
5058        });
5059
5060        workspace.read_with(cx, |workspace, cx| {
5061            assert!(workspace.right_dock().read(cx).is_open());
5062            assert!(panel.is_zoomed(cx));
5063            assert!(panel.has_focus(cx));
5064        });
5065
5066        // Transfer focus to the center closes the dock
5067        workspace.update(cx, |workspace, cx| {
5068            workspace.toggle_panel_focus::<TestPanel>(cx);
5069        });
5070
5071        workspace.read_with(cx, |workspace, cx| {
5072            assert!(!workspace.right_dock().read(cx).is_open());
5073            assert!(panel.is_zoomed(cx));
5074            assert!(!panel.has_focus(cx));
5075        });
5076
5077        // Transferring focus back to the panel keeps it zoomed
5078        workspace.update(cx, |workspace, cx| {
5079            workspace.toggle_panel_focus::<TestPanel>(cx);
5080        });
5081
5082        workspace.read_with(cx, |workspace, cx| {
5083            assert!(workspace.right_dock().read(cx).is_open());
5084            assert!(panel.is_zoomed(cx));
5085            assert!(panel.has_focus(cx));
5086        });
5087
5088        // Close the dock while it is zoomed
5089        workspace.update(cx, |workspace, cx| {
5090            workspace.toggle_dock(DockPosition::Right, cx)
5091        });
5092
5093        workspace.read_with(cx, |workspace, cx| {
5094            assert!(!workspace.right_dock().read(cx).is_open());
5095            assert!(panel.is_zoomed(cx));
5096            assert!(workspace.zoomed.is_none());
5097            assert!(!panel.has_focus(cx));
5098        });
5099
5100        // Opening the dock, when it's zoomed, retains focus
5101        workspace.update(cx, |workspace, cx| {
5102            workspace.toggle_dock(DockPosition::Right, cx)
5103        });
5104
5105        workspace.read_with(cx, |workspace, cx| {
5106            assert!(workspace.right_dock().read(cx).is_open());
5107            assert!(panel.is_zoomed(cx));
5108            assert!(workspace.zoomed.is_some());
5109            assert!(panel.has_focus(cx));
5110        });
5111
5112        // Unzoom and close the panel, zoom the active pane.
5113        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5114        workspace.update(cx, |workspace, cx| {
5115            workspace.toggle_dock(DockPosition::Right, cx)
5116        });
5117        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5118
5119        // Opening a dock unzooms the pane.
5120        workspace.update(cx, |workspace, cx| {
5121            workspace.toggle_dock(DockPosition::Right, cx)
5122        });
5123        workspace.read_with(cx, |workspace, cx| {
5124            let pane = pane.read(cx);
5125            assert!(!pane.is_zoomed());
5126            assert!(!pane.has_focus());
5127            assert!(workspace.right_dock().read(cx).is_open());
5128            assert!(workspace.zoomed.is_none());
5129        });
5130    }
5131
5132    #[gpui::test]
5133    async fn test_panels(cx: &mut gpui::TestAppContext) {
5134        init_test(cx);
5135        let fs = FakeFs::new(cx.background());
5136
5137        let project = Project::test(fs, [], cx).await;
5138        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5139        let workspace = window.root(cx);
5140
5141        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5142            // Add panel_1 on the left, panel_2 on the right.
5143            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
5144            workspace.add_panel(panel_1.clone(), cx);
5145            workspace
5146                .left_dock()
5147                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5148            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
5149            workspace.add_panel(panel_2.clone(), cx);
5150            workspace
5151                .right_dock()
5152                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5153
5154            let left_dock = workspace.left_dock();
5155            assert_eq!(
5156                left_dock.read(cx).visible_panel().unwrap().id(),
5157                panel_1.id()
5158            );
5159            assert_eq!(
5160                left_dock.read(cx).active_panel_size(cx).unwrap(),
5161                panel_1.size(cx)
5162            );
5163
5164            left_dock.update(cx, |left_dock, cx| {
5165                left_dock.resize_active_panel(Some(1337.), cx)
5166            });
5167            assert_eq!(
5168                workspace
5169                    .right_dock()
5170                    .read(cx)
5171                    .visible_panel()
5172                    .unwrap()
5173                    .id(),
5174                panel_2.id()
5175            );
5176
5177            (panel_1, panel_2)
5178        });
5179
5180        // Move panel_1 to the right
5181        panel_1.update(cx, |panel_1, cx| {
5182            panel_1.set_position(DockPosition::Right, cx)
5183        });
5184
5185        workspace.update(cx, |workspace, cx| {
5186            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5187            // Since it was the only panel on the left, the left dock should now be closed.
5188            assert!(!workspace.left_dock().read(cx).is_open());
5189            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5190            let right_dock = workspace.right_dock();
5191            assert_eq!(
5192                right_dock.read(cx).visible_panel().unwrap().id(),
5193                panel_1.id()
5194            );
5195            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5196
5197            // Now we move panel_2Β to the left
5198            panel_2.set_position(DockPosition::Left, cx);
5199        });
5200
5201        workspace.update(cx, |workspace, cx| {
5202            // Since panel_2 was not visible on the right, we don't open the left dock.
5203            assert!(!workspace.left_dock().read(cx).is_open());
5204            // And the right dock is unaffected in it's displaying of panel_1
5205            assert!(workspace.right_dock().read(cx).is_open());
5206            assert_eq!(
5207                workspace
5208                    .right_dock()
5209                    .read(cx)
5210                    .visible_panel()
5211                    .unwrap()
5212                    .id(),
5213                panel_1.id()
5214            );
5215        });
5216
5217        // Move panel_1 back to the left
5218        panel_1.update(cx, |panel_1, cx| {
5219            panel_1.set_position(DockPosition::Left, cx)
5220        });
5221
5222        workspace.update(cx, |workspace, cx| {
5223            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5224            let left_dock = workspace.left_dock();
5225            assert!(left_dock.read(cx).is_open());
5226            assert_eq!(
5227                left_dock.read(cx).visible_panel().unwrap().id(),
5228                panel_1.id()
5229            );
5230            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5231            // And right the dock should be closed as it no longer has any panels.
5232            assert!(!workspace.right_dock().read(cx).is_open());
5233
5234            // Now we move panel_1 to the bottom
5235            panel_1.set_position(DockPosition::Bottom, cx);
5236        });
5237
5238        workspace.update(cx, |workspace, cx| {
5239            // Since panel_1 was visible on the left, we close the left dock.
5240            assert!(!workspace.left_dock().read(cx).is_open());
5241            // The bottom dock is sized based on the panel's default size,
5242            // since the panel orientation changed from vertical to horizontal.
5243            let bottom_dock = workspace.bottom_dock();
5244            assert_eq!(
5245                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5246                panel_1.size(cx),
5247            );
5248            // Close bottom dock and move panel_1 back to the left.
5249            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5250            panel_1.set_position(DockPosition::Left, cx);
5251        });
5252
5253        // Emit activated event on panel 1
5254        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5255
5256        // Now the left dock is open and panel_1 is active and focused.
5257        workspace.read_with(cx, |workspace, cx| {
5258            let left_dock = workspace.left_dock();
5259            assert!(left_dock.read(cx).is_open());
5260            assert_eq!(
5261                left_dock.read(cx).visible_panel().unwrap().id(),
5262                panel_1.id()
5263            );
5264            assert!(panel_1.is_focused(cx));
5265        });
5266
5267        // Emit closed event on panel 2, which is not active
5268        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5269
5270        // Wo don't close the left dock, because panel_2 wasn't the active panel
5271        workspace.read_with(cx, |workspace, cx| {
5272            let left_dock = workspace.left_dock();
5273            assert!(left_dock.read(cx).is_open());
5274            assert_eq!(
5275                left_dock.read(cx).visible_panel().unwrap().id(),
5276                panel_1.id()
5277            );
5278        });
5279
5280        // Emitting a ZoomIn event shows the panel as zoomed.
5281        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5282        workspace.read_with(cx, |workspace, _| {
5283            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5284            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5285        });
5286
5287        // Move panel to another dock while it is zoomed
5288        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5289        workspace.read_with(cx, |workspace, _| {
5290            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5291            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5292        });
5293
5294        // If focus is transferred to another view that's not a panel or another pane, we still show
5295        // the panel as zoomed.
5296        let focus_receiver = window.add_view(cx, |_| EmptyView);
5297        focus_receiver.update(cx, |_, cx| cx.focus_self());
5298        workspace.read_with(cx, |workspace, _| {
5299            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5300            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5301        });
5302
5303        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5304        workspace.update(cx, |_, cx| cx.focus_self());
5305        workspace.read_with(cx, |workspace, _| {
5306            assert_eq!(workspace.zoomed, None);
5307            assert_eq!(workspace.zoomed_position, None);
5308        });
5309
5310        // If focus is transferred again to another view that's not a panel or a pane, we won't
5311        // show the panel as zoomed because it wasn't zoomed before.
5312        focus_receiver.update(cx, |_, cx| cx.focus_self());
5313        workspace.read_with(cx, |workspace, _| {
5314            assert_eq!(workspace.zoomed, None);
5315            assert_eq!(workspace.zoomed_position, None);
5316        });
5317
5318        // When focus is transferred back to the panel, it is zoomed again.
5319        panel_1.update(cx, |_, cx| cx.focus_self());
5320        workspace.read_with(cx, |workspace, _| {
5321            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5322            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5323        });
5324
5325        // Emitting a ZoomOut event unzooms the panel.
5326        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5327        workspace.read_with(cx, |workspace, _| {
5328            assert_eq!(workspace.zoomed, None);
5329            assert_eq!(workspace.zoomed_position, None);
5330        });
5331
5332        // Emit closed event on panel 1, which is active
5333        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5334
5335        // Now the left dock is closed, because panel_1 was the active panel
5336        workspace.read_with(cx, |workspace, cx| {
5337            let right_dock = workspace.right_dock();
5338            assert!(!right_dock.read(cx).is_open());
5339        });
5340    }
5341
5342    pub fn init_test(cx: &mut TestAppContext) {
5343        cx.foreground().forbid_parking();
5344        cx.update(|cx| {
5345            cx.set_global(SettingsStore::test(cx));
5346            theme::init((), cx);
5347            language::init(cx);
5348            crate::init_settings(cx);
5349            Project::init_settings(cx);
5350        });
5351    }
5352}