workspace.rs

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