variable_list.rs

   1use std::sync::{
   2    Arc,
   3    atomic::{AtomicBool, Ordering},
   4};
   5
   6use crate::{
   7    DebugPanel,
   8    session::running::variable_list::{CollapseSelectedEntry, ExpandSelectedEntry},
   9    tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
  10};
  11use collections::HashMap;
  12use dap::{
  13    Scope, StackFrame, Variable,
  14    requests::{Initialize, Launch, Scopes, StackTrace, Variables},
  15};
  16use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
  17use menu::{SelectFirst, SelectNext, SelectPrevious};
  18use project::{FakeFs, Project};
  19use serde_json::json;
  20use unindent::Unindent as _;
  21use util::path;
  22
  23/// This only tests fetching one scope and 2 variables for a single stackframe
  24#[gpui::test]
  25async fn test_basic_fetch_initial_scope_and_variables(
  26    executor: BackgroundExecutor,
  27    cx: &mut TestAppContext,
  28) {
  29    init_test(cx);
  30
  31    let fs = FakeFs::new(executor.clone());
  32
  33    let test_file_content = r#"
  34        const variable1 = "Value 1";
  35        const variable2 = "Value 2";
  36    "#
  37    .unindent();
  38
  39    fs.insert_tree(
  40        path!("/project"),
  41        json!({
  42           "src": {
  43               "test.js": test_file_content,
  44           }
  45        }),
  46    )
  47    .await;
  48
  49    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
  50    let workspace = init_test_workspace(&project, cx).await;
  51    workspace
  52        .update(cx, |workspace, window, cx| {
  53            workspace.focus_panel::<DebugPanel>(window, cx);
  54        })
  55        .unwrap();
  56    let cx = &mut VisualTestContext::from_window(*workspace, cx);
  57    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
  58    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
  59
  60    client.on_request::<dap::requests::Threads, _>(move |_, _| {
  61        Ok(dap::ThreadsResponse {
  62            threads: vec![dap::Thread {
  63                id: 1,
  64                name: "Thread 1".into(),
  65            }],
  66        })
  67    });
  68
  69    let stack_frames = vec![StackFrame {
  70        id: 1,
  71        name: "Stack Frame 1".into(),
  72        source: Some(dap::Source {
  73            name: Some("test.js".into()),
  74            path: Some(path!("/project/src/test.js").into()),
  75            source_reference: None,
  76            presentation_hint: None,
  77            origin: None,
  78            sources: None,
  79            adapter_data: None,
  80            checksums: None,
  81        }),
  82        line: 1,
  83        column: 1,
  84        end_line: None,
  85        end_column: None,
  86        can_restart: None,
  87        instruction_pointer_reference: None,
  88        module_id: None,
  89        presentation_hint: None,
  90    }];
  91
  92    client.on_request::<StackTrace, _>({
  93        let stack_frames = Arc::new(stack_frames.clone());
  94        move |_, args| {
  95            assert_eq!(1, args.thread_id);
  96
  97            Ok(dap::StackTraceResponse {
  98                stack_frames: (*stack_frames).clone(),
  99                total_frames: None,
 100            })
 101        }
 102    });
 103
 104    let scopes = vec![Scope {
 105        name: "Scope 1".into(),
 106        presentation_hint: None,
 107        variables_reference: 2,
 108        named_variables: None,
 109        indexed_variables: None,
 110        expensive: false,
 111        source: None,
 112        line: None,
 113        column: None,
 114        end_line: None,
 115        end_column: None,
 116    }];
 117
 118    client.on_request::<Scopes, _>({
 119        let scopes = Arc::new(scopes.clone());
 120        move |_, args| {
 121            assert_eq!(1, args.frame_id);
 122
 123            Ok(dap::ScopesResponse {
 124                scopes: (*scopes).clone(),
 125            })
 126        }
 127    });
 128
 129    let variables = vec![
 130        Variable {
 131            name: "variable1".into(),
 132            value: "value 1".into(),
 133            type_: None,
 134            presentation_hint: None,
 135            evaluate_name: None,
 136            variables_reference: 0,
 137            named_variables: None,
 138            indexed_variables: None,
 139            memory_reference: None,
 140            declaration_location_reference: None,
 141            value_location_reference: None,
 142        },
 143        Variable {
 144            name: "variable2".into(),
 145            value: "value 2".into(),
 146            type_: None,
 147            presentation_hint: None,
 148            evaluate_name: None,
 149            variables_reference: 0,
 150            named_variables: None,
 151            indexed_variables: None,
 152            memory_reference: None,
 153            declaration_location_reference: None,
 154            value_location_reference: None,
 155        },
 156    ];
 157
 158    client.on_request::<Variables, _>({
 159        let variables = Arc::new(variables.clone());
 160        move |_, args| {
 161            assert_eq!(2, args.variables_reference);
 162
 163            Ok(dap::VariablesResponse {
 164                variables: (*variables).clone(),
 165            })
 166        }
 167    });
 168
 169    client
 170        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 171            reason: dap::StoppedEventReason::Pause,
 172            description: None,
 173            thread_id: Some(1),
 174            preserve_focus_hint: None,
 175            text: None,
 176            all_threads_stopped: None,
 177            hit_breakpoint_ids: None,
 178        }))
 179        .await;
 180
 181    cx.run_until_parked();
 182
 183    let running_state =
 184        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
 185            cx.focus_self(window);
 186            item.mode()
 187                .as_running()
 188                .expect("Session should be running by this point")
 189                .clone()
 190        });
 191    cx.run_until_parked();
 192
 193    running_state.update(cx, |running_state, cx| {
 194        let (stack_frame_list, stack_frame_id) =
 195            running_state.stack_frame_list().update(cx, |list, _| {
 196                (list.flatten_entries(), list.selected_stack_frame_id())
 197            });
 198
 199        assert_eq!(stack_frames, stack_frame_list);
 200        assert_eq!(Some(1), stack_frame_id);
 201
 202        running_state
 203            .variable_list()
 204            .update(cx, |variable_list, _| {
 205                assert_eq!(scopes, variable_list.scopes());
 206                assert_eq!(
 207                    vec![variables[0].clone(), variables[1].clone(),],
 208                    variable_list.variables()
 209                );
 210
 211                variable_list.assert_visual_entries(vec![
 212                    "v Scope 1",
 213                    "    > variable1",
 214                    "    > variable2",
 215                ]);
 216            });
 217    });
 218}
 219
 220/// This tests fetching multiple scopes and variables for them with a single stackframe
 221#[gpui::test]
 222async fn test_fetch_variables_for_multiple_scopes(
 223    executor: BackgroundExecutor,
 224    cx: &mut TestAppContext,
 225) {
 226    init_test(cx);
 227
 228    let fs = FakeFs::new(executor.clone());
 229
 230    let test_file_content = r#"
 231        const variable1 = {
 232            nested1: "Nested 1",
 233            nested2: "Nested 2",
 234        };
 235        const variable2 = "Value 2";
 236        const variable3 = "Value 3";
 237    "#
 238    .unindent();
 239
 240    fs.insert_tree(
 241        path!("/project"),
 242        json!({
 243           "src": {
 244               "test.js": test_file_content,
 245           }
 246        }),
 247    )
 248    .await;
 249
 250    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 251    let workspace = init_test_workspace(&project, cx).await;
 252    workspace
 253        .update(cx, |workspace, window, cx| {
 254            workspace.focus_panel::<DebugPanel>(window, cx);
 255        })
 256        .unwrap();
 257    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 258
 259    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 260    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 261
 262    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 263        Ok(dap::ThreadsResponse {
 264            threads: vec![dap::Thread {
 265                id: 1,
 266                name: "Thread 1".into(),
 267            }],
 268        })
 269    });
 270
 271    client.on_request::<Initialize, _>(move |_, _| {
 272        Ok(dap::Capabilities {
 273            supports_step_back: Some(false),
 274            ..Default::default()
 275        })
 276    });
 277
 278    client.on_request::<Launch, _>(move |_, _| Ok(()));
 279
 280    let stack_frames = vec![StackFrame {
 281        id: 1,
 282        name: "Stack Frame 1".into(),
 283        source: Some(dap::Source {
 284            name: Some("test.js".into()),
 285            path: Some(path!("/project/src/test.js").into()),
 286            source_reference: None,
 287            presentation_hint: None,
 288            origin: None,
 289            sources: None,
 290            adapter_data: None,
 291            checksums: None,
 292        }),
 293        line: 1,
 294        column: 1,
 295        end_line: None,
 296        end_column: None,
 297        can_restart: None,
 298        instruction_pointer_reference: None,
 299        module_id: None,
 300        presentation_hint: None,
 301    }];
 302
 303    client.on_request::<StackTrace, _>({
 304        let stack_frames = Arc::new(stack_frames.clone());
 305        move |_, args| {
 306            assert_eq!(1, args.thread_id);
 307
 308            Ok(dap::StackTraceResponse {
 309                stack_frames: (*stack_frames).clone(),
 310                total_frames: None,
 311            })
 312        }
 313    });
 314
 315    let scopes = vec![
 316        Scope {
 317            name: "Scope 1".into(),
 318            presentation_hint: Some(dap::ScopePresentationHint::Locals),
 319            variables_reference: 2,
 320            named_variables: None,
 321            indexed_variables: None,
 322            expensive: false,
 323            source: None,
 324            line: None,
 325            column: None,
 326            end_line: None,
 327            end_column: None,
 328        },
 329        Scope {
 330            name: "Scope 2".into(),
 331            presentation_hint: None,
 332            variables_reference: 3,
 333            named_variables: None,
 334            indexed_variables: None,
 335            expensive: false,
 336            source: None,
 337            line: None,
 338            column: None,
 339            end_line: None,
 340            end_column: None,
 341        },
 342    ];
 343
 344    client.on_request::<Scopes, _>({
 345        let scopes = Arc::new(scopes.clone());
 346        move |_, args| {
 347            assert_eq!(1, args.frame_id);
 348
 349            Ok(dap::ScopesResponse {
 350                scopes: (*scopes).clone(),
 351            })
 352        }
 353    });
 354
 355    let mut variables = HashMap::default();
 356    variables.insert(
 357        2,
 358        vec![
 359            Variable {
 360                name: "variable1".into(),
 361                value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(),
 362                type_: None,
 363                presentation_hint: None,
 364                evaluate_name: None,
 365                variables_reference: 0,
 366                named_variables: None,
 367                indexed_variables: None,
 368                memory_reference: None,
 369                declaration_location_reference: None,
 370                value_location_reference: None,
 371            },
 372            Variable {
 373                name: "variable2".into(),
 374                value: "Value 2".into(),
 375                type_: None,
 376                presentation_hint: None,
 377                evaluate_name: None,
 378                variables_reference: 0,
 379                named_variables: None,
 380                indexed_variables: None,
 381                memory_reference: None,
 382                declaration_location_reference: None,
 383                value_location_reference: None,
 384            },
 385        ],
 386    );
 387    variables.insert(
 388        3,
 389        vec![Variable {
 390            name: "variable3".into(),
 391            value: "Value 3".into(),
 392            type_: None,
 393            presentation_hint: None,
 394            evaluate_name: None,
 395            variables_reference: 0,
 396            named_variables: None,
 397            indexed_variables: None,
 398            memory_reference: None,
 399            declaration_location_reference: None,
 400            value_location_reference: None,
 401        }],
 402    );
 403
 404    client.on_request::<Variables, _>({
 405        let variables = Arc::new(variables.clone());
 406        move |_, args| {
 407            Ok(dap::VariablesResponse {
 408                variables: variables.get(&args.variables_reference).unwrap().clone(),
 409            })
 410        }
 411    });
 412
 413    client
 414        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 415            reason: dap::StoppedEventReason::Pause,
 416            description: None,
 417            thread_id: Some(1),
 418            preserve_focus_hint: None,
 419            text: None,
 420            all_threads_stopped: None,
 421            hit_breakpoint_ids: None,
 422        }))
 423        .await;
 424
 425    cx.run_until_parked();
 426
 427    let running_state =
 428        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
 429            cx.focus_self(window);
 430            item.mode()
 431                .as_running()
 432                .expect("Session should be running by this point")
 433                .clone()
 434        });
 435    cx.run_until_parked();
 436
 437    running_state.update(cx, |running_state, cx| {
 438        let (stack_frame_list, stack_frame_id) =
 439            running_state.stack_frame_list().update(cx, |list, _| {
 440                (list.flatten_entries(), list.selected_stack_frame_id())
 441            });
 442
 443        assert_eq!(Some(1), stack_frame_id);
 444        assert_eq!(stack_frames, stack_frame_list);
 445
 446        running_state
 447            .variable_list()
 448            .update(cx, |variable_list, _| {
 449                assert_eq!(2, variable_list.scopes().len());
 450                assert_eq!(scopes, variable_list.scopes());
 451                let variables_by_scope = variable_list.variables_per_scope();
 452
 453                // scope 1
 454                assert_eq!(
 455                    vec![
 456                        variables.get(&2).unwrap()[0].clone(),
 457                        variables.get(&2).unwrap()[1].clone(),
 458                    ],
 459                    variables_by_scope[0].1
 460                );
 461
 462                // scope 2
 463                let empty_vec: Vec<dap::Variable> = vec![];
 464                assert_eq!(empty_vec, variables_by_scope[1].1);
 465
 466                variable_list.assert_visual_entries(vec![
 467                    "v Scope 1",
 468                    "    > variable1",
 469                    "    > variable2",
 470                    "> Scope 2",
 471                ]);
 472            });
 473    });
 474}
 475
 476// tests that toggling a variable will fetch its children and shows it
 477#[gpui::test]
 478async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 479    init_test(cx);
 480
 481    let fs = FakeFs::new(executor.clone());
 482
 483    let test_file_content = r#"
 484        const variable1 = {
 485            nested1: "Nested 1",
 486            nested2: "Nested 2",
 487        };
 488        const variable2 = "Value 2";
 489        const variable3 = "Value 3";
 490    "#
 491    .unindent();
 492
 493    fs.insert_tree(
 494        path!("/project"),
 495        json!({
 496           "src": {
 497               "test.js": test_file_content,
 498           }
 499        }),
 500    )
 501    .await;
 502
 503    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
 504    let workspace = init_test_workspace(&project, cx).await;
 505    workspace
 506        .update(cx, |workspace, window, cx| {
 507            workspace.focus_panel::<DebugPanel>(window, cx);
 508        })
 509        .unwrap();
 510    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 511    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
 512    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 513
 514    client.on_request::<dap::requests::Threads, _>(move |_, _| {
 515        Ok(dap::ThreadsResponse {
 516            threads: vec![dap::Thread {
 517                id: 1,
 518                name: "Thread 1".into(),
 519            }],
 520        })
 521    });
 522
 523    client.on_request::<Initialize, _>(move |_, _| {
 524        Ok(dap::Capabilities {
 525            supports_step_back: Some(false),
 526            ..Default::default()
 527        })
 528    });
 529
 530    client.on_request::<Launch, _>(move |_, _| Ok(()));
 531
 532    let stack_frames = vec![StackFrame {
 533        id: 1,
 534        name: "Stack Frame 1".into(),
 535        source: Some(dap::Source {
 536            name: Some("test.js".into()),
 537            path: Some(path!("/project/src/test.js").into()),
 538            source_reference: None,
 539            presentation_hint: None,
 540            origin: None,
 541            sources: None,
 542            adapter_data: None,
 543            checksums: None,
 544        }),
 545        line: 1,
 546        column: 1,
 547        end_line: None,
 548        end_column: None,
 549        can_restart: None,
 550        instruction_pointer_reference: None,
 551        module_id: None,
 552        presentation_hint: None,
 553    }];
 554
 555    client.on_request::<StackTrace, _>({
 556        let stack_frames = Arc::new(stack_frames.clone());
 557        move |_, args| {
 558            assert_eq!(1, args.thread_id);
 559
 560            Ok(dap::StackTraceResponse {
 561                stack_frames: (*stack_frames).clone(),
 562                total_frames: None,
 563            })
 564        }
 565    });
 566
 567    let scopes = vec![
 568        Scope {
 569            name: "Scope 1".into(),
 570            presentation_hint: Some(dap::ScopePresentationHint::Locals),
 571            variables_reference: 2,
 572            named_variables: None,
 573            indexed_variables: None,
 574            expensive: false,
 575            source: None,
 576            line: None,
 577            column: None,
 578            end_line: None,
 579            end_column: None,
 580        },
 581        Scope {
 582            name: "Scope 2".into(),
 583            presentation_hint: None,
 584            variables_reference: 4,
 585            named_variables: None,
 586            indexed_variables: None,
 587            expensive: false,
 588            source: None,
 589            line: None,
 590            column: None,
 591            end_line: None,
 592            end_column: None,
 593        },
 594    ];
 595
 596    client.on_request::<Scopes, _>({
 597        let scopes = Arc::new(scopes.clone());
 598        move |_, args| {
 599            assert_eq!(1, args.frame_id);
 600
 601            Ok(dap::ScopesResponse {
 602                scopes: (*scopes).clone(),
 603            })
 604        }
 605    });
 606
 607    let scope1_variables = vec![
 608        Variable {
 609            name: "variable1".into(),
 610            value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(),
 611            type_: None,
 612            presentation_hint: None,
 613            evaluate_name: None,
 614            variables_reference: 3,
 615            named_variables: None,
 616            indexed_variables: None,
 617            memory_reference: None,
 618            declaration_location_reference: None,
 619            value_location_reference: None,
 620        },
 621        Variable {
 622            name: "variable2".into(),
 623            value: "Value 2".into(),
 624            type_: None,
 625            presentation_hint: None,
 626            evaluate_name: None,
 627            variables_reference: 0,
 628            named_variables: None,
 629            indexed_variables: None,
 630            memory_reference: None,
 631            declaration_location_reference: None,
 632            value_location_reference: None,
 633        },
 634    ];
 635
 636    let nested_variables = vec![
 637        Variable {
 638            name: "nested1".into(),
 639            value: "Nested 1".into(),
 640            type_: None,
 641            presentation_hint: None,
 642            evaluate_name: None,
 643            variables_reference: 0,
 644            named_variables: None,
 645            indexed_variables: None,
 646            memory_reference: None,
 647            declaration_location_reference: None,
 648            value_location_reference: None,
 649        },
 650        Variable {
 651            name: "nested2".into(),
 652            value: "Nested 2".into(),
 653            type_: None,
 654            presentation_hint: None,
 655            evaluate_name: None,
 656            variables_reference: 0,
 657            named_variables: None,
 658            indexed_variables: None,
 659            memory_reference: None,
 660            declaration_location_reference: None,
 661            value_location_reference: None,
 662        },
 663    ];
 664
 665    let scope2_variables = vec![Variable {
 666        name: "variable3".into(),
 667        value: "Value 3".into(),
 668        type_: None,
 669        presentation_hint: None,
 670        evaluate_name: None,
 671        variables_reference: 0,
 672        named_variables: None,
 673        indexed_variables: None,
 674        memory_reference: None,
 675        declaration_location_reference: None,
 676        value_location_reference: None,
 677    }];
 678
 679    client.on_request::<Variables, _>({
 680        let scope1_variables = Arc::new(scope1_variables.clone());
 681        let nested_variables = Arc::new(nested_variables.clone());
 682        let scope2_variables = Arc::new(scope2_variables.clone());
 683        move |_, args| match args.variables_reference {
 684            4 => Ok(dap::VariablesResponse {
 685                variables: (*scope2_variables).clone(),
 686            }),
 687            3 => Ok(dap::VariablesResponse {
 688                variables: (*nested_variables).clone(),
 689            }),
 690            2 => Ok(dap::VariablesResponse {
 691                variables: (*scope1_variables).clone(),
 692            }),
 693            id => unreachable!("unexpected variables reference {id}"),
 694        }
 695    });
 696
 697    client
 698        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
 699            reason: dap::StoppedEventReason::Pause,
 700            description: None,
 701            thread_id: Some(1),
 702            preserve_focus_hint: None,
 703            text: None,
 704            all_threads_stopped: None,
 705            hit_breakpoint_ids: None,
 706        }))
 707        .await;
 708
 709    cx.run_until_parked();
 710    let running_state =
 711        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
 712            cx.focus_self(window);
 713            let running = item
 714                .mode()
 715                .as_running()
 716                .expect("Session should be running by this point")
 717                .clone();
 718
 719            let variable_list = running.read_with(cx, |state, _| state.variable_list().clone());
 720            variable_list.update(cx, |_, cx| cx.focus_self(window));
 721            running
 722        });
 723    cx.dispatch_action(SelectFirst);
 724    cx.dispatch_action(SelectFirst);
 725    cx.run_until_parked();
 726
 727    running_state.update(cx, |running_state, cx| {
 728        running_state
 729            .variable_list()
 730            .update(cx, |variable_list, _| {
 731                variable_list.assert_visual_entries(vec![
 732                    "v Scope 1 <=== selected",
 733                    "    > variable1",
 734                    "    > variable2",
 735                    "> Scope 2",
 736                ]);
 737            });
 738    });
 739
 740    cx.dispatch_action(SelectNext);
 741    cx.run_until_parked();
 742
 743    running_state.update(cx, |running_state, cx| {
 744        running_state
 745            .variable_list()
 746            .update(cx, |variable_list, _| {
 747                variable_list.assert_visual_entries(vec![
 748                    "v Scope 1",
 749                    "    > variable1 <=== selected",
 750                    "    > variable2",
 751                    "> Scope 2",
 752                ]);
 753            });
 754    });
 755
 756    // expand the nested variables of variable 1
 757    cx.dispatch_action(ExpandSelectedEntry);
 758    cx.run_until_parked();
 759    running_state.update(cx, |running_state, cx| {
 760        running_state
 761            .variable_list()
 762            .update(cx, |variable_list, _| {
 763                variable_list.assert_visual_entries(vec![
 764                    "v Scope 1",
 765                    "    v variable1 <=== selected",
 766                    "        > nested1",
 767                    "        > nested2",
 768                    "    > variable2",
 769                    "> Scope 2",
 770                ]);
 771            });
 772    });
 773
 774    // select the first nested variable of variable 1
 775    cx.dispatch_action(SelectNext);
 776    cx.run_until_parked();
 777    running_state.update(cx, |debug_panel_item, cx| {
 778        debug_panel_item
 779            .variable_list()
 780            .update(cx, |variable_list, _| {
 781                variable_list.assert_visual_entries(vec![
 782                    "v Scope 1",
 783                    "    v variable1",
 784                    "        > nested1 <=== selected",
 785                    "        > nested2",
 786                    "    > variable2",
 787                    "> Scope 2",
 788                ]);
 789            });
 790    });
 791
 792    // select the second nested variable of variable 1
 793    cx.dispatch_action(SelectNext);
 794    cx.run_until_parked();
 795    running_state.update(cx, |debug_panel_item, cx| {
 796        debug_panel_item
 797            .variable_list()
 798            .update(cx, |variable_list, _| {
 799                variable_list.assert_visual_entries(vec![
 800                    "v Scope 1",
 801                    "    v variable1",
 802                    "        > nested1",
 803                    "        > nested2 <=== selected",
 804                    "    > variable2",
 805                    "> Scope 2",
 806                ]);
 807            });
 808    });
 809
 810    // select variable 2 of scope 1
 811    cx.dispatch_action(SelectNext);
 812    cx.run_until_parked();
 813    running_state.update(cx, |debug_panel_item, cx| {
 814        debug_panel_item
 815            .variable_list()
 816            .update(cx, |variable_list, _| {
 817                variable_list.assert_visual_entries(vec![
 818                    "v Scope 1",
 819                    "    v variable1",
 820                    "        > nested1",
 821                    "        > nested2",
 822                    "    > variable2 <=== selected",
 823                    "> Scope 2",
 824                ]);
 825            });
 826    });
 827
 828    // select scope 2
 829    cx.dispatch_action(SelectNext);
 830    cx.run_until_parked();
 831    running_state.update(cx, |debug_panel_item, cx| {
 832        debug_panel_item
 833            .variable_list()
 834            .update(cx, |variable_list, _| {
 835                variable_list.assert_visual_entries(vec![
 836                    "v Scope 1",
 837                    "    v variable1",
 838                    "        > nested1",
 839                    "        > nested2",
 840                    "    > variable2",
 841                    "> Scope 2 <=== selected",
 842                ]);
 843            });
 844    });
 845
 846    // expand the nested variables of scope 2
 847    cx.dispatch_action(ExpandSelectedEntry);
 848    cx.run_until_parked();
 849    running_state.update(cx, |debug_panel_item, cx| {
 850        debug_panel_item
 851            .variable_list()
 852            .update(cx, |variable_list, _| {
 853                variable_list.assert_visual_entries(vec![
 854                    "v Scope 1",
 855                    "    v variable1",
 856                    "        > nested1",
 857                    "        > nested2",
 858                    "    > variable2",
 859                    "v Scope 2 <=== selected",
 860                    "    > variable3",
 861                ]);
 862            });
 863    });
 864
 865    // select variable 3 of scope 2
 866    cx.dispatch_action(SelectNext);
 867    cx.run_until_parked();
 868    running_state.update(cx, |debug_panel_item, cx| {
 869        debug_panel_item
 870            .variable_list()
 871            .update(cx, |variable_list, _| {
 872                variable_list.assert_visual_entries(vec![
 873                    "v Scope 1",
 874                    "    v variable1",
 875                    "        > nested1",
 876                    "        > nested2",
 877                    "    > variable2",
 878                    "v Scope 2",
 879                    "    > variable3 <=== selected",
 880                ]);
 881            });
 882    });
 883
 884    // select scope 2
 885    cx.dispatch_action(SelectPrevious);
 886    cx.run_until_parked();
 887    running_state.update(cx, |debug_panel_item, cx| {
 888        debug_panel_item
 889            .variable_list()
 890            .update(cx, |variable_list, _| {
 891                variable_list.assert_visual_entries(vec![
 892                    "v Scope 1",
 893                    "    v variable1",
 894                    "        > nested1",
 895                    "        > nested2",
 896                    "    > variable2",
 897                    "v Scope 2 <=== selected",
 898                    "    > variable3",
 899                ]);
 900            });
 901    });
 902
 903    // collapse variables of scope 2
 904    cx.dispatch_action(CollapseSelectedEntry);
 905    cx.run_until_parked();
 906    running_state.update(cx, |debug_panel_item, cx| {
 907        debug_panel_item
 908            .variable_list()
 909            .update(cx, |variable_list, _| {
 910                variable_list.assert_visual_entries(vec![
 911                    "v Scope 1",
 912                    "    v variable1",
 913                    "        > nested1",
 914                    "        > nested2",
 915                    "    > variable2",
 916                    "> Scope 2 <=== selected",
 917                ]);
 918            });
 919    });
 920
 921    // select variable 2 of scope 1
 922    cx.dispatch_action(SelectPrevious);
 923    cx.run_until_parked();
 924    running_state.update(cx, |debug_panel_item, cx| {
 925        debug_panel_item
 926            .variable_list()
 927            .update(cx, |variable_list, _| {
 928                variable_list.assert_visual_entries(vec![
 929                    "v Scope 1",
 930                    "    v variable1",
 931                    "        > nested1",
 932                    "        > nested2",
 933                    "    > variable2 <=== selected",
 934                    "> Scope 2",
 935                ]);
 936            });
 937    });
 938
 939    // select nested2 of variable 1
 940    cx.dispatch_action(SelectPrevious);
 941    cx.run_until_parked();
 942    running_state.update(cx, |debug_panel_item, cx| {
 943        debug_panel_item
 944            .variable_list()
 945            .update(cx, |variable_list, _| {
 946                variable_list.assert_visual_entries(vec![
 947                    "v Scope 1",
 948                    "    v variable1",
 949                    "        > nested1",
 950                    "        > nested2 <=== selected",
 951                    "    > variable2",
 952                    "> Scope 2",
 953                ]);
 954            });
 955    });
 956
 957    // select nested1 of variable 1
 958    cx.dispatch_action(SelectPrevious);
 959    cx.run_until_parked();
 960    running_state.update(cx, |debug_panel_item, cx| {
 961        debug_panel_item
 962            .variable_list()
 963            .update(cx, |variable_list, _| {
 964                variable_list.assert_visual_entries(vec![
 965                    "v Scope 1",
 966                    "    v variable1",
 967                    "        > nested1 <=== selected",
 968                    "        > nested2",
 969                    "    > variable2",
 970                    "> Scope 2",
 971                ]);
 972            });
 973    });
 974
 975    // select variable 1 of scope 1
 976    cx.dispatch_action(SelectPrevious);
 977    cx.run_until_parked();
 978    running_state.update(cx, |debug_panel_item, cx| {
 979        debug_panel_item
 980            .variable_list()
 981            .update(cx, |variable_list, _| {
 982                variable_list.assert_visual_entries(vec![
 983                    "v Scope 1",
 984                    "    v variable1 <=== selected",
 985                    "        > nested1",
 986                    "        > nested2",
 987                    "    > variable2",
 988                    "> Scope 2",
 989                ]);
 990            });
 991    });
 992
 993    // collapse variables of variable 1
 994    cx.dispatch_action(CollapseSelectedEntry);
 995    cx.run_until_parked();
 996    running_state.update(cx, |debug_panel_item, cx| {
 997        debug_panel_item
 998            .variable_list()
 999            .update(cx, |variable_list, _| {
1000                variable_list.assert_visual_entries(vec![
1001                    "v Scope 1",
1002                    "    > variable1 <=== selected",
1003                    "    > variable2",
1004                    "> Scope 2",
1005                ]);
1006            });
1007    });
1008
1009    // select scope 1
1010    cx.dispatch_action(SelectPrevious);
1011    cx.run_until_parked();
1012    running_state.update(cx, |running_state, cx| {
1013        running_state
1014            .variable_list()
1015            .update(cx, |variable_list, _| {
1016                variable_list.assert_visual_entries(vec![
1017                    "v Scope 1 <=== selected",
1018                    "    > variable1",
1019                    "    > variable2",
1020                    "> Scope 2",
1021                ]);
1022            });
1023    });
1024
1025    // collapse variables of scope 1
1026    cx.dispatch_action(CollapseSelectedEntry);
1027    cx.run_until_parked();
1028    running_state.update(cx, |debug_panel_item, cx| {
1029        debug_panel_item
1030            .variable_list()
1031            .update(cx, |variable_list, _| {
1032                variable_list.assert_visual_entries(vec!["> Scope 1 <=== selected", "> Scope 2"]);
1033            });
1034    });
1035
1036    // select scope 2 backwards
1037    cx.dispatch_action(SelectPrevious);
1038    cx.run_until_parked();
1039    running_state.update(cx, |debug_panel_item, cx| {
1040        debug_panel_item
1041            .variable_list()
1042            .update(cx, |variable_list, _| {
1043                variable_list.assert_visual_entries(vec!["> Scope 1", "> Scope 2 <=== selected"]);
1044            });
1045    });
1046
1047    // select scope 1 backwards
1048    cx.dispatch_action(SelectNext);
1049    cx.run_until_parked();
1050    running_state.update(cx, |debug_panel_item, cx| {
1051        debug_panel_item
1052            .variable_list()
1053            .update(cx, |variable_list, _| {
1054                variable_list.assert_visual_entries(vec!["> Scope 1 <=== selected", "> Scope 2"]);
1055            });
1056    });
1057
1058    // test stepping through nested with ExpandSelectedEntry/CollapseSelectedEntry actions
1059
1060    cx.dispatch_action(ExpandSelectedEntry);
1061    cx.run_until_parked();
1062    running_state.update(cx, |debug_panel_item, cx| {
1063        debug_panel_item
1064            .variable_list()
1065            .update(cx, |variable_list, _| {
1066                variable_list.assert_visual_entries(vec![
1067                    "v Scope 1 <=== selected",
1068                    "    > variable1",
1069                    "    > variable2",
1070                    "> Scope 2",
1071                ]);
1072            });
1073    });
1074
1075    cx.dispatch_action(ExpandSelectedEntry);
1076    cx.run_until_parked();
1077    running_state.update(cx, |debug_panel_item, cx| {
1078        debug_panel_item
1079            .variable_list()
1080            .update(cx, |variable_list, _| {
1081                variable_list.assert_visual_entries(vec![
1082                    "v Scope 1",
1083                    "    > variable1 <=== selected",
1084                    "    > variable2",
1085                    "> Scope 2",
1086                ]);
1087            });
1088    });
1089
1090    cx.dispatch_action(ExpandSelectedEntry);
1091    cx.run_until_parked();
1092    running_state.update(cx, |debug_panel_item, cx| {
1093        debug_panel_item
1094            .variable_list()
1095            .update(cx, |variable_list, _| {
1096                variable_list.assert_visual_entries(vec![
1097                    "v Scope 1",
1098                    "    v variable1 <=== selected",
1099                    "        > nested1",
1100                    "        > nested2",
1101                    "    > variable2",
1102                    "> Scope 2",
1103                ]);
1104            });
1105    });
1106
1107    cx.dispatch_action(ExpandSelectedEntry);
1108    cx.run_until_parked();
1109    running_state.update(cx, |debug_panel_item, cx| {
1110        debug_panel_item
1111            .variable_list()
1112            .update(cx, |variable_list, _| {
1113                variable_list.assert_visual_entries(vec![
1114                    "v Scope 1",
1115                    "    v variable1",
1116                    "        > nested1 <=== selected",
1117                    "        > nested2",
1118                    "    > variable2",
1119                    "> Scope 2",
1120                ]);
1121            });
1122    });
1123
1124    cx.dispatch_action(ExpandSelectedEntry);
1125    cx.run_until_parked();
1126    running_state.update(cx, |debug_panel_item, cx| {
1127        debug_panel_item
1128            .variable_list()
1129            .update(cx, |variable_list, _| {
1130                variable_list.assert_visual_entries(vec![
1131                    "v Scope 1",
1132                    "    v variable1",
1133                    "        > nested1",
1134                    "        > nested2 <=== selected",
1135                    "    > variable2",
1136                    "> Scope 2",
1137                ]);
1138            });
1139    });
1140
1141    cx.dispatch_action(ExpandSelectedEntry);
1142    cx.run_until_parked();
1143    running_state.update(cx, |debug_panel_item, cx| {
1144        debug_panel_item
1145            .variable_list()
1146            .update(cx, |variable_list, _| {
1147                variable_list.assert_visual_entries(vec![
1148                    "v Scope 1",
1149                    "    v variable1",
1150                    "        > nested1",
1151                    "        > nested2",
1152                    "    > variable2 <=== selected",
1153                    "> Scope 2",
1154                ]);
1155            });
1156    });
1157
1158    cx.dispatch_action(CollapseSelectedEntry);
1159    cx.run_until_parked();
1160    running_state.update(cx, |debug_panel_item, cx| {
1161        debug_panel_item
1162            .variable_list()
1163            .update(cx, |variable_list, _| {
1164                variable_list.assert_visual_entries(vec![
1165                    "v Scope 1",
1166                    "    v variable1",
1167                    "        > nested1",
1168                    "        > nested2 <=== selected",
1169                    "    > variable2",
1170                    "> Scope 2",
1171                ]);
1172            });
1173    });
1174
1175    cx.dispatch_action(CollapseSelectedEntry);
1176    cx.run_until_parked();
1177    running_state.update(cx, |debug_panel_item, cx| {
1178        debug_panel_item
1179            .variable_list()
1180            .update(cx, |variable_list, _| {
1181                variable_list.assert_visual_entries(vec![
1182                    "v Scope 1",
1183                    "    v variable1",
1184                    "        > nested1 <=== selected",
1185                    "        > nested2",
1186                    "    > variable2",
1187                    "> Scope 2",
1188                ]);
1189            });
1190    });
1191
1192    cx.dispatch_action(CollapseSelectedEntry);
1193    cx.run_until_parked();
1194    running_state.update(cx, |debug_panel_item, cx| {
1195        debug_panel_item
1196            .variable_list()
1197            .update(cx, |variable_list, _| {
1198                variable_list.assert_visual_entries(vec![
1199                    "v Scope 1",
1200                    "    v variable1 <=== selected",
1201                    "        > nested1",
1202                    "        > nested2",
1203                    "    > variable2",
1204                    "> Scope 2",
1205                ]);
1206            });
1207    });
1208
1209    cx.dispatch_action(CollapseSelectedEntry);
1210    cx.run_until_parked();
1211    running_state.update(cx, |debug_panel_item, cx| {
1212        debug_panel_item
1213            .variable_list()
1214            .update(cx, |variable_list, _| {
1215                variable_list.assert_visual_entries(vec![
1216                    "v Scope 1",
1217                    "    > variable1 <=== selected",
1218                    "    > variable2",
1219                    "> Scope 2",
1220                ]);
1221            });
1222    });
1223
1224    cx.dispatch_action(CollapseSelectedEntry);
1225    cx.run_until_parked();
1226    running_state.update(cx, |debug_panel_item, cx| {
1227        debug_panel_item
1228            .variable_list()
1229            .update(cx, |variable_list, _| {
1230                variable_list.assert_visual_entries(vec![
1231                    "v Scope 1 <=== selected",
1232                    "    > variable1",
1233                    "    > variable2",
1234                    "> Scope 2",
1235                ]);
1236            });
1237    });
1238
1239    cx.dispatch_action(CollapseSelectedEntry);
1240    cx.run_until_parked();
1241    running_state.update(cx, |debug_panel_item, cx| {
1242        debug_panel_item
1243            .variable_list()
1244            .update(cx, |variable_list, _| {
1245                variable_list.assert_visual_entries(vec!["> Scope 1 <=== selected", "> Scope 2"]);
1246            });
1247    });
1248}
1249
1250#[gpui::test]
1251async fn test_variable_list_only_sends_requests_when_rendering(
1252    executor: BackgroundExecutor,
1253    cx: &mut TestAppContext,
1254) {
1255    init_test(cx);
1256
1257    let fs = FakeFs::new(executor.clone());
1258
1259    let test_file_content = r#"
1260        import { SOME_VALUE } './module.js';
1261
1262        console.log(SOME_VALUE);
1263    "#
1264    .unindent();
1265
1266    let module_file_content = r#"
1267        export SOME_VALUE = 'some value';
1268    "#
1269    .unindent();
1270
1271    fs.insert_tree(
1272        path!("/project"),
1273        json!({
1274           "src": {
1275               "test.js": test_file_content,
1276               "module.js": module_file_content,
1277           }
1278        }),
1279    )
1280    .await;
1281
1282    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1283    let workspace = init_test_workspace(&project, cx).await;
1284    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1285
1286    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1287    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1288
1289    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1290        Ok(dap::ThreadsResponse {
1291            threads: vec![dap::Thread {
1292                id: 1,
1293                name: "Thread 1".into(),
1294            }],
1295        })
1296    });
1297
1298    client.on_request::<Initialize, _>(move |_, _| {
1299        Ok(dap::Capabilities {
1300            supports_step_back: Some(false),
1301            ..Default::default()
1302        })
1303    });
1304
1305    client.on_request::<Launch, _>(move |_, _| Ok(()));
1306
1307    let stack_frames = vec![
1308        StackFrame {
1309            id: 1,
1310            name: "Stack Frame 1".into(),
1311            source: Some(dap::Source {
1312                name: Some("test.js".into()),
1313                path: Some(path!("/project/src/test.js").into()),
1314                source_reference: None,
1315                presentation_hint: None,
1316                origin: None,
1317                sources: None,
1318                adapter_data: None,
1319                checksums: None,
1320            }),
1321            line: 3,
1322            column: 1,
1323            end_line: None,
1324            end_column: None,
1325            can_restart: None,
1326            instruction_pointer_reference: None,
1327            module_id: None,
1328            presentation_hint: None,
1329        },
1330        StackFrame {
1331            id: 2,
1332            name: "Stack Frame 2".into(),
1333            source: Some(dap::Source {
1334                name: Some("module.js".into()),
1335                path: Some(path!("/project/src/module.js").into()),
1336                source_reference: None,
1337                presentation_hint: None,
1338                origin: None,
1339                sources: None,
1340                adapter_data: None,
1341                checksums: None,
1342            }),
1343            line: 1,
1344            column: 1,
1345            end_line: None,
1346            end_column: None,
1347            can_restart: None,
1348            instruction_pointer_reference: None,
1349            module_id: None,
1350            presentation_hint: None,
1351        },
1352    ];
1353
1354    client.on_request::<StackTrace, _>({
1355        let stack_frames = Arc::new(stack_frames.clone());
1356        move |_, args| {
1357            assert_eq!(1, args.thread_id);
1358
1359            Ok(dap::StackTraceResponse {
1360                stack_frames: (*stack_frames).clone(),
1361                total_frames: None,
1362            })
1363        }
1364    });
1365
1366    let frame_1_scopes = vec![Scope {
1367        name: "Frame 1 Scope 1".into(),
1368        presentation_hint: None,
1369        variables_reference: 2,
1370        named_variables: None,
1371        indexed_variables: None,
1372        expensive: false,
1373        source: None,
1374        line: None,
1375        column: None,
1376        end_line: None,
1377        end_column: None,
1378    }];
1379
1380    let made_scopes_request = Arc::new(AtomicBool::new(false));
1381
1382    client.on_request::<Scopes, _>({
1383        let frame_1_scopes = Arc::new(frame_1_scopes.clone());
1384        let made_scopes_request = made_scopes_request.clone();
1385        move |_, args| {
1386            assert_eq!(1, args.frame_id);
1387            assert!(
1388                !made_scopes_request.load(Ordering::SeqCst),
1389                "We should be caching the scope request"
1390            );
1391
1392            made_scopes_request.store(true, Ordering::SeqCst);
1393
1394            Ok(dap::ScopesResponse {
1395                scopes: (*frame_1_scopes).clone(),
1396            })
1397        }
1398    });
1399
1400    let frame_1_variables = vec![
1401        Variable {
1402            name: "variable1".into(),
1403            value: "value 1".into(),
1404            type_: None,
1405            presentation_hint: None,
1406            evaluate_name: None,
1407            variables_reference: 0,
1408            named_variables: None,
1409            indexed_variables: None,
1410            memory_reference: None,
1411            declaration_location_reference: None,
1412            value_location_reference: None,
1413        },
1414        Variable {
1415            name: "variable2".into(),
1416            value: "value 2".into(),
1417            type_: None,
1418            presentation_hint: None,
1419            evaluate_name: None,
1420            variables_reference: 0,
1421            named_variables: None,
1422            indexed_variables: None,
1423            memory_reference: None,
1424            declaration_location_reference: None,
1425            value_location_reference: None,
1426        },
1427    ];
1428
1429    client.on_request::<Variables, _>({
1430        let frame_1_variables = Arc::new(frame_1_variables.clone());
1431        move |_, args| {
1432            assert_eq!(2, args.variables_reference);
1433
1434            Ok(dap::VariablesResponse {
1435                variables: (*frame_1_variables).clone(),
1436            })
1437        }
1438    });
1439
1440    cx.run_until_parked();
1441
1442    let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
1443        let state = item
1444            .mode()
1445            .as_running()
1446            .expect("Session should be running by this point")
1447            .clone();
1448
1449        state
1450    });
1451
1452    client
1453        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1454            reason: dap::StoppedEventReason::Pause,
1455            description: None,
1456            thread_id: Some(1),
1457            preserve_focus_hint: None,
1458            text: None,
1459            all_threads_stopped: None,
1460            hit_breakpoint_ids: None,
1461        }))
1462        .await;
1463
1464    cx.run_until_parked();
1465
1466    running_state.update(cx, |running_state, cx| {
1467        let (stack_frame_list, stack_frame_id) =
1468            running_state.stack_frame_list().update(cx, |list, _| {
1469                (list.flatten_entries(), list.selected_stack_frame_id())
1470            });
1471
1472        assert_eq!(Some(1), stack_frame_id);
1473        assert_eq!(stack_frames, stack_frame_list);
1474
1475        let variable_list = running_state.variable_list().read(cx);
1476
1477        assert_eq!(frame_1_variables, variable_list.variables());
1478        assert!(made_scopes_request.load(Ordering::SeqCst));
1479    });
1480}
1481
1482#[gpui::test]
1483async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
1484    executor: BackgroundExecutor,
1485    cx: &mut TestAppContext,
1486) {
1487    init_test(cx);
1488
1489    let fs = FakeFs::new(executor.clone());
1490
1491    let test_file_content = r#"
1492        import { SOME_VALUE } './module.js';
1493
1494        console.log(SOME_VALUE);
1495    "#
1496    .unindent();
1497
1498    let module_file_content = r#"
1499        export SOME_VALUE = 'some value';
1500    "#
1501    .unindent();
1502
1503    fs.insert_tree(
1504        path!("/project"),
1505        json!({
1506           "src": {
1507               "test.js": test_file_content,
1508               "module.js": module_file_content,
1509           }
1510        }),
1511    )
1512    .await;
1513
1514    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1515    let workspace = init_test_workspace(&project, cx).await;
1516    workspace
1517        .update(cx, |workspace, window, cx| {
1518            workspace.focus_panel::<DebugPanel>(window, cx);
1519        })
1520        .unwrap();
1521    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1522
1523    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1524    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1525
1526    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1527        Ok(dap::ThreadsResponse {
1528            threads: vec![dap::Thread {
1529                id: 1,
1530                name: "Thread 1".into(),
1531            }],
1532        })
1533    });
1534
1535    client.on_request::<Initialize, _>(move |_, _| {
1536        Ok(dap::Capabilities {
1537            supports_step_back: Some(false),
1538            ..Default::default()
1539        })
1540    });
1541
1542    client.on_request::<Launch, _>(move |_, _| Ok(()));
1543
1544    let stack_frames = vec![
1545        StackFrame {
1546            id: 1,
1547            name: "Stack Frame 1".into(),
1548            source: Some(dap::Source {
1549                name: Some("test.js".into()),
1550                path: Some(path!("/project/src/test.js").into()),
1551                source_reference: None,
1552                presentation_hint: None,
1553                origin: None,
1554                sources: None,
1555                adapter_data: None,
1556                checksums: None,
1557            }),
1558            line: 3,
1559            column: 1,
1560            end_line: None,
1561            end_column: None,
1562            can_restart: None,
1563            instruction_pointer_reference: None,
1564            module_id: None,
1565            presentation_hint: None,
1566        },
1567        StackFrame {
1568            id: 2,
1569            name: "Stack Frame 2".into(),
1570            source: Some(dap::Source {
1571                name: Some("module.js".into()),
1572                path: Some(path!("/project/src/module.js").into()),
1573                source_reference: None,
1574                presentation_hint: None,
1575                origin: None,
1576                sources: None,
1577                adapter_data: None,
1578                checksums: None,
1579            }),
1580            line: 1,
1581            column: 1,
1582            end_line: None,
1583            end_column: None,
1584            can_restart: None,
1585            instruction_pointer_reference: None,
1586            module_id: None,
1587            presentation_hint: None,
1588        },
1589    ];
1590
1591    client.on_request::<StackTrace, _>({
1592        let stack_frames = Arc::new(stack_frames.clone());
1593        move |_, args| {
1594            assert_eq!(1, args.thread_id);
1595
1596            Ok(dap::StackTraceResponse {
1597                stack_frames: (*stack_frames).clone(),
1598                total_frames: None,
1599            })
1600        }
1601    });
1602
1603    let frame_1_scopes = vec![Scope {
1604        name: "Frame 1 Scope 1".into(),
1605        presentation_hint: None,
1606        variables_reference: 2,
1607        named_variables: None,
1608        indexed_variables: None,
1609        expensive: false,
1610        source: None,
1611        line: None,
1612        column: None,
1613        end_line: None,
1614        end_column: None,
1615    }];
1616
1617    // add handlers for fetching the second stack frame's scopes and variables
1618    // after the user clicked the stack frame
1619    let frame_2_scopes = vec![Scope {
1620        name: "Frame 2 Scope 1".into(),
1621        presentation_hint: None,
1622        variables_reference: 3,
1623        named_variables: None,
1624        indexed_variables: None,
1625        expensive: false,
1626        source: None,
1627        line: None,
1628        column: None,
1629        end_line: None,
1630        end_column: None,
1631    }];
1632
1633    let called_second_stack_frame = Arc::new(AtomicBool::new(false));
1634    let called_first_stack_frame = Arc::new(AtomicBool::new(false));
1635
1636    client.on_request::<Scopes, _>({
1637        let frame_1_scopes = Arc::new(frame_1_scopes.clone());
1638        let frame_2_scopes = Arc::new(frame_2_scopes.clone());
1639        let called_first_stack_frame = called_first_stack_frame.clone();
1640        let called_second_stack_frame = called_second_stack_frame.clone();
1641        move |_, args| match args.frame_id {
1642            1 => {
1643                called_first_stack_frame.store(true, Ordering::SeqCst);
1644                Ok(dap::ScopesResponse {
1645                    scopes: (*frame_1_scopes).clone(),
1646                })
1647            }
1648            2 => {
1649                called_second_stack_frame.store(true, Ordering::SeqCst);
1650
1651                Ok(dap::ScopesResponse {
1652                    scopes: (*frame_2_scopes).clone(),
1653                })
1654            }
1655            _ => panic!("Made a scopes request with an invalid frame id"),
1656        }
1657    });
1658
1659    let frame_1_variables = vec![
1660        Variable {
1661            name: "variable1".into(),
1662            value: "value 1".into(),
1663            type_: None,
1664            presentation_hint: None,
1665            evaluate_name: None,
1666            variables_reference: 0,
1667            named_variables: None,
1668            indexed_variables: None,
1669            memory_reference: None,
1670            declaration_location_reference: None,
1671            value_location_reference: None,
1672        },
1673        Variable {
1674            name: "variable2".into(),
1675            value: "value 2".into(),
1676            type_: None,
1677            presentation_hint: None,
1678            evaluate_name: None,
1679            variables_reference: 0,
1680            named_variables: None,
1681            indexed_variables: None,
1682            memory_reference: None,
1683            declaration_location_reference: None,
1684            value_location_reference: None,
1685        },
1686    ];
1687
1688    let frame_2_variables = vec![
1689        Variable {
1690            name: "variable3".into(),
1691            value: "old value 1".into(),
1692            type_: None,
1693            presentation_hint: None,
1694            evaluate_name: None,
1695            variables_reference: 0,
1696            named_variables: None,
1697            indexed_variables: None,
1698            memory_reference: None,
1699            declaration_location_reference: None,
1700            value_location_reference: None,
1701        },
1702        Variable {
1703            name: "variable4".into(),
1704            value: "old value 2".into(),
1705            type_: None,
1706            presentation_hint: None,
1707            evaluate_name: None,
1708            variables_reference: 0,
1709            named_variables: None,
1710            indexed_variables: None,
1711            memory_reference: None,
1712            declaration_location_reference: None,
1713            value_location_reference: None,
1714        },
1715    ];
1716
1717    client.on_request::<Variables, _>({
1718        let frame_1_variables = Arc::new(frame_1_variables.clone());
1719        move |_, args| {
1720            assert_eq!(2, args.variables_reference);
1721
1722            Ok(dap::VariablesResponse {
1723                variables: (*frame_1_variables).clone(),
1724            })
1725        }
1726    });
1727
1728    client
1729        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1730            reason: dap::StoppedEventReason::Pause,
1731            description: None,
1732            thread_id: Some(1),
1733            preserve_focus_hint: None,
1734            text: None,
1735            all_threads_stopped: None,
1736            hit_breakpoint_ids: None,
1737        }))
1738        .await;
1739
1740    cx.run_until_parked();
1741
1742    let running_state =
1743        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
1744            cx.focus_self(window);
1745            item.mode()
1746                .as_running()
1747                .expect("Session should be running by this point")
1748                .clone()
1749        });
1750
1751    running_state.update(cx, |running_state, cx| {
1752        let (stack_frame_list, stack_frame_id) =
1753            running_state.stack_frame_list().update(cx, |list, _| {
1754                (list.flatten_entries(), list.selected_stack_frame_id())
1755            });
1756
1757        let variable_list = running_state.variable_list().read(cx);
1758        let variables = variable_list.variables();
1759
1760        assert_eq!(Some(1), stack_frame_id);
1761        assert_eq!(
1762            running_state
1763                .stack_frame_list()
1764                .read(cx)
1765                .selected_stack_frame_id(),
1766            Some(1)
1767        );
1768
1769        assert!(
1770            called_first_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
1771            "Request scopes shouldn't be called before it's needed"
1772        );
1773        assert!(
1774            !called_second_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
1775            "Request scopes shouldn't be called before it's needed"
1776        );
1777
1778        assert_eq!(stack_frames, stack_frame_list);
1779        assert_eq!(frame_1_variables, variables);
1780    });
1781
1782    client.on_request::<Variables, _>({
1783        let frame_2_variables = Arc::new(frame_2_variables.clone());
1784        move |_, args| {
1785            assert_eq!(3, args.variables_reference);
1786
1787            Ok(dap::VariablesResponse {
1788                variables: (*frame_2_variables).clone(),
1789            })
1790        }
1791    });
1792
1793    running_state
1794        .update_in(cx, |running_state, window, cx| {
1795            running_state
1796                .stack_frame_list()
1797                .update(cx, |stack_frame_list, cx| {
1798                    stack_frame_list.select_stack_frame(&stack_frames[1], true, window, cx)
1799                })
1800        })
1801        .await
1802        .unwrap();
1803
1804    cx.run_until_parked();
1805
1806    running_state.update(cx, |running_state, cx| {
1807        let (stack_frame_list, stack_frame_id) =
1808            running_state.stack_frame_list().update(cx, |list, _| {
1809                (list.flatten_entries(), list.selected_stack_frame_id())
1810            });
1811
1812        let variable_list = running_state.variable_list().read(cx);
1813        let variables = variable_list.variables();
1814
1815        assert_eq!(Some(2), stack_frame_id);
1816        assert!(
1817            called_second_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
1818            "Request scopes shouldn't be called before it's needed"
1819        );
1820
1821        assert_eq!(stack_frames, stack_frame_list);
1822
1823        assert_eq!(variables, frame_2_variables,);
1824    });
1825}