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