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