variable_list.rs

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