workspace.rs

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