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