Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:29:17.911Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

variable_list.rs

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