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