variable_list.rs

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