workspace.rs

   1pub mod dock;
   2/// NOTE: Focus only 'takes' after an update has flushed_effects.
   3///
   4/// This may cause issues when you're trying to write tests that use workspace focus to add items at
   5/// specific locations.
   6pub mod item;
   7pub mod notifications;
   8pub mod pane;
   9pub mod pane_group;
  10mod persistence;
  11pub mod searchable;
  12pub mod shared_screen;
  13mod status_bar;
  14mod toolbar;
  15mod workspace_settings;
  16
  17use anyhow::{anyhow, Context, Result};
  18use assets::Assets;
  19use call::ActiveCall;
  20use client::{
  21    proto::{self, PeerId},
  22    Client, TypedEnvelope, UserStore,
  23};
  24use collections::{hash_map, HashMap, HashSet};
  25use drag_and_drop::DragAndDrop;
  26use futures::{
  27    channel::{mpsc, oneshot},
  28    future::try_join_all,
  29    FutureExt, StreamExt,
  30};
  31use gpui::{
  32    actions,
  33    elements::*,
  34    geometry::{
  35        rect::RectF,
  36        vector::{vec2f, Vector2F},
  37    },
  38    impl_actions,
  39    platform::{
  40        CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
  41        WindowOptions,
  42    },
  43    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  44    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  45    WeakViewHandle, WindowContext,
  46};
  47use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  48use itertools::Itertools;
  49use language::{LanguageRegistry, Rope};
  50use std::{
  51    any::TypeId,
  52    borrow::Cow,
  53    cmp, env,
  54    future::Future,
  55    path::{Path, PathBuf},
  56    str,
  57    sync::{atomic::AtomicUsize, Arc},
  58    time::Duration,
  59};
  60
  61use crate::{
  62    notifications::simple_message_notification::MessageNotification,
  63    persistence::model::{
  64        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  65    },
  66};
  67use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  68use lazy_static::lazy_static;
  69use notifications::{NotificationHandle, NotifyResultExt};
  70pub use pane::*;
  71pub use pane_group::*;
  72use persistence::{model::SerializedItem, DB};
  73pub use persistence::{
  74    model::{ItemId, WorkspaceLocation},
  75    WorkspaceDb, DB as WORKSPACE_DB,
  76};
  77use postage::prelude::Stream;
  78use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  79use serde::Deserialize;
  80use shared_screen::SharedScreen;
  81use status_bar::StatusBar;
  82pub use status_bar::StatusItemView;
  83use theme::Theme;
  84pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  85use util::{async_iife, paths, ResultExt};
  86pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  87
  88lazy_static! {
  89    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  90        .ok()
  91        .as_deref()
  92        .and_then(parse_pixel_position_env_var);
  93    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  94        .ok()
  95        .as_deref()
  96        .and_then(parse_pixel_position_env_var);
  97}
  98
  99pub trait Modal: View {
 100    fn dismiss_on_event(event: &Self::Event) -> bool;
 101}
 102
 103#[derive(Clone, PartialEq)]
 104pub struct RemoveWorktreeFromProject(pub WorktreeId);
 105
 106#[derive(Copy, Clone, Default, Deserialize, PartialEq)]
 107pub struct ToggleLeftDock {
 108    #[serde(default = "default_true")]
 109    pub focus: bool,
 110}
 111
 112#[derive(Copy, Clone, Default, Deserialize, PartialEq)]
 113pub struct ToggleBottomDock {
 114    #[serde(default = "default_true")]
 115    pub focus: bool,
 116}
 117
 118#[derive(Copy, Clone, Default, Deserialize, PartialEq)]
 119pub struct ToggleRightDock {
 120    #[serde(default = "default_true")]
 121    pub focus: bool,
 122}
 123
 124actions!(
 125    workspace,
 126    [
 127        Open,
 128        NewFile,
 129        NewWindow,
 130        CloseWindow,
 131        AddFolderToProject,
 132        Unfollow,
 133        Save,
 134        SaveAs,
 135        SaveAll,
 136        ActivatePreviousPane,
 137        ActivateNextPane,
 138        FollowNextCollaborator,
 139        NewTerminal,
 140        ToggleTerminalFocus,
 141        NewSearch,
 142        Feedback,
 143        Restart,
 144        Welcome,
 145        ToggleZoom,
 146    ]
 147);
 148
 149actions!(zed, [OpenSettings]);
 150
 151impl_actions!(
 152    workspace,
 153    [ToggleLeftDock, ToggleBottomDock, ToggleRightDock]
 154);
 155
 156#[derive(Clone, PartialEq)]
 157pub struct OpenPaths {
 158    pub paths: Vec<PathBuf>,
 159}
 160
 161#[derive(Clone, Deserialize, PartialEq)]
 162pub struct ActivatePane(pub usize);
 163
 164pub struct Toast {
 165    id: usize,
 166    msg: Cow<'static, str>,
 167    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 168}
 169
 170impl Toast {
 171    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 172        Toast {
 173            id,
 174            msg: msg.into(),
 175            on_click: None,
 176        }
 177    }
 178
 179    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 180    where
 181        M: Into<Cow<'static, str>>,
 182        F: Fn(&mut WindowContext) + 'static,
 183    {
 184        self.on_click = Some((message.into(), Arc::new(on_click)));
 185        self
 186    }
 187}
 188
 189impl PartialEq for Toast {
 190    fn eq(&self, other: &Self) -> bool {
 191        self.id == other.id
 192            && self.msg == other.msg
 193            && self.on_click.is_some() == other.on_click.is_some()
 194    }
 195}
 196
 197impl Clone for Toast {
 198    fn clone(&self) -> Self {
 199        Toast {
 200            id: self.id,
 201            msg: self.msg.to_owned(),
 202            on_click: self.on_click.clone(),
 203        }
 204    }
 205}
 206
 207pub type WorkspaceId = i64;
 208
 209impl_actions!(workspace, [ActivatePane]);
 210
 211pub fn init_settings(cx: &mut AppContext) {
 212    settings::register::<WorkspaceSettings>(cx);
 213}
 214
 215pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 216    init_settings(cx);
 217    pane::init(cx);
 218    notifications::init(cx);
 219
 220    cx.add_global_action({
 221        let app_state = Arc::downgrade(&app_state);
 222        move |_: &Open, cx: &mut AppContext| {
 223            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 224                files: true,
 225                directories: true,
 226                multiple: true,
 227            });
 228
 229            if let Some(app_state) = app_state.upgrade() {
 230                cx.spawn(move |mut cx| async move {
 231                    if let Some(paths) = paths.recv().await.flatten() {
 232                        cx.update(|cx| {
 233                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 234                        });
 235                    }
 236                })
 237                .detach();
 238            }
 239        }
 240    });
 241    cx.add_async_action(Workspace::open);
 242
 243    cx.add_async_action(Workspace::follow_next_collaborator);
 244    cx.add_async_action(Workspace::close);
 245    cx.add_global_action(Workspace::close_global);
 246    cx.add_global_action(restart);
 247    cx.add_async_action(Workspace::save_all);
 248    cx.add_action(Workspace::add_folder_to_project);
 249    cx.add_action(
 250        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 251            let pane = workspace.active_pane().clone();
 252            workspace.unfollow(&pane, cx);
 253        },
 254    );
 255    cx.add_action(
 256        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 257            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 258        },
 259    );
 260    cx.add_action(
 261        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 262            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 263        },
 264    );
 265    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 266        workspace.activate_previous_pane(cx)
 267    });
 268    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 269        workspace.activate_next_pane(cx)
 270    });
 271    cx.add_action(|workspace: &mut Workspace, action: &ToggleLeftDock, cx| {
 272        workspace.toggle_dock(DockPosition::Left, action.focus, cx);
 273    });
 274    cx.add_action(|workspace: &mut Workspace, action: &ToggleRightDock, cx| {
 275        workspace.toggle_dock(DockPosition::Right, action.focus, cx);
 276    });
 277    cx.add_action(|workspace: &mut Workspace, action: &ToggleBottomDock, cx| {
 278        workspace.toggle_dock(DockPosition::Bottom, action.focus, cx);
 279    });
 280    cx.add_action(Workspace::activate_pane_at_index);
 281
 282    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 283        cx.spawn(|workspace, mut cx| async move {
 284            let err = install_cli::install_cli(&cx)
 285                .await
 286                .context("Failed to create CLI symlink");
 287
 288            workspace.update(&mut cx, |workspace, cx| {
 289                if matches!(err, Err(_)) {
 290                    err.notify_err(workspace, cx);
 291                } else {
 292                    workspace.show_notification(1, cx, |cx| {
 293                        cx.add_view(|_| {
 294                            MessageNotification::new("Successfully installed the `zed` binary")
 295                        })
 296                    });
 297                }
 298            })
 299        })
 300        .detach();
 301    });
 302
 303    cx.add_action(
 304        move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
 305            create_and_open_local_file(&paths::SETTINGS, cx, || {
 306                settings::initial_user_settings_content(&Assets)
 307                    .as_ref()
 308                    .into()
 309            })
 310            .detach_and_log_err(cx);
 311        },
 312    );
 313
 314    let client = &app_state.client;
 315    client.add_view_request_handler(Workspace::handle_follow);
 316    client.add_view_message_handler(Workspace::handle_unfollow);
 317    client.add_view_message_handler(Workspace::handle_update_followers);
 318}
 319
 320type ProjectItemBuilders = HashMap<
 321    TypeId,
 322    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 323>;
 324pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 325    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 326        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 327            let item = model.downcast::<I::Item>().unwrap();
 328            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 329        });
 330    });
 331}
 332
 333type FollowableItemBuilder = fn(
 334    ViewHandle<Pane>,
 335    ModelHandle<Project>,
 336    ViewId,
 337    &mut Option<proto::view::Variant>,
 338    &mut AppContext,
 339) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 340type FollowableItemBuilders = HashMap<
 341    TypeId,
 342    (
 343        FollowableItemBuilder,
 344        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 345    ),
 346>;
 347pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 348    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 349        builders.insert(
 350            TypeId::of::<I>(),
 351            (
 352                |pane, project, id, state, cx| {
 353                    I::from_state_proto(pane, project, id, state, cx).map(|task| {
 354                        cx.foreground()
 355                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 356                    })
 357                },
 358                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 359            ),
 360        );
 361    });
 362}
 363
 364type ItemDeserializers = HashMap<
 365    Arc<str>,
 366    fn(
 367        ModelHandle<Project>,
 368        WeakViewHandle<Workspace>,
 369        WorkspaceId,
 370        ItemId,
 371        &mut ViewContext<Pane>,
 372    ) -> Task<Result<Box<dyn ItemHandle>>>,
 373>;
 374pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 375    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 376        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 377            deserializers.insert(
 378                Arc::from(serialized_item_kind),
 379                |project, workspace, workspace_id, item_id, cx| {
 380                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 381                    cx.foreground()
 382                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 383                },
 384            );
 385        }
 386    });
 387}
 388
 389pub struct AppState {
 390    pub languages: Arc<LanguageRegistry>,
 391    pub client: Arc<client::Client>,
 392    pub user_store: ModelHandle<client::UserStore>,
 393    pub fs: Arc<dyn fs::Fs>,
 394    pub build_window_options:
 395        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 396    pub initialize_workspace:
 397        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 398    pub background_actions: BackgroundActions,
 399}
 400
 401impl AppState {
 402    #[cfg(any(test, feature = "test-support"))]
 403    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 404        use settings::SettingsStore;
 405
 406        if !cx.has_global::<SettingsStore>() {
 407            cx.set_global(SettingsStore::test(cx));
 408        }
 409
 410        let fs = fs::FakeFs::new(cx.background().clone());
 411        let languages = Arc::new(LanguageRegistry::test());
 412        let http_client = util::http::FakeHttpClient::with_404_response();
 413        let client = Client::new(http_client.clone(), cx);
 414        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 415
 416        theme::init((), cx);
 417        client::init(&client, cx);
 418        crate::init_settings(cx);
 419
 420        Arc::new(Self {
 421            client,
 422            fs,
 423            languages,
 424            user_store,
 425            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 426            build_window_options: |_, _, _| Default::default(),
 427            background_actions: || &[],
 428        })
 429    }
 430}
 431
 432struct DelayedDebouncedEditAction {
 433    task: Option<Task<()>>,
 434    cancel_channel: Option<oneshot::Sender<()>>,
 435}
 436
 437impl DelayedDebouncedEditAction {
 438    fn new() -> DelayedDebouncedEditAction {
 439        DelayedDebouncedEditAction {
 440            task: None,
 441            cancel_channel: None,
 442        }
 443    }
 444
 445    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 446    where
 447        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 448    {
 449        if let Some(channel) = self.cancel_channel.take() {
 450            _ = channel.send(());
 451        }
 452
 453        let (sender, mut receiver) = oneshot::channel::<()>();
 454        self.cancel_channel = Some(sender);
 455
 456        let previous_task = self.task.take();
 457        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 458            let mut timer = cx.background().timer(delay).fuse();
 459            if let Some(previous_task) = previous_task {
 460                previous_task.await;
 461            }
 462
 463            futures::select_biased! {
 464                _ = receiver => return,
 465                    _ = timer => {}
 466            }
 467
 468            if let Some(result) = workspace
 469                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 470                .log_err()
 471            {
 472                result.await.log_err();
 473            }
 474        }));
 475    }
 476}
 477
 478pub enum Event {
 479    PaneAdded(ViewHandle<Pane>),
 480    ContactRequestedJoin(u64),
 481}
 482
 483pub struct Workspace {
 484    weak_self: WeakViewHandle<Self>,
 485    remote_entity_subscription: Option<client::Subscription>,
 486    modal: Option<AnyViewHandle>,
 487    zoomed: Option<AnyWeakViewHandle>,
 488    center: PaneGroup,
 489    left_dock: ViewHandle<Dock>,
 490    bottom_dock: ViewHandle<Dock>,
 491    right_dock: ViewHandle<Dock>,
 492    panes: Vec<ViewHandle<Pane>>,
 493    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 494    active_pane: ViewHandle<Pane>,
 495    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 496    status_bar: ViewHandle<StatusBar>,
 497    titlebar_item: Option<AnyViewHandle>,
 498    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 499    project: ModelHandle<Project>,
 500    leader_state: LeaderState,
 501    follower_states_by_leader: FollowerStatesByLeader,
 502    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 503    window_edited: bool,
 504    active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
 505    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 506    database_id: WorkspaceId,
 507    app_state: Arc<AppState>,
 508    subscriptions: Vec<Subscription>,
 509    _apply_leader_updates: Task<Result<()>>,
 510    _observe_current_user: Task<Result<()>>,
 511    pane_history_timestamp: Arc<AtomicUsize>,
 512}
 513
 514#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 515pub struct ViewId {
 516    pub creator: PeerId,
 517    pub id: u64,
 518}
 519
 520#[derive(Default)]
 521struct LeaderState {
 522    followers: HashSet<PeerId>,
 523}
 524
 525type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 526
 527#[derive(Default)]
 528struct FollowerState {
 529    active_view_id: Option<ViewId>,
 530    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 531}
 532
 533impl Workspace {
 534    pub fn new(
 535        workspace_id: WorkspaceId,
 536        project: ModelHandle<Project>,
 537        app_state: Arc<AppState>,
 538        cx: &mut ViewContext<Self>,
 539    ) -> Self {
 540        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 541        cx.subscribe(&project, move |this, _, event, cx| {
 542            match event {
 543                project::Event::RemoteIdChanged(remote_id) => {
 544                    this.update_window_title(cx);
 545                    this.project_remote_id_changed(*remote_id, cx);
 546                }
 547
 548                project::Event::CollaboratorLeft(peer_id) => {
 549                    this.collaborator_left(*peer_id, cx);
 550                }
 551
 552                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 553                    this.update_window_title(cx);
 554                    this.serialize_workspace(cx);
 555                }
 556
 557                project::Event::DisconnectedFromHost => {
 558                    this.update_window_edited(cx);
 559                    cx.blur();
 560                }
 561
 562                project::Event::Closed => {
 563                    cx.remove_window();
 564                }
 565
 566                project::Event::DeletedEntry(entry_id) => {
 567                    for pane in this.panes.iter() {
 568                        pane.update(cx, |pane, cx| {
 569                            pane.handle_deleted_project_item(*entry_id, cx)
 570                        });
 571                    }
 572                }
 573
 574                _ => {}
 575            }
 576            cx.notify()
 577        })
 578        .detach();
 579
 580        let weak_handle = cx.weak_handle();
 581        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 582
 583        let center_pane = cx.add_view(|cx| {
 584            Pane::new(
 585                weak_handle.clone(),
 586                app_state.background_actions,
 587                pane_history_timestamp.clone(),
 588                cx,
 589            )
 590        });
 591        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 592        cx.focus(&center_pane);
 593        cx.emit(Event::PaneAdded(center_pane.clone()));
 594
 595        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 596        let mut connection_status = app_state.client.status();
 597        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 598            current_user.recv().await;
 599            connection_status.recv().await;
 600            let mut stream =
 601                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 602
 603            while stream.recv().await.is_some() {
 604                this.update(&mut cx, |_, cx| cx.notify())?;
 605            }
 606            anyhow::Ok(())
 607        });
 608
 609        // All leader updates are enqueued and then processed in a single task, so
 610        // that each asynchronous operation can be run in order.
 611        let (leader_updates_tx, mut leader_updates_rx) =
 612            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 613        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 614            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 615                Self::process_leader_update(&this, leader_id, update, &mut cx)
 616                    .await
 617                    .log_err();
 618            }
 619
 620            Ok(())
 621        });
 622
 623        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 624
 625        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 626        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 627        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 628        let left_dock_buttons =
 629            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 630        let bottom_dock_buttons =
 631            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 632        let right_dock_buttons =
 633            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 634        let status_bar = cx.add_view(|cx| {
 635            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 636            status_bar.add_left_item(left_dock_buttons, cx);
 637            status_bar.add_right_item(right_dock_buttons, cx);
 638            status_bar.add_right_item(bottom_dock_buttons, cx);
 639            status_bar
 640        });
 641
 642        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 643            drag_and_drop.register_container(weak_handle.clone());
 644        });
 645
 646        let mut active_call = None;
 647        if cx.has_global::<ModelHandle<ActiveCall>>() {
 648            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 649            let mut subscriptions = Vec::new();
 650            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 651            active_call = Some((call, subscriptions));
 652        }
 653
 654        let subscriptions = vec![
 655            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 656            cx.observe_window_activation(Self::on_window_activation_changed),
 657            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 658                // Transform fixed bounds to be stored in terms of the containing display
 659                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 660                    if let Some(screen) = cx.platform().screen_by_id(display) {
 661                        let screen_bounds = screen.bounds();
 662                        window_bounds
 663                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 664                        window_bounds
 665                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 666                        bounds = WindowBounds::Fixed(window_bounds);
 667                    }
 668                }
 669
 670                cx.background()
 671                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 672                    .detach_and_log_err(cx);
 673            }),
 674            cx.observe(&left_dock, |this, _, cx| {
 675                this.serialize_workspace(cx);
 676                cx.notify();
 677            }),
 678            cx.observe(&bottom_dock, |this, _, cx| {
 679                this.serialize_workspace(cx);
 680                cx.notify();
 681            }),
 682            cx.observe(&right_dock, |this, _, cx| {
 683                this.serialize_workspace(cx);
 684                cx.notify();
 685            }),
 686        ];
 687
 688        let mut this = Workspace {
 689            weak_self: weak_handle.clone(),
 690            modal: None,
 691            zoomed: None,
 692            center: PaneGroup::new(center_pane.clone()),
 693            panes: vec![center_pane.clone()],
 694            panes_by_item: Default::default(),
 695            active_pane: center_pane.clone(),
 696            last_active_center_pane: Some(center_pane.downgrade()),
 697            status_bar,
 698            titlebar_item: None,
 699            notifications: Default::default(),
 700            remote_entity_subscription: None,
 701            left_dock,
 702            bottom_dock,
 703            right_dock,
 704            project: project.clone(),
 705            leader_state: Default::default(),
 706            follower_states_by_leader: Default::default(),
 707            last_leaders_by_pane: Default::default(),
 708            window_edited: false,
 709            active_call,
 710            database_id: workspace_id,
 711            app_state,
 712            _observe_current_user,
 713            _apply_leader_updates,
 714            leader_updates_tx,
 715            subscriptions,
 716            pane_history_timestamp,
 717        };
 718        this.project_remote_id_changed(project.read(cx).remote_id(), cx);
 719        cx.defer(|this, cx| this.update_window_title(cx));
 720        this
 721    }
 722
 723    fn new_local(
 724        abs_paths: Vec<PathBuf>,
 725        app_state: Arc<AppState>,
 726        requesting_window_id: Option<usize>,
 727        cx: &mut AppContext,
 728    ) -> Task<(
 729        WeakViewHandle<Workspace>,
 730        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 731    )> {
 732        let project_handle = Project::local(
 733            app_state.client.clone(),
 734            app_state.user_store.clone(),
 735            app_state.languages.clone(),
 736            app_state.fs.clone(),
 737            cx,
 738        );
 739
 740        cx.spawn(|mut cx| async move {
 741            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 742
 743            let paths_to_open = Arc::new(abs_paths);
 744
 745            // Get project paths for all of the abs_paths
 746            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 747            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 748                Vec::with_capacity(paths_to_open.len());
 749            for path in paths_to_open.iter().cloned() {
 750                if let Some((worktree, project_entry)) = cx
 751                    .update(|cx| {
 752                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 753                    })
 754                    .await
 755                    .log_err()
 756                {
 757                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 758                    project_paths.push((path, Some(project_entry)));
 759                } else {
 760                    project_paths.push((path, None));
 761                }
 762            }
 763
 764            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 765                serialized_workspace.id
 766            } else {
 767                DB.next_id().await.unwrap_or(0)
 768            };
 769
 770            let window_bounds_override =
 771                ZED_WINDOW_POSITION
 772                    .zip(*ZED_WINDOW_SIZE)
 773                    .map(|(position, size)| {
 774                        WindowBounds::Fixed(RectF::new(
 775                            cx.platform().screens()[0].bounds().origin() + position,
 776                            size,
 777                        ))
 778                    });
 779
 780            let build_workspace = |cx: &mut ViewContext<Workspace>| {
 781                Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
 782            };
 783
 784            let workspace = requesting_window_id
 785                .and_then(|window_id| {
 786                    cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx)))
 787                })
 788                .unwrap_or_else(|| {
 789                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 790                        (Some(bounds), None)
 791                    } else {
 792                        serialized_workspace
 793                            .as_ref()
 794                            .and_then(|serialized_workspace| {
 795                                let display = serialized_workspace.display?;
 796                                let mut bounds = serialized_workspace.bounds?;
 797
 798                                // Stored bounds are relative to the containing display.
 799                                // So convert back to global coordinates if that screen still exists
 800                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 801                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 802                                        let screen_bounds = screen.bounds();
 803                                        window_bounds.set_origin_x(
 804                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 805                                        );
 806                                        window_bounds.set_origin_y(
 807                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 808                                        );
 809                                        bounds = WindowBounds::Fixed(window_bounds);
 810                                    } else {
 811                                        // Screen no longer exists. Return none here.
 812                                        return None;
 813                                    }
 814                                }
 815
 816                                Some((bounds, display))
 817                            })
 818                            .unzip()
 819                    };
 820
 821                    // Use the serialized workspace to construct the new window
 822                    cx.add_window(
 823                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 824                        |cx| build_workspace(cx),
 825                    )
 826                    .1
 827                });
 828
 829            (app_state.initialize_workspace)(
 830                workspace.downgrade(),
 831                serialized_workspace.is_some(),
 832                app_state.clone(),
 833                cx.clone(),
 834            )
 835            .await
 836            .log_err();
 837
 838            cx.update_window(workspace.window_id(), |cx| cx.activate_window());
 839
 840            let workspace = workspace.downgrade();
 841            notify_if_database_failed(&workspace, &mut cx);
 842            let opened_items = open_items(
 843                serialized_workspace,
 844                &workspace,
 845                project_paths,
 846                app_state,
 847                cx,
 848            )
 849            .await;
 850
 851            (workspace, opened_items)
 852        })
 853    }
 854
 855    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 856        self.weak_self.clone()
 857    }
 858
 859    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 860        &self.left_dock
 861    }
 862
 863    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 864        &self.bottom_dock
 865    }
 866
 867    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 868        &self.right_dock
 869    }
 870
 871    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
 872        let dock = match panel.position(cx) {
 873            DockPosition::Left => &self.left_dock,
 874            DockPosition::Bottom => &self.bottom_dock,
 875            DockPosition::Right => &self.right_dock,
 876        };
 877
 878        self.subscriptions.push(cx.subscribe(&panel, {
 879            let mut dock = dock.clone();
 880            let mut prev_position = panel.position(cx);
 881            move |this, panel, event, cx| {
 882                if T::should_change_position_on_event(event) {
 883                    let new_position = panel.read(cx).position(cx);
 884                    let mut was_visible = false;
 885                    dock.update(cx, |dock, cx| {
 886                        prev_position = new_position;
 887
 888                        was_visible = dock.is_open()
 889                            && dock
 890                                .active_panel()
 891                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 892                        dock.remove_panel(&panel, cx);
 893                    });
 894                    dock = match panel.read(cx).position(cx) {
 895                        DockPosition::Left => &this.left_dock,
 896                        DockPosition::Bottom => &this.bottom_dock,
 897                        DockPosition::Right => &this.right_dock,
 898                    }
 899                    .clone();
 900                    dock.update(cx, |dock, cx| {
 901                        dock.add_panel(panel.clone(), cx);
 902                        if was_visible {
 903                            dock.set_open(true, cx);
 904                            dock.activate_panel(dock.panels_len() - 1, cx);
 905                        }
 906                    });
 907                } else if T::should_zoom_in_on_event(event) {
 908                    this.zoom_out(cx);
 909                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
 910                    if panel.has_focus(cx) {
 911                        this.zoomed = Some(panel.downgrade().into_any());
 912                    }
 913                } else if T::should_zoom_out_on_event(event) {
 914                    this.zoom_out(cx);
 915                } else if T::is_focus_event(event) {
 916                    if panel.is_zoomed(cx) {
 917                        this.zoomed = Some(panel.downgrade().into_any());
 918                    } else {
 919                        this.zoomed = None;
 920                    }
 921                    cx.notify();
 922                }
 923            }
 924        }));
 925
 926        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
 927    }
 928
 929    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 930        &self.status_bar
 931    }
 932
 933    pub fn app_state(&self) -> &Arc<AppState> {
 934        &self.app_state
 935    }
 936
 937    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 938        &self.app_state.user_store
 939    }
 940
 941    pub fn project(&self) -> &ModelHandle<Project> {
 942        &self.project
 943    }
 944
 945    pub fn recent_navigation_history(
 946        &self,
 947        limit: Option<usize>,
 948        cx: &AppContext,
 949    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 950        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 951        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 952        for pane in &self.panes {
 953            let pane = pane.read(cx);
 954            pane.nav_history()
 955                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 956                    if let Some(fs_path) = &fs_path {
 957                        abs_paths_opened
 958                            .entry(fs_path.clone())
 959                            .or_default()
 960                            .insert(project_path.clone());
 961                    }
 962                    let timestamp = entry.timestamp;
 963                    match history.entry(project_path) {
 964                        hash_map::Entry::Occupied(mut entry) => {
 965                            let (old_fs_path, old_timestamp) = entry.get();
 966                            if &timestamp > old_timestamp {
 967                                assert_eq!(&fs_path, old_fs_path, "Inconsistent nav history");
 968                                entry.insert((fs_path, timestamp));
 969                            }
 970                        }
 971                        hash_map::Entry::Vacant(entry) => {
 972                            entry.insert((fs_path, timestamp));
 973                        }
 974                    }
 975                });
 976        }
 977
 978        history
 979            .into_iter()
 980            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 981            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 982            .rev()
 983            .filter(|(history_path, abs_path)| {
 984                let latest_project_path_opened = abs_path
 985                    .as_ref()
 986                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
 987                    .and_then(|project_paths| {
 988                        project_paths
 989                            .iter()
 990                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
 991                    });
 992
 993                match latest_project_path_opened {
 994                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
 995                    None => true,
 996                }
 997            })
 998            .take(limit.unwrap_or(usize::MAX))
 999            .collect()
1000    }
1001
1002    pub fn client(&self) -> &Client {
1003        &self.app_state.client
1004    }
1005
1006    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1007        self.titlebar_item = Some(item);
1008        cx.notify();
1009    }
1010
1011    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1012        self.titlebar_item.clone()
1013    }
1014
1015    /// Call the given callback with a workspace whose project is local.
1016    ///
1017    /// If the given workspace has a local project, then it will be passed
1018    /// to the callback. Otherwise, a new empty window will be created.
1019    pub fn with_local_workspace<T, F>(
1020        &mut self,
1021        cx: &mut ViewContext<Self>,
1022        callback: F,
1023    ) -> Task<Result<T>>
1024    where
1025        T: 'static,
1026        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1027    {
1028        if self.project.read(cx).is_local() {
1029            Task::Ready(Some(Ok(callback(self, cx))))
1030        } else {
1031            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1032            cx.spawn(|_vh, mut cx| async move {
1033                let (workspace, _) = task.await;
1034                workspace.update(&mut cx, callback)
1035            })
1036        }
1037    }
1038
1039    pub fn worktrees<'a>(
1040        &self,
1041        cx: &'a AppContext,
1042    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1043        self.project.read(cx).worktrees(cx)
1044    }
1045
1046    pub fn visible_worktrees<'a>(
1047        &self,
1048        cx: &'a AppContext,
1049    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1050        self.project.read(cx).visible_worktrees(cx)
1051    }
1052
1053    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1054        let futures = self
1055            .worktrees(cx)
1056            .filter_map(|worktree| worktree.read(cx).as_local())
1057            .map(|worktree| worktree.scan_complete())
1058            .collect::<Vec<_>>();
1059        async move {
1060            for future in futures {
1061                future.await;
1062            }
1063        }
1064    }
1065
1066    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1067        cx.spawn(|mut cx| async move {
1068            let id = cx
1069                .window_ids()
1070                .into_iter()
1071                .find(|&id| cx.window_is_active(id));
1072            if let Some(id) = id {
1073                //This can only get called when the window's project connection has been lost
1074                //so we don't need to prompt the user for anything and instead just close the window
1075                cx.remove_window(id);
1076            }
1077        })
1078        .detach();
1079    }
1080
1081    pub fn close(
1082        &mut self,
1083        _: &CloseWindow,
1084        cx: &mut ViewContext<Self>,
1085    ) -> Option<Task<Result<()>>> {
1086        let window_id = cx.window_id();
1087        let prepare = self.prepare_to_close(false, cx);
1088        Some(cx.spawn(|_, mut cx| async move {
1089            if prepare.await? {
1090                cx.remove_window(window_id);
1091            }
1092            Ok(())
1093        }))
1094    }
1095
1096    pub fn prepare_to_close(
1097        &mut self,
1098        quitting: bool,
1099        cx: &mut ViewContext<Self>,
1100    ) -> Task<Result<bool>> {
1101        let active_call = self.active_call().cloned();
1102        let window_id = cx.window_id();
1103
1104        cx.spawn(|this, mut cx| async move {
1105            let workspace_count = cx
1106                .window_ids()
1107                .into_iter()
1108                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
1109                .count();
1110
1111            if let Some(active_call) = active_call {
1112                if !quitting
1113                    && workspace_count == 1
1114                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1115                {
1116                    let answer = cx.prompt(
1117                        window_id,
1118                        PromptLevel::Warning,
1119                        "Do you want to leave the current call?",
1120                        &["Close window and hang up", "Cancel"],
1121                    );
1122
1123                    if let Some(mut answer) = answer {
1124                        if answer.next().await == Some(1) {
1125                            return anyhow::Ok(false);
1126                        } else {
1127                            active_call
1128                                .update(&mut cx, |call, cx| call.hang_up(cx))
1129                                .await
1130                                .log_err();
1131                        }
1132                    }
1133                }
1134            }
1135
1136            Ok(this
1137                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1138                .await?)
1139        })
1140    }
1141
1142    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1143        let save_all = self.save_all_internal(false, cx);
1144        Some(cx.foreground().spawn(async move {
1145            save_all.await?;
1146            Ok(())
1147        }))
1148    }
1149
1150    fn save_all_internal(
1151        &mut self,
1152        should_prompt_to_save: bool,
1153        cx: &mut ViewContext<Self>,
1154    ) -> Task<Result<bool>> {
1155        if self.project.read(cx).is_read_only() {
1156            return Task::ready(Ok(true));
1157        }
1158
1159        let dirty_items = self
1160            .panes
1161            .iter()
1162            .flat_map(|pane| {
1163                pane.read(cx).items().filter_map(|item| {
1164                    if item.is_dirty(cx) {
1165                        Some((pane.downgrade(), item.boxed_clone()))
1166                    } else {
1167                        None
1168                    }
1169                })
1170            })
1171            .collect::<Vec<_>>();
1172
1173        let project = self.project.clone();
1174        cx.spawn(|_, mut cx| async move {
1175            for (pane, item) in dirty_items {
1176                let (singleton, project_entry_ids) =
1177                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1178                if singleton || !project_entry_ids.is_empty() {
1179                    if let Some(ix) =
1180                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1181                    {
1182                        if !Pane::save_item(
1183                            project.clone(),
1184                            &pane,
1185                            ix,
1186                            &*item,
1187                            should_prompt_to_save,
1188                            &mut cx,
1189                        )
1190                        .await?
1191                        {
1192                            return Ok(false);
1193                        }
1194                    }
1195                }
1196            }
1197            Ok(true)
1198        })
1199    }
1200
1201    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1202        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1203            files: true,
1204            directories: true,
1205            multiple: true,
1206        });
1207
1208        Some(cx.spawn(|this, mut cx| async move {
1209            if let Some(paths) = paths.recv().await.flatten() {
1210                if let Some(task) = this
1211                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1212                    .log_err()
1213                {
1214                    task.await?
1215                }
1216            }
1217            Ok(())
1218        }))
1219    }
1220
1221    pub fn open_workspace_for_paths(
1222        &mut self,
1223        paths: Vec<PathBuf>,
1224        cx: &mut ViewContext<Self>,
1225    ) -> Task<Result<()>> {
1226        let window_id = cx.window_id();
1227        let is_remote = self.project.read(cx).is_remote();
1228        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1229        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1230        let close_task = if is_remote || has_worktree || has_dirty_items {
1231            None
1232        } else {
1233            Some(self.prepare_to_close(false, cx))
1234        };
1235        let app_state = self.app_state.clone();
1236
1237        cx.spawn(|_, mut cx| async move {
1238            let window_id_to_replace = if let Some(close_task) = close_task {
1239                if !close_task.await? {
1240                    return Ok(());
1241                }
1242                Some(window_id)
1243            } else {
1244                None
1245            };
1246            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
1247                .await?;
1248            Ok(())
1249        })
1250    }
1251
1252    #[allow(clippy::type_complexity)]
1253    pub fn open_paths(
1254        &mut self,
1255        mut abs_paths: Vec<PathBuf>,
1256        visible: bool,
1257        cx: &mut ViewContext<Self>,
1258    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1259        log::info!("open paths {:?}", abs_paths);
1260
1261        let fs = self.app_state.fs.clone();
1262
1263        // Sort the paths to ensure we add worktrees for parents before their children.
1264        abs_paths.sort_unstable();
1265        cx.spawn(|this, mut cx| async move {
1266            let mut project_paths = Vec::new();
1267            for path in &abs_paths {
1268                if let Some(project_path) = this
1269                    .update(&mut cx, |this, cx| {
1270                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1271                    })
1272                    .log_err()
1273                {
1274                    project_paths.push(project_path.await.log_err());
1275                } else {
1276                    project_paths.push(None);
1277                }
1278            }
1279
1280            let tasks = abs_paths
1281                .iter()
1282                .cloned()
1283                .zip(project_paths.into_iter())
1284                .map(|(abs_path, project_path)| {
1285                    let this = this.clone();
1286                    cx.spawn(|mut cx| {
1287                        let fs = fs.clone();
1288                        async move {
1289                            let (_worktree, project_path) = project_path?;
1290                            if fs.is_file(&abs_path).await {
1291                                Some(
1292                                    this.update(&mut cx, |this, cx| {
1293                                        this.open_path(project_path, None, true, cx)
1294                                    })
1295                                    .log_err()?
1296                                    .await,
1297                                )
1298                            } else {
1299                                None
1300                            }
1301                        }
1302                    })
1303                })
1304                .collect::<Vec<_>>();
1305
1306            futures::future::join_all(tasks).await
1307        })
1308    }
1309
1310    pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
1311        let workspace_root = self
1312            .project()
1313            .read(cx)
1314            .worktree_for_id(project_path.worktree_id, cx)?
1315            .read(cx)
1316            .abs_path();
1317        let project_path = project_path.path.as_ref();
1318
1319        Some(if project_path == Path::new("") {
1320            workspace_root.to_path_buf()
1321        } else {
1322            workspace_root.join(project_path)
1323        })
1324    }
1325
1326    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1327        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1328            files: false,
1329            directories: true,
1330            multiple: true,
1331        });
1332        cx.spawn(|this, mut cx| async move {
1333            if let Some(paths) = paths.recv().await.flatten() {
1334                let results = this
1335                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1336                    .await;
1337                for result in results.into_iter().flatten() {
1338                    result.log_err();
1339                }
1340            }
1341            anyhow::Ok(())
1342        })
1343        .detach_and_log_err(cx);
1344    }
1345
1346    fn project_path_for_path(
1347        project: ModelHandle<Project>,
1348        abs_path: &Path,
1349        visible: bool,
1350        cx: &mut AppContext,
1351    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1352        let entry = project.update(cx, |project, cx| {
1353            project.find_or_create_local_worktree(abs_path, visible, cx)
1354        });
1355        cx.spawn(|cx| async move {
1356            let (worktree, path) = entry.await?;
1357            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1358            Ok((
1359                worktree,
1360                ProjectPath {
1361                    worktree_id,
1362                    path: path.into(),
1363                },
1364            ))
1365        })
1366    }
1367
1368    /// Returns the modal that was toggled closed if it was open.
1369    pub fn toggle_modal<V, F>(
1370        &mut self,
1371        cx: &mut ViewContext<Self>,
1372        add_view: F,
1373    ) -> Option<ViewHandle<V>>
1374    where
1375        V: 'static + Modal,
1376        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1377    {
1378        cx.notify();
1379        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1380        // it. Otherwise, create a new modal and set it as active.
1381        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1382        if let Some(already_open_modal) = already_open_modal {
1383            cx.focus_self();
1384            Some(already_open_modal)
1385        } else {
1386            let modal = add_view(self, cx);
1387            cx.subscribe(&modal, |this, _, event, cx| {
1388                if V::dismiss_on_event(event) {
1389                    this.dismiss_modal(cx);
1390                }
1391            })
1392            .detach();
1393            cx.focus(&modal);
1394            self.modal = Some(modal.into_any());
1395            None
1396        }
1397    }
1398
1399    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1400        self.modal
1401            .as_ref()
1402            .and_then(|modal| modal.clone().downcast::<V>())
1403    }
1404
1405    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1406        if self.modal.take().is_some() {
1407            cx.focus(&self.active_pane);
1408            cx.notify();
1409        }
1410    }
1411
1412    pub fn items<'a>(
1413        &'a self,
1414        cx: &'a AppContext,
1415    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1416        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1417    }
1418
1419    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1420        self.items_of_type(cx).max_by_key(|item| item.id())
1421    }
1422
1423    pub fn items_of_type<'a, T: Item>(
1424        &'a self,
1425        cx: &'a AppContext,
1426    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1427        self.panes
1428            .iter()
1429            .flat_map(|pane| pane.read(cx).items_of_type())
1430    }
1431
1432    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1433        self.active_pane().read(cx).active_item()
1434    }
1435
1436    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1437        self.active_item(cx).and_then(|item| item.project_path(cx))
1438    }
1439
1440    pub fn save_active_item(
1441        &mut self,
1442        force_name_change: bool,
1443        cx: &mut ViewContext<Self>,
1444    ) -> Task<Result<()>> {
1445        let project = self.project.clone();
1446        if let Some(item) = self.active_item(cx) {
1447            if !force_name_change && item.can_save(cx) {
1448                if item.has_conflict(cx) {
1449                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1450
1451                    let mut answer = cx.prompt(
1452                        PromptLevel::Warning,
1453                        CONFLICT_MESSAGE,
1454                        &["Overwrite", "Cancel"],
1455                    );
1456                    cx.spawn(|this, mut cx| async move {
1457                        let answer = answer.recv().await;
1458                        if answer == Some(0) {
1459                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1460                                .await?;
1461                        }
1462                        Ok(())
1463                    })
1464                } else {
1465                    item.save(self.project.clone(), cx)
1466                }
1467            } else if item.is_singleton(cx) {
1468                let worktree = self.worktrees(cx).next();
1469                let start_abs_path = worktree
1470                    .and_then(|w| w.read(cx).as_local())
1471                    .map_or(Path::new(""), |w| w.abs_path())
1472                    .to_path_buf();
1473                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1474                cx.spawn(|this, mut cx| async move {
1475                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1476                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1477                            .await?;
1478                    }
1479                    Ok(())
1480                })
1481            } else {
1482                Task::ready(Ok(()))
1483            }
1484        } else {
1485            Task::ready(Ok(()))
1486        }
1487    }
1488
1489    pub fn toggle_dock(
1490        &mut self,
1491        dock_side: DockPosition,
1492        focus: bool,
1493        cx: &mut ViewContext<Self>,
1494    ) {
1495        let dock = match dock_side {
1496            DockPosition::Left => &self.left_dock,
1497            DockPosition::Bottom => &self.bottom_dock,
1498            DockPosition::Right => &self.right_dock,
1499        };
1500        dock.update(cx, |dock, cx| {
1501            let open = !dock.is_open();
1502            dock.set_open(open, cx);
1503        });
1504
1505        if dock.read(cx).is_open() && focus {
1506            cx.focus(dock);
1507        } else {
1508            cx.focus_self();
1509        }
1510        cx.notify();
1511        self.serialize_workspace(cx);
1512    }
1513
1514    pub fn toggle_panel(
1515        &mut self,
1516        position: DockPosition,
1517        panel_index: usize,
1518        cx: &mut ViewContext<Self>,
1519    ) {
1520        let dock = match position {
1521            DockPosition::Left => &mut self.left_dock,
1522            DockPosition::Bottom => &mut self.bottom_dock,
1523            DockPosition::Right => &mut self.right_dock,
1524        };
1525        let active_item = dock.update(cx, move |dock, cx| {
1526            if dock.is_open() && dock.active_panel_index() == panel_index {
1527                dock.set_open(false, cx);
1528                None
1529            } else {
1530                dock.set_open(true, cx);
1531                dock.activate_panel(panel_index, cx);
1532                dock.active_panel().cloned()
1533            }
1534        });
1535
1536        if let Some(active_item) = active_item {
1537            if active_item.has_focus(cx) {
1538                cx.focus_self();
1539            } else {
1540                cx.focus(active_item.as_any());
1541            }
1542        } else {
1543            cx.focus_self();
1544        }
1545
1546        self.serialize_workspace(cx);
1547
1548        cx.notify();
1549    }
1550
1551    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1552        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1553            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1554                let active_item = dock.update(cx, |dock, cx| {
1555                    dock.set_open(true, cx);
1556                    dock.activate_panel(panel_index, cx);
1557                    dock.active_panel().cloned()
1558                });
1559                if let Some(active_item) = active_item {
1560                    if active_item.has_focus(cx) {
1561                        cx.focus_self();
1562                    } else {
1563                        cx.focus(active_item.as_any());
1564                    }
1565                }
1566
1567                self.serialize_workspace(cx);
1568                cx.notify();
1569                break;
1570            }
1571        }
1572    }
1573
1574    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1575        for pane in &self.panes {
1576            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1577        }
1578
1579        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1580        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1581        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1582        self.zoomed = None;
1583
1584        cx.notify();
1585    }
1586
1587    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1588        let pane = cx.add_view(|cx| {
1589            Pane::new(
1590                self.weak_handle(),
1591                self.app_state.background_actions,
1592                self.pane_history_timestamp.clone(),
1593                cx,
1594            )
1595        });
1596        cx.subscribe(&pane, Self::handle_pane_event).detach();
1597        self.panes.push(pane.clone());
1598        cx.focus(&pane);
1599        cx.emit(Event::PaneAdded(pane.clone()));
1600        pane
1601    }
1602
1603    pub fn add_item_to_center(
1604        &mut self,
1605        item: Box<dyn ItemHandle>,
1606        cx: &mut ViewContext<Self>,
1607    ) -> bool {
1608        if let Some(center_pane) = self.last_active_center_pane.clone() {
1609            if let Some(center_pane) = center_pane.upgrade(cx) {
1610                Pane::add_item(self, &center_pane, item, true, true, None, cx);
1611                true
1612            } else {
1613                false
1614            }
1615        } else {
1616            false
1617        }
1618    }
1619
1620    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1621        let active_pane = self.active_pane().clone();
1622        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1623    }
1624
1625    pub fn open_abs_path(
1626        &mut self,
1627        abs_path: PathBuf,
1628        visible: bool,
1629        cx: &mut ViewContext<Self>,
1630    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1631        cx.spawn(|workspace, mut cx| async move {
1632            let open_paths_task_result = workspace
1633                .update(&mut cx, |workspace, cx| {
1634                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1635                })
1636                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1637                .await;
1638            anyhow::ensure!(
1639                open_paths_task_result.len() == 1,
1640                "open abs path {abs_path:?} task returned incorrect number of results"
1641            );
1642            match open_paths_task_result
1643                .into_iter()
1644                .next()
1645                .expect("ensured single task result")
1646            {
1647                Some(open_result) => {
1648                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1649                }
1650                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1651            }
1652        })
1653    }
1654
1655    pub fn open_path(
1656        &mut self,
1657        path: impl Into<ProjectPath>,
1658        pane: Option<WeakViewHandle<Pane>>,
1659        focus_item: bool,
1660        cx: &mut ViewContext<Self>,
1661    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1662        let pane = pane.unwrap_or_else(|| {
1663            self.last_active_center_pane.clone().unwrap_or_else(|| {
1664                self.panes
1665                    .first()
1666                    .expect("There must be an active pane")
1667                    .downgrade()
1668            })
1669        });
1670
1671        let task = self.load_path(path.into(), cx);
1672        cx.spawn(|this, mut cx| async move {
1673            let (project_entry_id, build_item) = task.await?;
1674            let pane = pane
1675                .upgrade(&cx)
1676                .ok_or_else(|| anyhow!("pane was closed"))?;
1677            this.update(&mut cx, |this, cx| {
1678                Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
1679            })
1680        })
1681    }
1682
1683    pub(crate) fn load_path(
1684        &mut self,
1685        path: ProjectPath,
1686        cx: &mut ViewContext<Self>,
1687    ) -> Task<
1688        Result<(
1689            ProjectEntryId,
1690            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1691        )>,
1692    > {
1693        let project = self.project().clone();
1694        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1695        cx.spawn(|_, mut cx| async move {
1696            let (project_entry_id, project_item) = project_item.await?;
1697            let build_item = cx.update(|cx| {
1698                cx.default_global::<ProjectItemBuilders>()
1699                    .get(&project_item.model_type())
1700                    .ok_or_else(|| anyhow!("no item builder for project item"))
1701                    .cloned()
1702            })?;
1703            let build_item =
1704                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1705            Ok((project_entry_id, build_item))
1706        })
1707    }
1708
1709    pub fn open_project_item<T>(
1710        &mut self,
1711        project_item: ModelHandle<T::Item>,
1712        cx: &mut ViewContext<Self>,
1713    ) -> ViewHandle<T>
1714    where
1715        T: ProjectItem,
1716    {
1717        use project::Item as _;
1718
1719        let entry_id = project_item.read(cx).entry_id(cx);
1720        if let Some(item) = entry_id
1721            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1722            .and_then(|item| item.downcast())
1723        {
1724            self.activate_item(&item, cx);
1725            return item;
1726        }
1727
1728        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1729        self.add_item(Box::new(item.clone()), cx);
1730        item
1731    }
1732
1733    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1734        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1735            let pane = self.active_pane.clone();
1736            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1737        }
1738    }
1739
1740    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1741        let result = self.panes.iter().find_map(|pane| {
1742            pane.read(cx)
1743                .index_for_item(item)
1744                .map(|ix| (pane.clone(), ix))
1745        });
1746        if let Some((pane, ix)) = result {
1747            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1748            true
1749        } else {
1750            false
1751        }
1752    }
1753
1754    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1755        let panes = self.center.panes();
1756        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1757            cx.focus(&pane);
1758        } else {
1759            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1760        }
1761    }
1762
1763    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1764        let panes = self.center.panes();
1765        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1766            let next_ix = (ix + 1) % panes.len();
1767            let next_pane = panes[next_ix].clone();
1768            cx.focus(&next_pane);
1769        }
1770    }
1771
1772    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1773        let panes = self.center.panes();
1774        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1775            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1776            let prev_pane = panes[prev_ix].clone();
1777            cx.focus(&prev_pane);
1778        }
1779    }
1780
1781    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1782        if self.active_pane != pane {
1783            self.active_pane
1784                .update(cx, |pane, cx| pane.set_active(false, cx));
1785            self.active_pane = pane.clone();
1786            self.active_pane
1787                .update(cx, |pane, cx| pane.set_active(true, cx));
1788            self.status_bar.update(cx, |status_bar, cx| {
1789                status_bar.set_active_pane(&self.active_pane, cx);
1790            });
1791            self.active_item_path_changed(cx);
1792            self.last_active_center_pane = Some(pane.downgrade());
1793        }
1794
1795        if pane.read(cx).is_zoomed() {
1796            self.zoomed = Some(pane.downgrade().into_any());
1797        } else {
1798            self.zoomed = None;
1799        }
1800
1801        self.update_followers(
1802            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1803                id: self.active_item(cx).and_then(|item| {
1804                    item.to_followable_item_handle(cx)?
1805                        .remote_id(&self.app_state.client, cx)
1806                        .map(|id| id.to_proto())
1807                }),
1808                leader_id: self.leader_for_pane(&pane),
1809            }),
1810            cx,
1811        );
1812
1813        cx.notify();
1814    }
1815
1816    fn handle_pane_event(
1817        &mut self,
1818        pane: ViewHandle<Pane>,
1819        event: &pane::Event,
1820        cx: &mut ViewContext<Self>,
1821    ) {
1822        match event {
1823            pane::Event::Split(direction) => {
1824                self.split_pane(pane, *direction, cx);
1825            }
1826            pane::Event::Remove => self.remove_pane(pane, cx),
1827            pane::Event::ActivateItem { local } => {
1828                if *local {
1829                    self.unfollow(&pane, cx);
1830                }
1831                if &pane == self.active_pane() {
1832                    self.active_item_path_changed(cx);
1833                }
1834            }
1835            pane::Event::ChangeItemTitle => {
1836                if pane == self.active_pane {
1837                    self.active_item_path_changed(cx);
1838                }
1839                self.update_window_edited(cx);
1840            }
1841            pane::Event::RemoveItem { item_id } => {
1842                self.update_window_edited(cx);
1843                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1844                    if entry.get().id() == pane.id() {
1845                        entry.remove();
1846                    }
1847                }
1848            }
1849            pane::Event::Focus => {
1850                self.handle_pane_focused(pane.clone(), cx);
1851            }
1852            pane::Event::ZoomIn => {
1853                if pane == self.active_pane {
1854                    self.zoom_out(cx);
1855                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
1856                    if pane.read(cx).has_focus() {
1857                        self.zoomed = Some(pane.downgrade().into_any());
1858                    }
1859                    cx.notify();
1860                }
1861            }
1862            pane::Event::ZoomOut => self.zoom_out(cx),
1863        }
1864
1865        self.serialize_workspace(cx);
1866    }
1867
1868    pub fn split_pane(
1869        &mut self,
1870        pane: ViewHandle<Pane>,
1871        direction: SplitDirection,
1872        cx: &mut ViewContext<Self>,
1873    ) -> Option<ViewHandle<Pane>> {
1874        let item = pane.read(cx).active_item()?;
1875        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
1876            let new_pane = self.add_pane(cx);
1877            Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1878            self.center.split(&pane, &new_pane, direction).unwrap();
1879            Some(new_pane)
1880        } else {
1881            None
1882        };
1883        cx.notify();
1884        maybe_pane_handle
1885    }
1886
1887    pub fn split_pane_with_item(
1888        &mut self,
1889        pane_to_split: WeakViewHandle<Pane>,
1890        split_direction: SplitDirection,
1891        from: WeakViewHandle<Pane>,
1892        item_id_to_move: usize,
1893        cx: &mut ViewContext<Self>,
1894    ) {
1895        let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
1896        let Some(from) = from.upgrade(cx) else { return; };
1897
1898        let new_pane = self.add_pane(cx);
1899        Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
1900        self.center
1901            .split(&pane_to_split, &new_pane, split_direction)
1902            .unwrap();
1903        cx.notify();
1904    }
1905
1906    pub fn split_pane_with_project_entry(
1907        &mut self,
1908        pane_to_split: WeakViewHandle<Pane>,
1909        split_direction: SplitDirection,
1910        project_entry: ProjectEntryId,
1911        cx: &mut ViewContext<Self>,
1912    ) -> Option<Task<Result<()>>> {
1913        let pane_to_split = pane_to_split.upgrade(cx)?;
1914        let new_pane = self.add_pane(cx);
1915        self.center
1916            .split(&pane_to_split, &new_pane, split_direction)
1917            .unwrap();
1918
1919        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
1920        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
1921        Some(cx.foreground().spawn(async move {
1922            task.await?;
1923            Ok(())
1924        }))
1925    }
1926
1927    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1928        if self.center.remove(&pane).unwrap() {
1929            self.force_remove_pane(&pane, cx);
1930            self.unfollow(&pane, cx);
1931            self.last_leaders_by_pane.remove(&pane.downgrade());
1932            for removed_item in pane.read(cx).items() {
1933                self.panes_by_item.remove(&removed_item.id());
1934            }
1935
1936            cx.notify();
1937        } else {
1938            self.active_item_path_changed(cx);
1939        }
1940    }
1941
1942    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1943        &self.panes
1944    }
1945
1946    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1947        &self.active_pane
1948    }
1949
1950    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1951        if let Some(remote_id) = remote_id {
1952            self.remote_entity_subscription = Some(
1953                self.app_state
1954                    .client
1955                    .add_view_for_remote_entity(remote_id, cx),
1956            );
1957        } else {
1958            self.remote_entity_subscription.take();
1959        }
1960    }
1961
1962    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1963        self.leader_state.followers.remove(&peer_id);
1964        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1965            for state in states_by_pane.into_values() {
1966                for item in state.items_by_leader_view_id.into_values() {
1967                    item.set_leader_replica_id(None, cx);
1968                }
1969            }
1970        }
1971        cx.notify();
1972    }
1973
1974    pub fn toggle_follow(
1975        &mut self,
1976        leader_id: PeerId,
1977        cx: &mut ViewContext<Self>,
1978    ) -> Option<Task<Result<()>>> {
1979        let pane = self.active_pane().clone();
1980
1981        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1982            if leader_id == prev_leader_id {
1983                return None;
1984            }
1985        }
1986
1987        self.last_leaders_by_pane
1988            .insert(pane.downgrade(), leader_id);
1989        self.follower_states_by_leader
1990            .entry(leader_id)
1991            .or_default()
1992            .insert(pane.clone(), Default::default());
1993        cx.notify();
1994
1995        let project_id = self.project.read(cx).remote_id()?;
1996        let request = self.app_state.client.request(proto::Follow {
1997            project_id,
1998            leader_id: Some(leader_id),
1999        });
2000
2001        Some(cx.spawn(|this, mut cx| async move {
2002            let response = request.await?;
2003            this.update(&mut cx, |this, _| {
2004                let state = this
2005                    .follower_states_by_leader
2006                    .get_mut(&leader_id)
2007                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2008                    .ok_or_else(|| anyhow!("following interrupted"))?;
2009                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2010                    Some(ViewId::from_proto(active_view_id)?)
2011                } else {
2012                    None
2013                };
2014                Ok::<_, anyhow::Error>(())
2015            })??;
2016            Self::add_views_from_leader(
2017                this.clone(),
2018                leader_id,
2019                vec![pane],
2020                response.views,
2021                &mut cx,
2022            )
2023            .await?;
2024            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2025            Ok(())
2026        }))
2027    }
2028
2029    pub fn follow_next_collaborator(
2030        &mut self,
2031        _: &FollowNextCollaborator,
2032        cx: &mut ViewContext<Self>,
2033    ) -> Option<Task<Result<()>>> {
2034        let collaborators = self.project.read(cx).collaborators();
2035        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2036            let mut collaborators = collaborators.keys().copied();
2037            for peer_id in collaborators.by_ref() {
2038                if peer_id == leader_id {
2039                    break;
2040                }
2041            }
2042            collaborators.next()
2043        } else if let Some(last_leader_id) =
2044            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2045        {
2046            if collaborators.contains_key(last_leader_id) {
2047                Some(*last_leader_id)
2048            } else {
2049                None
2050            }
2051        } else {
2052            None
2053        };
2054
2055        next_leader_id
2056            .or_else(|| collaborators.keys().copied().next())
2057            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2058    }
2059
2060    pub fn unfollow(
2061        &mut self,
2062        pane: &ViewHandle<Pane>,
2063        cx: &mut ViewContext<Self>,
2064    ) -> Option<PeerId> {
2065        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2066            let leader_id = *leader_id;
2067            if let Some(state) = states_by_pane.remove(pane) {
2068                for (_, item) in state.items_by_leader_view_id {
2069                    item.set_leader_replica_id(None, cx);
2070                }
2071
2072                if states_by_pane.is_empty() {
2073                    self.follower_states_by_leader.remove(&leader_id);
2074                    if let Some(project_id) = self.project.read(cx).remote_id() {
2075                        self.app_state
2076                            .client
2077                            .send(proto::Unfollow {
2078                                project_id,
2079                                leader_id: Some(leader_id),
2080                            })
2081                            .log_err();
2082                    }
2083                }
2084
2085                cx.notify();
2086                return Some(leader_id);
2087            }
2088        }
2089        None
2090    }
2091
2092    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2093        self.follower_states_by_leader.contains_key(&peer_id)
2094    }
2095
2096    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2097        self.leader_state.followers.contains(&peer_id)
2098    }
2099
2100    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2101        // TODO: There should be a better system in place for this
2102        // (https://github.com/zed-industries/zed/issues/1290)
2103        let is_fullscreen = cx.window_is_fullscreen();
2104        let container_theme = if is_fullscreen {
2105            let mut container_theme = theme.workspace.titlebar.container;
2106            container_theme.padding.left = container_theme.padding.right;
2107            container_theme
2108        } else {
2109            theme.workspace.titlebar.container
2110        };
2111
2112        enum TitleBar {}
2113        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2114            Stack::new()
2115                .with_children(
2116                    self.titlebar_item
2117                        .as_ref()
2118                        .map(|item| ChildView::new(item, cx)),
2119                )
2120                .contained()
2121                .with_style(container_theme)
2122        })
2123        .on_click(MouseButton::Left, |event, _, cx| {
2124            if event.click_count == 2 {
2125                cx.zoom_window();
2126            }
2127        })
2128        .constrained()
2129        .with_height(theme.workspace.titlebar.height)
2130        .into_any_named("titlebar")
2131    }
2132
2133    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2134        let active_entry = self.active_project_path(cx);
2135        self.project
2136            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2137        self.update_window_title(cx);
2138    }
2139
2140    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2141        let project = self.project().read(cx);
2142        let mut title = String::new();
2143
2144        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2145            let filename = path
2146                .path
2147                .file_name()
2148                .map(|s| s.to_string_lossy())
2149                .or_else(|| {
2150                    Some(Cow::Borrowed(
2151                        project
2152                            .worktree_for_id(path.worktree_id, cx)?
2153                            .read(cx)
2154                            .root_name(),
2155                    ))
2156                });
2157
2158            if let Some(filename) = filename {
2159                title.push_str(filename.as_ref());
2160                title.push_str(" β€” ");
2161            }
2162        }
2163
2164        for (i, name) in project.worktree_root_names(cx).enumerate() {
2165            if i > 0 {
2166                title.push_str(", ");
2167            }
2168            title.push_str(name);
2169        }
2170
2171        if title.is_empty() {
2172            title = "empty project".to_string();
2173        }
2174
2175        if project.is_remote() {
2176            title.push_str(" ↙");
2177        } else if project.is_shared() {
2178            title.push_str(" β†—");
2179        }
2180
2181        cx.set_window_title(&title);
2182    }
2183
2184    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2185        let is_edited = !self.project.read(cx).is_read_only()
2186            && self
2187                .items(cx)
2188                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2189        if is_edited != self.window_edited {
2190            self.window_edited = is_edited;
2191            cx.set_window_edited(self.window_edited)
2192        }
2193    }
2194
2195    fn render_disconnected_overlay(
2196        &self,
2197        cx: &mut ViewContext<Workspace>,
2198    ) -> Option<AnyElement<Workspace>> {
2199        if self.project.read(cx).is_read_only() {
2200            enum DisconnectedOverlay {}
2201            Some(
2202                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2203                    let theme = &theme::current(cx);
2204                    Label::new(
2205                        "Your connection to the remote project has been lost.",
2206                        theme.workspace.disconnected_overlay.text.clone(),
2207                    )
2208                    .aligned()
2209                    .contained()
2210                    .with_style(theme.workspace.disconnected_overlay.container)
2211                })
2212                .with_cursor_style(CursorStyle::Arrow)
2213                .capture_all()
2214                .into_any_named("disconnected overlay"),
2215            )
2216        } else {
2217            None
2218        }
2219    }
2220
2221    fn render_notifications(
2222        &self,
2223        theme: &theme::Workspace,
2224        cx: &AppContext,
2225    ) -> Option<AnyElement<Workspace>> {
2226        if self.notifications.is_empty() {
2227            None
2228        } else {
2229            Some(
2230                Flex::column()
2231                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2232                        ChildView::new(notification.as_any(), cx)
2233                            .contained()
2234                            .with_style(theme.notification)
2235                    }))
2236                    .constrained()
2237                    .with_width(theme.notifications.width)
2238                    .contained()
2239                    .with_style(theme.notifications.container)
2240                    .aligned()
2241                    .bottom()
2242                    .right()
2243                    .into_any(),
2244            )
2245        }
2246    }
2247
2248    // RPC handlers
2249
2250    async fn handle_follow(
2251        this: WeakViewHandle<Self>,
2252        envelope: TypedEnvelope<proto::Follow>,
2253        _: Arc<Client>,
2254        mut cx: AsyncAppContext,
2255    ) -> Result<proto::FollowResponse> {
2256        this.update(&mut cx, |this, cx| {
2257            let client = &this.app_state.client;
2258            this.leader_state
2259                .followers
2260                .insert(envelope.original_sender_id()?);
2261
2262            let active_view_id = this.active_item(cx).and_then(|i| {
2263                Some(
2264                    i.to_followable_item_handle(cx)?
2265                        .remote_id(client, cx)?
2266                        .to_proto(),
2267                )
2268            });
2269
2270            cx.notify();
2271
2272            Ok(proto::FollowResponse {
2273                active_view_id,
2274                views: this
2275                    .panes()
2276                    .iter()
2277                    .flat_map(|pane| {
2278                        let leader_id = this.leader_for_pane(pane);
2279                        pane.read(cx).items().filter_map({
2280                            let cx = &cx;
2281                            move |item| {
2282                                let item = item.to_followable_item_handle(cx)?;
2283                                let id = item.remote_id(client, cx)?.to_proto();
2284                                let variant = item.to_state_proto(cx)?;
2285                                Some(proto::View {
2286                                    id: Some(id),
2287                                    leader_id,
2288                                    variant: Some(variant),
2289                                })
2290                            }
2291                        })
2292                    })
2293                    .collect(),
2294            })
2295        })?
2296    }
2297
2298    async fn handle_unfollow(
2299        this: WeakViewHandle<Self>,
2300        envelope: TypedEnvelope<proto::Unfollow>,
2301        _: Arc<Client>,
2302        mut cx: AsyncAppContext,
2303    ) -> Result<()> {
2304        this.update(&mut cx, |this, cx| {
2305            this.leader_state
2306                .followers
2307                .remove(&envelope.original_sender_id()?);
2308            cx.notify();
2309            Ok(())
2310        })?
2311    }
2312
2313    async fn handle_update_followers(
2314        this: WeakViewHandle<Self>,
2315        envelope: TypedEnvelope<proto::UpdateFollowers>,
2316        _: Arc<Client>,
2317        cx: AsyncAppContext,
2318    ) -> Result<()> {
2319        let leader_id = envelope.original_sender_id()?;
2320        this.read_with(&cx, |this, _| {
2321            this.leader_updates_tx
2322                .unbounded_send((leader_id, envelope.payload))
2323        })??;
2324        Ok(())
2325    }
2326
2327    async fn process_leader_update(
2328        this: &WeakViewHandle<Self>,
2329        leader_id: PeerId,
2330        update: proto::UpdateFollowers,
2331        cx: &mut AsyncAppContext,
2332    ) -> Result<()> {
2333        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2334            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2335                this.update(cx, |this, _| {
2336                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2337                        for state in state.values_mut() {
2338                            state.active_view_id =
2339                                if let Some(active_view_id) = update_active_view.id.clone() {
2340                                    Some(ViewId::from_proto(active_view_id)?)
2341                                } else {
2342                                    None
2343                                };
2344                        }
2345                    }
2346                    anyhow::Ok(())
2347                })??;
2348            }
2349            proto::update_followers::Variant::UpdateView(update_view) => {
2350                let variant = update_view
2351                    .variant
2352                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2353                let id = update_view
2354                    .id
2355                    .ok_or_else(|| anyhow!("missing update view id"))?;
2356                let mut tasks = Vec::new();
2357                this.update(cx, |this, cx| {
2358                    let project = this.project.clone();
2359                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2360                        for state in state.values_mut() {
2361                            let view_id = ViewId::from_proto(id.clone())?;
2362                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2363                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2364                            }
2365                        }
2366                    }
2367                    anyhow::Ok(())
2368                })??;
2369                try_join_all(tasks).await.log_err();
2370            }
2371            proto::update_followers::Variant::CreateView(view) => {
2372                let panes = this.read_with(cx, |this, _| {
2373                    this.follower_states_by_leader
2374                        .get(&leader_id)
2375                        .into_iter()
2376                        .flat_map(|states_by_pane| states_by_pane.keys())
2377                        .cloned()
2378                        .collect()
2379                })?;
2380                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2381            }
2382        }
2383        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2384        Ok(())
2385    }
2386
2387    async fn add_views_from_leader(
2388        this: WeakViewHandle<Self>,
2389        leader_id: PeerId,
2390        panes: Vec<ViewHandle<Pane>>,
2391        views: Vec<proto::View>,
2392        cx: &mut AsyncAppContext,
2393    ) -> Result<()> {
2394        let project = this.read_with(cx, |this, _| this.project.clone())?;
2395        let replica_id = project
2396            .read_with(cx, |project, _| {
2397                project
2398                    .collaborators()
2399                    .get(&leader_id)
2400                    .map(|c| c.replica_id)
2401            })
2402            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2403
2404        let item_builders = cx.update(|cx| {
2405            cx.default_global::<FollowableItemBuilders>()
2406                .values()
2407                .map(|b| b.0)
2408                .collect::<Vec<_>>()
2409        });
2410
2411        let mut item_tasks_by_pane = HashMap::default();
2412        for pane in panes {
2413            let mut item_tasks = Vec::new();
2414            let mut leader_view_ids = Vec::new();
2415            for view in &views {
2416                let Some(id) = &view.id else { continue };
2417                let id = ViewId::from_proto(id.clone())?;
2418                let mut variant = view.variant.clone();
2419                if variant.is_none() {
2420                    Err(anyhow!("missing variant"))?;
2421                }
2422                for build_item in &item_builders {
2423                    let task = cx.update(|cx| {
2424                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2425                    });
2426                    if let Some(task) = task {
2427                        item_tasks.push(task);
2428                        leader_view_ids.push(id);
2429                        break;
2430                    } else {
2431                        assert!(variant.is_some());
2432                    }
2433                }
2434            }
2435
2436            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2437        }
2438
2439        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2440            let items = futures::future::try_join_all(item_tasks).await?;
2441            this.update(cx, |this, cx| {
2442                let state = this
2443                    .follower_states_by_leader
2444                    .get_mut(&leader_id)?
2445                    .get_mut(&pane)?;
2446
2447                for (id, item) in leader_view_ids.into_iter().zip(items) {
2448                    item.set_leader_replica_id(Some(replica_id), cx);
2449                    state.items_by_leader_view_id.insert(id, item);
2450                }
2451
2452                Some(())
2453            })?;
2454        }
2455        Ok(())
2456    }
2457
2458    fn update_followers(
2459        &self,
2460        update: proto::update_followers::Variant,
2461        cx: &AppContext,
2462    ) -> Option<()> {
2463        let project_id = self.project.read(cx).remote_id()?;
2464        if !self.leader_state.followers.is_empty() {
2465            self.app_state
2466                .client
2467                .send(proto::UpdateFollowers {
2468                    project_id,
2469                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2470                    variant: Some(update),
2471                })
2472                .log_err();
2473        }
2474        None
2475    }
2476
2477    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2478        self.follower_states_by_leader
2479            .iter()
2480            .find_map(|(leader_id, state)| {
2481                if state.contains_key(pane) {
2482                    Some(*leader_id)
2483                } else {
2484                    None
2485                }
2486            })
2487    }
2488
2489    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2490        cx.notify();
2491
2492        let call = self.active_call()?;
2493        let room = call.read(cx).room()?.read(cx);
2494        let participant = room.remote_participant_for_peer_id(leader_id)?;
2495        let mut items_to_activate = Vec::new();
2496        match participant.location {
2497            call::ParticipantLocation::SharedProject { project_id } => {
2498                if Some(project_id) == self.project.read(cx).remote_id() {
2499                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2500                        if let Some(item) = state
2501                            .active_view_id
2502                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2503                        {
2504                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2505                        } else {
2506                            if let Some(shared_screen) =
2507                                self.shared_screen_for_peer(leader_id, pane, cx)
2508                            {
2509                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2510                            }
2511                        }
2512                    }
2513                }
2514            }
2515            call::ParticipantLocation::UnsharedProject => {}
2516            call::ParticipantLocation::External => {
2517                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2518                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2519                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2520                    }
2521                }
2522            }
2523        }
2524
2525        for (pane, item) in items_to_activate {
2526            let pane_was_focused = pane.read(cx).has_focus();
2527            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2528                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2529            } else {
2530                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2531            }
2532
2533            if pane_was_focused {
2534                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2535            }
2536        }
2537
2538        None
2539    }
2540
2541    fn shared_screen_for_peer(
2542        &self,
2543        peer_id: PeerId,
2544        pane: &ViewHandle<Pane>,
2545        cx: &mut ViewContext<Self>,
2546    ) -> Option<ViewHandle<SharedScreen>> {
2547        let call = self.active_call()?;
2548        let room = call.read(cx).room()?.read(cx);
2549        let participant = room.remote_participant_for_peer_id(peer_id)?;
2550        let track = participant.tracks.values().next()?.clone();
2551        let user = participant.user.clone();
2552
2553        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2554            if item.read(cx).peer_id == peer_id {
2555                return Some(item);
2556            }
2557        }
2558
2559        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2560    }
2561
2562    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2563        if active {
2564            cx.background()
2565                .spawn(persistence::DB.update_timestamp(self.database_id()))
2566                .detach();
2567        } else {
2568            for pane in &self.panes {
2569                pane.update(cx, |pane, cx| {
2570                    if let Some(item) = pane.active_item() {
2571                        item.workspace_deactivated(cx);
2572                    }
2573                    if matches!(
2574                        settings::get::<WorkspaceSettings>(cx).autosave,
2575                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2576                    ) {
2577                        for item in pane.items() {
2578                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2579                                .detach_and_log_err(cx);
2580                        }
2581                    }
2582                });
2583            }
2584        }
2585    }
2586
2587    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2588        self.active_call.as_ref().map(|(call, _)| call)
2589    }
2590
2591    fn on_active_call_event(
2592        &mut self,
2593        _: ModelHandle<ActiveCall>,
2594        event: &call::room::Event,
2595        cx: &mut ViewContext<Self>,
2596    ) {
2597        match event {
2598            call::room::Event::ParticipantLocationChanged { participant_id }
2599            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2600                self.leader_updated(*participant_id, cx);
2601            }
2602            _ => {}
2603        }
2604    }
2605
2606    pub fn database_id(&self) -> WorkspaceId {
2607        self.database_id
2608    }
2609
2610    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2611        let project = self.project().read(cx);
2612
2613        if project.is_local() {
2614            Some(
2615                project
2616                    .visible_worktrees(cx)
2617                    .map(|worktree| worktree.read(cx).abs_path())
2618                    .collect::<Vec<_>>()
2619                    .into(),
2620            )
2621        } else {
2622            None
2623        }
2624    }
2625
2626    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2627        match member {
2628            Member::Axis(PaneAxis { members, .. }) => {
2629                for child in members.iter() {
2630                    self.remove_panes(child.clone(), cx)
2631                }
2632            }
2633            Member::Pane(pane) => {
2634                self.force_remove_pane(&pane, cx);
2635            }
2636        }
2637    }
2638
2639    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
2640        self.panes.retain(|p| p != pane);
2641        cx.focus(self.panes.last().unwrap());
2642        if self.last_active_center_pane == Some(pane.downgrade()) {
2643            self.last_active_center_pane = None;
2644        }
2645        cx.notify();
2646    }
2647
2648    fn serialize_workspace(&self, cx: &AppContext) {
2649        fn serialize_pane_handle(
2650            pane_handle: &ViewHandle<Pane>,
2651            cx: &AppContext,
2652        ) -> SerializedPane {
2653            let (items, active) = {
2654                let pane = pane_handle.read(cx);
2655                let active_item_id = pane.active_item().map(|item| item.id());
2656                (
2657                    pane.items()
2658                        .filter_map(|item_handle| {
2659                            Some(SerializedItem {
2660                                kind: Arc::from(item_handle.serialized_item_kind()?),
2661                                item_id: item_handle.id(),
2662                                active: Some(item_handle.id()) == active_item_id,
2663                            })
2664                        })
2665                        .collect::<Vec<_>>(),
2666                    pane.is_active(),
2667                )
2668            };
2669
2670            SerializedPane::new(items, active)
2671        }
2672
2673        fn build_serialized_pane_group(
2674            pane_group: &Member,
2675            cx: &AppContext,
2676        ) -> SerializedPaneGroup {
2677            match pane_group {
2678                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2679                    axis: *axis,
2680                    children: members
2681                        .iter()
2682                        .map(|member| build_serialized_pane_group(member, cx))
2683                        .collect::<Vec<_>>(),
2684                },
2685                Member::Pane(pane_handle) => {
2686                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2687                }
2688            }
2689        }
2690
2691        fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure {
2692            let left_dock = this.left_dock.read(cx);
2693            let left_visible = left_dock.is_open();
2694            let left_active_panel = left_dock.active_panel().and_then(|panel| {
2695                Some(
2696                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2697                        .to_string(),
2698                )
2699            });
2700
2701            let right_dock = this.right_dock.read(cx);
2702            let right_visible = right_dock.is_open();
2703            let right_active_panel = right_dock.active_panel().and_then(|panel| {
2704                Some(
2705                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2706                        .to_string(),
2707                )
2708            });
2709
2710            let bottom_dock = this.bottom_dock.read(cx);
2711            let bottom_visible = bottom_dock.is_open();
2712            let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| {
2713                Some(
2714                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2715                        .to_string(),
2716                )
2717            });
2718
2719            DockStructure {
2720                left: DockData {
2721                    visible: left_visible,
2722                    active_panel: left_active_panel,
2723                },
2724                right: DockData {
2725                    visible: right_visible,
2726                    active_panel: right_active_panel,
2727                },
2728                bottom: DockData {
2729                    visible: bottom_visible,
2730                    active_panel: bottom_active_panel,
2731                },
2732            }
2733        }
2734
2735        if let Some(location) = self.location(cx) {
2736            // Load bearing special case:
2737            //  - with_local_workspace() relies on this to not have other stuff open
2738            //    when you open your log
2739            if !location.paths().is_empty() {
2740                let center_group = build_serialized_pane_group(&self.center.root, cx);
2741                let docks = build_serialized_docks(self, cx);
2742
2743                let serialized_workspace = SerializedWorkspace {
2744                    id: self.database_id,
2745                    location,
2746                    center_group,
2747                    bounds: Default::default(),
2748                    display: Default::default(),
2749                    docks,
2750                };
2751
2752                cx.background()
2753                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2754                    .detach();
2755            }
2756        }
2757    }
2758
2759    pub(crate) fn load_workspace(
2760        workspace: WeakViewHandle<Workspace>,
2761        serialized_workspace: SerializedWorkspace,
2762        paths_to_open: Vec<Option<ProjectPath>>,
2763        cx: &mut AppContext,
2764    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
2765        cx.spawn(|mut cx| async move {
2766            let result = async_iife! {{
2767                let (project, old_center_pane) =
2768                workspace.read_with(&cx, |workspace, _| {
2769                    (
2770                        workspace.project().clone(),
2771                        workspace.last_active_center_pane.clone(),
2772                    )
2773                })?;
2774
2775                let mut center_items = None;
2776                let mut center_group = None;
2777                // Traverse the splits tree and add to things
2778                if let Some((group, active_pane, items)) = serialized_workspace
2779                        .center_group
2780                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2781                        .await {
2782                    center_items = Some(items);
2783                    center_group = Some((group, active_pane))
2784                }
2785
2786                let resulting_list = cx.read(|cx| {
2787                    let mut opened_items = center_items
2788                        .unwrap_or_default()
2789                        .into_iter()
2790                        .filter_map(|item| {
2791                            let item = item?;
2792                            let project_path = item.project_path(cx)?;
2793                            Some((project_path, item))
2794                        })
2795                        .collect::<HashMap<_, _>>();
2796
2797                    paths_to_open
2798                        .into_iter()
2799                        .map(|path_to_open| {
2800                            path_to_open.map(|path_to_open| {
2801                                Ok(opened_items.remove(&path_to_open))
2802                            })
2803                            .transpose()
2804                            .map(|item| item.flatten())
2805                            .transpose()
2806                        })
2807                        .collect::<Vec<_>>()
2808                });
2809
2810                // Remove old panes from workspace panes list
2811                workspace.update(&mut cx, |workspace, cx| {
2812                    if let Some((center_group, active_pane)) = center_group {
2813                        workspace.remove_panes(workspace.center.root.clone(), cx);
2814
2815                        // Swap workspace center group
2816                        workspace.center = PaneGroup::with_root(center_group);
2817
2818                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
2819                        cx.focus_self();
2820
2821                        if let Some(active_pane) = active_pane {
2822                            cx.focus(&active_pane);
2823                        } else {
2824                            cx.focus(workspace.panes.last().unwrap());
2825                        }
2826                    } else {
2827                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2828                        if let Some(old_center_handle) = old_center_handle {
2829                            cx.focus(&old_center_handle)
2830                        } else {
2831                            cx.focus_self()
2832                        }
2833                    }
2834
2835                    let docks = serialized_workspace.docks;
2836                    workspace.left_dock.update(cx, |dock, cx| {
2837                        dock.set_open(docks.left.visible, cx);
2838                        if let Some(active_panel) = docks.left.active_panel {
2839                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
2840                                dock.activate_panel(ix, cx);
2841                            }
2842                        }
2843                    });
2844                    workspace.right_dock.update(cx, |dock, cx| {
2845                        dock.set_open(docks.right.visible, cx);
2846                        if let Some(active_panel) = docks.right.active_panel {
2847                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
2848                                dock.activate_panel(ix, cx);
2849                            }
2850                        }
2851                    });
2852                    workspace.bottom_dock.update(cx, |dock, cx| {
2853                        dock.set_open(docks.bottom.visible, cx);
2854                        if let Some(active_panel) = docks.bottom.active_panel {
2855                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
2856                                dock.activate_panel(ix, cx);
2857                            }
2858                        }
2859                    });
2860
2861                    cx.notify();
2862                })?;
2863
2864                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2865                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
2866
2867                Ok::<_, anyhow::Error>(resulting_list)
2868            }};
2869
2870            result.await.unwrap_or_default()
2871        })
2872    }
2873
2874    #[cfg(any(test, feature = "test-support"))]
2875    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2876        let app_state = Arc::new(AppState {
2877            languages: project.read(cx).languages().clone(),
2878            client: project.read(cx).client(),
2879            user_store: project.read(cx).user_store(),
2880            fs: project.read(cx).fs().clone(),
2881            build_window_options: |_, _, _| Default::default(),
2882            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
2883            background_actions: || &[],
2884        });
2885        Self::new(0, project, app_state, cx)
2886    }
2887
2888    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
2889        let dock = match position {
2890            DockPosition::Left => &self.left_dock,
2891            DockPosition::Right => &self.right_dock,
2892            DockPosition::Bottom => &self.bottom_dock,
2893        };
2894        let active_panel = dock.read(cx).active_panel()?;
2895        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
2896            dock.read(cx).render_placeholder(cx)
2897        } else {
2898            ChildView::new(dock, cx).into_any()
2899        };
2900
2901        Some(
2902            element
2903                .constrained()
2904                .dynamically(move |constraint, _, cx| match position {
2905                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
2906                        Vector2F::new(20., constraint.min.y()),
2907                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
2908                    ),
2909                    DockPosition::Bottom => SizeConstraint::new(
2910                        Vector2F::new(constraint.min.x(), 20.),
2911                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
2912                    ),
2913                })
2914                .into_any(),
2915        )
2916    }
2917}
2918
2919async fn open_items(
2920    serialized_workspace: Option<SerializedWorkspace>,
2921    workspace: &WeakViewHandle<Workspace>,
2922    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
2923    app_state: Arc<AppState>,
2924    mut cx: AsyncAppContext,
2925) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
2926    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
2927
2928    if let Some(serialized_workspace) = serialized_workspace {
2929        let workspace = workspace.clone();
2930        let restored_items = cx
2931            .update(|cx| {
2932                Workspace::load_workspace(
2933                    workspace,
2934                    serialized_workspace,
2935                    project_paths_to_open
2936                        .iter()
2937                        .map(|(_, project_path)| project_path)
2938                        .cloned()
2939                        .collect(),
2940                    cx,
2941                )
2942            })
2943            .await;
2944
2945        let restored_project_paths = cx.read(|cx| {
2946            restored_items
2947                .iter()
2948                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
2949                .collect::<HashSet<_>>()
2950        });
2951
2952        opened_items = restored_items;
2953        project_paths_to_open
2954            .iter_mut()
2955            .for_each(|(_, project_path)| {
2956                if let Some(project_path_to_open) = project_path {
2957                    if restored_project_paths.contains(project_path_to_open) {
2958                        *project_path = None;
2959                    }
2960                }
2961            });
2962    } else {
2963        for _ in 0..project_paths_to_open.len() {
2964            opened_items.push(None);
2965        }
2966    }
2967    assert!(opened_items.len() == project_paths_to_open.len());
2968
2969    let tasks =
2970        project_paths_to_open
2971            .into_iter()
2972            .enumerate()
2973            .map(|(i, (abs_path, project_path))| {
2974                let workspace = workspace.clone();
2975                cx.spawn(|mut cx| {
2976                    let fs = app_state.fs.clone();
2977                    async move {
2978                        let file_project_path = project_path?;
2979                        if fs.is_file(&abs_path).await {
2980                            Some((
2981                                i,
2982                                workspace
2983                                    .update(&mut cx, |workspace, cx| {
2984                                        workspace.open_path(file_project_path, None, true, cx)
2985                                    })
2986                                    .log_err()?
2987                                    .await,
2988                            ))
2989                        } else {
2990                            None
2991                        }
2992                    }
2993                })
2994            });
2995
2996    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
2997        .await
2998        .into_iter()
2999    {
3000        if let Some((i, path_open_result)) = maybe_opened_path {
3001            opened_items[i] = Some(path_open_result);
3002        }
3003    }
3004
3005    opened_items
3006}
3007
3008fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3009    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3010
3011    workspace
3012        .update(cx, |workspace, cx| {
3013            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3014                workspace.show_notification_once(0, cx, |cx| {
3015                    cx.add_view(|_| {
3016                        MessageNotification::new("Failed to load any database file.")
3017                            .with_click_message("Click to let us know about this error")
3018                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3019                    })
3020                });
3021            } else {
3022                let backup_path = (*db::BACKUP_DB_PATH).read();
3023                if let Some(backup_path) = backup_path.clone() {
3024                    workspace.show_notification_once(0, cx, move |cx| {
3025                        cx.add_view(move |_| {
3026                            MessageNotification::new(format!(
3027                                "Database file was corrupted. Old database backed up to {}",
3028                                backup_path.display()
3029                            ))
3030                            .with_click_message("Click to show old database in finder")
3031                            .on_click(move |cx| {
3032                                cx.platform().open_url(&backup_path.to_string_lossy())
3033                            })
3034                        })
3035                    });
3036                }
3037            }
3038        })
3039        .log_err();
3040}
3041
3042impl Entity for Workspace {
3043    type Event = Event;
3044}
3045
3046impl View for Workspace {
3047    fn ui_name() -> &'static str {
3048        "Workspace"
3049    }
3050
3051    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3052        let theme = theme::current(cx).clone();
3053        Stack::new()
3054            .with_child(
3055                Flex::column()
3056                    .with_child(self.render_titlebar(&theme, cx))
3057                    .with_child(
3058                        Stack::new()
3059                            .with_child({
3060                                let project = self.project.clone();
3061                                Flex::row()
3062                                    .with_children(self.render_dock(DockPosition::Left, cx))
3063                                    .with_child(
3064                                        Flex::column()
3065                                            .with_child(
3066                                                FlexItem::new(
3067                                                    self.center.render(
3068                                                        &project,
3069                                                        &theme,
3070                                                        &self.follower_states_by_leader,
3071                                                        self.active_call(),
3072                                                        self.active_pane(),
3073                                                        self.zoomed
3074                                                            .as_ref()
3075                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3076                                                            .as_ref(),
3077                                                        &self.app_state,
3078                                                        cx,
3079                                                    ),
3080                                                )
3081                                                .flex(1., true),
3082                                            )
3083                                            .with_children(
3084                                                self.render_dock(DockPosition::Bottom, cx),
3085                                            )
3086                                            .flex(1., true),
3087                                    )
3088                                    .with_children(self.render_dock(DockPosition::Right, cx))
3089                            })
3090                            .with_child(Overlay::new(
3091                                Stack::new()
3092                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3093                                        enum ZoomBackground {}
3094                                        let zoomed = zoomed.upgrade(cx)?;
3095                                        Some(
3096                                            ChildView::new(&zoomed, cx)
3097                                                .contained()
3098                                                .with_style(theme.workspace.zoomed_foreground)
3099                                                .aligned()
3100                                                .contained()
3101                                                .with_style(theme.workspace.zoomed_background)
3102                                                .mouse::<ZoomBackground>(0)
3103                                                .capture_all()
3104                                                .on_down(
3105                                                    MouseButton::Left,
3106                                                    |_, this: &mut Self, cx| {
3107                                                        this.zoom_out(cx);
3108                                                    },
3109                                                ),
3110                                        )
3111                                    }))
3112                                    .with_children(self.modal.as_ref().map(|modal| {
3113                                        ChildView::new(modal, cx)
3114                                            .contained()
3115                                            .with_style(theme.workspace.modal)
3116                                            .aligned()
3117                                            .top()
3118                                    }))
3119                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3120                            ))
3121                            .flex(1.0, true),
3122                    )
3123                    .with_child(ChildView::new(&self.status_bar, cx))
3124                    .contained()
3125                    .with_background_color(theme.workspace.background),
3126            )
3127            .with_children(DragAndDrop::render(cx))
3128            .with_children(self.render_disconnected_overlay(cx))
3129            .into_any_named("workspace")
3130    }
3131
3132    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3133        if cx.is_self_focused() {
3134            cx.focus(&self.active_pane);
3135        }
3136    }
3137}
3138
3139impl ViewId {
3140    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3141        Ok(Self {
3142            creator: message
3143                .creator
3144                .ok_or_else(|| anyhow!("creator is missing"))?,
3145            id: message.id,
3146        })
3147    }
3148
3149    pub(crate) fn to_proto(&self) -> proto::ViewId {
3150        proto::ViewId {
3151            creator: Some(self.creator),
3152            id: self.id,
3153        }
3154    }
3155}
3156
3157pub trait WorkspaceHandle {
3158    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3159}
3160
3161impl WorkspaceHandle for ViewHandle<Workspace> {
3162    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3163        self.read(cx)
3164            .worktrees(cx)
3165            .flat_map(|worktree| {
3166                let worktree_id = worktree.read(cx).id();
3167                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3168                    worktree_id,
3169                    path: f.path.clone(),
3170                })
3171            })
3172            .collect::<Vec<_>>()
3173    }
3174}
3175
3176impl std::fmt::Debug for OpenPaths {
3177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3178        f.debug_struct("OpenPaths")
3179            .field("paths", &self.paths)
3180            .finish()
3181    }
3182}
3183
3184pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3185
3186pub fn activate_workspace_for_project(
3187    cx: &mut AsyncAppContext,
3188    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3189) -> Option<WeakViewHandle<Workspace>> {
3190    for window_id in cx.window_ids() {
3191        let handle = cx
3192            .update_window(window_id, |cx| {
3193                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3194                    let project = workspace_handle.read(cx).project.clone();
3195                    if project.update(cx, &predicate) {
3196                        cx.activate_window();
3197                        return Some(workspace_handle.clone());
3198                    }
3199                }
3200                None
3201            })
3202            .flatten();
3203
3204        if let Some(handle) = handle {
3205            return Some(handle.downgrade());
3206        }
3207    }
3208    None
3209}
3210
3211pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3212    DB.last_workspace().await.log_err().flatten()
3213}
3214
3215#[allow(clippy::type_complexity)]
3216pub fn open_paths(
3217    abs_paths: &[PathBuf],
3218    app_state: &Arc<AppState>,
3219    requesting_window_id: Option<usize>,
3220    cx: &mut AppContext,
3221) -> Task<
3222    Result<(
3223        WeakViewHandle<Workspace>,
3224        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3225    )>,
3226> {
3227    let app_state = app_state.clone();
3228    let abs_paths = abs_paths.to_vec();
3229    cx.spawn(|mut cx| async move {
3230        // Open paths in existing workspace if possible
3231        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3232            project.contains_paths(&abs_paths, cx)
3233        });
3234
3235        if let Some(existing) = existing {
3236            Ok((
3237                existing.clone(),
3238                existing
3239                    .update(&mut cx, |workspace, cx| {
3240                        workspace.open_paths(abs_paths, true, cx)
3241                    })?
3242                    .await,
3243            ))
3244        } else {
3245            Ok(cx
3246                .update(|cx| {
3247                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3248                })
3249                .await)
3250        }
3251    })
3252}
3253
3254pub fn open_new(
3255    app_state: &Arc<AppState>,
3256    cx: &mut AppContext,
3257    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3258) -> Task<()> {
3259    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3260    cx.spawn(|mut cx| async move {
3261        let (workspace, opened_paths) = task.await;
3262
3263        workspace
3264            .update(&mut cx, |workspace, cx| {
3265                if opened_paths.is_empty() {
3266                    init(workspace, cx)
3267                }
3268            })
3269            .log_err();
3270    })
3271}
3272
3273pub fn create_and_open_local_file(
3274    path: &'static Path,
3275    cx: &mut ViewContext<Workspace>,
3276    default_content: impl 'static + Send + FnOnce() -> Rope,
3277) -> Task<Result<Box<dyn ItemHandle>>> {
3278    cx.spawn(|workspace, mut cx| async move {
3279        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3280        if !fs.is_file(path).await {
3281            fs.create_file(path, Default::default()).await?;
3282            fs.save(path, &default_content(), Default::default())
3283                .await?;
3284        }
3285
3286        let mut items = workspace
3287            .update(&mut cx, |workspace, cx| {
3288                workspace.with_local_workspace(cx, |workspace, cx| {
3289                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3290                })
3291            })?
3292            .await?
3293            .await;
3294
3295        let item = items.pop().flatten();
3296        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3297    })
3298}
3299
3300pub fn join_remote_project(
3301    project_id: u64,
3302    follow_user_id: u64,
3303    app_state: Arc<AppState>,
3304    cx: &mut AppContext,
3305) -> Task<Result<()>> {
3306    cx.spawn(|mut cx| async move {
3307        let existing_workspace = cx
3308            .window_ids()
3309            .into_iter()
3310            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3311            .find(|workspace| {
3312                cx.read_window(workspace.window_id(), |cx| {
3313                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3314                })
3315                .unwrap_or(false)
3316            });
3317
3318        let workspace = if let Some(existing_workspace) = existing_workspace {
3319            existing_workspace.downgrade()
3320        } else {
3321            let active_call = cx.read(ActiveCall::global);
3322            let room = active_call
3323                .read_with(&cx, |call, _| call.room().cloned())
3324                .ok_or_else(|| anyhow!("not in a call"))?;
3325            let project = room
3326                .update(&mut cx, |room, cx| {
3327                    room.join_project(
3328                        project_id,
3329                        app_state.languages.clone(),
3330                        app_state.fs.clone(),
3331                        cx,
3332                    )
3333                })
3334                .await?;
3335
3336            let (_, workspace) = cx.add_window(
3337                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3338                |cx| Workspace::new(0, project, app_state.clone(), cx),
3339            );
3340            (app_state.initialize_workspace)(
3341                workspace.downgrade(),
3342                false,
3343                app_state.clone(),
3344                cx.clone(),
3345            )
3346            .await
3347            .log_err();
3348
3349            workspace.downgrade()
3350        };
3351
3352        cx.activate_window(workspace.window_id());
3353        cx.platform().activate(true);
3354
3355        workspace.update(&mut cx, |workspace, cx| {
3356            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3357                let follow_peer_id = room
3358                    .read(cx)
3359                    .remote_participants()
3360                    .iter()
3361                    .find(|(_, participant)| participant.user.id == follow_user_id)
3362                    .map(|(_, p)| p.peer_id)
3363                    .or_else(|| {
3364                        // If we couldn't follow the given user, follow the host instead.
3365                        let collaborator = workspace
3366                            .project()
3367                            .read(cx)
3368                            .collaborators()
3369                            .values()
3370                            .find(|collaborator| collaborator.replica_id == 0)?;
3371                        Some(collaborator.peer_id)
3372                    });
3373
3374                if let Some(follow_peer_id) = follow_peer_id {
3375                    if !workspace.is_being_followed(follow_peer_id) {
3376                        workspace
3377                            .toggle_follow(follow_peer_id, cx)
3378                            .map(|follow| follow.detach_and_log_err(cx));
3379                    }
3380                }
3381            }
3382        })?;
3383
3384        anyhow::Ok(())
3385    })
3386}
3387
3388pub fn restart(_: &Restart, cx: &mut AppContext) {
3389    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3390    cx.spawn(|mut cx| async move {
3391        let mut workspaces = cx
3392            .window_ids()
3393            .into_iter()
3394            .filter_map(|window_id| {
3395                Some(
3396                    cx.root_view(window_id)?
3397                        .clone()
3398                        .downcast::<Workspace>()?
3399                        .downgrade(),
3400                )
3401            })
3402            .collect::<Vec<_>>();
3403
3404        // If multiple windows have unsaved changes, and need a save prompt,
3405        // prompt in the active window before switching to a different window.
3406        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3407
3408        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3409            let answer = cx.prompt(
3410                workspace.window_id(),
3411                PromptLevel::Info,
3412                "Are you sure you want to restart?",
3413                &["Restart", "Cancel"],
3414            );
3415
3416            if let Some(mut answer) = answer {
3417                let answer = answer.next().await;
3418                if answer != Some(0) {
3419                    return Ok(());
3420                }
3421            }
3422        }
3423
3424        // If the user cancels any save prompt, then keep the app open.
3425        for workspace in workspaces {
3426            if !workspace
3427                .update(&mut cx, |workspace, cx| {
3428                    workspace.prepare_to_close(true, cx)
3429                })?
3430                .await?
3431            {
3432                return Ok(());
3433            }
3434        }
3435        cx.platform().restart();
3436        anyhow::Ok(())
3437    })
3438    .detach_and_log_err(cx);
3439}
3440
3441fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3442    let mut parts = value.split(',');
3443    let width: usize = parts.next()?.parse().ok()?;
3444    let height: usize = parts.next()?.parse().ok()?;
3445    Some(vec2f(width as f32, height as f32))
3446}
3447
3448fn default_true() -> bool {
3449    true
3450}
3451
3452#[cfg(test)]
3453mod tests {
3454    use super::*;
3455    use crate::{
3456        dock::test::{TestPanel, TestPanelEvent},
3457        item::test::{TestItem, TestItemEvent, TestProjectItem},
3458    };
3459    use fs::FakeFs;
3460    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3461    use project::{Project, ProjectEntryId};
3462    use serde_json::json;
3463    use settings::SettingsStore;
3464    use std::{cell::RefCell, rc::Rc};
3465
3466    #[gpui::test]
3467    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3468        init_test(cx);
3469
3470        let fs = FakeFs::new(cx.background());
3471        let project = Project::test(fs, [], cx).await;
3472        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3473
3474        // Adding an item with no ambiguity renders the tab without detail.
3475        let item1 = cx.add_view(window_id, |_| {
3476            let mut item = TestItem::new();
3477            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3478            item
3479        });
3480        workspace.update(cx, |workspace, cx| {
3481            workspace.add_item(Box::new(item1.clone()), cx);
3482        });
3483        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3484
3485        // Adding an item that creates ambiguity increases the level of detail on
3486        // both tabs.
3487        let item2 = cx.add_view(window_id, |_| {
3488            let mut item = TestItem::new();
3489            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3490            item
3491        });
3492        workspace.update(cx, |workspace, cx| {
3493            workspace.add_item(Box::new(item2.clone()), cx);
3494        });
3495        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3496        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3497
3498        // Adding an item that creates ambiguity increases the level of detail only
3499        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3500        // we stop at the highest detail available.
3501        let item3 = cx.add_view(window_id, |_| {
3502            let mut item = TestItem::new();
3503            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3504            item
3505        });
3506        workspace.update(cx, |workspace, cx| {
3507            workspace.add_item(Box::new(item3.clone()), cx);
3508        });
3509        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3510        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3511        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3512    }
3513
3514    #[gpui::test]
3515    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3516        init_test(cx);
3517
3518        let fs = FakeFs::new(cx.background());
3519        fs.insert_tree(
3520            "/root1",
3521            json!({
3522                "one.txt": "",
3523                "two.txt": "",
3524            }),
3525        )
3526        .await;
3527        fs.insert_tree(
3528            "/root2",
3529            json!({
3530                "three.txt": "",
3531            }),
3532        )
3533        .await;
3534
3535        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3536        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3537        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3538        let worktree_id = project.read_with(cx, |project, cx| {
3539            project.worktrees(cx).next().unwrap().read(cx).id()
3540        });
3541
3542        let item1 = cx.add_view(window_id, |cx| {
3543            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3544        });
3545        let item2 = cx.add_view(window_id, |cx| {
3546            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3547        });
3548
3549        // Add an item to an empty pane
3550        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3551        project.read_with(cx, |project, cx| {
3552            assert_eq!(
3553                project.active_entry(),
3554                project
3555                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3556                    .map(|e| e.id)
3557            );
3558        });
3559        assert_eq!(
3560            cx.current_window_title(window_id).as_deref(),
3561            Some("one.txt β€” root1")
3562        );
3563
3564        // Add a second item to a non-empty pane
3565        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3566        assert_eq!(
3567            cx.current_window_title(window_id).as_deref(),
3568            Some("two.txt β€” root1")
3569        );
3570        project.read_with(cx, |project, cx| {
3571            assert_eq!(
3572                project.active_entry(),
3573                project
3574                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3575                    .map(|e| e.id)
3576            );
3577        });
3578
3579        // Close the active item
3580        pane.update(cx, |pane, cx| {
3581            pane.close_active_item(&Default::default(), cx).unwrap()
3582        })
3583        .await
3584        .unwrap();
3585        assert_eq!(
3586            cx.current_window_title(window_id).as_deref(),
3587            Some("one.txt β€” root1")
3588        );
3589        project.read_with(cx, |project, cx| {
3590            assert_eq!(
3591                project.active_entry(),
3592                project
3593                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3594                    .map(|e| e.id)
3595            );
3596        });
3597
3598        // Add a project folder
3599        project
3600            .update(cx, |project, cx| {
3601                project.find_or_create_local_worktree("/root2", true, cx)
3602            })
3603            .await
3604            .unwrap();
3605        assert_eq!(
3606            cx.current_window_title(window_id).as_deref(),
3607            Some("one.txt β€” root1, root2")
3608        );
3609
3610        // Remove a project folder
3611        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3612        assert_eq!(
3613            cx.current_window_title(window_id).as_deref(),
3614            Some("one.txt β€” root2")
3615        );
3616    }
3617
3618    #[gpui::test]
3619    async fn test_close_window(cx: &mut TestAppContext) {
3620        init_test(cx);
3621
3622        let fs = FakeFs::new(cx.background());
3623        fs.insert_tree("/root", json!({ "one": "" })).await;
3624
3625        let project = Project::test(fs, ["root".as_ref()], cx).await;
3626        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3627
3628        // When there are no dirty items, there's nothing to do.
3629        let item1 = cx.add_view(window_id, |_| TestItem::new());
3630        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3631        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3632        assert!(task.await.unwrap());
3633
3634        // When there are dirty untitled items, prompt to save each one. If the user
3635        // cancels any prompt, then abort.
3636        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3637        let item3 = cx.add_view(window_id, |cx| {
3638            TestItem::new()
3639                .with_dirty(true)
3640                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3641        });
3642        workspace.update(cx, |w, cx| {
3643            w.add_item(Box::new(item2.clone()), cx);
3644            w.add_item(Box::new(item3.clone()), cx);
3645        });
3646        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3647        cx.foreground().run_until_parked();
3648        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3649        cx.foreground().run_until_parked();
3650        assert!(!cx.has_pending_prompt(window_id));
3651        assert!(!task.await.unwrap());
3652    }
3653
3654    #[gpui::test]
3655    async fn test_close_pane_items(cx: &mut TestAppContext) {
3656        init_test(cx);
3657
3658        let fs = FakeFs::new(cx.background());
3659
3660        let project = Project::test(fs, None, cx).await;
3661        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3662
3663        let item1 = cx.add_view(window_id, |cx| {
3664            TestItem::new()
3665                .with_dirty(true)
3666                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3667        });
3668        let item2 = cx.add_view(window_id, |cx| {
3669            TestItem::new()
3670                .with_dirty(true)
3671                .with_conflict(true)
3672                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3673        });
3674        let item3 = cx.add_view(window_id, |cx| {
3675            TestItem::new()
3676                .with_dirty(true)
3677                .with_conflict(true)
3678                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3679        });
3680        let item4 = cx.add_view(window_id, |cx| {
3681            TestItem::new()
3682                .with_dirty(true)
3683                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3684        });
3685        let pane = workspace.update(cx, |workspace, cx| {
3686            workspace.add_item(Box::new(item1.clone()), cx);
3687            workspace.add_item(Box::new(item2.clone()), cx);
3688            workspace.add_item(Box::new(item3.clone()), cx);
3689            workspace.add_item(Box::new(item4.clone()), cx);
3690            workspace.active_pane().clone()
3691        });
3692
3693        let close_items = pane.update(cx, |pane, cx| {
3694            pane.activate_item(1, true, true, cx);
3695            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3696            let item1_id = item1.id();
3697            let item3_id = item3.id();
3698            let item4_id = item4.id();
3699            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
3700        });
3701        cx.foreground().run_until_parked();
3702
3703        // There's a prompt to save item 1.
3704        pane.read_with(cx, |pane, _| {
3705            assert_eq!(pane.items_len(), 4);
3706            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3707        });
3708        assert!(cx.has_pending_prompt(window_id));
3709
3710        // Confirm saving item 1.
3711        cx.simulate_prompt_answer(window_id, 0);
3712        cx.foreground().run_until_parked();
3713
3714        // Item 1 is saved. There's a prompt to save item 3.
3715        pane.read_with(cx, |pane, cx| {
3716            assert_eq!(item1.read(cx).save_count, 1);
3717            assert_eq!(item1.read(cx).save_as_count, 0);
3718            assert_eq!(item1.read(cx).reload_count, 0);
3719            assert_eq!(pane.items_len(), 3);
3720            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3721        });
3722        assert!(cx.has_pending_prompt(window_id));
3723
3724        // Cancel saving item 3.
3725        cx.simulate_prompt_answer(window_id, 1);
3726        cx.foreground().run_until_parked();
3727
3728        // Item 3 is reloaded. There's a prompt to save item 4.
3729        pane.read_with(cx, |pane, cx| {
3730            assert_eq!(item3.read(cx).save_count, 0);
3731            assert_eq!(item3.read(cx).save_as_count, 0);
3732            assert_eq!(item3.read(cx).reload_count, 1);
3733            assert_eq!(pane.items_len(), 2);
3734            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3735        });
3736        assert!(cx.has_pending_prompt(window_id));
3737
3738        // Confirm saving item 4.
3739        cx.simulate_prompt_answer(window_id, 0);
3740        cx.foreground().run_until_parked();
3741
3742        // There's a prompt for a path for item 4.
3743        cx.simulate_new_path_selection(|_| Some(Default::default()));
3744        close_items.await.unwrap();
3745
3746        // The requested items are closed.
3747        pane.read_with(cx, |pane, cx| {
3748            assert_eq!(item4.read(cx).save_count, 0);
3749            assert_eq!(item4.read(cx).save_as_count, 1);
3750            assert_eq!(item4.read(cx).reload_count, 0);
3751            assert_eq!(pane.items_len(), 1);
3752            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3753        });
3754    }
3755
3756    #[gpui::test]
3757    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3758        init_test(cx);
3759
3760        let fs = FakeFs::new(cx.background());
3761
3762        let project = Project::test(fs, [], cx).await;
3763        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3764
3765        // Create several workspace items with single project entries, and two
3766        // workspace items with multiple project entries.
3767        let single_entry_items = (0..=4)
3768            .map(|project_entry_id| {
3769                cx.add_view(window_id, |cx| {
3770                    TestItem::new()
3771                        .with_dirty(true)
3772                        .with_project_items(&[TestProjectItem::new(
3773                            project_entry_id,
3774                            &format!("{project_entry_id}.txt"),
3775                            cx,
3776                        )])
3777                })
3778            })
3779            .collect::<Vec<_>>();
3780        let item_2_3 = cx.add_view(window_id, |cx| {
3781            TestItem::new()
3782                .with_dirty(true)
3783                .with_singleton(false)
3784                .with_project_items(&[
3785                    single_entry_items[2].read(cx).project_items[0].clone(),
3786                    single_entry_items[3].read(cx).project_items[0].clone(),
3787                ])
3788        });
3789        let item_3_4 = cx.add_view(window_id, |cx| {
3790            TestItem::new()
3791                .with_dirty(true)
3792                .with_singleton(false)
3793                .with_project_items(&[
3794                    single_entry_items[3].read(cx).project_items[0].clone(),
3795                    single_entry_items[4].read(cx).project_items[0].clone(),
3796                ])
3797        });
3798
3799        // Create two panes that contain the following project entries:
3800        //   left pane:
3801        //     multi-entry items:   (2, 3)
3802        //     single-entry items:  0, 1, 2, 3, 4
3803        //   right pane:
3804        //     single-entry items:  1
3805        //     multi-entry items:   (3, 4)
3806        let left_pane = workspace.update(cx, |workspace, cx| {
3807            let left_pane = workspace.active_pane().clone();
3808            workspace.add_item(Box::new(item_2_3.clone()), cx);
3809            for item in single_entry_items {
3810                workspace.add_item(Box::new(item), cx);
3811            }
3812            left_pane.update(cx, |pane, cx| {
3813                pane.activate_item(2, true, true, cx);
3814            });
3815
3816            workspace
3817                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3818                .unwrap();
3819
3820            left_pane
3821        });
3822
3823        //Need to cause an effect flush in order to respect new focus
3824        workspace.update(cx, |workspace, cx| {
3825            workspace.add_item(Box::new(item_3_4.clone()), cx);
3826            cx.focus(&left_pane);
3827        });
3828
3829        // When closing all of the items in the left pane, we should be prompted twice:
3830        // once for project entry 0, and once for project entry 2. After those two
3831        // prompts, the task should complete.
3832
3833        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
3834        cx.foreground().run_until_parked();
3835        left_pane.read_with(cx, |pane, cx| {
3836            assert_eq!(
3837                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3838                &[ProjectEntryId::from_proto(0)]
3839            );
3840        });
3841        cx.simulate_prompt_answer(window_id, 0);
3842
3843        cx.foreground().run_until_parked();
3844        left_pane.read_with(cx, |pane, cx| {
3845            assert_eq!(
3846                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3847                &[ProjectEntryId::from_proto(2)]
3848            );
3849        });
3850        cx.simulate_prompt_answer(window_id, 0);
3851
3852        cx.foreground().run_until_parked();
3853        close.await.unwrap();
3854        left_pane.read_with(cx, |pane, _| {
3855            assert_eq!(pane.items_len(), 0);
3856        });
3857    }
3858
3859    #[gpui::test]
3860    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3861        init_test(cx);
3862
3863        let fs = FakeFs::new(cx.background());
3864
3865        let project = Project::test(fs, [], cx).await;
3866        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3867        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3868
3869        let item = cx.add_view(window_id, |cx| {
3870            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3871        });
3872        let item_id = item.id();
3873        workspace.update(cx, |workspace, cx| {
3874            workspace.add_item(Box::new(item.clone()), cx);
3875        });
3876
3877        // Autosave on window change.
3878        item.update(cx, |item, cx| {
3879            cx.update_global(|settings: &mut SettingsStore, cx| {
3880                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3881                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
3882                })
3883            });
3884            item.is_dirty = true;
3885        });
3886
3887        // Deactivating the window saves the file.
3888        cx.simulate_window_activation(None);
3889        deterministic.run_until_parked();
3890        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3891
3892        // Autosave on focus change.
3893        item.update(cx, |item, cx| {
3894            cx.focus_self();
3895            cx.update_global(|settings: &mut SettingsStore, cx| {
3896                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3897                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
3898                })
3899            });
3900            item.is_dirty = true;
3901        });
3902
3903        // Blurring the item saves the file.
3904        item.update(cx, |_, cx| cx.blur());
3905        deterministic.run_until_parked();
3906        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3907
3908        // Deactivating the window still saves the file.
3909        cx.simulate_window_activation(Some(window_id));
3910        item.update(cx, |item, cx| {
3911            cx.focus_self();
3912            item.is_dirty = true;
3913        });
3914        cx.simulate_window_activation(None);
3915
3916        deterministic.run_until_parked();
3917        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3918
3919        // Autosave after delay.
3920        item.update(cx, |item, cx| {
3921            cx.update_global(|settings: &mut SettingsStore, cx| {
3922                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3923                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
3924                })
3925            });
3926            item.is_dirty = true;
3927            cx.emit(TestItemEvent::Edit);
3928        });
3929
3930        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3931        deterministic.advance_clock(Duration::from_millis(250));
3932        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3933
3934        // After delay expires, the file is saved.
3935        deterministic.advance_clock(Duration::from_millis(250));
3936        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3937
3938        // Autosave on focus change, ensuring closing the tab counts as such.
3939        item.update(cx, |item, cx| {
3940            cx.update_global(|settings: &mut SettingsStore, cx| {
3941                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3942                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
3943                })
3944            });
3945            item.is_dirty = true;
3946        });
3947
3948        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
3949            .await
3950            .unwrap();
3951        assert!(!cx.has_pending_prompt(window_id));
3952        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3953
3954        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3955        workspace.update(cx, |workspace, cx| {
3956            workspace.add_item(Box::new(item.clone()), cx);
3957        });
3958        item.update(cx, |item, cx| {
3959            item.project_items[0].update(cx, |item, _| {
3960                item.entry_id = None;
3961            });
3962            item.is_dirty = true;
3963            cx.blur();
3964        });
3965        deterministic.run_until_parked();
3966        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3967
3968        // Ensure autosave is prevented for deleted files also when closing the buffer.
3969        let _close_items =
3970            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
3971        deterministic.run_until_parked();
3972        assert!(cx.has_pending_prompt(window_id));
3973        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3974    }
3975
3976    #[gpui::test]
3977    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
3978        init_test(cx);
3979
3980        let fs = FakeFs::new(cx.background());
3981
3982        let project = Project::test(fs, [], cx).await;
3983        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3984
3985        let item = cx.add_view(window_id, |cx| {
3986            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3987        });
3988        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3989        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3990        let toolbar_notify_count = Rc::new(RefCell::new(0));
3991
3992        workspace.update(cx, |workspace, cx| {
3993            workspace.add_item(Box::new(item.clone()), cx);
3994            let toolbar_notification_count = toolbar_notify_count.clone();
3995            cx.observe(&toolbar, move |_, _, _| {
3996                *toolbar_notification_count.borrow_mut() += 1
3997            })
3998            .detach();
3999        });
4000
4001        pane.read_with(cx, |pane, _| {
4002            assert!(!pane.can_navigate_backward());
4003            assert!(!pane.can_navigate_forward());
4004        });
4005
4006        item.update(cx, |item, cx| {
4007            item.set_state("one".to_string(), cx);
4008        });
4009
4010        // Toolbar must be notified to re-render the navigation buttons
4011        assert_eq!(*toolbar_notify_count.borrow(), 1);
4012
4013        pane.read_with(cx, |pane, _| {
4014            assert!(pane.can_navigate_backward());
4015            assert!(!pane.can_navigate_forward());
4016        });
4017
4018        workspace
4019            .update(cx, |workspace, cx| {
4020                Pane::go_back(workspace, Some(pane.downgrade()), cx)
4021            })
4022            .await
4023            .unwrap();
4024
4025        assert_eq!(*toolbar_notify_count.borrow(), 3);
4026        pane.read_with(cx, |pane, _| {
4027            assert!(!pane.can_navigate_backward());
4028            assert!(pane.can_navigate_forward());
4029        });
4030    }
4031
4032    #[gpui::test]
4033    async fn test_panels(cx: &mut gpui::TestAppContext) {
4034        init_test(cx);
4035        let fs = FakeFs::new(cx.background());
4036
4037        let project = Project::test(fs, [], cx).await;
4038        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4039
4040        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4041            // Add panel_1 on the left, panel_2 on the right.
4042            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4043            workspace.add_panel(panel_1.clone(), cx);
4044            workspace
4045                .left_dock()
4046                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4047            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4048            workspace.add_panel(panel_2.clone(), cx);
4049            workspace
4050                .right_dock()
4051                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4052
4053            let left_dock = workspace.left_dock();
4054            assert_eq!(
4055                left_dock.read(cx).active_panel().unwrap().id(),
4056                panel_1.id()
4057            );
4058            assert_eq!(
4059                left_dock.read(cx).active_panel_size(cx).unwrap(),
4060                panel_1.size(cx)
4061            );
4062
4063            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4064            assert_eq!(
4065                workspace.right_dock().read(cx).active_panel().unwrap().id(),
4066                panel_2.id()
4067            );
4068
4069            (panel_1, panel_2)
4070        });
4071
4072        // Move panel_1 to the right
4073        panel_1.update(cx, |panel_1, cx| {
4074            panel_1.set_position(DockPosition::Right, cx)
4075        });
4076
4077        workspace.update(cx, |workspace, cx| {
4078            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4079            // Since it was the only panel on the left, the left dock should now be closed.
4080            assert!(!workspace.left_dock().read(cx).is_open());
4081            assert!(workspace.left_dock().read(cx).active_panel().is_none());
4082            let right_dock = workspace.right_dock();
4083            assert_eq!(
4084                right_dock.read(cx).active_panel().unwrap().id(),
4085                panel_1.id()
4086            );
4087            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4088
4089            // Now we move panel_2Β to the left
4090            panel_2.set_position(DockPosition::Left, cx);
4091        });
4092
4093        workspace.update(cx, |workspace, cx| {
4094            // Since panel_2 was not visible on the right, we don't open the left dock.
4095            assert!(!workspace.left_dock().read(cx).is_open());
4096            // And the right dock is unaffected in it's displaying of panel_1
4097            assert!(workspace.right_dock().read(cx).is_open());
4098            assert_eq!(
4099                workspace.right_dock().read(cx).active_panel().unwrap().id(),
4100                panel_1.id()
4101            );
4102        });
4103
4104        // Move panel_1 back to the left
4105        panel_1.update(cx, |panel_1, cx| {
4106            panel_1.set_position(DockPosition::Left, cx)
4107        });
4108
4109        workspace.update(cx, |workspace, cx| {
4110            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4111            let left_dock = workspace.left_dock();
4112            assert!(left_dock.read(cx).is_open());
4113            assert_eq!(
4114                left_dock.read(cx).active_panel().unwrap().id(),
4115                panel_1.id()
4116            );
4117            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4118            // And right the dock should be closed as it no longer has any panels.
4119            assert!(!workspace.right_dock().read(cx).is_open());
4120
4121            // Now we move panel_1 to the bottom
4122            panel_1.set_position(DockPosition::Bottom, cx);
4123        });
4124
4125        workspace.update(cx, |workspace, cx| {
4126            // Since panel_1 was visible on the left, we close the left dock.
4127            assert!(!workspace.left_dock().read(cx).is_open());
4128            // The bottom dock is sized based on the panel's default size,
4129            // since the panel orientation changed from vertical to horizontal.
4130            let bottom_dock = workspace.bottom_dock();
4131            assert_eq!(
4132                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4133                panel_1.size(cx),
4134            );
4135            // Close bottom dock and move panel_1 back to the left.
4136            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4137            panel_1.set_position(DockPosition::Left, cx);
4138        });
4139
4140        // Emit activated event on panel 1
4141        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4142
4143        // Now the left dock is open and panel_1 is active and focused.
4144        workspace.read_with(cx, |workspace, cx| {
4145            let left_dock = workspace.left_dock();
4146            assert!(left_dock.read(cx).is_open());
4147            assert_eq!(
4148                left_dock.read(cx).active_panel().unwrap().id(),
4149                panel_1.id()
4150            );
4151            assert!(panel_1.is_focused(cx));
4152        });
4153
4154        // Emit closed event on panel 2, which is not active
4155        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4156
4157        // Wo don't close the left dock, because panel_2 wasn't the active panel
4158        workspace.read_with(cx, |workspace, cx| {
4159            let left_dock = workspace.left_dock();
4160            assert!(left_dock.read(cx).is_open());
4161            assert_eq!(
4162                left_dock.read(cx).active_panel().unwrap().id(),
4163                panel_1.id()
4164            );
4165        });
4166
4167        // Emitting a ZoomIn event shows the panel as zoomed.
4168        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4169        workspace.read_with(cx, |workspace, _| {
4170            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4171        });
4172
4173        // If focus is transferred to another view that's not a panel or another pane, we still show
4174        // the panel as zoomed.
4175        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4176        focus_receiver.update(cx, |_, cx| cx.focus_self());
4177        workspace.read_with(cx, |workspace, _| {
4178            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4179        });
4180
4181        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4182        workspace.update(cx, |_, cx| cx.focus_self());
4183        workspace.read_with(cx, |workspace, _| {
4184            assert_eq!(workspace.zoomed, None);
4185        });
4186
4187        // If focus is transferred again to another view that's not a panel or a pane, we won't
4188        // show the panel as zoomed because it wasn't zoomed before.
4189        focus_receiver.update(cx, |_, cx| cx.focus_self());
4190        workspace.read_with(cx, |workspace, _| {
4191            assert_eq!(workspace.zoomed, None);
4192        });
4193
4194        // When focus is transferred back to the panel, it is zoomed again.
4195        panel_1.update(cx, |_, cx| cx.focus_self());
4196        workspace.read_with(cx, |workspace, _| {
4197            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4198        });
4199
4200        // Emitting a ZoomOut event unzooms the panel.
4201        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4202        workspace.read_with(cx, |workspace, _| {
4203            assert_eq!(workspace.zoomed, None);
4204        });
4205
4206        // Emit closed event on panel 1, which is active
4207        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4208
4209        // Now the left dock is closed, because panel_1 was the active panel
4210        workspace.read_with(cx, |workspace, cx| {
4211            let left_dock = workspace.left_dock();
4212            assert!(!left_dock.read(cx).is_open());
4213        });
4214    }
4215
4216    pub fn init_test(cx: &mut TestAppContext) {
4217        cx.foreground().forbid_parking();
4218        cx.update(|cx| {
4219            cx.set_global(SettingsStore::test(cx));
4220            theme::init((), cx);
4221            language::init(cx);
4222            crate::init_settings(cx);
4223        });
4224    }
4225}