variable_list.rs

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