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