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        let project = self.project.read(cx);
 979        history
 980            .into_iter()
 981            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
 982            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
 983            .rev()
 984            .filter(|(history_path, abs_path)| {
 985                project
 986                    .worktree_for_id(history_path.worktree_id, cx)
 987                    .is_some()
 988                    || abs_path
 989                        .as_ref()
 990                        .and_then(|abs_path| {
 991                            let buffers_opened = abs_paths_opened.get(abs_path)?;
 992                            Some(buffers_opened.len() < 2)
 993                        })
 994                        .unwrap_or(false)
 995            })
 996            .take(limit.unwrap_or(usize::MAX))
 997            .collect()
 998    }
 999
1000    pub fn client(&self) -> &Client {
1001        &self.app_state.client
1002    }
1003
1004    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1005        self.titlebar_item = Some(item);
1006        cx.notify();
1007    }
1008
1009    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1010        self.titlebar_item.clone()
1011    }
1012
1013    /// Call the given callback with a workspace whose project is local.
1014    ///
1015    /// If the given workspace has a local project, then it will be passed
1016    /// to the callback. Otherwise, a new empty window will be created.
1017    pub fn with_local_workspace<T, F>(
1018        &mut self,
1019        cx: &mut ViewContext<Self>,
1020        callback: F,
1021    ) -> Task<Result<T>>
1022    where
1023        T: 'static,
1024        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1025    {
1026        if self.project.read(cx).is_local() {
1027            Task::Ready(Some(Ok(callback(self, cx))))
1028        } else {
1029            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1030            cx.spawn(|_vh, mut cx| async move {
1031                let (workspace, _) = task.await;
1032                workspace.update(&mut cx, callback)
1033            })
1034        }
1035    }
1036
1037    pub fn worktrees<'a>(
1038        &self,
1039        cx: &'a AppContext,
1040    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1041        self.project.read(cx).worktrees(cx)
1042    }
1043
1044    pub fn visible_worktrees<'a>(
1045        &self,
1046        cx: &'a AppContext,
1047    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1048        self.project.read(cx).visible_worktrees(cx)
1049    }
1050
1051    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1052        let futures = self
1053            .worktrees(cx)
1054            .filter_map(|worktree| worktree.read(cx).as_local())
1055            .map(|worktree| worktree.scan_complete())
1056            .collect::<Vec<_>>();
1057        async move {
1058            for future in futures {
1059                future.await;
1060            }
1061        }
1062    }
1063
1064    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1065        cx.spawn(|mut cx| async move {
1066            let id = cx
1067                .window_ids()
1068                .into_iter()
1069                .find(|&id| cx.window_is_active(id));
1070            if let Some(id) = id {
1071                //This can only get called when the window's project connection has been lost
1072                //so we don't need to prompt the user for anything and instead just close the window
1073                cx.remove_window(id);
1074            }
1075        })
1076        .detach();
1077    }
1078
1079    pub fn close(
1080        &mut self,
1081        _: &CloseWindow,
1082        cx: &mut ViewContext<Self>,
1083    ) -> Option<Task<Result<()>>> {
1084        let window_id = cx.window_id();
1085        let prepare = self.prepare_to_close(false, cx);
1086        Some(cx.spawn(|_, mut cx| async move {
1087            if prepare.await? {
1088                cx.remove_window(window_id);
1089            }
1090            Ok(())
1091        }))
1092    }
1093
1094    pub fn prepare_to_close(
1095        &mut self,
1096        quitting: bool,
1097        cx: &mut ViewContext<Self>,
1098    ) -> Task<Result<bool>> {
1099        let active_call = self.active_call().cloned();
1100        let window_id = cx.window_id();
1101
1102        cx.spawn(|this, mut cx| async move {
1103            let workspace_count = cx
1104                .window_ids()
1105                .into_iter()
1106                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
1107                .count();
1108
1109            if let Some(active_call) = active_call {
1110                if !quitting
1111                    && workspace_count == 1
1112                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1113                {
1114                    let answer = cx.prompt(
1115                        window_id,
1116                        PromptLevel::Warning,
1117                        "Do you want to leave the current call?",
1118                        &["Close window and hang up", "Cancel"],
1119                    );
1120
1121                    if let Some(mut answer) = answer {
1122                        if answer.next().await == Some(1) {
1123                            return anyhow::Ok(false);
1124                        } else {
1125                            active_call
1126                                .update(&mut cx, |call, cx| call.hang_up(cx))
1127                                .await
1128                                .log_err();
1129                        }
1130                    }
1131                }
1132            }
1133
1134            Ok(this
1135                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1136                .await?)
1137        })
1138    }
1139
1140    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1141        let save_all = self.save_all_internal(false, cx);
1142        Some(cx.foreground().spawn(async move {
1143            save_all.await?;
1144            Ok(())
1145        }))
1146    }
1147
1148    fn save_all_internal(
1149        &mut self,
1150        should_prompt_to_save: bool,
1151        cx: &mut ViewContext<Self>,
1152    ) -> Task<Result<bool>> {
1153        if self.project.read(cx).is_read_only() {
1154            return Task::ready(Ok(true));
1155        }
1156
1157        let dirty_items = self
1158            .panes
1159            .iter()
1160            .flat_map(|pane| {
1161                pane.read(cx).items().filter_map(|item| {
1162                    if item.is_dirty(cx) {
1163                        Some((pane.downgrade(), item.boxed_clone()))
1164                    } else {
1165                        None
1166                    }
1167                })
1168            })
1169            .collect::<Vec<_>>();
1170
1171        let project = self.project.clone();
1172        cx.spawn(|_, mut cx| async move {
1173            for (pane, item) in dirty_items {
1174                let (singleton, project_entry_ids) =
1175                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1176                if singleton || !project_entry_ids.is_empty() {
1177                    if let Some(ix) =
1178                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1179                    {
1180                        if !Pane::save_item(
1181                            project.clone(),
1182                            &pane,
1183                            ix,
1184                            &*item,
1185                            should_prompt_to_save,
1186                            &mut cx,
1187                        )
1188                        .await?
1189                        {
1190                            return Ok(false);
1191                        }
1192                    }
1193                }
1194            }
1195            Ok(true)
1196        })
1197    }
1198
1199    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1200        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1201            files: true,
1202            directories: true,
1203            multiple: true,
1204        });
1205
1206        Some(cx.spawn(|this, mut cx| async move {
1207            if let Some(paths) = paths.recv().await.flatten() {
1208                if let Some(task) = this
1209                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1210                    .log_err()
1211                {
1212                    task.await?
1213                }
1214            }
1215            Ok(())
1216        }))
1217    }
1218
1219    pub fn open_workspace_for_paths(
1220        &mut self,
1221        paths: Vec<PathBuf>,
1222        cx: &mut ViewContext<Self>,
1223    ) -> Task<Result<()>> {
1224        let window_id = cx.window_id();
1225        let is_remote = self.project.read(cx).is_remote();
1226        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1227        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1228        let close_task = if is_remote || has_worktree || has_dirty_items {
1229            None
1230        } else {
1231            Some(self.prepare_to_close(false, cx))
1232        };
1233        let app_state = self.app_state.clone();
1234
1235        cx.spawn(|_, mut cx| async move {
1236            let window_id_to_replace = if let Some(close_task) = close_task {
1237                if !close_task.await? {
1238                    return Ok(());
1239                }
1240                Some(window_id)
1241            } else {
1242                None
1243            };
1244            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
1245                .await?;
1246            Ok(())
1247        })
1248    }
1249
1250    #[allow(clippy::type_complexity)]
1251    pub fn open_paths(
1252        &mut self,
1253        mut abs_paths: Vec<PathBuf>,
1254        visible: bool,
1255        cx: &mut ViewContext<Self>,
1256    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1257        log::info!("open paths {:?}", abs_paths);
1258
1259        let fs = self.app_state.fs.clone();
1260
1261        // Sort the paths to ensure we add worktrees for parents before their children.
1262        abs_paths.sort_unstable();
1263        cx.spawn(|this, mut cx| async move {
1264            let mut project_paths = Vec::new();
1265            for path in &abs_paths {
1266                if let Some(project_path) = this
1267                    .update(&mut cx, |this, cx| {
1268                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1269                    })
1270                    .log_err()
1271                {
1272                    project_paths.push(project_path.await.log_err());
1273                } else {
1274                    project_paths.push(None);
1275                }
1276            }
1277
1278            let tasks = abs_paths
1279                .iter()
1280                .cloned()
1281                .zip(project_paths.into_iter())
1282                .map(|(abs_path, project_path)| {
1283                    let this = this.clone();
1284                    cx.spawn(|mut cx| {
1285                        let fs = fs.clone();
1286                        async move {
1287                            let (_worktree, project_path) = project_path?;
1288                            if fs.is_file(&abs_path).await {
1289                                Some(
1290                                    this.update(&mut cx, |this, cx| {
1291                                        this.open_path(project_path, None, true, cx)
1292                                    })
1293                                    .log_err()?
1294                                    .await,
1295                                )
1296                            } else {
1297                                None
1298                            }
1299                        }
1300                    })
1301                })
1302                .collect::<Vec<_>>();
1303
1304            futures::future::join_all(tasks).await
1305        })
1306    }
1307
1308    pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
1309        let workspace_root = self
1310            .project()
1311            .read(cx)
1312            .worktree_for_id(project_path.worktree_id, cx)?
1313            .read(cx)
1314            .abs_path();
1315        let project_path = project_path.path.as_ref();
1316
1317        Some(if project_path == Path::new("") {
1318            workspace_root.to_path_buf()
1319        } else {
1320            workspace_root.join(project_path)
1321        })
1322    }
1323
1324    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1325        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1326            files: false,
1327            directories: true,
1328            multiple: true,
1329        });
1330        cx.spawn(|this, mut cx| async move {
1331            if let Some(paths) = paths.recv().await.flatten() {
1332                let results = this
1333                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1334                    .await;
1335                for result in results.into_iter().flatten() {
1336                    result.log_err();
1337                }
1338            }
1339            anyhow::Ok(())
1340        })
1341        .detach_and_log_err(cx);
1342    }
1343
1344    fn project_path_for_path(
1345        project: ModelHandle<Project>,
1346        abs_path: &Path,
1347        visible: bool,
1348        cx: &mut AppContext,
1349    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1350        let entry = project.update(cx, |project, cx| {
1351            project.find_or_create_local_worktree(abs_path, visible, cx)
1352        });
1353        cx.spawn(|cx| async move {
1354            let (worktree, path) = entry.await?;
1355            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1356            Ok((
1357                worktree,
1358                ProjectPath {
1359                    worktree_id,
1360                    path: path.into(),
1361                },
1362            ))
1363        })
1364    }
1365
1366    /// Returns the modal that was toggled closed if it was open.
1367    pub fn toggle_modal<V, F>(
1368        &mut self,
1369        cx: &mut ViewContext<Self>,
1370        add_view: F,
1371    ) -> Option<ViewHandle<V>>
1372    where
1373        V: 'static + Modal,
1374        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1375    {
1376        cx.notify();
1377        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1378        // it. Otherwise, create a new modal and set it as active.
1379        let already_open_modal = self.modal.take().and_then(|modal| modal.downcast::<V>());
1380        if let Some(already_open_modal) = already_open_modal {
1381            cx.focus_self();
1382            Some(already_open_modal)
1383        } else {
1384            let modal = add_view(self, cx);
1385            cx.subscribe(&modal, |this, _, event, cx| {
1386                if V::dismiss_on_event(event) {
1387                    this.dismiss_modal(cx);
1388                }
1389            })
1390            .detach();
1391            cx.focus(&modal);
1392            self.modal = Some(modal.into_any());
1393            None
1394        }
1395    }
1396
1397    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1398        self.modal
1399            .as_ref()
1400            .and_then(|modal| modal.clone().downcast::<V>())
1401    }
1402
1403    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) {
1404        if self.modal.take().is_some() {
1405            cx.focus(&self.active_pane);
1406            cx.notify();
1407        }
1408    }
1409
1410    pub fn items<'a>(
1411        &'a self,
1412        cx: &'a AppContext,
1413    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1414        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1415    }
1416
1417    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1418        self.items_of_type(cx).max_by_key(|item| item.id())
1419    }
1420
1421    pub fn items_of_type<'a, T: Item>(
1422        &'a self,
1423        cx: &'a AppContext,
1424    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1425        self.panes
1426            .iter()
1427            .flat_map(|pane| pane.read(cx).items_of_type())
1428    }
1429
1430    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1431        self.active_pane().read(cx).active_item()
1432    }
1433
1434    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1435        self.active_item(cx).and_then(|item| item.project_path(cx))
1436    }
1437
1438    pub fn save_active_item(
1439        &mut self,
1440        force_name_change: bool,
1441        cx: &mut ViewContext<Self>,
1442    ) -> Task<Result<()>> {
1443        let project = self.project.clone();
1444        if let Some(item) = self.active_item(cx) {
1445            if !force_name_change && item.can_save(cx) {
1446                if item.has_conflict(cx) {
1447                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1448
1449                    let mut answer = cx.prompt(
1450                        PromptLevel::Warning,
1451                        CONFLICT_MESSAGE,
1452                        &["Overwrite", "Cancel"],
1453                    );
1454                    cx.spawn(|this, mut cx| async move {
1455                        let answer = answer.recv().await;
1456                        if answer == Some(0) {
1457                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1458                                .await?;
1459                        }
1460                        Ok(())
1461                    })
1462                } else {
1463                    item.save(self.project.clone(), cx)
1464                }
1465            } else if item.is_singleton(cx) {
1466                let worktree = self.worktrees(cx).next();
1467                let start_abs_path = worktree
1468                    .and_then(|w| w.read(cx).as_local())
1469                    .map_or(Path::new(""), |w| w.abs_path())
1470                    .to_path_buf();
1471                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1472                cx.spawn(|this, mut cx| async move {
1473                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1474                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1475                            .await?;
1476                    }
1477                    Ok(())
1478                })
1479            } else {
1480                Task::ready(Ok(()))
1481            }
1482        } else {
1483            Task::ready(Ok(()))
1484        }
1485    }
1486
1487    pub fn toggle_dock(
1488        &mut self,
1489        dock_side: DockPosition,
1490        focus: bool,
1491        cx: &mut ViewContext<Self>,
1492    ) {
1493        let dock = match dock_side {
1494            DockPosition::Left => &self.left_dock,
1495            DockPosition::Bottom => &self.bottom_dock,
1496            DockPosition::Right => &self.right_dock,
1497        };
1498        dock.update(cx, |dock, cx| {
1499            let open = !dock.is_open();
1500            dock.set_open(open, cx);
1501        });
1502
1503        if dock.read(cx).is_open() && focus {
1504            cx.focus(dock);
1505        } else {
1506            cx.focus_self();
1507        }
1508        cx.notify();
1509        self.serialize_workspace(cx);
1510    }
1511
1512    pub fn toggle_panel(
1513        &mut self,
1514        position: DockPosition,
1515        panel_index: usize,
1516        cx: &mut ViewContext<Self>,
1517    ) {
1518        let dock = match position {
1519            DockPosition::Left => &mut self.left_dock,
1520            DockPosition::Bottom => &mut self.bottom_dock,
1521            DockPosition::Right => &mut self.right_dock,
1522        };
1523        let active_item = dock.update(cx, move |dock, cx| {
1524            if dock.is_open() && dock.active_panel_index() == panel_index {
1525                dock.set_open(false, cx);
1526                None
1527            } else {
1528                dock.set_open(true, cx);
1529                dock.activate_panel(panel_index, cx);
1530                dock.active_panel().cloned()
1531            }
1532        });
1533
1534        if let Some(active_item) = active_item {
1535            if active_item.has_focus(cx) {
1536                cx.focus_self();
1537            } else {
1538                cx.focus(active_item.as_any());
1539            }
1540        } else {
1541            cx.focus_self();
1542        }
1543
1544        self.serialize_workspace(cx);
1545
1546        cx.notify();
1547    }
1548
1549    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1550        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1551            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1552                let active_item = dock.update(cx, |dock, cx| {
1553                    dock.set_open(true, cx);
1554                    dock.activate_panel(panel_index, cx);
1555                    dock.active_panel().cloned()
1556                });
1557                if let Some(active_item) = active_item {
1558                    if active_item.has_focus(cx) {
1559                        cx.focus_self();
1560                    } else {
1561                        cx.focus(active_item.as_any());
1562                    }
1563                }
1564
1565                self.serialize_workspace(cx);
1566                cx.notify();
1567                break;
1568            }
1569        }
1570    }
1571
1572    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1573        for pane in &self.panes {
1574            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1575        }
1576
1577        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1578        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1579        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1580        self.zoomed = None;
1581
1582        cx.notify();
1583    }
1584
1585    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1586        let pane = cx.add_view(|cx| {
1587            Pane::new(
1588                self.weak_handle(),
1589                self.app_state.background_actions,
1590                self.pane_history_timestamp.clone(),
1591                cx,
1592            )
1593        });
1594        cx.subscribe(&pane, Self::handle_pane_event).detach();
1595        self.panes.push(pane.clone());
1596        cx.focus(&pane);
1597        cx.emit(Event::PaneAdded(pane.clone()));
1598        pane
1599    }
1600
1601    pub fn add_item_to_center(
1602        &mut self,
1603        item: Box<dyn ItemHandle>,
1604        cx: &mut ViewContext<Self>,
1605    ) -> bool {
1606        if let Some(center_pane) = self.last_active_center_pane.clone() {
1607            if let Some(center_pane) = center_pane.upgrade(cx) {
1608                Pane::add_item(self, &center_pane, item, true, true, None, cx);
1609                true
1610            } else {
1611                false
1612            }
1613        } else {
1614            false
1615        }
1616    }
1617
1618    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1619        let active_pane = self.active_pane().clone();
1620        Pane::add_item(self, &active_pane, item, true, true, None, cx);
1621    }
1622
1623    pub fn open_abs_path(
1624        &mut self,
1625        abs_path: PathBuf,
1626        visible: bool,
1627        cx: &mut ViewContext<Self>,
1628    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1629        cx.spawn(|workspace, mut cx| async move {
1630            let open_paths_task_result = workspace
1631                .update(&mut cx, |workspace, cx| {
1632                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1633                })
1634                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1635                .await;
1636            anyhow::ensure!(
1637                open_paths_task_result.len() == 1,
1638                "open abs path {abs_path:?} task returned incorrect number of results"
1639            );
1640            match open_paths_task_result
1641                .into_iter()
1642                .next()
1643                .expect("ensured single task result")
1644            {
1645                Some(open_result) => {
1646                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1647                }
1648                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1649            }
1650        })
1651    }
1652
1653    pub fn open_path(
1654        &mut self,
1655        path: impl Into<ProjectPath>,
1656        pane: Option<WeakViewHandle<Pane>>,
1657        focus_item: bool,
1658        cx: &mut ViewContext<Self>,
1659    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1660        let pane = pane.unwrap_or_else(|| {
1661            self.last_active_center_pane.clone().unwrap_or_else(|| {
1662                self.panes
1663                    .first()
1664                    .expect("There must be an active pane")
1665                    .downgrade()
1666            })
1667        });
1668
1669        let task = self.load_path(path.into(), cx);
1670        cx.spawn(|this, mut cx| async move {
1671            let (project_entry_id, build_item) = task.await?;
1672            let pane = pane
1673                .upgrade(&cx)
1674                .ok_or_else(|| anyhow!("pane was closed"))?;
1675            this.update(&mut cx, |this, cx| {
1676                Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
1677            })
1678        })
1679    }
1680
1681    pub(crate) fn load_path(
1682        &mut self,
1683        path: ProjectPath,
1684        cx: &mut ViewContext<Self>,
1685    ) -> Task<
1686        Result<(
1687            ProjectEntryId,
1688            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1689        )>,
1690    > {
1691        let project = self.project().clone();
1692        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1693        cx.spawn(|_, mut cx| async move {
1694            let (project_entry_id, project_item) = project_item.await?;
1695            let build_item = cx.update(|cx| {
1696                cx.default_global::<ProjectItemBuilders>()
1697                    .get(&project_item.model_type())
1698                    .ok_or_else(|| anyhow!("no item builder for project item"))
1699                    .cloned()
1700            })?;
1701            let build_item =
1702                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1703            Ok((project_entry_id, build_item))
1704        })
1705    }
1706
1707    pub fn open_project_item<T>(
1708        &mut self,
1709        project_item: ModelHandle<T::Item>,
1710        cx: &mut ViewContext<Self>,
1711    ) -> ViewHandle<T>
1712    where
1713        T: ProjectItem,
1714    {
1715        use project::Item as _;
1716
1717        let entry_id = project_item.read(cx).entry_id(cx);
1718        if let Some(item) = entry_id
1719            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1720            .and_then(|item| item.downcast())
1721        {
1722            self.activate_item(&item, cx);
1723            return item;
1724        }
1725
1726        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1727        self.add_item(Box::new(item.clone()), cx);
1728        item
1729    }
1730
1731    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1732        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1733            let pane = self.active_pane.clone();
1734            Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
1735        }
1736    }
1737
1738    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1739        let result = self.panes.iter().find_map(|pane| {
1740            pane.read(cx)
1741                .index_for_item(item)
1742                .map(|ix| (pane.clone(), ix))
1743        });
1744        if let Some((pane, ix)) = result {
1745            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1746            true
1747        } else {
1748            false
1749        }
1750    }
1751
1752    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1753        let panes = self.center.panes();
1754        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1755            cx.focus(&pane);
1756        } else {
1757            self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1758        }
1759    }
1760
1761    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1762        let panes = self.center.panes();
1763        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1764            let next_ix = (ix + 1) % panes.len();
1765            let next_pane = panes[next_ix].clone();
1766            cx.focus(&next_pane);
1767        }
1768    }
1769
1770    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1771        let panes = self.center.panes();
1772        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1773            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1774            let prev_pane = panes[prev_ix].clone();
1775            cx.focus(&prev_pane);
1776        }
1777    }
1778
1779    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1780        if self.active_pane != pane {
1781            self.active_pane
1782                .update(cx, |pane, cx| pane.set_active(false, cx));
1783            self.active_pane = pane.clone();
1784            self.active_pane
1785                .update(cx, |pane, cx| pane.set_active(true, cx));
1786            self.status_bar.update(cx, |status_bar, cx| {
1787                status_bar.set_active_pane(&self.active_pane, cx);
1788            });
1789            self.active_item_path_changed(cx);
1790            self.last_active_center_pane = Some(pane.downgrade());
1791        }
1792
1793        if pane.read(cx).is_zoomed() {
1794            self.zoomed = Some(pane.downgrade().into_any());
1795        } else {
1796            self.zoomed = None;
1797        }
1798
1799        self.update_followers(
1800            proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
1801                id: self.active_item(cx).and_then(|item| {
1802                    item.to_followable_item_handle(cx)?
1803                        .remote_id(&self.app_state.client, cx)
1804                        .map(|id| id.to_proto())
1805                }),
1806                leader_id: self.leader_for_pane(&pane),
1807            }),
1808            cx,
1809        );
1810
1811        cx.notify();
1812    }
1813
1814    fn handle_pane_event(
1815        &mut self,
1816        pane: ViewHandle<Pane>,
1817        event: &pane::Event,
1818        cx: &mut ViewContext<Self>,
1819    ) {
1820        match event {
1821            pane::Event::Split(direction) => {
1822                self.split_pane(pane, *direction, cx);
1823            }
1824            pane::Event::Remove => self.remove_pane(pane, cx),
1825            pane::Event::ActivateItem { local } => {
1826                if *local {
1827                    self.unfollow(&pane, cx);
1828                }
1829                if &pane == self.active_pane() {
1830                    self.active_item_path_changed(cx);
1831                }
1832            }
1833            pane::Event::ChangeItemTitle => {
1834                if pane == self.active_pane {
1835                    self.active_item_path_changed(cx);
1836                }
1837                self.update_window_edited(cx);
1838            }
1839            pane::Event::RemoveItem { item_id } => {
1840                self.update_window_edited(cx);
1841                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
1842                    if entry.get().id() == pane.id() {
1843                        entry.remove();
1844                    }
1845                }
1846            }
1847            pane::Event::Focus => {
1848                self.handle_pane_focused(pane.clone(), cx);
1849            }
1850            pane::Event::ZoomIn => {
1851                if pane == self.active_pane {
1852                    self.zoom_out(cx);
1853                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
1854                    if pane.read(cx).has_focus() {
1855                        self.zoomed = Some(pane.downgrade().into_any());
1856                    }
1857                    cx.notify();
1858                }
1859            }
1860            pane::Event::ZoomOut => self.zoom_out(cx),
1861        }
1862
1863        self.serialize_workspace(cx);
1864    }
1865
1866    pub fn split_pane(
1867        &mut self,
1868        pane: ViewHandle<Pane>,
1869        direction: SplitDirection,
1870        cx: &mut ViewContext<Self>,
1871    ) -> Option<ViewHandle<Pane>> {
1872        let item = pane.read(cx).active_item()?;
1873        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
1874            let new_pane = self.add_pane(cx);
1875            Pane::add_item(self, &new_pane, clone, true, true, None, cx);
1876            self.center.split(&pane, &new_pane, direction).unwrap();
1877            Some(new_pane)
1878        } else {
1879            None
1880        };
1881        cx.notify();
1882        maybe_pane_handle
1883    }
1884
1885    pub fn split_pane_with_item(
1886        &mut self,
1887        pane_to_split: WeakViewHandle<Pane>,
1888        split_direction: SplitDirection,
1889        from: WeakViewHandle<Pane>,
1890        item_id_to_move: usize,
1891        cx: &mut ViewContext<Self>,
1892    ) {
1893        let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
1894        let Some(from) = from.upgrade(cx) else { return; };
1895
1896        let new_pane = self.add_pane(cx);
1897        Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
1898        self.center
1899            .split(&pane_to_split, &new_pane, split_direction)
1900            .unwrap();
1901        cx.notify();
1902    }
1903
1904    pub fn split_pane_with_project_entry(
1905        &mut self,
1906        pane_to_split: WeakViewHandle<Pane>,
1907        split_direction: SplitDirection,
1908        project_entry: ProjectEntryId,
1909        cx: &mut ViewContext<Self>,
1910    ) -> Option<Task<Result<()>>> {
1911        let pane_to_split = pane_to_split.upgrade(cx)?;
1912        let new_pane = self.add_pane(cx);
1913        self.center
1914            .split(&pane_to_split, &new_pane, split_direction)
1915            .unwrap();
1916
1917        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
1918        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
1919        Some(cx.foreground().spawn(async move {
1920            task.await?;
1921            Ok(())
1922        }))
1923    }
1924
1925    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
1926        if self.center.remove(&pane).unwrap() {
1927            self.force_remove_pane(&pane, cx);
1928            self.unfollow(&pane, cx);
1929            self.last_leaders_by_pane.remove(&pane.downgrade());
1930            for removed_item in pane.read(cx).items() {
1931                self.panes_by_item.remove(&removed_item.id());
1932            }
1933
1934            cx.notify();
1935        } else {
1936            self.active_item_path_changed(cx);
1937        }
1938    }
1939
1940    pub fn panes(&self) -> &[ViewHandle<Pane>] {
1941        &self.panes
1942    }
1943
1944    pub fn active_pane(&self) -> &ViewHandle<Pane> {
1945        &self.active_pane
1946    }
1947
1948    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
1949        if let Some(remote_id) = remote_id {
1950            self.remote_entity_subscription = Some(
1951                self.app_state
1952                    .client
1953                    .add_view_for_remote_entity(remote_id, cx),
1954            );
1955        } else {
1956            self.remote_entity_subscription.take();
1957        }
1958    }
1959
1960    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1961        self.leader_state.followers.remove(&peer_id);
1962        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
1963            for state in states_by_pane.into_values() {
1964                for item in state.items_by_leader_view_id.into_values() {
1965                    item.set_leader_replica_id(None, cx);
1966                }
1967            }
1968        }
1969        cx.notify();
1970    }
1971
1972    pub fn toggle_follow(
1973        &mut self,
1974        leader_id: PeerId,
1975        cx: &mut ViewContext<Self>,
1976    ) -> Option<Task<Result<()>>> {
1977        let pane = self.active_pane().clone();
1978
1979        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
1980            if leader_id == prev_leader_id {
1981                return None;
1982            }
1983        }
1984
1985        self.last_leaders_by_pane
1986            .insert(pane.downgrade(), leader_id);
1987        self.follower_states_by_leader
1988            .entry(leader_id)
1989            .or_default()
1990            .insert(pane.clone(), Default::default());
1991        cx.notify();
1992
1993        let project_id = self.project.read(cx).remote_id()?;
1994        let request = self.app_state.client.request(proto::Follow {
1995            project_id,
1996            leader_id: Some(leader_id),
1997        });
1998
1999        Some(cx.spawn(|this, mut cx| async move {
2000            let response = request.await?;
2001            this.update(&mut cx, |this, _| {
2002                let state = this
2003                    .follower_states_by_leader
2004                    .get_mut(&leader_id)
2005                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2006                    .ok_or_else(|| anyhow!("following interrupted"))?;
2007                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2008                    Some(ViewId::from_proto(active_view_id)?)
2009                } else {
2010                    None
2011                };
2012                Ok::<_, anyhow::Error>(())
2013            })??;
2014            Self::add_views_from_leader(
2015                this.clone(),
2016                leader_id,
2017                vec![pane],
2018                response.views,
2019                &mut cx,
2020            )
2021            .await?;
2022            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2023            Ok(())
2024        }))
2025    }
2026
2027    pub fn follow_next_collaborator(
2028        &mut self,
2029        _: &FollowNextCollaborator,
2030        cx: &mut ViewContext<Self>,
2031    ) -> Option<Task<Result<()>>> {
2032        let collaborators = self.project.read(cx).collaborators();
2033        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2034            let mut collaborators = collaborators.keys().copied();
2035            for peer_id in collaborators.by_ref() {
2036                if peer_id == leader_id {
2037                    break;
2038                }
2039            }
2040            collaborators.next()
2041        } else if let Some(last_leader_id) =
2042            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2043        {
2044            if collaborators.contains_key(last_leader_id) {
2045                Some(*last_leader_id)
2046            } else {
2047                None
2048            }
2049        } else {
2050            None
2051        };
2052
2053        next_leader_id
2054            .or_else(|| collaborators.keys().copied().next())
2055            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2056    }
2057
2058    pub fn unfollow(
2059        &mut self,
2060        pane: &ViewHandle<Pane>,
2061        cx: &mut ViewContext<Self>,
2062    ) -> Option<PeerId> {
2063        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2064            let leader_id = *leader_id;
2065            if let Some(state) = states_by_pane.remove(pane) {
2066                for (_, item) in state.items_by_leader_view_id {
2067                    item.set_leader_replica_id(None, cx);
2068                }
2069
2070                if states_by_pane.is_empty() {
2071                    self.follower_states_by_leader.remove(&leader_id);
2072                    if let Some(project_id) = self.project.read(cx).remote_id() {
2073                        self.app_state
2074                            .client
2075                            .send(proto::Unfollow {
2076                                project_id,
2077                                leader_id: Some(leader_id),
2078                            })
2079                            .log_err();
2080                    }
2081                }
2082
2083                cx.notify();
2084                return Some(leader_id);
2085            }
2086        }
2087        None
2088    }
2089
2090    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2091        self.follower_states_by_leader.contains_key(&peer_id)
2092    }
2093
2094    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2095        self.leader_state.followers.contains(&peer_id)
2096    }
2097
2098    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2099        // TODO: There should be a better system in place for this
2100        // (https://github.com/zed-industries/zed/issues/1290)
2101        let is_fullscreen = cx.window_is_fullscreen();
2102        let container_theme = if is_fullscreen {
2103            let mut container_theme = theme.workspace.titlebar.container;
2104            container_theme.padding.left = container_theme.padding.right;
2105            container_theme
2106        } else {
2107            theme.workspace.titlebar.container
2108        };
2109
2110        enum TitleBar {}
2111        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2112            Stack::new()
2113                .with_children(
2114                    self.titlebar_item
2115                        .as_ref()
2116                        .map(|item| ChildView::new(item, cx)),
2117                )
2118                .contained()
2119                .with_style(container_theme)
2120        })
2121        .on_click(MouseButton::Left, |event, _, cx| {
2122            if event.click_count == 2 {
2123                cx.zoom_window();
2124            }
2125        })
2126        .constrained()
2127        .with_height(theme.workspace.titlebar.height)
2128        .into_any_named("titlebar")
2129    }
2130
2131    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2132        let active_entry = self.active_project_path(cx);
2133        self.project
2134            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2135        self.update_window_title(cx);
2136    }
2137
2138    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2139        let project = self.project().read(cx);
2140        let mut title = String::new();
2141
2142        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2143            let filename = path
2144                .path
2145                .file_name()
2146                .map(|s| s.to_string_lossy())
2147                .or_else(|| {
2148                    Some(Cow::Borrowed(
2149                        project
2150                            .worktree_for_id(path.worktree_id, cx)?
2151                            .read(cx)
2152                            .root_name(),
2153                    ))
2154                });
2155
2156            if let Some(filename) = filename {
2157                title.push_str(filename.as_ref());
2158                title.push_str(" β€” ");
2159            }
2160        }
2161
2162        for (i, name) in project.worktree_root_names(cx).enumerate() {
2163            if i > 0 {
2164                title.push_str(", ");
2165            }
2166            title.push_str(name);
2167        }
2168
2169        if title.is_empty() {
2170            title = "empty project".to_string();
2171        }
2172
2173        if project.is_remote() {
2174            title.push_str(" ↙");
2175        } else if project.is_shared() {
2176            title.push_str(" β†—");
2177        }
2178
2179        cx.set_window_title(&title);
2180    }
2181
2182    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2183        let is_edited = !self.project.read(cx).is_read_only()
2184            && self
2185                .items(cx)
2186                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2187        if is_edited != self.window_edited {
2188            self.window_edited = is_edited;
2189            cx.set_window_edited(self.window_edited)
2190        }
2191    }
2192
2193    fn render_disconnected_overlay(
2194        &self,
2195        cx: &mut ViewContext<Workspace>,
2196    ) -> Option<AnyElement<Workspace>> {
2197        if self.project.read(cx).is_read_only() {
2198            enum DisconnectedOverlay {}
2199            Some(
2200                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2201                    let theme = &theme::current(cx);
2202                    Label::new(
2203                        "Your connection to the remote project has been lost.",
2204                        theme.workspace.disconnected_overlay.text.clone(),
2205                    )
2206                    .aligned()
2207                    .contained()
2208                    .with_style(theme.workspace.disconnected_overlay.container)
2209                })
2210                .with_cursor_style(CursorStyle::Arrow)
2211                .capture_all()
2212                .into_any_named("disconnected overlay"),
2213            )
2214        } else {
2215            None
2216        }
2217    }
2218
2219    fn render_notifications(
2220        &self,
2221        theme: &theme::Workspace,
2222        cx: &AppContext,
2223    ) -> Option<AnyElement<Workspace>> {
2224        if self.notifications.is_empty() {
2225            None
2226        } else {
2227            Some(
2228                Flex::column()
2229                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2230                        ChildView::new(notification.as_any(), cx)
2231                            .contained()
2232                            .with_style(theme.notification)
2233                    }))
2234                    .constrained()
2235                    .with_width(theme.notifications.width)
2236                    .contained()
2237                    .with_style(theme.notifications.container)
2238                    .aligned()
2239                    .bottom()
2240                    .right()
2241                    .into_any(),
2242            )
2243        }
2244    }
2245
2246    // RPC handlers
2247
2248    async fn handle_follow(
2249        this: WeakViewHandle<Self>,
2250        envelope: TypedEnvelope<proto::Follow>,
2251        _: Arc<Client>,
2252        mut cx: AsyncAppContext,
2253    ) -> Result<proto::FollowResponse> {
2254        this.update(&mut cx, |this, cx| {
2255            let client = &this.app_state.client;
2256            this.leader_state
2257                .followers
2258                .insert(envelope.original_sender_id()?);
2259
2260            let active_view_id = this.active_item(cx).and_then(|i| {
2261                Some(
2262                    i.to_followable_item_handle(cx)?
2263                        .remote_id(client, cx)?
2264                        .to_proto(),
2265                )
2266            });
2267
2268            cx.notify();
2269
2270            Ok(proto::FollowResponse {
2271                active_view_id,
2272                views: this
2273                    .panes()
2274                    .iter()
2275                    .flat_map(|pane| {
2276                        let leader_id = this.leader_for_pane(pane);
2277                        pane.read(cx).items().filter_map({
2278                            let cx = &cx;
2279                            move |item| {
2280                                let item = item.to_followable_item_handle(cx)?;
2281                                let id = item.remote_id(client, cx)?.to_proto();
2282                                let variant = item.to_state_proto(cx)?;
2283                                Some(proto::View {
2284                                    id: Some(id),
2285                                    leader_id,
2286                                    variant: Some(variant),
2287                                })
2288                            }
2289                        })
2290                    })
2291                    .collect(),
2292            })
2293        })?
2294    }
2295
2296    async fn handle_unfollow(
2297        this: WeakViewHandle<Self>,
2298        envelope: TypedEnvelope<proto::Unfollow>,
2299        _: Arc<Client>,
2300        mut cx: AsyncAppContext,
2301    ) -> Result<()> {
2302        this.update(&mut cx, |this, cx| {
2303            this.leader_state
2304                .followers
2305                .remove(&envelope.original_sender_id()?);
2306            cx.notify();
2307            Ok(())
2308        })?
2309    }
2310
2311    async fn handle_update_followers(
2312        this: WeakViewHandle<Self>,
2313        envelope: TypedEnvelope<proto::UpdateFollowers>,
2314        _: Arc<Client>,
2315        cx: AsyncAppContext,
2316    ) -> Result<()> {
2317        let leader_id = envelope.original_sender_id()?;
2318        this.read_with(&cx, |this, _| {
2319            this.leader_updates_tx
2320                .unbounded_send((leader_id, envelope.payload))
2321        })??;
2322        Ok(())
2323    }
2324
2325    async fn process_leader_update(
2326        this: &WeakViewHandle<Self>,
2327        leader_id: PeerId,
2328        update: proto::UpdateFollowers,
2329        cx: &mut AsyncAppContext,
2330    ) -> Result<()> {
2331        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2332            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2333                this.update(cx, |this, _| {
2334                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2335                        for state in state.values_mut() {
2336                            state.active_view_id =
2337                                if let Some(active_view_id) = update_active_view.id.clone() {
2338                                    Some(ViewId::from_proto(active_view_id)?)
2339                                } else {
2340                                    None
2341                                };
2342                        }
2343                    }
2344                    anyhow::Ok(())
2345                })??;
2346            }
2347            proto::update_followers::Variant::UpdateView(update_view) => {
2348                let variant = update_view
2349                    .variant
2350                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2351                let id = update_view
2352                    .id
2353                    .ok_or_else(|| anyhow!("missing update view id"))?;
2354                let mut tasks = Vec::new();
2355                this.update(cx, |this, cx| {
2356                    let project = this.project.clone();
2357                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2358                        for state in state.values_mut() {
2359                            let view_id = ViewId::from_proto(id.clone())?;
2360                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2361                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2362                            }
2363                        }
2364                    }
2365                    anyhow::Ok(())
2366                })??;
2367                try_join_all(tasks).await.log_err();
2368            }
2369            proto::update_followers::Variant::CreateView(view) => {
2370                let panes = this.read_with(cx, |this, _| {
2371                    this.follower_states_by_leader
2372                        .get(&leader_id)
2373                        .into_iter()
2374                        .flat_map(|states_by_pane| states_by_pane.keys())
2375                        .cloned()
2376                        .collect()
2377                })?;
2378                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2379            }
2380        }
2381        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2382        Ok(())
2383    }
2384
2385    async fn add_views_from_leader(
2386        this: WeakViewHandle<Self>,
2387        leader_id: PeerId,
2388        panes: Vec<ViewHandle<Pane>>,
2389        views: Vec<proto::View>,
2390        cx: &mut AsyncAppContext,
2391    ) -> Result<()> {
2392        let project = this.read_with(cx, |this, _| this.project.clone())?;
2393        let replica_id = project
2394            .read_with(cx, |project, _| {
2395                project
2396                    .collaborators()
2397                    .get(&leader_id)
2398                    .map(|c| c.replica_id)
2399            })
2400            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2401
2402        let item_builders = cx.update(|cx| {
2403            cx.default_global::<FollowableItemBuilders>()
2404                .values()
2405                .map(|b| b.0)
2406                .collect::<Vec<_>>()
2407        });
2408
2409        let mut item_tasks_by_pane = HashMap::default();
2410        for pane in panes {
2411            let mut item_tasks = Vec::new();
2412            let mut leader_view_ids = Vec::new();
2413            for view in &views {
2414                let Some(id) = &view.id else { continue };
2415                let id = ViewId::from_proto(id.clone())?;
2416                let mut variant = view.variant.clone();
2417                if variant.is_none() {
2418                    Err(anyhow!("missing variant"))?;
2419                }
2420                for build_item in &item_builders {
2421                    let task = cx.update(|cx| {
2422                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2423                    });
2424                    if let Some(task) = task {
2425                        item_tasks.push(task);
2426                        leader_view_ids.push(id);
2427                        break;
2428                    } else {
2429                        assert!(variant.is_some());
2430                    }
2431                }
2432            }
2433
2434            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2435        }
2436
2437        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2438            let items = futures::future::try_join_all(item_tasks).await?;
2439            this.update(cx, |this, cx| {
2440                let state = this
2441                    .follower_states_by_leader
2442                    .get_mut(&leader_id)?
2443                    .get_mut(&pane)?;
2444
2445                for (id, item) in leader_view_ids.into_iter().zip(items) {
2446                    item.set_leader_replica_id(Some(replica_id), cx);
2447                    state.items_by_leader_view_id.insert(id, item);
2448                }
2449
2450                Some(())
2451            })?;
2452        }
2453        Ok(())
2454    }
2455
2456    fn update_followers(
2457        &self,
2458        update: proto::update_followers::Variant,
2459        cx: &AppContext,
2460    ) -> Option<()> {
2461        let project_id = self.project.read(cx).remote_id()?;
2462        if !self.leader_state.followers.is_empty() {
2463            self.app_state
2464                .client
2465                .send(proto::UpdateFollowers {
2466                    project_id,
2467                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2468                    variant: Some(update),
2469                })
2470                .log_err();
2471        }
2472        None
2473    }
2474
2475    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2476        self.follower_states_by_leader
2477            .iter()
2478            .find_map(|(leader_id, state)| {
2479                if state.contains_key(pane) {
2480                    Some(*leader_id)
2481                } else {
2482                    None
2483                }
2484            })
2485    }
2486
2487    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2488        cx.notify();
2489
2490        let call = self.active_call()?;
2491        let room = call.read(cx).room()?.read(cx);
2492        let participant = room.remote_participant_for_peer_id(leader_id)?;
2493        let mut items_to_activate = Vec::new();
2494        match participant.location {
2495            call::ParticipantLocation::SharedProject { project_id } => {
2496                if Some(project_id) == self.project.read(cx).remote_id() {
2497                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2498                        if let Some(item) = state
2499                            .active_view_id
2500                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2501                        {
2502                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2503                        } else {
2504                            if let Some(shared_screen) =
2505                                self.shared_screen_for_peer(leader_id, pane, cx)
2506                            {
2507                                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2508                            }
2509                        }
2510                    }
2511                }
2512            }
2513            call::ParticipantLocation::UnsharedProject => {}
2514            call::ParticipantLocation::External => {
2515                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2516                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2517                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2518                    }
2519                }
2520            }
2521        }
2522
2523        for (pane, item) in items_to_activate {
2524            let pane_was_focused = pane.read(cx).has_focus();
2525            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2526                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2527            } else {
2528                Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
2529            }
2530
2531            if pane_was_focused {
2532                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2533            }
2534        }
2535
2536        None
2537    }
2538
2539    fn shared_screen_for_peer(
2540        &self,
2541        peer_id: PeerId,
2542        pane: &ViewHandle<Pane>,
2543        cx: &mut ViewContext<Self>,
2544    ) -> Option<ViewHandle<SharedScreen>> {
2545        let call = self.active_call()?;
2546        let room = call.read(cx).room()?.read(cx);
2547        let participant = room.remote_participant_for_peer_id(peer_id)?;
2548        let track = participant.tracks.values().next()?.clone();
2549        let user = participant.user.clone();
2550
2551        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2552            if item.read(cx).peer_id == peer_id {
2553                return Some(item);
2554            }
2555        }
2556
2557        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2558    }
2559
2560    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2561        if active {
2562            cx.background()
2563                .spawn(persistence::DB.update_timestamp(self.database_id()))
2564                .detach();
2565        } else {
2566            for pane in &self.panes {
2567                pane.update(cx, |pane, cx| {
2568                    if let Some(item) = pane.active_item() {
2569                        item.workspace_deactivated(cx);
2570                    }
2571                    if matches!(
2572                        settings::get::<WorkspaceSettings>(cx).autosave,
2573                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2574                    ) {
2575                        for item in pane.items() {
2576                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2577                                .detach_and_log_err(cx);
2578                        }
2579                    }
2580                });
2581            }
2582        }
2583    }
2584
2585    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2586        self.active_call.as_ref().map(|(call, _)| call)
2587    }
2588
2589    fn on_active_call_event(
2590        &mut self,
2591        _: ModelHandle<ActiveCall>,
2592        event: &call::room::Event,
2593        cx: &mut ViewContext<Self>,
2594    ) {
2595        match event {
2596            call::room::Event::ParticipantLocationChanged { participant_id }
2597            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2598                self.leader_updated(*participant_id, cx);
2599            }
2600            _ => {}
2601        }
2602    }
2603
2604    pub fn database_id(&self) -> WorkspaceId {
2605        self.database_id
2606    }
2607
2608    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2609        let project = self.project().read(cx);
2610
2611        if project.is_local() {
2612            Some(
2613                project
2614                    .visible_worktrees(cx)
2615                    .map(|worktree| worktree.read(cx).abs_path())
2616                    .collect::<Vec<_>>()
2617                    .into(),
2618            )
2619        } else {
2620            None
2621        }
2622    }
2623
2624    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2625        match member {
2626            Member::Axis(PaneAxis { members, .. }) => {
2627                for child in members.iter() {
2628                    self.remove_panes(child.clone(), cx)
2629                }
2630            }
2631            Member::Pane(pane) => {
2632                self.force_remove_pane(&pane, cx);
2633            }
2634        }
2635    }
2636
2637    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
2638        self.panes.retain(|p| p != pane);
2639        cx.focus(self.panes.last().unwrap());
2640        if self.last_active_center_pane == Some(pane.downgrade()) {
2641            self.last_active_center_pane = None;
2642        }
2643        cx.notify();
2644    }
2645
2646    fn serialize_workspace(&self, cx: &AppContext) {
2647        fn serialize_pane_handle(
2648            pane_handle: &ViewHandle<Pane>,
2649            cx: &AppContext,
2650        ) -> SerializedPane {
2651            let (items, active) = {
2652                let pane = pane_handle.read(cx);
2653                let active_item_id = pane.active_item().map(|item| item.id());
2654                (
2655                    pane.items()
2656                        .filter_map(|item_handle| {
2657                            Some(SerializedItem {
2658                                kind: Arc::from(item_handle.serialized_item_kind()?),
2659                                item_id: item_handle.id(),
2660                                active: Some(item_handle.id()) == active_item_id,
2661                            })
2662                        })
2663                        .collect::<Vec<_>>(),
2664                    pane.is_active(),
2665                )
2666            };
2667
2668            SerializedPane::new(items, active)
2669        }
2670
2671        fn build_serialized_pane_group(
2672            pane_group: &Member,
2673            cx: &AppContext,
2674        ) -> SerializedPaneGroup {
2675            match pane_group {
2676                Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group {
2677                    axis: *axis,
2678                    children: members
2679                        .iter()
2680                        .map(|member| build_serialized_pane_group(member, cx))
2681                        .collect::<Vec<_>>(),
2682                },
2683                Member::Pane(pane_handle) => {
2684                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
2685                }
2686            }
2687        }
2688
2689        fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure {
2690            let left_dock = this.left_dock.read(cx);
2691            let left_visible = left_dock.is_open();
2692            let left_active_panel = left_dock.active_panel().and_then(|panel| {
2693                Some(
2694                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2695                        .to_string(),
2696                )
2697            });
2698
2699            let right_dock = this.right_dock.read(cx);
2700            let right_visible = right_dock.is_open();
2701            let right_active_panel = right_dock.active_panel().and_then(|panel| {
2702                Some(
2703                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2704                        .to_string(),
2705                )
2706            });
2707
2708            let bottom_dock = this.bottom_dock.read(cx);
2709            let bottom_visible = bottom_dock.is_open();
2710            let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| {
2711                Some(
2712                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
2713                        .to_string(),
2714                )
2715            });
2716
2717            DockStructure {
2718                left: DockData {
2719                    visible: left_visible,
2720                    active_panel: left_active_panel,
2721                },
2722                right: DockData {
2723                    visible: right_visible,
2724                    active_panel: right_active_panel,
2725                },
2726                bottom: DockData {
2727                    visible: bottom_visible,
2728                    active_panel: bottom_active_panel,
2729                },
2730            }
2731        }
2732
2733        if let Some(location) = self.location(cx) {
2734            // Load bearing special case:
2735            //  - with_local_workspace() relies on this to not have other stuff open
2736            //    when you open your log
2737            if !location.paths().is_empty() {
2738                let center_group = build_serialized_pane_group(&self.center.root, cx);
2739                let docks = build_serialized_docks(self, cx);
2740
2741                let serialized_workspace = SerializedWorkspace {
2742                    id: self.database_id,
2743                    location,
2744                    center_group,
2745                    bounds: Default::default(),
2746                    display: Default::default(),
2747                    docks,
2748                };
2749
2750                cx.background()
2751                    .spawn(persistence::DB.save_workspace(serialized_workspace))
2752                    .detach();
2753            }
2754        }
2755    }
2756
2757    pub(crate) fn load_workspace(
2758        workspace: WeakViewHandle<Workspace>,
2759        serialized_workspace: SerializedWorkspace,
2760        paths_to_open: Vec<Option<ProjectPath>>,
2761        cx: &mut AppContext,
2762    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
2763        cx.spawn(|mut cx| async move {
2764            let result = async_iife! {{
2765                let (project, old_center_pane) =
2766                workspace.read_with(&cx, |workspace, _| {
2767                    (
2768                        workspace.project().clone(),
2769                        workspace.last_active_center_pane.clone(),
2770                    )
2771                })?;
2772
2773                let mut center_items = None;
2774                let mut center_group = None;
2775                // Traverse the splits tree and add to things
2776                if let Some((group, active_pane, items)) = serialized_workspace
2777                        .center_group
2778                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
2779                        .await {
2780                    center_items = Some(items);
2781                    center_group = Some((group, active_pane))
2782                }
2783
2784                let resulting_list = cx.read(|cx| {
2785                    let mut opened_items = center_items
2786                        .unwrap_or_default()
2787                        .into_iter()
2788                        .filter_map(|item| {
2789                            let item = item?;
2790                            let project_path = item.project_path(cx)?;
2791                            Some((project_path, item))
2792                        })
2793                        .collect::<HashMap<_, _>>();
2794
2795                    paths_to_open
2796                        .into_iter()
2797                        .map(|path_to_open| {
2798                            path_to_open.map(|path_to_open| {
2799                                Ok(opened_items.remove(&path_to_open))
2800                            })
2801                            .transpose()
2802                            .map(|item| item.flatten())
2803                            .transpose()
2804                        })
2805                        .collect::<Vec<_>>()
2806                });
2807
2808                // Remove old panes from workspace panes list
2809                workspace.update(&mut cx, |workspace, cx| {
2810                    if let Some((center_group, active_pane)) = center_group {
2811                        workspace.remove_panes(workspace.center.root.clone(), cx);
2812
2813                        // Swap workspace center group
2814                        workspace.center = PaneGroup::with_root(center_group);
2815
2816                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
2817                        cx.focus_self();
2818
2819                        if let Some(active_pane) = active_pane {
2820                            cx.focus(&active_pane);
2821                        } else {
2822                            cx.focus(workspace.panes.last().unwrap());
2823                        }
2824                    } else {
2825                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
2826                        if let Some(old_center_handle) = old_center_handle {
2827                            cx.focus(&old_center_handle)
2828                        } else {
2829                            cx.focus_self()
2830                        }
2831                    }
2832
2833                    let docks = serialized_workspace.docks;
2834                    workspace.left_dock.update(cx, |dock, cx| {
2835                        dock.set_open(docks.left.visible, cx);
2836                        if let Some(active_panel) = docks.left.active_panel {
2837                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
2838                                dock.activate_panel(ix, cx);
2839                            }
2840                        }
2841                    });
2842                    workspace.right_dock.update(cx, |dock, cx| {
2843                        dock.set_open(docks.right.visible, cx);
2844                        if let Some(active_panel) = docks.right.active_panel {
2845                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
2846                                dock.activate_panel(ix, cx);
2847                            }
2848                        }
2849                    });
2850                    workspace.bottom_dock.update(cx, |dock, cx| {
2851                        dock.set_open(docks.bottom.visible, cx);
2852                        if let Some(active_panel) = docks.bottom.active_panel {
2853                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
2854                                dock.activate_panel(ix, cx);
2855                            }
2856                        }
2857                    });
2858
2859                    cx.notify();
2860                })?;
2861
2862                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
2863                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
2864
2865                Ok::<_, anyhow::Error>(resulting_list)
2866            }};
2867
2868            result.await.unwrap_or_default()
2869        })
2870    }
2871
2872    #[cfg(any(test, feature = "test-support"))]
2873    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
2874        let app_state = Arc::new(AppState {
2875            languages: project.read(cx).languages().clone(),
2876            client: project.read(cx).client(),
2877            user_store: project.read(cx).user_store(),
2878            fs: project.read(cx).fs().clone(),
2879            build_window_options: |_, _, _| Default::default(),
2880            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
2881            background_actions: || &[],
2882        });
2883        Self::new(0, project, app_state, cx)
2884    }
2885
2886    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
2887        let dock = match position {
2888            DockPosition::Left => &self.left_dock,
2889            DockPosition::Right => &self.right_dock,
2890            DockPosition::Bottom => &self.bottom_dock,
2891        };
2892        let active_panel = dock.read(cx).active_panel()?;
2893        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
2894            dock.read(cx).render_placeholder(cx)
2895        } else {
2896            ChildView::new(dock, cx).into_any()
2897        };
2898
2899        Some(
2900            element
2901                .constrained()
2902                .dynamically(move |constraint, _, cx| match position {
2903                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
2904                        Vector2F::new(20., constraint.min.y()),
2905                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
2906                    ),
2907                    DockPosition::Bottom => SizeConstraint::new(
2908                        Vector2F::new(constraint.min.x(), 20.),
2909                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
2910                    ),
2911                })
2912                .into_any(),
2913        )
2914    }
2915}
2916
2917async fn open_items(
2918    serialized_workspace: Option<SerializedWorkspace>,
2919    workspace: &WeakViewHandle<Workspace>,
2920    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
2921    app_state: Arc<AppState>,
2922    mut cx: AsyncAppContext,
2923) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
2924    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
2925
2926    if let Some(serialized_workspace) = serialized_workspace {
2927        let workspace = workspace.clone();
2928        let restored_items = cx
2929            .update(|cx| {
2930                Workspace::load_workspace(
2931                    workspace,
2932                    serialized_workspace,
2933                    project_paths_to_open
2934                        .iter()
2935                        .map(|(_, project_path)| project_path)
2936                        .cloned()
2937                        .collect(),
2938                    cx,
2939                )
2940            })
2941            .await;
2942
2943        let restored_project_paths = cx.read(|cx| {
2944            restored_items
2945                .iter()
2946                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
2947                .collect::<HashSet<_>>()
2948        });
2949
2950        opened_items = restored_items;
2951        project_paths_to_open
2952            .iter_mut()
2953            .for_each(|(_, project_path)| {
2954                if let Some(project_path_to_open) = project_path {
2955                    if restored_project_paths.contains(project_path_to_open) {
2956                        *project_path = None;
2957                    }
2958                }
2959            });
2960    } else {
2961        for _ in 0..project_paths_to_open.len() {
2962            opened_items.push(None);
2963        }
2964    }
2965    assert!(opened_items.len() == project_paths_to_open.len());
2966
2967    let tasks =
2968        project_paths_to_open
2969            .into_iter()
2970            .enumerate()
2971            .map(|(i, (abs_path, project_path))| {
2972                let workspace = workspace.clone();
2973                cx.spawn(|mut cx| {
2974                    let fs = app_state.fs.clone();
2975                    async move {
2976                        let file_project_path = project_path?;
2977                        if fs.is_file(&abs_path).await {
2978                            Some((
2979                                i,
2980                                workspace
2981                                    .update(&mut cx, |workspace, cx| {
2982                                        workspace.open_path(file_project_path, None, true, cx)
2983                                    })
2984                                    .log_err()?
2985                                    .await,
2986                            ))
2987                        } else {
2988                            None
2989                        }
2990                    }
2991                })
2992            });
2993
2994    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
2995        .await
2996        .into_iter()
2997    {
2998        if let Some((i, path_open_result)) = maybe_opened_path {
2999            opened_items[i] = Some(path_open_result);
3000        }
3001    }
3002
3003    opened_items
3004}
3005
3006fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3007    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3008
3009    workspace
3010        .update(cx, |workspace, cx| {
3011            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3012                workspace.show_notification_once(0, cx, |cx| {
3013                    cx.add_view(|_| {
3014                        MessageNotification::new("Failed to load any database file.")
3015                            .with_click_message("Click to let us know about this error")
3016                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3017                    })
3018                });
3019            } else {
3020                let backup_path = (*db::BACKUP_DB_PATH).read();
3021                if let Some(backup_path) = backup_path.clone() {
3022                    workspace.show_notification_once(0, cx, move |cx| {
3023                        cx.add_view(move |_| {
3024                            MessageNotification::new(format!(
3025                                "Database file was corrupted. Old database backed up to {}",
3026                                backup_path.display()
3027                            ))
3028                            .with_click_message("Click to show old database in finder")
3029                            .on_click(move |cx| {
3030                                cx.platform().open_url(&backup_path.to_string_lossy())
3031                            })
3032                        })
3033                    });
3034                }
3035            }
3036        })
3037        .log_err();
3038}
3039
3040impl Entity for Workspace {
3041    type Event = Event;
3042}
3043
3044impl View for Workspace {
3045    fn ui_name() -> &'static str {
3046        "Workspace"
3047    }
3048
3049    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3050        let theme = theme::current(cx).clone();
3051        Stack::new()
3052            .with_child(
3053                Flex::column()
3054                    .with_child(self.render_titlebar(&theme, cx))
3055                    .with_child(
3056                        Stack::new()
3057                            .with_child({
3058                                let project = self.project.clone();
3059                                Flex::row()
3060                                    .with_children(self.render_dock(DockPosition::Left, cx))
3061                                    .with_child(
3062                                        Flex::column()
3063                                            .with_child(
3064                                                FlexItem::new(
3065                                                    self.center.render(
3066                                                        &project,
3067                                                        &theme,
3068                                                        &self.follower_states_by_leader,
3069                                                        self.active_call(),
3070                                                        self.active_pane(),
3071                                                        self.zoomed
3072                                                            .as_ref()
3073                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3074                                                            .as_ref(),
3075                                                        &self.app_state,
3076                                                        cx,
3077                                                    ),
3078                                                )
3079                                                .flex(1., true),
3080                                            )
3081                                            .with_children(
3082                                                self.render_dock(DockPosition::Bottom, cx),
3083                                            )
3084                                            .flex(1., true),
3085                                    )
3086                                    .with_children(self.render_dock(DockPosition::Right, cx))
3087                            })
3088                            .with_child(Overlay::new(
3089                                Stack::new()
3090                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3091                                        enum ZoomBackground {}
3092                                        let zoomed = zoomed.upgrade(cx)?;
3093                                        Some(
3094                                            ChildView::new(&zoomed, cx)
3095                                                .contained()
3096                                                .with_style(theme.workspace.zoomed_foreground)
3097                                                .aligned()
3098                                                .contained()
3099                                                .with_style(theme.workspace.zoomed_background)
3100                                                .mouse::<ZoomBackground>(0)
3101                                                .capture_all()
3102                                                .on_down(
3103                                                    MouseButton::Left,
3104                                                    |_, this: &mut Self, cx| {
3105                                                        this.zoom_out(cx);
3106                                                    },
3107                                                ),
3108                                        )
3109                                    }))
3110                                    .with_children(self.modal.as_ref().map(|modal| {
3111                                        ChildView::new(modal, cx)
3112                                            .contained()
3113                                            .with_style(theme.workspace.modal)
3114                                            .aligned()
3115                                            .top()
3116                                    }))
3117                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3118                            ))
3119                            .flex(1.0, true),
3120                    )
3121                    .with_child(ChildView::new(&self.status_bar, cx))
3122                    .contained()
3123                    .with_background_color(theme.workspace.background),
3124            )
3125            .with_children(DragAndDrop::render(cx))
3126            .with_children(self.render_disconnected_overlay(cx))
3127            .into_any_named("workspace")
3128    }
3129
3130    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3131        if cx.is_self_focused() {
3132            cx.focus(&self.active_pane);
3133        }
3134    }
3135}
3136
3137impl ViewId {
3138    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3139        Ok(Self {
3140            creator: message
3141                .creator
3142                .ok_or_else(|| anyhow!("creator is missing"))?,
3143            id: message.id,
3144        })
3145    }
3146
3147    pub(crate) fn to_proto(&self) -> proto::ViewId {
3148        proto::ViewId {
3149            creator: Some(self.creator),
3150            id: self.id,
3151        }
3152    }
3153}
3154
3155pub trait WorkspaceHandle {
3156    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3157}
3158
3159impl WorkspaceHandle for ViewHandle<Workspace> {
3160    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3161        self.read(cx)
3162            .worktrees(cx)
3163            .flat_map(|worktree| {
3164                let worktree_id = worktree.read(cx).id();
3165                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3166                    worktree_id,
3167                    path: f.path.clone(),
3168                })
3169            })
3170            .collect::<Vec<_>>()
3171    }
3172}
3173
3174impl std::fmt::Debug for OpenPaths {
3175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3176        f.debug_struct("OpenPaths")
3177            .field("paths", &self.paths)
3178            .finish()
3179    }
3180}
3181
3182pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3183
3184pub fn activate_workspace_for_project(
3185    cx: &mut AsyncAppContext,
3186    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3187) -> Option<WeakViewHandle<Workspace>> {
3188    for window_id in cx.window_ids() {
3189        let handle = cx
3190            .update_window(window_id, |cx| {
3191                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3192                    let project = workspace_handle.read(cx).project.clone();
3193                    if project.update(cx, &predicate) {
3194                        cx.activate_window();
3195                        return Some(workspace_handle.clone());
3196                    }
3197                }
3198                None
3199            })
3200            .flatten();
3201
3202        if let Some(handle) = handle {
3203            return Some(handle.downgrade());
3204        }
3205    }
3206    None
3207}
3208
3209pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3210    DB.last_workspace().await.log_err().flatten()
3211}
3212
3213#[allow(clippy::type_complexity)]
3214pub fn open_paths(
3215    abs_paths: &[PathBuf],
3216    app_state: &Arc<AppState>,
3217    requesting_window_id: Option<usize>,
3218    cx: &mut AppContext,
3219) -> Task<
3220    Result<(
3221        WeakViewHandle<Workspace>,
3222        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3223    )>,
3224> {
3225    let app_state = app_state.clone();
3226    let abs_paths = abs_paths.to_vec();
3227    cx.spawn(|mut cx| async move {
3228        // Open paths in existing workspace if possible
3229        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3230            project.contains_paths(&abs_paths, cx)
3231        });
3232
3233        if let Some(existing) = existing {
3234            Ok((
3235                existing.clone(),
3236                existing
3237                    .update(&mut cx, |workspace, cx| {
3238                        workspace.open_paths(abs_paths, true, cx)
3239                    })?
3240                    .await,
3241            ))
3242        } else {
3243            Ok(cx
3244                .update(|cx| {
3245                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3246                })
3247                .await)
3248        }
3249    })
3250}
3251
3252pub fn open_new(
3253    app_state: &Arc<AppState>,
3254    cx: &mut AppContext,
3255    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3256) -> Task<()> {
3257    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3258    cx.spawn(|mut cx| async move {
3259        let (workspace, opened_paths) = task.await;
3260
3261        workspace
3262            .update(&mut cx, |workspace, cx| {
3263                if opened_paths.is_empty() {
3264                    init(workspace, cx)
3265                }
3266            })
3267            .log_err();
3268    })
3269}
3270
3271pub fn create_and_open_local_file(
3272    path: &'static Path,
3273    cx: &mut ViewContext<Workspace>,
3274    default_content: impl 'static + Send + FnOnce() -> Rope,
3275) -> Task<Result<Box<dyn ItemHandle>>> {
3276    cx.spawn(|workspace, mut cx| async move {
3277        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3278        if !fs.is_file(path).await {
3279            fs.create_file(path, Default::default()).await?;
3280            fs.save(path, &default_content(), Default::default())
3281                .await?;
3282        }
3283
3284        let mut items = workspace
3285            .update(&mut cx, |workspace, cx| {
3286                workspace.with_local_workspace(cx, |workspace, cx| {
3287                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3288                })
3289            })?
3290            .await?
3291            .await;
3292
3293        let item = items.pop().flatten();
3294        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3295    })
3296}
3297
3298pub fn join_remote_project(
3299    project_id: u64,
3300    follow_user_id: u64,
3301    app_state: Arc<AppState>,
3302    cx: &mut AppContext,
3303) -> Task<Result<()>> {
3304    cx.spawn(|mut cx| async move {
3305        let existing_workspace = cx
3306            .window_ids()
3307            .into_iter()
3308            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3309            .find(|workspace| {
3310                cx.read_window(workspace.window_id(), |cx| {
3311                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3312                })
3313                .unwrap_or(false)
3314            });
3315
3316        let workspace = if let Some(existing_workspace) = existing_workspace {
3317            existing_workspace.downgrade()
3318        } else {
3319            let active_call = cx.read(ActiveCall::global);
3320            let room = active_call
3321                .read_with(&cx, |call, _| call.room().cloned())
3322                .ok_or_else(|| anyhow!("not in a call"))?;
3323            let project = room
3324                .update(&mut cx, |room, cx| {
3325                    room.join_project(
3326                        project_id,
3327                        app_state.languages.clone(),
3328                        app_state.fs.clone(),
3329                        cx,
3330                    )
3331                })
3332                .await?;
3333
3334            let (_, workspace) = cx.add_window(
3335                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3336                |cx| Workspace::new(0, project, app_state.clone(), cx),
3337            );
3338            (app_state.initialize_workspace)(
3339                workspace.downgrade(),
3340                false,
3341                app_state.clone(),
3342                cx.clone(),
3343            )
3344            .await
3345            .log_err();
3346
3347            workspace.downgrade()
3348        };
3349
3350        cx.activate_window(workspace.window_id());
3351        cx.platform().activate(true);
3352
3353        workspace.update(&mut cx, |workspace, cx| {
3354            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3355                let follow_peer_id = room
3356                    .read(cx)
3357                    .remote_participants()
3358                    .iter()
3359                    .find(|(_, participant)| participant.user.id == follow_user_id)
3360                    .map(|(_, p)| p.peer_id)
3361                    .or_else(|| {
3362                        // If we couldn't follow the given user, follow the host instead.
3363                        let collaborator = workspace
3364                            .project()
3365                            .read(cx)
3366                            .collaborators()
3367                            .values()
3368                            .find(|collaborator| collaborator.replica_id == 0)?;
3369                        Some(collaborator.peer_id)
3370                    });
3371
3372                if let Some(follow_peer_id) = follow_peer_id {
3373                    if !workspace.is_being_followed(follow_peer_id) {
3374                        workspace
3375                            .toggle_follow(follow_peer_id, cx)
3376                            .map(|follow| follow.detach_and_log_err(cx));
3377                    }
3378                }
3379            }
3380        })?;
3381
3382        anyhow::Ok(())
3383    })
3384}
3385
3386pub fn restart(_: &Restart, cx: &mut AppContext) {
3387    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3388    cx.spawn(|mut cx| async move {
3389        let mut workspaces = cx
3390            .window_ids()
3391            .into_iter()
3392            .filter_map(|window_id| {
3393                Some(
3394                    cx.root_view(window_id)?
3395                        .clone()
3396                        .downcast::<Workspace>()?
3397                        .downgrade(),
3398                )
3399            })
3400            .collect::<Vec<_>>();
3401
3402        // If multiple windows have unsaved changes, and need a save prompt,
3403        // prompt in the active window before switching to a different window.
3404        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3405
3406        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3407            let answer = cx.prompt(
3408                workspace.window_id(),
3409                PromptLevel::Info,
3410                "Are you sure you want to restart?",
3411                &["Restart", "Cancel"],
3412            );
3413
3414            if let Some(mut answer) = answer {
3415                let answer = answer.next().await;
3416                if answer != Some(0) {
3417                    return Ok(());
3418                }
3419            }
3420        }
3421
3422        // If the user cancels any save prompt, then keep the app open.
3423        for workspace in workspaces {
3424            if !workspace
3425                .update(&mut cx, |workspace, cx| {
3426                    workspace.prepare_to_close(true, cx)
3427                })?
3428                .await?
3429            {
3430                return Ok(());
3431            }
3432        }
3433        cx.platform().restart();
3434        anyhow::Ok(())
3435    })
3436    .detach_and_log_err(cx);
3437}
3438
3439fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3440    let mut parts = value.split(',');
3441    let width: usize = parts.next()?.parse().ok()?;
3442    let height: usize = parts.next()?.parse().ok()?;
3443    Some(vec2f(width as f32, height as f32))
3444}
3445
3446fn default_true() -> bool {
3447    true
3448}
3449
3450#[cfg(test)]
3451mod tests {
3452    use super::*;
3453    use crate::{
3454        dock::test::{TestPanel, TestPanelEvent},
3455        item::test::{TestItem, TestItemEvent, TestProjectItem},
3456    };
3457    use fs::FakeFs;
3458    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3459    use project::{Project, ProjectEntryId};
3460    use serde_json::json;
3461    use settings::SettingsStore;
3462    use std::{cell::RefCell, rc::Rc};
3463
3464    #[gpui::test]
3465    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3466        init_test(cx);
3467
3468        let fs = FakeFs::new(cx.background());
3469        let project = Project::test(fs, [], cx).await;
3470        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3471
3472        // Adding an item with no ambiguity renders the tab without detail.
3473        let item1 = cx.add_view(window_id, |_| {
3474            let mut item = TestItem::new();
3475            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3476            item
3477        });
3478        workspace.update(cx, |workspace, cx| {
3479            workspace.add_item(Box::new(item1.clone()), cx);
3480        });
3481        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3482
3483        // Adding an item that creates ambiguity increases the level of detail on
3484        // both tabs.
3485        let item2 = cx.add_view(window_id, |_| {
3486            let mut item = TestItem::new();
3487            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3488            item
3489        });
3490        workspace.update(cx, |workspace, cx| {
3491            workspace.add_item(Box::new(item2.clone()), cx);
3492        });
3493        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3494        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3495
3496        // Adding an item that creates ambiguity increases the level of detail only
3497        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3498        // we stop at the highest detail available.
3499        let item3 = cx.add_view(window_id, |_| {
3500            let mut item = TestItem::new();
3501            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3502            item
3503        });
3504        workspace.update(cx, |workspace, cx| {
3505            workspace.add_item(Box::new(item3.clone()), cx);
3506        });
3507        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3508        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3509        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3510    }
3511
3512    #[gpui::test]
3513    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3514        init_test(cx);
3515
3516        let fs = FakeFs::new(cx.background());
3517        fs.insert_tree(
3518            "/root1",
3519            json!({
3520                "one.txt": "",
3521                "two.txt": "",
3522            }),
3523        )
3524        .await;
3525        fs.insert_tree(
3526            "/root2",
3527            json!({
3528                "three.txt": "",
3529            }),
3530        )
3531        .await;
3532
3533        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3534        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3535        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3536        let worktree_id = project.read_with(cx, |project, cx| {
3537            project.worktrees(cx).next().unwrap().read(cx).id()
3538        });
3539
3540        let item1 = cx.add_view(window_id, |cx| {
3541            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3542        });
3543        let item2 = cx.add_view(window_id, |cx| {
3544            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3545        });
3546
3547        // Add an item to an empty pane
3548        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3549        project.read_with(cx, |project, cx| {
3550            assert_eq!(
3551                project.active_entry(),
3552                project
3553                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3554                    .map(|e| e.id)
3555            );
3556        });
3557        assert_eq!(
3558            cx.current_window_title(window_id).as_deref(),
3559            Some("one.txt β€” root1")
3560        );
3561
3562        // Add a second item to a non-empty pane
3563        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3564        assert_eq!(
3565            cx.current_window_title(window_id).as_deref(),
3566            Some("two.txt β€” root1")
3567        );
3568        project.read_with(cx, |project, cx| {
3569            assert_eq!(
3570                project.active_entry(),
3571                project
3572                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3573                    .map(|e| e.id)
3574            );
3575        });
3576
3577        // Close the active item
3578        pane.update(cx, |pane, cx| {
3579            pane.close_active_item(&Default::default(), cx).unwrap()
3580        })
3581        .await
3582        .unwrap();
3583        assert_eq!(
3584            cx.current_window_title(window_id).as_deref(),
3585            Some("one.txt β€” root1")
3586        );
3587        project.read_with(cx, |project, cx| {
3588            assert_eq!(
3589                project.active_entry(),
3590                project
3591                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3592                    .map(|e| e.id)
3593            );
3594        });
3595
3596        // Add a project folder
3597        project
3598            .update(cx, |project, cx| {
3599                project.find_or_create_local_worktree("/root2", true, cx)
3600            })
3601            .await
3602            .unwrap();
3603        assert_eq!(
3604            cx.current_window_title(window_id).as_deref(),
3605            Some("one.txt β€” root1, root2")
3606        );
3607
3608        // Remove a project folder
3609        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3610        assert_eq!(
3611            cx.current_window_title(window_id).as_deref(),
3612            Some("one.txt β€” root2")
3613        );
3614    }
3615
3616    #[gpui::test]
3617    async fn test_close_window(cx: &mut TestAppContext) {
3618        init_test(cx);
3619
3620        let fs = FakeFs::new(cx.background());
3621        fs.insert_tree("/root", json!({ "one": "" })).await;
3622
3623        let project = Project::test(fs, ["root".as_ref()], cx).await;
3624        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3625
3626        // When there are no dirty items, there's nothing to do.
3627        let item1 = cx.add_view(window_id, |_| TestItem::new());
3628        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3629        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3630        assert!(task.await.unwrap());
3631
3632        // When there are dirty untitled items, prompt to save each one. If the user
3633        // cancels any prompt, then abort.
3634        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3635        let item3 = cx.add_view(window_id, |cx| {
3636            TestItem::new()
3637                .with_dirty(true)
3638                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3639        });
3640        workspace.update(cx, |w, cx| {
3641            w.add_item(Box::new(item2.clone()), cx);
3642            w.add_item(Box::new(item3.clone()), cx);
3643        });
3644        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3645        cx.foreground().run_until_parked();
3646        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3647        cx.foreground().run_until_parked();
3648        assert!(!cx.has_pending_prompt(window_id));
3649        assert!(!task.await.unwrap());
3650    }
3651
3652    #[gpui::test]
3653    async fn test_close_pane_items(cx: &mut TestAppContext) {
3654        init_test(cx);
3655
3656        let fs = FakeFs::new(cx.background());
3657
3658        let project = Project::test(fs, None, cx).await;
3659        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3660
3661        let item1 = cx.add_view(window_id, |cx| {
3662            TestItem::new()
3663                .with_dirty(true)
3664                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3665        });
3666        let item2 = cx.add_view(window_id, |cx| {
3667            TestItem::new()
3668                .with_dirty(true)
3669                .with_conflict(true)
3670                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3671        });
3672        let item3 = cx.add_view(window_id, |cx| {
3673            TestItem::new()
3674                .with_dirty(true)
3675                .with_conflict(true)
3676                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3677        });
3678        let item4 = cx.add_view(window_id, |cx| {
3679            TestItem::new()
3680                .with_dirty(true)
3681                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3682        });
3683        let pane = workspace.update(cx, |workspace, cx| {
3684            workspace.add_item(Box::new(item1.clone()), cx);
3685            workspace.add_item(Box::new(item2.clone()), cx);
3686            workspace.add_item(Box::new(item3.clone()), cx);
3687            workspace.add_item(Box::new(item4.clone()), cx);
3688            workspace.active_pane().clone()
3689        });
3690
3691        let close_items = pane.update(cx, |pane, cx| {
3692            pane.activate_item(1, true, true, cx);
3693            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3694            let item1_id = item1.id();
3695            let item3_id = item3.id();
3696            let item4_id = item4.id();
3697            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
3698        });
3699        cx.foreground().run_until_parked();
3700
3701        // There's a prompt to save item 1.
3702        pane.read_with(cx, |pane, _| {
3703            assert_eq!(pane.items_len(), 4);
3704            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3705        });
3706        assert!(cx.has_pending_prompt(window_id));
3707
3708        // Confirm saving item 1.
3709        cx.simulate_prompt_answer(window_id, 0);
3710        cx.foreground().run_until_parked();
3711
3712        // Item 1 is saved. There's a prompt to save item 3.
3713        pane.read_with(cx, |pane, cx| {
3714            assert_eq!(item1.read(cx).save_count, 1);
3715            assert_eq!(item1.read(cx).save_as_count, 0);
3716            assert_eq!(item1.read(cx).reload_count, 0);
3717            assert_eq!(pane.items_len(), 3);
3718            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3719        });
3720        assert!(cx.has_pending_prompt(window_id));
3721
3722        // Cancel saving item 3.
3723        cx.simulate_prompt_answer(window_id, 1);
3724        cx.foreground().run_until_parked();
3725
3726        // Item 3 is reloaded. There's a prompt to save item 4.
3727        pane.read_with(cx, |pane, cx| {
3728            assert_eq!(item3.read(cx).save_count, 0);
3729            assert_eq!(item3.read(cx).save_as_count, 0);
3730            assert_eq!(item3.read(cx).reload_count, 1);
3731            assert_eq!(pane.items_len(), 2);
3732            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3733        });
3734        assert!(cx.has_pending_prompt(window_id));
3735
3736        // Confirm saving item 4.
3737        cx.simulate_prompt_answer(window_id, 0);
3738        cx.foreground().run_until_parked();
3739
3740        // There's a prompt for a path for item 4.
3741        cx.simulate_new_path_selection(|_| Some(Default::default()));
3742        close_items.await.unwrap();
3743
3744        // The requested items are closed.
3745        pane.read_with(cx, |pane, cx| {
3746            assert_eq!(item4.read(cx).save_count, 0);
3747            assert_eq!(item4.read(cx).save_as_count, 1);
3748            assert_eq!(item4.read(cx).reload_count, 0);
3749            assert_eq!(pane.items_len(), 1);
3750            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3751        });
3752    }
3753
3754    #[gpui::test]
3755    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3756        init_test(cx);
3757
3758        let fs = FakeFs::new(cx.background());
3759
3760        let project = Project::test(fs, [], cx).await;
3761        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3762
3763        // Create several workspace items with single project entries, and two
3764        // workspace items with multiple project entries.
3765        let single_entry_items = (0..=4)
3766            .map(|project_entry_id| {
3767                cx.add_view(window_id, |cx| {
3768                    TestItem::new()
3769                        .with_dirty(true)
3770                        .with_project_items(&[TestProjectItem::new(
3771                            project_entry_id,
3772                            &format!("{project_entry_id}.txt"),
3773                            cx,
3774                        )])
3775                })
3776            })
3777            .collect::<Vec<_>>();
3778        let item_2_3 = cx.add_view(window_id, |cx| {
3779            TestItem::new()
3780                .with_dirty(true)
3781                .with_singleton(false)
3782                .with_project_items(&[
3783                    single_entry_items[2].read(cx).project_items[0].clone(),
3784                    single_entry_items[3].read(cx).project_items[0].clone(),
3785                ])
3786        });
3787        let item_3_4 = cx.add_view(window_id, |cx| {
3788            TestItem::new()
3789                .with_dirty(true)
3790                .with_singleton(false)
3791                .with_project_items(&[
3792                    single_entry_items[3].read(cx).project_items[0].clone(),
3793                    single_entry_items[4].read(cx).project_items[0].clone(),
3794                ])
3795        });
3796
3797        // Create two panes that contain the following project entries:
3798        //   left pane:
3799        //     multi-entry items:   (2, 3)
3800        //     single-entry items:  0, 1, 2, 3, 4
3801        //   right pane:
3802        //     single-entry items:  1
3803        //     multi-entry items:   (3, 4)
3804        let left_pane = workspace.update(cx, |workspace, cx| {
3805            let left_pane = workspace.active_pane().clone();
3806            workspace.add_item(Box::new(item_2_3.clone()), cx);
3807            for item in single_entry_items {
3808                workspace.add_item(Box::new(item), cx);
3809            }
3810            left_pane.update(cx, |pane, cx| {
3811                pane.activate_item(2, true, true, cx);
3812            });
3813
3814            workspace
3815                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3816                .unwrap();
3817
3818            left_pane
3819        });
3820
3821        //Need to cause an effect flush in order to respect new focus
3822        workspace.update(cx, |workspace, cx| {
3823            workspace.add_item(Box::new(item_3_4.clone()), cx);
3824            cx.focus(&left_pane);
3825        });
3826
3827        // When closing all of the items in the left pane, we should be prompted twice:
3828        // once for project entry 0, and once for project entry 2. After those two
3829        // prompts, the task should complete.
3830
3831        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
3832        cx.foreground().run_until_parked();
3833        left_pane.read_with(cx, |pane, cx| {
3834            assert_eq!(
3835                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3836                &[ProjectEntryId::from_proto(0)]
3837            );
3838        });
3839        cx.simulate_prompt_answer(window_id, 0);
3840
3841        cx.foreground().run_until_parked();
3842        left_pane.read_with(cx, |pane, cx| {
3843            assert_eq!(
3844                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3845                &[ProjectEntryId::from_proto(2)]
3846            );
3847        });
3848        cx.simulate_prompt_answer(window_id, 0);
3849
3850        cx.foreground().run_until_parked();
3851        close.await.unwrap();
3852        left_pane.read_with(cx, |pane, _| {
3853            assert_eq!(pane.items_len(), 0);
3854        });
3855    }
3856
3857    #[gpui::test]
3858    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3859        init_test(cx);
3860
3861        let fs = FakeFs::new(cx.background());
3862
3863        let project = Project::test(fs, [], cx).await;
3864        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3865        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3866
3867        let item = cx.add_view(window_id, |cx| {
3868            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3869        });
3870        let item_id = item.id();
3871        workspace.update(cx, |workspace, cx| {
3872            workspace.add_item(Box::new(item.clone()), cx);
3873        });
3874
3875        // Autosave on window change.
3876        item.update(cx, |item, cx| {
3877            cx.update_global(|settings: &mut SettingsStore, cx| {
3878                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3879                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
3880                })
3881            });
3882            item.is_dirty = true;
3883        });
3884
3885        // Deactivating the window saves the file.
3886        cx.simulate_window_activation(None);
3887        deterministic.run_until_parked();
3888        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3889
3890        // Autosave on focus change.
3891        item.update(cx, |item, cx| {
3892            cx.focus_self();
3893            cx.update_global(|settings: &mut SettingsStore, cx| {
3894                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3895                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
3896                })
3897            });
3898            item.is_dirty = true;
3899        });
3900
3901        // Blurring the item saves the file.
3902        item.update(cx, |_, cx| cx.blur());
3903        deterministic.run_until_parked();
3904        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3905
3906        // Deactivating the window still saves the file.
3907        cx.simulate_window_activation(Some(window_id));
3908        item.update(cx, |item, cx| {
3909            cx.focus_self();
3910            item.is_dirty = true;
3911        });
3912        cx.simulate_window_activation(None);
3913
3914        deterministic.run_until_parked();
3915        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3916
3917        // Autosave after delay.
3918        item.update(cx, |item, cx| {
3919            cx.update_global(|settings: &mut SettingsStore, cx| {
3920                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3921                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
3922                })
3923            });
3924            item.is_dirty = true;
3925            cx.emit(TestItemEvent::Edit);
3926        });
3927
3928        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3929        deterministic.advance_clock(Duration::from_millis(250));
3930        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3931
3932        // After delay expires, the file is saved.
3933        deterministic.advance_clock(Duration::from_millis(250));
3934        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3935
3936        // Autosave on focus change, ensuring closing the tab counts as such.
3937        item.update(cx, |item, cx| {
3938            cx.update_global(|settings: &mut SettingsStore, cx| {
3939                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3940                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
3941                })
3942            });
3943            item.is_dirty = true;
3944        });
3945
3946        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
3947            .await
3948            .unwrap();
3949        assert!(!cx.has_pending_prompt(window_id));
3950        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3951
3952        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3953        workspace.update(cx, |workspace, cx| {
3954            workspace.add_item(Box::new(item.clone()), cx);
3955        });
3956        item.update(cx, |item, cx| {
3957            item.project_items[0].update(cx, |item, _| {
3958                item.entry_id = None;
3959            });
3960            item.is_dirty = true;
3961            cx.blur();
3962        });
3963        deterministic.run_until_parked();
3964        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3965
3966        // Ensure autosave is prevented for deleted files also when closing the buffer.
3967        let _close_items =
3968            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
3969        deterministic.run_until_parked();
3970        assert!(cx.has_pending_prompt(window_id));
3971        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3972    }
3973
3974    #[gpui::test]
3975    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
3976        init_test(cx);
3977
3978        let fs = FakeFs::new(cx.background());
3979
3980        let project = Project::test(fs, [], cx).await;
3981        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3982
3983        let item = cx.add_view(window_id, |cx| {
3984            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3985        });
3986        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3987        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3988        let toolbar_notify_count = Rc::new(RefCell::new(0));
3989
3990        workspace.update(cx, |workspace, cx| {
3991            workspace.add_item(Box::new(item.clone()), cx);
3992            let toolbar_notification_count = toolbar_notify_count.clone();
3993            cx.observe(&toolbar, move |_, _, _| {
3994                *toolbar_notification_count.borrow_mut() += 1
3995            })
3996            .detach();
3997        });
3998
3999        pane.read_with(cx, |pane, _| {
4000            assert!(!pane.can_navigate_backward());
4001            assert!(!pane.can_navigate_forward());
4002        });
4003
4004        item.update(cx, |item, cx| {
4005            item.set_state("one".to_string(), cx);
4006        });
4007
4008        // Toolbar must be notified to re-render the navigation buttons
4009        assert_eq!(*toolbar_notify_count.borrow(), 1);
4010
4011        pane.read_with(cx, |pane, _| {
4012            assert!(pane.can_navigate_backward());
4013            assert!(!pane.can_navigate_forward());
4014        });
4015
4016        workspace
4017            .update(cx, |workspace, cx| {
4018                Pane::go_back(workspace, Some(pane.downgrade()), cx)
4019            })
4020            .await
4021            .unwrap();
4022
4023        assert_eq!(*toolbar_notify_count.borrow(), 3);
4024        pane.read_with(cx, |pane, _| {
4025            assert!(!pane.can_navigate_backward());
4026            assert!(pane.can_navigate_forward());
4027        });
4028    }
4029
4030    #[gpui::test]
4031    async fn test_panels(cx: &mut gpui::TestAppContext) {
4032        init_test(cx);
4033        let fs = FakeFs::new(cx.background());
4034
4035        let project = Project::test(fs, [], cx).await;
4036        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4037
4038        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4039            // Add panel_1 on the left, panel_2 on the right.
4040            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4041            workspace.add_panel(panel_1.clone(), cx);
4042            workspace
4043                .left_dock()
4044                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4045            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4046            workspace.add_panel(panel_2.clone(), cx);
4047            workspace
4048                .right_dock()
4049                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4050
4051            let left_dock = workspace.left_dock();
4052            assert_eq!(
4053                left_dock.read(cx).active_panel().unwrap().id(),
4054                panel_1.id()
4055            );
4056            assert_eq!(
4057                left_dock.read(cx).active_panel_size(cx).unwrap(),
4058                panel_1.size(cx)
4059            );
4060
4061            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4062            assert_eq!(
4063                workspace.right_dock().read(cx).active_panel().unwrap().id(),
4064                panel_2.id()
4065            );
4066
4067            (panel_1, panel_2)
4068        });
4069
4070        // Move panel_1 to the right
4071        panel_1.update(cx, |panel_1, cx| {
4072            panel_1.set_position(DockPosition::Right, cx)
4073        });
4074
4075        workspace.update(cx, |workspace, cx| {
4076            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4077            // Since it was the only panel on the left, the left dock should now be closed.
4078            assert!(!workspace.left_dock().read(cx).is_open());
4079            assert!(workspace.left_dock().read(cx).active_panel().is_none());
4080            let right_dock = workspace.right_dock();
4081            assert_eq!(
4082                right_dock.read(cx).active_panel().unwrap().id(),
4083                panel_1.id()
4084            );
4085            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4086
4087            // Now we move panel_2Β to the left
4088            panel_2.set_position(DockPosition::Left, cx);
4089        });
4090
4091        workspace.update(cx, |workspace, cx| {
4092            // Since panel_2 was not visible on the right, we don't open the left dock.
4093            assert!(!workspace.left_dock().read(cx).is_open());
4094            // And the right dock is unaffected in it's displaying of panel_1
4095            assert!(workspace.right_dock().read(cx).is_open());
4096            assert_eq!(
4097                workspace.right_dock().read(cx).active_panel().unwrap().id(),
4098                panel_1.id()
4099            );
4100        });
4101
4102        // Move panel_1 back to the left
4103        panel_1.update(cx, |panel_1, cx| {
4104            panel_1.set_position(DockPosition::Left, cx)
4105        });
4106
4107        workspace.update(cx, |workspace, cx| {
4108            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4109            let left_dock = workspace.left_dock();
4110            assert!(left_dock.read(cx).is_open());
4111            assert_eq!(
4112                left_dock.read(cx).active_panel().unwrap().id(),
4113                panel_1.id()
4114            );
4115            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4116            // And right the dock should be closed as it no longer has any panels.
4117            assert!(!workspace.right_dock().read(cx).is_open());
4118
4119            // Now we move panel_1 to the bottom
4120            panel_1.set_position(DockPosition::Bottom, cx);
4121        });
4122
4123        workspace.update(cx, |workspace, cx| {
4124            // Since panel_1 was visible on the left, we close the left dock.
4125            assert!(!workspace.left_dock().read(cx).is_open());
4126            // The bottom dock is sized based on the panel's default size,
4127            // since the panel orientation changed from vertical to horizontal.
4128            let bottom_dock = workspace.bottom_dock();
4129            assert_eq!(
4130                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4131                panel_1.size(cx),
4132            );
4133            // Close bottom dock and move panel_1 back to the left.
4134            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4135            panel_1.set_position(DockPosition::Left, cx);
4136        });
4137
4138        // Emit activated event on panel 1
4139        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4140
4141        // Now the left dock is open and panel_1 is active and focused.
4142        workspace.read_with(cx, |workspace, cx| {
4143            let left_dock = workspace.left_dock();
4144            assert!(left_dock.read(cx).is_open());
4145            assert_eq!(
4146                left_dock.read(cx).active_panel().unwrap().id(),
4147                panel_1.id()
4148            );
4149            assert!(panel_1.is_focused(cx));
4150        });
4151
4152        // Emit closed event on panel 2, which is not active
4153        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4154
4155        // Wo don't close the left dock, because panel_2 wasn't the active panel
4156        workspace.read_with(cx, |workspace, cx| {
4157            let left_dock = workspace.left_dock();
4158            assert!(left_dock.read(cx).is_open());
4159            assert_eq!(
4160                left_dock.read(cx).active_panel().unwrap().id(),
4161                panel_1.id()
4162            );
4163        });
4164
4165        // Emitting a ZoomIn event shows the panel as zoomed.
4166        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4167        workspace.read_with(cx, |workspace, _| {
4168            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4169        });
4170
4171        // If focus is transferred to another view that's not a panel or another pane, we still show
4172        // the panel as zoomed.
4173        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4174        focus_receiver.update(cx, |_, cx| cx.focus_self());
4175        workspace.read_with(cx, |workspace, _| {
4176            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4177        });
4178
4179        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4180        workspace.update(cx, |_, cx| cx.focus_self());
4181        workspace.read_with(cx, |workspace, _| {
4182            assert_eq!(workspace.zoomed, None);
4183        });
4184
4185        // If focus is transferred again to another view that's not a panel or a pane, we won't
4186        // show the panel as zoomed because it wasn't zoomed before.
4187        focus_receiver.update(cx, |_, cx| cx.focus_self());
4188        workspace.read_with(cx, |workspace, _| {
4189            assert_eq!(workspace.zoomed, None);
4190        });
4191
4192        // When focus is transferred back to the panel, it is zoomed again.
4193        panel_1.update(cx, |_, cx| cx.focus_self());
4194        workspace.read_with(cx, |workspace, _| {
4195            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4196        });
4197
4198        // Emitting a ZoomOut event unzooms the panel.
4199        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4200        workspace.read_with(cx, |workspace, _| {
4201            assert_eq!(workspace.zoomed, None);
4202        });
4203
4204        // Emit closed event on panel 1, which is active
4205        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4206
4207        // Now the left dock is closed, because panel_1 was the active panel
4208        workspace.read_with(cx, |workspace, cx| {
4209            let left_dock = workspace.left_dock();
4210            assert!(!left_dock.read(cx).is_open());
4211        });
4212    }
4213
4214    pub fn init_test(cx: &mut TestAppContext) {
4215        cx.foreground().forbid_parking();
4216        cx.update(|cx| {
4217            cx.set_global(SettingsStore::test(cx));
4218            theme::init((), cx);
4219            language::init(cx);
4220            crate::init_settings(cx);
4221        });
4222    }
4223}