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