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