workspace.rs

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