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).update_in(cx, |item, _, _| {
1449        let state = item.running_state().clone();
1450
1451        state
1452    });
1453
1454    client
1455        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1456            reason: dap::StoppedEventReason::Pause,
1457            description: None,
1458            thread_id: Some(1),
1459            preserve_focus_hint: None,
1460            text: None,
1461            all_threads_stopped: None,
1462            hit_breakpoint_ids: None,
1463        }))
1464        .await;
1465
1466    cx.run_until_parked();
1467
1468    running_state.update(cx, |running_state, cx| {
1469        let (stack_frame_list, stack_frame_id) =
1470            running_state.stack_frame_list().update(cx, |list, _| {
1471                (
1472                    list.flatten_entries(true, true),
1473                    list.opened_stack_frame_id(),
1474                )
1475            });
1476
1477        assert_eq!(Some(1), stack_frame_id);
1478        assert_eq!(stack_frames, stack_frame_list);
1479
1480        let variable_list = running_state.variable_list().read(cx);
1481
1482        assert_eq!(frame_1_variables, variable_list.variables());
1483        assert!(made_scopes_request.load(Ordering::SeqCst));
1484    });
1485}
1486
1487#[gpui::test]
1488async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
1489    executor: BackgroundExecutor,
1490    cx: &mut TestAppContext,
1491) {
1492    init_test(cx);
1493
1494    let fs = FakeFs::new(executor.clone());
1495
1496    let test_file_content = r#"
1497        import { SOME_VALUE } './module.js';
1498
1499        console.log(SOME_VALUE);
1500    "#
1501    .unindent();
1502
1503    let module_file_content = r#"
1504        export SOME_VALUE = 'some value';
1505    "#
1506    .unindent();
1507
1508    fs.insert_tree(
1509        path!("/project"),
1510        json!({
1511           "src": {
1512               "test.js": test_file_content,
1513               "module.js": module_file_content,
1514           }
1515        }),
1516    )
1517    .await;
1518
1519    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1520    let workspace = init_test_workspace(&project, cx).await;
1521    workspace
1522        .update(cx, |workspace, window, cx| {
1523            workspace.focus_panel::<DebugPanel>(window, cx);
1524        })
1525        .unwrap();
1526    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1527
1528    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1529    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1530
1531    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1532        Ok(dap::ThreadsResponse {
1533            threads: vec![dap::Thread {
1534                id: 1,
1535                name: "Thread 1".into(),
1536            }],
1537        })
1538    });
1539
1540    client.on_request::<Initialize, _>(move |_, _| {
1541        Ok(dap::Capabilities {
1542            supports_step_back: Some(false),
1543            ..Default::default()
1544        })
1545    });
1546
1547    client.on_request::<Launch, _>(move |_, _| Ok(()));
1548
1549    let stack_frames = vec![
1550        StackFrame {
1551            id: 1,
1552            name: "Stack Frame 1".into(),
1553            source: Some(dap::Source {
1554                name: Some("test.js".into()),
1555                path: Some(path!("/project/src/test.js").into()),
1556                source_reference: None,
1557                presentation_hint: None,
1558                origin: None,
1559                sources: None,
1560                adapter_data: None,
1561                checksums: None,
1562            }),
1563            line: 3,
1564            column: 1,
1565            end_line: None,
1566            end_column: None,
1567            can_restart: None,
1568            instruction_pointer_reference: None,
1569            module_id: None,
1570            presentation_hint: None,
1571        },
1572        StackFrame {
1573            id: 2,
1574            name: "Stack Frame 2".into(),
1575            source: Some(dap::Source {
1576                name: Some("module.js".into()),
1577                path: Some(path!("/project/src/module.js").into()),
1578                source_reference: None,
1579                presentation_hint: None,
1580                origin: None,
1581                sources: None,
1582                adapter_data: None,
1583                checksums: None,
1584            }),
1585            line: 1,
1586            column: 1,
1587            end_line: None,
1588            end_column: None,
1589            can_restart: None,
1590            instruction_pointer_reference: None,
1591            module_id: None,
1592            presentation_hint: None,
1593        },
1594    ];
1595
1596    client.on_request::<StackTrace, _>({
1597        let stack_frames = Arc::new(stack_frames.clone());
1598        move |_, args| {
1599            assert_eq!(1, args.thread_id);
1600
1601            Ok(dap::StackTraceResponse {
1602                stack_frames: (*stack_frames).clone(),
1603                total_frames: None,
1604            })
1605        }
1606    });
1607
1608    let frame_1_scopes = vec![Scope {
1609        name: "Frame 1 Scope 1".into(),
1610        presentation_hint: None,
1611        variables_reference: 2,
1612        named_variables: None,
1613        indexed_variables: None,
1614        expensive: false,
1615        source: None,
1616        line: None,
1617        column: None,
1618        end_line: None,
1619        end_column: None,
1620    }];
1621
1622    // add handlers for fetching the second stack frame's scopes and variables
1623    // after the user clicked the stack frame
1624    let frame_2_scopes = vec![Scope {
1625        name: "Frame 2 Scope 1".into(),
1626        presentation_hint: None,
1627        variables_reference: 3,
1628        named_variables: None,
1629        indexed_variables: None,
1630        expensive: false,
1631        source: None,
1632        line: None,
1633        column: None,
1634        end_line: None,
1635        end_column: None,
1636    }];
1637
1638    let called_second_stack_frame = Arc::new(AtomicBool::new(false));
1639    let called_first_stack_frame = Arc::new(AtomicBool::new(false));
1640
1641    client.on_request::<Scopes, _>({
1642        let frame_1_scopes = Arc::new(frame_1_scopes.clone());
1643        let frame_2_scopes = Arc::new(frame_2_scopes.clone());
1644        let called_first_stack_frame = called_first_stack_frame.clone();
1645        let called_second_stack_frame = called_second_stack_frame.clone();
1646        move |_, args| match args.frame_id {
1647            1 => {
1648                called_first_stack_frame.store(true, Ordering::SeqCst);
1649                Ok(dap::ScopesResponse {
1650                    scopes: (*frame_1_scopes).clone(),
1651                })
1652            }
1653            2 => {
1654                called_second_stack_frame.store(true, Ordering::SeqCst);
1655
1656                Ok(dap::ScopesResponse {
1657                    scopes: (*frame_2_scopes).clone(),
1658                })
1659            }
1660            _ => panic!("Made a scopes request with an invalid frame id"),
1661        }
1662    });
1663
1664    let frame_1_variables = vec![
1665        Variable {
1666            name: "variable1".into(),
1667            value: "value 1".into(),
1668            type_: None,
1669            presentation_hint: None,
1670            evaluate_name: None,
1671            variables_reference: 0,
1672            named_variables: None,
1673            indexed_variables: None,
1674            memory_reference: None,
1675            declaration_location_reference: None,
1676            value_location_reference: None,
1677        },
1678        Variable {
1679            name: "variable2".into(),
1680            value: "value 2".into(),
1681            type_: None,
1682            presentation_hint: None,
1683            evaluate_name: None,
1684            variables_reference: 0,
1685            named_variables: None,
1686            indexed_variables: None,
1687            memory_reference: None,
1688            declaration_location_reference: None,
1689            value_location_reference: None,
1690        },
1691    ];
1692
1693    let frame_2_variables = vec![
1694        Variable {
1695            name: "variable3".into(),
1696            value: "old value 1".into(),
1697            type_: None,
1698            presentation_hint: None,
1699            evaluate_name: None,
1700            variables_reference: 0,
1701            named_variables: None,
1702            indexed_variables: None,
1703            memory_reference: None,
1704            declaration_location_reference: None,
1705            value_location_reference: None,
1706        },
1707        Variable {
1708            name: "variable4".into(),
1709            value: "old value 2".into(),
1710            type_: None,
1711            presentation_hint: None,
1712            evaluate_name: None,
1713            variables_reference: 0,
1714            named_variables: None,
1715            indexed_variables: None,
1716            memory_reference: None,
1717            declaration_location_reference: None,
1718            value_location_reference: None,
1719        },
1720    ];
1721
1722    client.on_request::<Variables, _>({
1723        let frame_1_variables = Arc::new(frame_1_variables.clone());
1724        move |_, args| {
1725            assert_eq!(2, args.variables_reference);
1726
1727            Ok(dap::VariablesResponse {
1728                variables: (*frame_1_variables).clone(),
1729            })
1730        }
1731    });
1732
1733    client
1734        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1735            reason: dap::StoppedEventReason::Pause,
1736            description: None,
1737            thread_id: Some(1),
1738            preserve_focus_hint: None,
1739            text: None,
1740            all_threads_stopped: None,
1741            hit_breakpoint_ids: None,
1742        }))
1743        .await;
1744
1745    cx.run_until_parked();
1746
1747    let running_state =
1748        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
1749            cx.focus_self(window);
1750            item.running_state().clone()
1751        });
1752
1753    running_state.update(cx, |running_state, cx| {
1754        let (stack_frame_list, stack_frame_id) =
1755            running_state.stack_frame_list().update(cx, |list, _| {
1756                (
1757                    list.flatten_entries(true, true),
1758                    list.opened_stack_frame_id(),
1759                )
1760            });
1761
1762        let variable_list = running_state.variable_list().read(cx);
1763        let variables = variable_list.variables();
1764
1765        assert_eq!(Some(1), stack_frame_id);
1766        assert_eq!(
1767            running_state
1768                .stack_frame_list()
1769                .read(cx)
1770                .opened_stack_frame_id(),
1771            Some(1)
1772        );
1773
1774        assert!(
1775            called_first_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
1776            "Request scopes shouldn't be called before it's needed"
1777        );
1778        assert!(
1779            !called_second_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
1780            "Request scopes shouldn't be called before it's needed"
1781        );
1782
1783        assert_eq!(stack_frames, stack_frame_list);
1784        assert_eq!(frame_1_variables, variables);
1785    });
1786
1787    client.on_request::<Variables, _>({
1788        let frame_2_variables = Arc::new(frame_2_variables.clone());
1789        move |_, args| {
1790            assert_eq!(3, args.variables_reference);
1791
1792            Ok(dap::VariablesResponse {
1793                variables: (*frame_2_variables).clone(),
1794            })
1795        }
1796    });
1797
1798    running_state
1799        .update_in(cx, |running_state, window, cx| {
1800            running_state
1801                .stack_frame_list()
1802                .update(cx, |stack_frame_list, cx| {
1803                    stack_frame_list.go_to_stack_frame(stack_frames[1].id, window, cx)
1804                })
1805        })
1806        .await
1807        .unwrap();
1808
1809    cx.run_until_parked();
1810
1811    running_state.update(cx, |running_state, cx| {
1812        let (stack_frame_list, stack_frame_id) =
1813            running_state.stack_frame_list().update(cx, |list, _| {
1814                (
1815                    list.flatten_entries(true, true),
1816                    list.opened_stack_frame_id(),
1817                )
1818            });
1819
1820        let variable_list = running_state.variable_list().read(cx);
1821        let variables = variable_list.variables();
1822
1823        assert_eq!(Some(2), stack_frame_id);
1824        assert!(
1825            called_second_stack_frame.load(std::sync::atomic::Ordering::SeqCst),
1826            "Request scopes shouldn't be called before it's needed"
1827        );
1828
1829        assert_eq!(stack_frames, stack_frame_list);
1830
1831        assert_eq!(variables, frame_2_variables,);
1832    });
1833}
1834
1835#[gpui::test]
1836async fn test_add_and_remove_watcher(executor: BackgroundExecutor, cx: &mut TestAppContext) {
1837    init_test(cx);
1838
1839    let fs = FakeFs::new(executor.clone());
1840
1841    let test_file_content = r#"
1842        const variable1 = "Value 1";
1843        const variable2 = "Value 2";
1844    "#
1845    .unindent();
1846
1847    fs.insert_tree(
1848        path!("/project"),
1849        json!({
1850           "src": {
1851               "test.js": test_file_content,
1852           }
1853        }),
1854    )
1855    .await;
1856
1857    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
1858    let workspace = init_test_workspace(&project, cx).await;
1859    workspace
1860        .update(cx, |workspace, window, cx| {
1861            workspace.focus_panel::<DebugPanel>(window, cx);
1862        })
1863        .unwrap();
1864    let cx = &mut VisualTestContext::from_window(*workspace, cx);
1865    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
1866    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
1867
1868    client.on_request::<dap::requests::Threads, _>(move |_, _| {
1869        Ok(dap::ThreadsResponse {
1870            threads: vec![dap::Thread {
1871                id: 1,
1872                name: "Thread 1".into(),
1873            }],
1874        })
1875    });
1876
1877    let stack_frames = vec![StackFrame {
1878        id: 1,
1879        name: "Stack Frame 1".into(),
1880        source: Some(dap::Source {
1881            name: Some("test.js".into()),
1882            path: Some(path!("/project/src/test.js").into()),
1883            source_reference: None,
1884            presentation_hint: None,
1885            origin: None,
1886            sources: None,
1887            adapter_data: None,
1888            checksums: None,
1889        }),
1890        line: 1,
1891        column: 1,
1892        end_line: None,
1893        end_column: None,
1894        can_restart: None,
1895        instruction_pointer_reference: None,
1896        module_id: None,
1897        presentation_hint: None,
1898    }];
1899
1900    client.on_request::<StackTrace, _>({
1901        let stack_frames = Arc::new(stack_frames.clone());
1902        move |_, args| {
1903            assert_eq!(1, args.thread_id);
1904
1905            Ok(dap::StackTraceResponse {
1906                stack_frames: (*stack_frames).clone(),
1907                total_frames: None,
1908            })
1909        }
1910    });
1911
1912    let scopes = vec![Scope {
1913        name: "Scope 1".into(),
1914        presentation_hint: None,
1915        variables_reference: 2,
1916        named_variables: None,
1917        indexed_variables: None,
1918        expensive: false,
1919        source: None,
1920        line: None,
1921        column: None,
1922        end_line: None,
1923        end_column: None,
1924    }];
1925
1926    client.on_request::<Scopes, _>({
1927        let scopes = Arc::new(scopes.clone());
1928        move |_, args| {
1929            assert_eq!(1, args.frame_id);
1930
1931            Ok(dap::ScopesResponse {
1932                scopes: (*scopes).clone(),
1933            })
1934        }
1935    });
1936
1937    let variables = vec![
1938        Variable {
1939            name: "variable1".into(),
1940            value: "value 1".into(),
1941            type_: None,
1942            presentation_hint: None,
1943            evaluate_name: None,
1944            variables_reference: 0,
1945            named_variables: None,
1946            indexed_variables: None,
1947            memory_reference: None,
1948            declaration_location_reference: None,
1949            value_location_reference: None,
1950        },
1951        Variable {
1952            name: "variable2".into(),
1953            value: "value 2".into(),
1954            type_: None,
1955            presentation_hint: None,
1956            evaluate_name: None,
1957            variables_reference: 0,
1958            named_variables: None,
1959            indexed_variables: None,
1960            memory_reference: None,
1961            declaration_location_reference: None,
1962            value_location_reference: None,
1963        },
1964    ];
1965
1966    client.on_request::<Variables, _>({
1967        let variables = Arc::new(variables.clone());
1968        move |_, args| {
1969            assert_eq!(2, args.variables_reference);
1970
1971            Ok(dap::VariablesResponse {
1972                variables: (*variables).clone(),
1973            })
1974        }
1975    });
1976
1977    client.on_request::<Evaluate, _>({
1978        move |_, args| {
1979            assert_eq!("variable1", args.expression);
1980
1981            Ok(dap::EvaluateResponse {
1982                result: "value1".to_owned(),
1983                type_: None,
1984                presentation_hint: None,
1985                variables_reference: 2,
1986                named_variables: None,
1987                indexed_variables: None,
1988                memory_reference: None,
1989                value_location_reference: None,
1990            })
1991        }
1992    });
1993
1994    client
1995        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
1996            reason: dap::StoppedEventReason::Pause,
1997            description: None,
1998            thread_id: Some(1),
1999            preserve_focus_hint: None,
2000            text: None,
2001            all_threads_stopped: None,
2002            hit_breakpoint_ids: None,
2003        }))
2004        .await;
2005
2006    cx.run_until_parked();
2007
2008    let running_state =
2009        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
2010            cx.focus_self(window);
2011            let running = item.running_state().clone();
2012
2013            let variable_list = running.update(cx, |state, cx| {
2014                // have to do this because the variable list pane should be shown/active
2015                // for testing the variable list
2016                state.activate_item(DebuggerPaneItem::Variables, window, cx);
2017
2018                state.variable_list().clone()
2019            });
2020            variable_list.update(cx, |_, cx| cx.focus_self(window));
2021            running
2022        });
2023    cx.run_until_parked();
2024
2025    // select variable 1 from first scope
2026    running_state.update(cx, |running_state, cx| {
2027        running_state.variable_list().update(cx, |_, cx| {
2028            cx.dispatch_action(&SelectFirst);
2029            cx.dispatch_action(&SelectNext);
2030        });
2031    });
2032    cx.run_until_parked();
2033
2034    running_state.update(cx, |running_state, cx| {
2035        running_state.variable_list().update(cx, |_, cx| {
2036            cx.dispatch_action(&AddWatch);
2037        });
2038    });
2039    cx.run_until_parked();
2040
2041    // assert watcher for variable1 was added
2042    running_state.update(cx, |running_state, cx| {
2043        running_state.variable_list().update(cx, |list, _| {
2044            list.assert_visual_entries(vec![
2045                "> variable1",
2046                "v Scope 1",
2047                "    > variable1 <=== selected",
2048                "    > variable2",
2049            ]);
2050        });
2051    });
2052
2053    session.update(cx, |session, _| {
2054        let watcher = session
2055            .watchers()
2056            .get(&SharedString::from("variable1"))
2057            .unwrap();
2058
2059        assert_eq!("value1", watcher.value.to_string());
2060        assert_eq!("variable1", watcher.expression.to_string());
2061        assert_eq!(2, watcher.variables_reference);
2062    });
2063
2064    // select added watcher for variable1
2065    running_state.update(cx, |running_state, cx| {
2066        running_state.variable_list().update(cx, |_, cx| {
2067            cx.dispatch_action(&SelectFirst);
2068        });
2069    });
2070    cx.run_until_parked();
2071
2072    running_state.update(cx, |running_state, cx| {
2073        running_state.variable_list().update(cx, |_, cx| {
2074            cx.dispatch_action(&RemoveWatch);
2075        });
2076    });
2077    cx.run_until_parked();
2078
2079    // assert watcher for variable1 was removed
2080    running_state.update(cx, |running_state, cx| {
2081        running_state.variable_list().update(cx, |list, _| {
2082            list.assert_visual_entries(vec!["v Scope 1", "    > variable1", "    > variable2"]);
2083        });
2084    });
2085}
2086
2087#[gpui::test]
2088async fn test_refresh_watchers(executor: BackgroundExecutor, cx: &mut TestAppContext) {
2089    init_test(cx);
2090
2091    let fs = FakeFs::new(executor.clone());
2092
2093    let test_file_content = r#"
2094        const variable1 = "Value 1";
2095        const variable2 = "Value 2";
2096    "#
2097    .unindent();
2098
2099    fs.insert_tree(
2100        path!("/project"),
2101        json!({
2102           "src": {
2103               "test.js": test_file_content,
2104           }
2105        }),
2106    )
2107    .await;
2108
2109    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
2110    let workspace = init_test_workspace(&project, cx).await;
2111    workspace
2112        .update(cx, |workspace, window, cx| {
2113            workspace.focus_panel::<DebugPanel>(window, cx);
2114        })
2115        .unwrap();
2116    let cx = &mut VisualTestContext::from_window(*workspace, cx);
2117    let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
2118    let client = session.update(cx, |session, _| session.adapter_client().unwrap());
2119
2120    client.on_request::<dap::requests::Threads, _>(move |_, _| {
2121        Ok(dap::ThreadsResponse {
2122            threads: vec![dap::Thread {
2123                id: 1,
2124                name: "Thread 1".into(),
2125            }],
2126        })
2127    });
2128
2129    let stack_frames = vec![StackFrame {
2130        id: 1,
2131        name: "Stack Frame 1".into(),
2132        source: Some(dap::Source {
2133            name: Some("test.js".into()),
2134            path: Some(path!("/project/src/test.js").into()),
2135            source_reference: None,
2136            presentation_hint: None,
2137            origin: None,
2138            sources: None,
2139            adapter_data: None,
2140            checksums: None,
2141        }),
2142        line: 1,
2143        column: 1,
2144        end_line: None,
2145        end_column: None,
2146        can_restart: None,
2147        instruction_pointer_reference: None,
2148        module_id: None,
2149        presentation_hint: None,
2150    }];
2151
2152    client.on_request::<StackTrace, _>({
2153        let stack_frames = Arc::new(stack_frames.clone());
2154        move |_, args| {
2155            assert_eq!(1, args.thread_id);
2156
2157            Ok(dap::StackTraceResponse {
2158                stack_frames: (*stack_frames).clone(),
2159                total_frames: None,
2160            })
2161        }
2162    });
2163
2164    let scopes = vec![Scope {
2165        name: "Scope 1".into(),
2166        presentation_hint: None,
2167        variables_reference: 2,
2168        named_variables: None,
2169        indexed_variables: None,
2170        expensive: false,
2171        source: None,
2172        line: None,
2173        column: None,
2174        end_line: None,
2175        end_column: None,
2176    }];
2177
2178    client.on_request::<Scopes, _>({
2179        let scopes = Arc::new(scopes.clone());
2180        move |_, args| {
2181            assert_eq!(1, args.frame_id);
2182
2183            Ok(dap::ScopesResponse {
2184                scopes: (*scopes).clone(),
2185            })
2186        }
2187    });
2188
2189    let variables = vec![
2190        Variable {
2191            name: "variable1".into(),
2192            value: "value 1".into(),
2193            type_: None,
2194            presentation_hint: None,
2195            evaluate_name: None,
2196            variables_reference: 0,
2197            named_variables: None,
2198            indexed_variables: None,
2199            memory_reference: None,
2200            declaration_location_reference: None,
2201            value_location_reference: None,
2202        },
2203        Variable {
2204            name: "variable2".into(),
2205            value: "value 2".into(),
2206            type_: None,
2207            presentation_hint: None,
2208            evaluate_name: None,
2209            variables_reference: 0,
2210            named_variables: None,
2211            indexed_variables: None,
2212            memory_reference: None,
2213            declaration_location_reference: None,
2214            value_location_reference: None,
2215        },
2216    ];
2217
2218    client.on_request::<Variables, _>({
2219        let variables = Arc::new(variables.clone());
2220        move |_, args| {
2221            assert_eq!(2, args.variables_reference);
2222
2223            Ok(dap::VariablesResponse {
2224                variables: (*variables).clone(),
2225            })
2226        }
2227    });
2228
2229    client.on_request::<Evaluate, _>({
2230        move |_, args| {
2231            assert_eq!("variable1", args.expression);
2232
2233            Ok(dap::EvaluateResponse {
2234                result: "value1".to_owned(),
2235                type_: None,
2236                presentation_hint: None,
2237                variables_reference: 2,
2238                named_variables: None,
2239                indexed_variables: None,
2240                memory_reference: None,
2241                value_location_reference: None,
2242            })
2243        }
2244    });
2245
2246    client
2247        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
2248            reason: dap::StoppedEventReason::Pause,
2249            description: None,
2250            thread_id: Some(1),
2251            preserve_focus_hint: None,
2252            text: None,
2253            all_threads_stopped: None,
2254            hit_breakpoint_ids: None,
2255        }))
2256        .await;
2257
2258    cx.run_until_parked();
2259
2260    let running_state =
2261        active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
2262            cx.focus_self(window);
2263            let running = item.running_state().clone();
2264
2265            let variable_list = running.update(cx, |state, cx| {
2266                // have to do this because the variable list pane should be shown/active
2267                // for testing the variable list
2268                state.activate_item(DebuggerPaneItem::Variables, window, cx);
2269
2270                state.variable_list().clone()
2271            });
2272            variable_list.update(cx, |_, cx| cx.focus_self(window));
2273            running
2274        });
2275    cx.run_until_parked();
2276
2277    // select variable 1 from first scope
2278    running_state.update(cx, |running_state, cx| {
2279        running_state.variable_list().update(cx, |_, cx| {
2280            cx.dispatch_action(&SelectFirst);
2281            cx.dispatch_action(&SelectNext);
2282        });
2283    });
2284    cx.run_until_parked();
2285
2286    running_state.update(cx, |running_state, cx| {
2287        running_state.variable_list().update(cx, |_, cx| {
2288            cx.dispatch_action(&AddWatch);
2289        });
2290    });
2291    cx.run_until_parked();
2292
2293    session.update(cx, |session, _| {
2294        let watcher = session
2295            .watchers()
2296            .get(&SharedString::from("variable1"))
2297            .unwrap();
2298
2299        assert_eq!("value1", watcher.value.to_string());
2300        assert_eq!("variable1", watcher.expression.to_string());
2301        assert_eq!(2, watcher.variables_reference);
2302    });
2303
2304    client.on_request::<Evaluate, _>({
2305        move |_, args| {
2306            assert_eq!("variable1", args.expression);
2307
2308            Ok(dap::EvaluateResponse {
2309                result: "value updated".to_owned(),
2310                type_: None,
2311                presentation_hint: None,
2312                variables_reference: 3,
2313                named_variables: None,
2314                indexed_variables: None,
2315                memory_reference: None,
2316                value_location_reference: None,
2317            })
2318        }
2319    });
2320
2321    client
2322        .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
2323            reason: dap::StoppedEventReason::Pause,
2324            description: None,
2325            thread_id: Some(1),
2326            preserve_focus_hint: None,
2327            text: None,
2328            all_threads_stopped: None,
2329            hit_breakpoint_ids: None,
2330        }))
2331        .await;
2332
2333    cx.run_until_parked();
2334
2335    session.update(cx, |session, _| {
2336        let watcher = session
2337            .watchers()
2338            .get(&SharedString::from("variable1"))
2339            .unwrap();
2340
2341        assert_eq!("value updated", watcher.value.to_string());
2342        assert_eq!("variable1", watcher.expression.to_string());
2343        assert_eq!(3, watcher.variables_reference);
2344    });
2345}