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                    _ => constraint,
2840                })
2841                .into_any(),
2842        )
2843    }
2844}
2845
2846async fn open_items(
2847    serialized_workspace: Option<SerializedWorkspace>,
2848    workspace: &WeakViewHandle<Workspace>,
2849    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
2850    app_state: Arc<AppState>,
2851    mut cx: AsyncAppContext,
2852) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
2853    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
2854
2855    if let Some(serialized_workspace) = serialized_workspace {
2856        let workspace = workspace.clone();
2857        let restored_items = cx
2858            .update(|cx| {
2859                Workspace::load_workspace(
2860                    workspace,
2861                    serialized_workspace,
2862                    project_paths_to_open
2863                        .iter()
2864                        .map(|(_, project_path)| project_path)
2865                        .cloned()
2866                        .collect(),
2867                    cx,
2868                )
2869            })
2870            .await;
2871
2872        let restored_project_paths = cx.read(|cx| {
2873            restored_items
2874                .iter()
2875                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
2876                .collect::<HashSet<_>>()
2877        });
2878
2879        opened_items = restored_items;
2880        project_paths_to_open
2881            .iter_mut()
2882            .for_each(|(_, project_path)| {
2883                if let Some(project_path_to_open) = project_path {
2884                    if restored_project_paths.contains(project_path_to_open) {
2885                        *project_path = None;
2886                    }
2887                }
2888            });
2889    } else {
2890        for _ in 0..project_paths_to_open.len() {
2891            opened_items.push(None);
2892        }
2893    }
2894    assert!(opened_items.len() == project_paths_to_open.len());
2895
2896    let tasks =
2897        project_paths_to_open
2898            .into_iter()
2899            .enumerate()
2900            .map(|(i, (abs_path, project_path))| {
2901                let workspace = workspace.clone();
2902                cx.spawn(|mut cx| {
2903                    let fs = app_state.fs.clone();
2904                    async move {
2905                        let file_project_path = project_path?;
2906                        if fs.is_file(&abs_path).await {
2907                            Some((
2908                                i,
2909                                workspace
2910                                    .update(&mut cx, |workspace, cx| {
2911                                        workspace.open_path(file_project_path, None, true, cx)
2912                                    })
2913                                    .log_err()?
2914                                    .await,
2915                            ))
2916                        } else {
2917                            None
2918                        }
2919                    }
2920                })
2921            });
2922
2923    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
2924        .await
2925        .into_iter()
2926    {
2927        if let Some((i, path_open_result)) = maybe_opened_path {
2928            opened_items[i] = Some(path_open_result);
2929        }
2930    }
2931
2932    opened_items
2933}
2934
2935fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
2936    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
2937
2938    workspace
2939        .update(cx, |workspace, cx| {
2940            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
2941                workspace.show_notification_once(0, cx, |cx| {
2942                    cx.add_view(|_| {
2943                        MessageNotification::new("Failed to load any database file.")
2944                            .with_click_message("Click to let us know about this error")
2945                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
2946                    })
2947                });
2948            } else {
2949                let backup_path = (*db::BACKUP_DB_PATH).read();
2950                if let Some(backup_path) = backup_path.clone() {
2951                    workspace.show_notification_once(0, cx, move |cx| {
2952                        cx.add_view(move |_| {
2953                            MessageNotification::new(format!(
2954                                "Database file was corrupted. Old database backed up to {}",
2955                                backup_path.display()
2956                            ))
2957                            .with_click_message("Click to show old database in finder")
2958                            .on_click(move |cx| {
2959                                cx.platform().open_url(&backup_path.to_string_lossy())
2960                            })
2961                        })
2962                    });
2963                }
2964            }
2965        })
2966        .log_err();
2967}
2968
2969impl Entity for Workspace {
2970    type Event = Event;
2971}
2972
2973impl View for Workspace {
2974    fn ui_name() -> &'static str {
2975        "Workspace"
2976    }
2977
2978    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2979        let theme = theme::current(cx).clone();
2980        Stack::new()
2981            .with_child(
2982                Flex::column()
2983                    .with_child(self.render_titlebar(&theme, cx))
2984                    .with_child(
2985                        Stack::new()
2986                            .with_child({
2987                                let project = self.project.clone();
2988                                Flex::row()
2989                                    .with_children(self.render_dock(DockPosition::Left, cx))
2990                                    .with_child(
2991                                        Flex::column()
2992                                            .with_child(
2993                                                FlexItem::new(
2994                                                    self.center.render(
2995                                                        &project,
2996                                                        &theme,
2997                                                        &self.follower_states_by_leader,
2998                                                        self.active_call(),
2999                                                        self.active_pane(),
3000                                                        self.zoomed
3001                                                            .as_ref()
3002                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3003                                                            .as_ref(),
3004                                                        &self.app_state,
3005                                                        cx,
3006                                                    ),
3007                                                )
3008                                                .flex(1., true),
3009                                            )
3010                                            .with_children(
3011                                                self.render_dock(DockPosition::Bottom, cx),
3012                                            )
3013                                            .flex(1., true),
3014                                    )
3015                                    .with_children(self.render_dock(DockPosition::Right, cx))
3016                            })
3017                            .with_child(Overlay::new(
3018                                Stack::new()
3019                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3020                                        enum ZoomBackground {}
3021                                        let zoomed = zoomed.upgrade(cx)?;
3022                                        Some(
3023                                            ChildView::new(&zoomed, cx)
3024                                                .contained()
3025                                                .with_style(theme.workspace.zoomed_foreground)
3026                                                .aligned()
3027                                                .contained()
3028                                                .with_style(theme.workspace.zoomed_background)
3029                                                .mouse::<ZoomBackground>(0)
3030                                                .capture_all()
3031                                                .on_down(
3032                                                    MouseButton::Left,
3033                                                    |_, this: &mut Self, cx| {
3034                                                        this.zoom_out(cx);
3035                                                    },
3036                                                ),
3037                                        )
3038                                    }))
3039                                    .with_children(self.modal.as_ref().map(|modal| {
3040                                        ChildView::new(modal, cx)
3041                                            .contained()
3042                                            .with_style(theme.workspace.modal)
3043                                            .aligned()
3044                                            .top()
3045                                    }))
3046                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3047                            ))
3048                            .flex(1.0, true),
3049                    )
3050                    .with_child(ChildView::new(&self.status_bar, cx))
3051                    .contained()
3052                    .with_background_color(theme.workspace.background),
3053            )
3054            .with_children(DragAndDrop::render(cx))
3055            .with_children(self.render_disconnected_overlay(cx))
3056            .into_any_named("workspace")
3057    }
3058
3059    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3060        if cx.is_self_focused() {
3061            cx.focus(&self.active_pane);
3062        }
3063    }
3064}
3065
3066impl ViewId {
3067    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3068        Ok(Self {
3069            creator: message
3070                .creator
3071                .ok_or_else(|| anyhow!("creator is missing"))?,
3072            id: message.id,
3073        })
3074    }
3075
3076    pub(crate) fn to_proto(&self) -> proto::ViewId {
3077        proto::ViewId {
3078            creator: Some(self.creator),
3079            id: self.id,
3080        }
3081    }
3082}
3083
3084pub trait WorkspaceHandle {
3085    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3086}
3087
3088impl WorkspaceHandle for ViewHandle<Workspace> {
3089    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3090        self.read(cx)
3091            .worktrees(cx)
3092            .flat_map(|worktree| {
3093                let worktree_id = worktree.read(cx).id();
3094                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3095                    worktree_id,
3096                    path: f.path.clone(),
3097                })
3098            })
3099            .collect::<Vec<_>>()
3100    }
3101}
3102
3103impl std::fmt::Debug for OpenPaths {
3104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3105        f.debug_struct("OpenPaths")
3106            .field("paths", &self.paths)
3107            .finish()
3108    }
3109}
3110
3111pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
3112
3113pub fn activate_workspace_for_project(
3114    cx: &mut AsyncAppContext,
3115    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3116) -> Option<WeakViewHandle<Workspace>> {
3117    for window_id in cx.window_ids() {
3118        let handle = cx
3119            .update_window(window_id, |cx| {
3120                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3121                    let project = workspace_handle.read(cx).project.clone();
3122                    if project.update(cx, &predicate) {
3123                        cx.activate_window();
3124                        return Some(workspace_handle.clone());
3125                    }
3126                }
3127                None
3128            })
3129            .flatten();
3130
3131        if let Some(handle) = handle {
3132            return Some(handle.downgrade());
3133        }
3134    }
3135    None
3136}
3137
3138pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3139    DB.last_workspace().await.log_err().flatten()
3140}
3141
3142#[allow(clippy::type_complexity)]
3143pub fn open_paths(
3144    abs_paths: &[PathBuf],
3145    app_state: &Arc<AppState>,
3146    requesting_window_id: Option<usize>,
3147    cx: &mut AppContext,
3148) -> Task<
3149    Result<(
3150        WeakViewHandle<Workspace>,
3151        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3152    )>,
3153> {
3154    let app_state = app_state.clone();
3155    let abs_paths = abs_paths.to_vec();
3156    cx.spawn(|mut cx| async move {
3157        // Open paths in existing workspace if possible
3158        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3159            project.contains_paths(&abs_paths, cx)
3160        });
3161
3162        if let Some(existing) = existing {
3163            Ok((
3164                existing.clone(),
3165                existing
3166                    .update(&mut cx, |workspace, cx| {
3167                        workspace.open_paths(abs_paths, true, cx)
3168                    })?
3169                    .await,
3170            ))
3171        } else {
3172            Ok(cx
3173                .update(|cx| {
3174                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3175                })
3176                .await)
3177        }
3178    })
3179}
3180
3181pub fn open_new(
3182    app_state: &Arc<AppState>,
3183    cx: &mut AppContext,
3184    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3185) -> Task<()> {
3186    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3187    cx.spawn(|mut cx| async move {
3188        let (workspace, opened_paths) = task.await;
3189
3190        workspace
3191            .update(&mut cx, |workspace, cx| {
3192                if opened_paths.is_empty() {
3193                    init(workspace, cx)
3194                }
3195            })
3196            .log_err();
3197    })
3198}
3199
3200pub fn create_and_open_local_file(
3201    path: &'static Path,
3202    cx: &mut ViewContext<Workspace>,
3203    default_content: impl 'static + Send + FnOnce() -> Rope,
3204) -> Task<Result<Box<dyn ItemHandle>>> {
3205    cx.spawn(|workspace, mut cx| async move {
3206        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3207        if !fs.is_file(path).await {
3208            fs.create_file(path, Default::default()).await?;
3209            fs.save(path, &default_content(), Default::default())
3210                .await?;
3211        }
3212
3213        let mut items = workspace
3214            .update(&mut cx, |workspace, cx| {
3215                workspace.with_local_workspace(cx, |workspace, cx| {
3216                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3217                })
3218            })?
3219            .await?
3220            .await;
3221
3222        let item = items.pop().flatten();
3223        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3224    })
3225}
3226
3227pub fn join_remote_project(
3228    project_id: u64,
3229    follow_user_id: u64,
3230    app_state: Arc<AppState>,
3231    cx: &mut AppContext,
3232) -> Task<Result<()>> {
3233    cx.spawn(|mut cx| async move {
3234        let existing_workspace = cx
3235            .window_ids()
3236            .into_iter()
3237            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3238            .find(|workspace| {
3239                cx.read_window(workspace.window_id(), |cx| {
3240                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3241                })
3242                .unwrap_or(false)
3243            });
3244
3245        let workspace = if let Some(existing_workspace) = existing_workspace {
3246            existing_workspace.downgrade()
3247        } else {
3248            let active_call = cx.read(ActiveCall::global);
3249            let room = active_call
3250                .read_with(&cx, |call, _| call.room().cloned())
3251                .ok_or_else(|| anyhow!("not in a call"))?;
3252            let project = room
3253                .update(&mut cx, |room, cx| {
3254                    room.join_project(
3255                        project_id,
3256                        app_state.languages.clone(),
3257                        app_state.fs.clone(),
3258                        cx,
3259                    )
3260                })
3261                .await?;
3262
3263            let (_, workspace) = cx.add_window(
3264                (app_state.build_window_options)(None, None, cx.platform().as_ref()),
3265                |cx| Workspace::new(0, project, app_state.clone(), cx),
3266            );
3267            (app_state.initialize_workspace)(
3268                workspace.downgrade(),
3269                false,
3270                app_state.clone(),
3271                cx.clone(),
3272            )
3273            .await
3274            .log_err();
3275
3276            workspace.downgrade()
3277        };
3278
3279        cx.activate_window(workspace.window_id());
3280        cx.platform().activate(true);
3281
3282        workspace.update(&mut cx, |workspace, cx| {
3283            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3284                let follow_peer_id = room
3285                    .read(cx)
3286                    .remote_participants()
3287                    .iter()
3288                    .find(|(_, participant)| participant.user.id == follow_user_id)
3289                    .map(|(_, p)| p.peer_id)
3290                    .or_else(|| {
3291                        // If we couldn't follow the given user, follow the host instead.
3292                        let collaborator = workspace
3293                            .project()
3294                            .read(cx)
3295                            .collaborators()
3296                            .values()
3297                            .find(|collaborator| collaborator.replica_id == 0)?;
3298                        Some(collaborator.peer_id)
3299                    });
3300
3301                if let Some(follow_peer_id) = follow_peer_id {
3302                    if !workspace.is_being_followed(follow_peer_id) {
3303                        workspace
3304                            .toggle_follow(follow_peer_id, cx)
3305                            .map(|follow| follow.detach_and_log_err(cx));
3306                    }
3307                }
3308            }
3309        })?;
3310
3311        anyhow::Ok(())
3312    })
3313}
3314
3315pub fn restart(_: &Restart, cx: &mut AppContext) {
3316    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3317    cx.spawn(|mut cx| async move {
3318        let mut workspaces = cx
3319            .window_ids()
3320            .into_iter()
3321            .filter_map(|window_id| {
3322                Some(
3323                    cx.root_view(window_id)?
3324                        .clone()
3325                        .downcast::<Workspace>()?
3326                        .downgrade(),
3327                )
3328            })
3329            .collect::<Vec<_>>();
3330
3331        // If multiple windows have unsaved changes, and need a save prompt,
3332        // prompt in the active window before switching to a different window.
3333        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3334
3335        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3336            let answer = cx.prompt(
3337                workspace.window_id(),
3338                PromptLevel::Info,
3339                "Are you sure you want to restart?",
3340                &["Restart", "Cancel"],
3341            );
3342
3343            if let Some(mut answer) = answer {
3344                let answer = answer.next().await;
3345                if answer != Some(0) {
3346                    return Ok(());
3347                }
3348            }
3349        }
3350
3351        // If the user cancels any save prompt, then keep the app open.
3352        for workspace in workspaces {
3353            if !workspace
3354                .update(&mut cx, |workspace, cx| {
3355                    workspace.prepare_to_close(true, cx)
3356                })?
3357                .await?
3358            {
3359                return Ok(());
3360            }
3361        }
3362        cx.platform().restart();
3363        anyhow::Ok(())
3364    })
3365    .detach_and_log_err(cx);
3366}
3367
3368fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3369    let mut parts = value.split(',');
3370    let width: usize = parts.next()?.parse().ok()?;
3371    let height: usize = parts.next()?.parse().ok()?;
3372    Some(vec2f(width as f32, height as f32))
3373}
3374
3375fn default_true() -> bool {
3376    true
3377}
3378
3379#[cfg(test)]
3380mod tests {
3381    use super::*;
3382    use crate::{
3383        dock::test::{TestPanel, TestPanelEvent},
3384        item::test::{TestItem, TestItemEvent, TestProjectItem},
3385    };
3386    use fs::FakeFs;
3387    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3388    use project::{Project, ProjectEntryId};
3389    use serde_json::json;
3390    use settings::SettingsStore;
3391    use std::{cell::RefCell, rc::Rc};
3392
3393    #[gpui::test]
3394    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3395        init_test(cx);
3396
3397        let fs = FakeFs::new(cx.background());
3398        let project = Project::test(fs, [], cx).await;
3399        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3400
3401        // Adding an item with no ambiguity renders the tab without detail.
3402        let item1 = cx.add_view(window_id, |_| {
3403            let mut item = TestItem::new();
3404            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3405            item
3406        });
3407        workspace.update(cx, |workspace, cx| {
3408            workspace.add_item(Box::new(item1.clone()), cx);
3409        });
3410        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3411
3412        // Adding an item that creates ambiguity increases the level of detail on
3413        // both tabs.
3414        let item2 = cx.add_view(window_id, |_| {
3415            let mut item = TestItem::new();
3416            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3417            item
3418        });
3419        workspace.update(cx, |workspace, cx| {
3420            workspace.add_item(Box::new(item2.clone()), cx);
3421        });
3422        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3423        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3424
3425        // Adding an item that creates ambiguity increases the level of detail only
3426        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
3427        // we stop at the highest detail available.
3428        let item3 = cx.add_view(window_id, |_| {
3429            let mut item = TestItem::new();
3430            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3431            item
3432        });
3433        workspace.update(cx, |workspace, cx| {
3434            workspace.add_item(Box::new(item3.clone()), cx);
3435        });
3436        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
3437        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3438        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
3439    }
3440
3441    #[gpui::test]
3442    async fn test_tracking_active_path(cx: &mut TestAppContext) {
3443        init_test(cx);
3444
3445        let fs = FakeFs::new(cx.background());
3446        fs.insert_tree(
3447            "/root1",
3448            json!({
3449                "one.txt": "",
3450                "two.txt": "",
3451            }),
3452        )
3453        .await;
3454        fs.insert_tree(
3455            "/root2",
3456            json!({
3457                "three.txt": "",
3458            }),
3459        )
3460        .await;
3461
3462        let project = Project::test(fs, ["root1".as_ref()], cx).await;
3463        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3464        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3465        let worktree_id = project.read_with(cx, |project, cx| {
3466            project.worktrees(cx).next().unwrap().read(cx).id()
3467        });
3468
3469        let item1 = cx.add_view(window_id, |cx| {
3470            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
3471        });
3472        let item2 = cx.add_view(window_id, |cx| {
3473            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
3474        });
3475
3476        // Add an item to an empty pane
3477        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
3478        project.read_with(cx, |project, cx| {
3479            assert_eq!(
3480                project.active_entry(),
3481                project
3482                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3483                    .map(|e| e.id)
3484            );
3485        });
3486        assert_eq!(
3487            cx.current_window_title(window_id).as_deref(),
3488            Some("one.txt β€” root1")
3489        );
3490
3491        // Add a second item to a non-empty pane
3492        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
3493        assert_eq!(
3494            cx.current_window_title(window_id).as_deref(),
3495            Some("two.txt β€” root1")
3496        );
3497        project.read_with(cx, |project, cx| {
3498            assert_eq!(
3499                project.active_entry(),
3500                project
3501                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
3502                    .map(|e| e.id)
3503            );
3504        });
3505
3506        // Close the active item
3507        pane.update(cx, |pane, cx| {
3508            pane.close_active_item(&Default::default(), cx).unwrap()
3509        })
3510        .await
3511        .unwrap();
3512        assert_eq!(
3513            cx.current_window_title(window_id).as_deref(),
3514            Some("one.txt β€” root1")
3515        );
3516        project.read_with(cx, |project, cx| {
3517            assert_eq!(
3518                project.active_entry(),
3519                project
3520                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
3521                    .map(|e| e.id)
3522            );
3523        });
3524
3525        // Add a project folder
3526        project
3527            .update(cx, |project, cx| {
3528                project.find_or_create_local_worktree("/root2", true, cx)
3529            })
3530            .await
3531            .unwrap();
3532        assert_eq!(
3533            cx.current_window_title(window_id).as_deref(),
3534            Some("one.txt β€” root1, root2")
3535        );
3536
3537        // Remove a project folder
3538        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
3539        assert_eq!(
3540            cx.current_window_title(window_id).as_deref(),
3541            Some("one.txt β€” root2")
3542        );
3543    }
3544
3545    #[gpui::test]
3546    async fn test_close_window(cx: &mut TestAppContext) {
3547        init_test(cx);
3548
3549        let fs = FakeFs::new(cx.background());
3550        fs.insert_tree("/root", json!({ "one": "" })).await;
3551
3552        let project = Project::test(fs, ["root".as_ref()], cx).await;
3553        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3554
3555        // When there are no dirty items, there's nothing to do.
3556        let item1 = cx.add_view(window_id, |_| TestItem::new());
3557        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
3558        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3559        assert!(task.await.unwrap());
3560
3561        // When there are dirty untitled items, prompt to save each one. If the user
3562        // cancels any prompt, then abort.
3563        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
3564        let item3 = cx.add_view(window_id, |cx| {
3565            TestItem::new()
3566                .with_dirty(true)
3567                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3568        });
3569        workspace.update(cx, |w, cx| {
3570            w.add_item(Box::new(item2.clone()), cx);
3571            w.add_item(Box::new(item3.clone()), cx);
3572        });
3573        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
3574        cx.foreground().run_until_parked();
3575        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
3576        cx.foreground().run_until_parked();
3577        assert!(!cx.has_pending_prompt(window_id));
3578        assert!(!task.await.unwrap());
3579    }
3580
3581    #[gpui::test]
3582    async fn test_close_pane_items(cx: &mut TestAppContext) {
3583        init_test(cx);
3584
3585        let fs = FakeFs::new(cx.background());
3586
3587        let project = Project::test(fs, None, cx).await;
3588        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3589
3590        let item1 = cx.add_view(window_id, |cx| {
3591            TestItem::new()
3592                .with_dirty(true)
3593                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3594        });
3595        let item2 = cx.add_view(window_id, |cx| {
3596            TestItem::new()
3597                .with_dirty(true)
3598                .with_conflict(true)
3599                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
3600        });
3601        let item3 = cx.add_view(window_id, |cx| {
3602            TestItem::new()
3603                .with_dirty(true)
3604                .with_conflict(true)
3605                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
3606        });
3607        let item4 = cx.add_view(window_id, |cx| {
3608            TestItem::new()
3609                .with_dirty(true)
3610                .with_project_items(&[TestProjectItem::new_untitled(cx)])
3611        });
3612        let pane = workspace.update(cx, |workspace, cx| {
3613            workspace.add_item(Box::new(item1.clone()), cx);
3614            workspace.add_item(Box::new(item2.clone()), cx);
3615            workspace.add_item(Box::new(item3.clone()), cx);
3616            workspace.add_item(Box::new(item4.clone()), cx);
3617            workspace.active_pane().clone()
3618        });
3619
3620        let close_items = pane.update(cx, |pane, cx| {
3621            pane.activate_item(1, true, true, cx);
3622            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3623            let item1_id = item1.id();
3624            let item3_id = item3.id();
3625            let item4_id = item4.id();
3626            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
3627        });
3628        cx.foreground().run_until_parked();
3629
3630        // There's a prompt to save item 1.
3631        pane.read_with(cx, |pane, _| {
3632            assert_eq!(pane.items_len(), 4);
3633            assert_eq!(pane.active_item().unwrap().id(), item1.id());
3634        });
3635        assert!(cx.has_pending_prompt(window_id));
3636
3637        // Confirm saving item 1.
3638        cx.simulate_prompt_answer(window_id, 0);
3639        cx.foreground().run_until_parked();
3640
3641        // Item 1 is saved. There's a prompt to save item 3.
3642        pane.read_with(cx, |pane, cx| {
3643            assert_eq!(item1.read(cx).save_count, 1);
3644            assert_eq!(item1.read(cx).save_as_count, 0);
3645            assert_eq!(item1.read(cx).reload_count, 0);
3646            assert_eq!(pane.items_len(), 3);
3647            assert_eq!(pane.active_item().unwrap().id(), item3.id());
3648        });
3649        assert!(cx.has_pending_prompt(window_id));
3650
3651        // Cancel saving item 3.
3652        cx.simulate_prompt_answer(window_id, 1);
3653        cx.foreground().run_until_parked();
3654
3655        // Item 3 is reloaded. There's a prompt to save item 4.
3656        pane.read_with(cx, |pane, cx| {
3657            assert_eq!(item3.read(cx).save_count, 0);
3658            assert_eq!(item3.read(cx).save_as_count, 0);
3659            assert_eq!(item3.read(cx).reload_count, 1);
3660            assert_eq!(pane.items_len(), 2);
3661            assert_eq!(pane.active_item().unwrap().id(), item4.id());
3662        });
3663        assert!(cx.has_pending_prompt(window_id));
3664
3665        // Confirm saving item 4.
3666        cx.simulate_prompt_answer(window_id, 0);
3667        cx.foreground().run_until_parked();
3668
3669        // There's a prompt for a path for item 4.
3670        cx.simulate_new_path_selection(|_| Some(Default::default()));
3671        close_items.await.unwrap();
3672
3673        // The requested items are closed.
3674        pane.read_with(cx, |pane, cx| {
3675            assert_eq!(item4.read(cx).save_count, 0);
3676            assert_eq!(item4.read(cx).save_as_count, 1);
3677            assert_eq!(item4.read(cx).reload_count, 0);
3678            assert_eq!(pane.items_len(), 1);
3679            assert_eq!(pane.active_item().unwrap().id(), item2.id());
3680        });
3681    }
3682
3683    #[gpui::test]
3684    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
3685        init_test(cx);
3686
3687        let fs = FakeFs::new(cx.background());
3688
3689        let project = Project::test(fs, [], cx).await;
3690        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3691
3692        // Create several workspace items with single project entries, and two
3693        // workspace items with multiple project entries.
3694        let single_entry_items = (0..=4)
3695            .map(|project_entry_id| {
3696                cx.add_view(window_id, |cx| {
3697                    TestItem::new()
3698                        .with_dirty(true)
3699                        .with_project_items(&[TestProjectItem::new(
3700                            project_entry_id,
3701                            &format!("{project_entry_id}.txt"),
3702                            cx,
3703                        )])
3704                })
3705            })
3706            .collect::<Vec<_>>();
3707        let item_2_3 = cx.add_view(window_id, |cx| {
3708            TestItem::new()
3709                .with_dirty(true)
3710                .with_singleton(false)
3711                .with_project_items(&[
3712                    single_entry_items[2].read(cx).project_items[0].clone(),
3713                    single_entry_items[3].read(cx).project_items[0].clone(),
3714                ])
3715        });
3716        let item_3_4 = cx.add_view(window_id, |cx| {
3717            TestItem::new()
3718                .with_dirty(true)
3719                .with_singleton(false)
3720                .with_project_items(&[
3721                    single_entry_items[3].read(cx).project_items[0].clone(),
3722                    single_entry_items[4].read(cx).project_items[0].clone(),
3723                ])
3724        });
3725
3726        // Create two panes that contain the following project entries:
3727        //   left pane:
3728        //     multi-entry items:   (2, 3)
3729        //     single-entry items:  0, 1, 2, 3, 4
3730        //   right pane:
3731        //     single-entry items:  1
3732        //     multi-entry items:   (3, 4)
3733        let left_pane = workspace.update(cx, |workspace, cx| {
3734            let left_pane = workspace.active_pane().clone();
3735            workspace.add_item(Box::new(item_2_3.clone()), cx);
3736            for item in single_entry_items {
3737                workspace.add_item(Box::new(item), cx);
3738            }
3739            left_pane.update(cx, |pane, cx| {
3740                pane.activate_item(2, true, true, cx);
3741            });
3742
3743            workspace
3744                .split_pane(left_pane.clone(), SplitDirection::Right, cx)
3745                .unwrap();
3746
3747            left_pane
3748        });
3749
3750        //Need to cause an effect flush in order to respect new focus
3751        workspace.update(cx, |workspace, cx| {
3752            workspace.add_item(Box::new(item_3_4.clone()), cx);
3753            cx.focus(&left_pane);
3754        });
3755
3756        // When closing all of the items in the left pane, we should be prompted twice:
3757        // once for project entry 0, and once for project entry 2. After those two
3758        // prompts, the task should complete.
3759
3760        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
3761        cx.foreground().run_until_parked();
3762        left_pane.read_with(cx, |pane, cx| {
3763            assert_eq!(
3764                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3765                &[ProjectEntryId::from_proto(0)]
3766            );
3767        });
3768        cx.simulate_prompt_answer(window_id, 0);
3769
3770        cx.foreground().run_until_parked();
3771        left_pane.read_with(cx, |pane, cx| {
3772            assert_eq!(
3773                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
3774                &[ProjectEntryId::from_proto(2)]
3775            );
3776        });
3777        cx.simulate_prompt_answer(window_id, 0);
3778
3779        cx.foreground().run_until_parked();
3780        close.await.unwrap();
3781        left_pane.read_with(cx, |pane, _| {
3782            assert_eq!(pane.items_len(), 0);
3783        });
3784    }
3785
3786    #[gpui::test]
3787    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
3788        init_test(cx);
3789
3790        let fs = FakeFs::new(cx.background());
3791
3792        let project = Project::test(fs, [], cx).await;
3793        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3794        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3795
3796        let item = cx.add_view(window_id, |cx| {
3797            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3798        });
3799        let item_id = item.id();
3800        workspace.update(cx, |workspace, cx| {
3801            workspace.add_item(Box::new(item.clone()), cx);
3802        });
3803
3804        // Autosave on window change.
3805        item.update(cx, |item, cx| {
3806            cx.update_global(|settings: &mut SettingsStore, cx| {
3807                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3808                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
3809                })
3810            });
3811            item.is_dirty = true;
3812        });
3813
3814        // Deactivating the window saves the file.
3815        cx.simulate_window_activation(None);
3816        deterministic.run_until_parked();
3817        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
3818
3819        // Autosave on focus change.
3820        item.update(cx, |item, cx| {
3821            cx.focus_self();
3822            cx.update_global(|settings: &mut SettingsStore, cx| {
3823                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3824                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
3825                })
3826            });
3827            item.is_dirty = true;
3828        });
3829
3830        // Blurring the item saves the file.
3831        item.update(cx, |_, cx| cx.blur());
3832        deterministic.run_until_parked();
3833        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
3834
3835        // Deactivating the window still saves the file.
3836        cx.simulate_window_activation(Some(window_id));
3837        item.update(cx, |item, cx| {
3838            cx.focus_self();
3839            item.is_dirty = true;
3840        });
3841        cx.simulate_window_activation(None);
3842
3843        deterministic.run_until_parked();
3844        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3845
3846        // Autosave after delay.
3847        item.update(cx, |item, cx| {
3848            cx.update_global(|settings: &mut SettingsStore, cx| {
3849                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3850                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
3851                })
3852            });
3853            item.is_dirty = true;
3854            cx.emit(TestItemEvent::Edit);
3855        });
3856
3857        // Delay hasn't fully expired, so the file is still dirty and unsaved.
3858        deterministic.advance_clock(Duration::from_millis(250));
3859        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
3860
3861        // After delay expires, the file is saved.
3862        deterministic.advance_clock(Duration::from_millis(250));
3863        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
3864
3865        // Autosave on focus change, ensuring closing the tab counts as such.
3866        item.update(cx, |item, cx| {
3867            cx.update_global(|settings: &mut SettingsStore, cx| {
3868                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
3869                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
3870                })
3871            });
3872            item.is_dirty = true;
3873        });
3874
3875        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
3876            .await
3877            .unwrap();
3878        assert!(!cx.has_pending_prompt(window_id));
3879        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3880
3881        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
3882        workspace.update(cx, |workspace, cx| {
3883            workspace.add_item(Box::new(item.clone()), cx);
3884        });
3885        item.update(cx, |item, cx| {
3886            item.project_items[0].update(cx, |item, _| {
3887                item.entry_id = None;
3888            });
3889            item.is_dirty = true;
3890            cx.blur();
3891        });
3892        deterministic.run_until_parked();
3893        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3894
3895        // Ensure autosave is prevented for deleted files also when closing the buffer.
3896        let _close_items =
3897            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
3898        deterministic.run_until_parked();
3899        assert!(cx.has_pending_prompt(window_id));
3900        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
3901    }
3902
3903    #[gpui::test]
3904    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
3905        init_test(cx);
3906
3907        let fs = FakeFs::new(cx.background());
3908
3909        let project = Project::test(fs, [], cx).await;
3910        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3911
3912        let item = cx.add_view(window_id, |cx| {
3913            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
3914        });
3915        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
3916        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
3917        let toolbar_notify_count = Rc::new(RefCell::new(0));
3918
3919        workspace.update(cx, |workspace, cx| {
3920            workspace.add_item(Box::new(item.clone()), cx);
3921            let toolbar_notification_count = toolbar_notify_count.clone();
3922            cx.observe(&toolbar, move |_, _, _| {
3923                *toolbar_notification_count.borrow_mut() += 1
3924            })
3925            .detach();
3926        });
3927
3928        pane.read_with(cx, |pane, _| {
3929            assert!(!pane.can_navigate_backward());
3930            assert!(!pane.can_navigate_forward());
3931        });
3932
3933        item.update(cx, |item, cx| {
3934            item.set_state("one".to_string(), cx);
3935        });
3936
3937        // Toolbar must be notified to re-render the navigation buttons
3938        assert_eq!(*toolbar_notify_count.borrow(), 1);
3939
3940        pane.read_with(cx, |pane, _| {
3941            assert!(pane.can_navigate_backward());
3942            assert!(!pane.can_navigate_forward());
3943        });
3944
3945        workspace
3946            .update(cx, |workspace, cx| {
3947                Pane::go_back(workspace, Some(pane.downgrade()), cx)
3948            })
3949            .await
3950            .unwrap();
3951
3952        assert_eq!(*toolbar_notify_count.borrow(), 3);
3953        pane.read_with(cx, |pane, _| {
3954            assert!(!pane.can_navigate_backward());
3955            assert!(pane.can_navigate_forward());
3956        });
3957    }
3958
3959    #[gpui::test]
3960    async fn test_panels(cx: &mut gpui::TestAppContext) {
3961        init_test(cx);
3962        let fs = FakeFs::new(cx.background());
3963
3964        let project = Project::test(fs, [], cx).await;
3965        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
3966
3967        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
3968            // Add panel_1 on the left, panel_2 on the right.
3969            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
3970            workspace.add_panel(panel_1.clone(), cx);
3971            workspace
3972                .left_dock()
3973                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
3974            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
3975            workspace.add_panel(panel_2.clone(), cx);
3976            workspace
3977                .right_dock()
3978                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
3979
3980            let left_dock = workspace.left_dock();
3981            assert_eq!(
3982                left_dock.read(cx).active_panel().unwrap().id(),
3983                panel_1.id()
3984            );
3985            assert_eq!(
3986                left_dock.read(cx).active_panel_size(cx).unwrap(),
3987                panel_1.size(cx)
3988            );
3989
3990            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
3991            assert_eq!(
3992                workspace.right_dock().read(cx).active_panel().unwrap().id(),
3993                panel_2.id()
3994            );
3995
3996            (panel_1, panel_2)
3997        });
3998
3999        // Move panel_1 to the right
4000        panel_1.update(cx, |panel_1, cx| {
4001            panel_1.set_position(DockPosition::Right, cx)
4002        });
4003
4004        workspace.update(cx, |workspace, cx| {
4005            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4006            // Since it was the only panel on the left, the left dock should now be closed.
4007            assert!(!workspace.left_dock().read(cx).is_open());
4008            assert!(workspace.left_dock().read(cx).active_panel().is_none());
4009            let right_dock = workspace.right_dock();
4010            assert_eq!(
4011                right_dock.read(cx).active_panel().unwrap().id(),
4012                panel_1.id()
4013            );
4014            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4015
4016            // Now we move panel_2Β to the left
4017            panel_2.set_position(DockPosition::Left, cx);
4018        });
4019
4020        workspace.update(cx, |workspace, cx| {
4021            // Since panel_2 was not visible on the right, we don't open the left dock.
4022            assert!(!workspace.left_dock().read(cx).is_open());
4023            // And the right dock is unaffected in it's displaying of panel_1
4024            assert!(workspace.right_dock().read(cx).is_open());
4025            assert_eq!(
4026                workspace.right_dock().read(cx).active_panel().unwrap().id(),
4027                panel_1.id()
4028            );
4029        });
4030
4031        // Move panel_1 back to the left
4032        panel_1.update(cx, |panel_1, cx| {
4033            panel_1.set_position(DockPosition::Left, cx)
4034        });
4035
4036        workspace.update(cx, |workspace, cx| {
4037            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4038            let left_dock = workspace.left_dock();
4039            assert!(left_dock.read(cx).is_open());
4040            assert_eq!(
4041                left_dock.read(cx).active_panel().unwrap().id(),
4042                panel_1.id()
4043            );
4044            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4045            // And right the dock should be closed as it no longer has any panels.
4046            assert!(!workspace.right_dock().read(cx).is_open());
4047
4048            // Now we move panel_1 to the bottom
4049            panel_1.set_position(DockPosition::Bottom, cx);
4050        });
4051
4052        workspace.update(cx, |workspace, cx| {
4053            // Since panel_1 was visible on the left, we close the left dock.
4054            assert!(!workspace.left_dock().read(cx).is_open());
4055            // The bottom dock is sized based on the panel's default size,
4056            // since the panel orientation changed from vertical to horizontal.
4057            let bottom_dock = workspace.bottom_dock();
4058            assert_eq!(
4059                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4060                panel_1.size(cx),
4061            );
4062            // Close bottom dock and move panel_1 back to the left.
4063            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4064            panel_1.set_position(DockPosition::Left, cx);
4065        });
4066
4067        // Emit activated event on panel 1
4068        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4069
4070        // Now the left dock is open and panel_1 is active and focused.
4071        workspace.read_with(cx, |workspace, cx| {
4072            let left_dock = workspace.left_dock();
4073            assert!(left_dock.read(cx).is_open());
4074            assert_eq!(
4075                left_dock.read(cx).active_panel().unwrap().id(),
4076                panel_1.id()
4077            );
4078            assert!(panel_1.is_focused(cx));
4079        });
4080
4081        // Emit closed event on panel 2, which is not active
4082        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4083
4084        // Wo don't close the left dock, because panel_2 wasn't the active panel
4085        workspace.read_with(cx, |workspace, cx| {
4086            let left_dock = workspace.left_dock();
4087            assert!(left_dock.read(cx).is_open());
4088            assert_eq!(
4089                left_dock.read(cx).active_panel().unwrap().id(),
4090                panel_1.id()
4091            );
4092        });
4093
4094        // Emitting a ZoomIn event shows the panel as zoomed.
4095        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4096        workspace.read_with(cx, |workspace, _| {
4097            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4098        });
4099
4100        // If focus is transferred to another view that's not a panel or another pane, we still show
4101        // the panel as zoomed.
4102        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4103        focus_receiver.update(cx, |_, cx| cx.focus_self());
4104        workspace.read_with(cx, |workspace, _| {
4105            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4106        });
4107
4108        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4109        workspace.update(cx, |_, cx| cx.focus_self());
4110        workspace.read_with(cx, |workspace, _| {
4111            assert_eq!(workspace.zoomed, None);
4112        });
4113
4114        // If focus is transferred again to another view that's not a panel or a pane, we won't
4115        // show the panel as zoomed because it wasn't zoomed before.
4116        focus_receiver.update(cx, |_, cx| cx.focus_self());
4117        workspace.read_with(cx, |workspace, _| {
4118            assert_eq!(workspace.zoomed, None);
4119        });
4120
4121        // When focus is transferred back to the panel, it is zoomed again.
4122        panel_1.update(cx, |_, cx| cx.focus_self());
4123        workspace.read_with(cx, |workspace, _| {
4124            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4125        });
4126
4127        // Emitting a ZoomOut event unzooms the panel.
4128        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4129        workspace.read_with(cx, |workspace, _| {
4130            assert_eq!(workspace.zoomed, None);
4131        });
4132
4133        // Emit closed event on panel 1, which is active
4134        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4135
4136        // Now the left dock is closed, because panel_1 was the active panel
4137        workspace.read_with(cx, |workspace, cx| {
4138            let left_dock = workspace.left_dock();
4139            assert!(!left_dock.read(cx).is_open());
4140        });
4141    }
4142
4143    pub fn init_test(cx: &mut TestAppContext) {
4144        cx.foreground().forbid_parking();
4145        cx.update(|cx| {
4146            cx.set_global(SettingsStore::test(cx));
4147            theme::init((), cx);
4148            language::init(cx);
4149            crate::init_settings(cx);
4150        });
4151    }
4152}