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