variable_list.rs

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