Skip to repository content2595 lines · 80.2 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:34:18.356Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
variable_list.rs
1#![expect(clippy::result_large_err)]
2use std::sync::{
3 Arc,
4 atomic::{AtomicBool, Ordering},
5};
6
7use crate::{
8 DebugPanel,
9 persistence::DebuggerPaneItem,
10 session::running::variable_list::{
11 AddWatch, CollapseSelectedEntry, ExpandSelectedEntry, RemoveWatch,
12 },
13 tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
14};
15use collections::HashMap;
16use dap::{
17 Scope, StackFrame, Variable,
18 requests::{Evaluate, Initialize, Launch, Scopes, StackTrace, Variables},
19};
20use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
21use menu::{SelectFirst, SelectNext, SelectPrevious};
22use project::{FakeFs, Project};
23use serde_json::json;
24use ui::SharedString;
25use unindent::Unindent as _;
26use util::path;
27
28/// This only tests fetching one scope and 2 variables for a single stackframe
29#[gpui::test]
30async fn test_basic_fetch_initial_scope_and_variables(
31 executor: BackgroundExecutor,
32 cx: &mut TestAppContext,
33) {
34 init_test(cx);
35
36 let fs = FakeFs::new(executor.clone());
37
38 let test_file_content = r#"
39 const variable1 = "Value 1";
40 const variable2 = "Value 2";
41 "#
42 .unindent();
43
44 fs.insert_tree(
45 path!("/project"),
46 json!({
47 "src": {
48 "test.js": test_file_content,
49 }
50 }),
51 )
52 .await;
53
54 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
55 let workspace = init_test_workspace(&project, cx).await;
56 workspace
57 .update(cx, |workspace, window, cx| {
58 workspace.focus_panel::<DebugPanel>(window, cx);
59 })
60 .unwrap();
61 let cx = &mut VisualTestContext::from_window(*workspace, cx);
62 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
63 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
64
65 client.on_request::<dap::requests::Threads, _>(move |_, _| {
66 Ok(dap::ThreadsResponse {
67 threads: vec![dap::Thread {
68 id: 1,
69 name: "Thread 1".into(),
70 }],
71 })
72 });
73
74 let stack_frames = vec![StackFrame {
75 id: 1,
76 name: "Stack Frame 1".into(),
77 source: Some(dap::Source {
78 name: Some("test.js".into()),
79 path: Some(path!("/project/src/test.js").into()),
80 source_reference: None,
81 presentation_hint: None,
82 origin: None,
83 sources: None,
84 adapter_data: None,
85 checksums: None,
86 }),
87 line: 1,
88 column: 1,
89 end_line: None,
90 end_column: None,
91 can_restart: None,
92 instruction_pointer_reference: None,
93 module_id: None,
94 presentation_hint: None,
95 }];
96
97 client.on_request::<StackTrace, _>({
98 let stack_frames = Arc::new(stack_frames.clone());
99 move |_, args| {
100 assert_eq!(1, args.thread_id);
101
102 Ok(dap::StackTraceResponse {
103 stack_frames: (*stack_frames).clone(),
104 total_frames: None,
105 })
106 }
107 });
108
109 let scopes = vec![Scope {
110 name: "Scope 1".into(),
111 presentation_hint: None,
112 variables_reference: 2,
113 named_variables: None,
114 indexed_variables: None,
115 expensive: false,
116 source: None,
117 line: None,
118 column: None,
119 end_line: None,
120 end_column: None,
121 }];
122
123 client.on_request::<Scopes, _>({
124 let scopes = Arc::new(scopes.clone());
125 move |_, args| {
126 assert_eq!(1, args.frame_id);
127
128 Ok(dap::ScopesResponse {
129 scopes: (*scopes).clone(),
130 })
131 }
132 });
133
134 let variables = vec![
135 Variable {
136 name: "variable1".into(),
137 value: "value 1".into(),
138 type_: None,
139 presentation_hint: None,
140 evaluate_name: None,
141 variables_reference: 0,
142 named_variables: None,
143 indexed_variables: None,
144 memory_reference: None,
145 declaration_location_reference: None,
146 value_location_reference: None,
147 },
148 Variable {
149 name: "variable2".into(),
150 value: "value 2".into(),
151 type_: None,
152 presentation_hint: None,
153 evaluate_name: None,
154 variables_reference: 0,
155 named_variables: None,
156 indexed_variables: None,
157 memory_reference: None,
158 declaration_location_reference: None,
159 value_location_reference: None,
160 },
161 ];
162
163 client.on_request::<Variables, _>({
164 let variables = Arc::new(variables.clone());
165 move |_, args| {
166 assert_eq!(2, args.variables_reference);
167
168 Ok(dap::VariablesResponse {
169 variables: (*variables).clone(),
170 })
171 }
172 });
173
174 client
175 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
176 reason: dap::StoppedEventReason::Pause,
177 description: None,
178 thread_id: Some(1),
179 preserve_focus_hint: None,
180 text: None,
181 all_threads_stopped: None,
182 hit_breakpoint_ids: None,
183 }))
184 .await;
185
186 cx.run_until_parked();
187
188 let running_state =
189 active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
190 cx.focus_self(window);
191 item.running_state().clone()
192 });
193 cx.run_until_parked();
194
195 running_state.update(cx, |running_state, cx| {
196 let (stack_frame_list, stack_frame_id) =
197 running_state.stack_frame_list().update(cx, |list, _| {
198 (
199 list.flatten_entries(true, true),
200 list.opened_stack_frame_id(),
201 )
202 });
203
204 assert_eq!(stack_frames, stack_frame_list);
205 assert_eq!(Some(1), stack_frame_id);
206
207 running_state
208 .variable_list()
209 .update(cx, |variable_list, _| {
210 assert_eq!(scopes, variable_list.scopes());
211 assert_eq!(
212 vec![variables[0].clone(), variables[1].clone(),],
213 variable_list.variables()
214 );
215
216 variable_list.assert_visual_entries(vec![
217 "v Scope 1",
218 " > variable1",
219 " > variable2",
220 ]);
221 });
222 });
223}
224
225/// This tests fetching multiple scopes and variables for them with a single stackframe
226#[gpui::test]
227async fn test_fetch_variables_for_multiple_scopes(
228 executor: BackgroundExecutor,
229 cx: &mut TestAppContext,
230) {
231 init_test(cx);
232
233 let fs = FakeFs::new(executor.clone());
234
235 let test_file_content = r#"
236 const variable1 = {
237 nested1: "Nested 1",
238 nested2: "Nested 2",
239 };
240 const variable2 = "Value 2";
241 const variable3 = "Value 3";
242 "#
243 .unindent();
244
245 fs.insert_tree(
246 path!("/project"),
247 json!({
248 "src": {
249 "test.js": test_file_content,
250 }
251 }),
252 )
253 .await;
254
255 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
256 let workspace = init_test_workspace(&project, cx).await;
257 workspace
258 .update(cx, |workspace, window, cx| {
259 workspace.focus_panel::<DebugPanel>(window, cx);
260 })
261 .unwrap();
262 let cx = &mut VisualTestContext::from_window(*workspace, cx);
263
264 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
265 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
266
267 client.on_request::<dap::requests::Threads, _>(move |_, _| {
268 Ok(dap::ThreadsResponse {
269 threads: vec![dap::Thread {
270 id: 1,
271 name: "Thread 1".into(),
272 }],
273 })
274 });
275
276 client.on_request::<Initialize, _>(move |_, _| {
277 Ok(dap::Capabilities {
278 supports_step_back: Some(false),
279 ..Default::default()
280 })
281 });
282
283 client.on_request::<Launch, _>(move |_, _| Ok(()));
284
285 let stack_frames = vec![StackFrame {
286 id: 1,
287 name: "Stack Frame 1".into(),
288 source: Some(dap::Source {
289 name: Some("test.js".into()),
290 path: Some(path!("/project/src/test.js").into()),
291 source_reference: None,
292 presentation_hint: None,
293 origin: None,
294 sources: None,
295 adapter_data: None,
296 checksums: None,
297 }),
298 line: 1,
299 column: 1,
300 end_line: None,
301 end_column: None,
302 can_restart: None,
303 instruction_pointer_reference: None,
304 module_id: None,
305 presentation_hint: None,
306 }];
307
308 client.on_request::<StackTrace, _>({
309 let stack_frames = Arc::new(stack_frames.clone());
310 move |_, args| {
311 assert_eq!(1, args.thread_id);
312
313 Ok(dap::StackTraceResponse {
314 stack_frames: (*stack_frames).clone(),
315 total_frames: None,
316 })
317 }
318 });
319
320 let scopes = vec![
321 Scope {
322 name: "Scope 1".into(),
323 presentation_hint: Some(dap::ScopePresentationHint::Locals),
324 variables_reference: 2,
325 named_variables: None,
326 indexed_variables: None,
327 expensive: false,
328 source: None,
329 line: None,
330 column: None,
331 end_line: None,
332 end_column: None,
333 },
334 Scope {
335 name: "Scope 2".into(),
336 presentation_hint: None,
337 variables_reference: 3,
338 named_variables: None,
339 indexed_variables: None,
340 expensive: false,
341 source: None,
342 line: None,
343 column: None,
344 end_line: None,
345 end_column: None,
346 },
347 ];
348
349 client.on_request::<Scopes, _>({
350 let scopes = Arc::new(scopes.clone());
351 move |_, args| {
352 assert_eq!(1, args.frame_id);
353
354 Ok(dap::ScopesResponse {
355 scopes: (*scopes).clone(),
356 })
357 }
358 });
359
360 let mut variables = HashMap::default();
361 variables.insert(
362 2,
363 vec![
364 Variable {
365 name: "variable1".into(),
366 value: "{nested1: \"Nested 1\", nested2: \"Nested 2\"}".into(),
367 type_: None,
368 presentation_hint: None,
369 evaluate_name: None,
370 variables_reference: 0,
371 named_variables: None,
372 indexed_variables: None,
373 memory_reference: None,
374 declaration_location_reference: None,
375 value_location_reference: None,
376 },
377 Variable {
378 name: "variable2".into(),
379 value: "Value 2".into(),
380 type_: None,
381 presentation_hint: None,
382 evaluate_name: None,
383 variables_reference: 0,
384 named_variables: None,
385 indexed_variables: None,
386 memory_reference: None,
387 declaration_location_reference: None,
388 value_location_reference: None,
389 },
390 ],
391 );
392 variables.insert(
393 3,
394 vec![Variable {
395 name: "variable3".into(),
396 value: "Value 3".into(),
397 type_: None,
398 presentation_hint: None,
399 evaluate_name: None,
400 variables_reference: 0,
401 named_variables: None,
402 indexed_variables: None,
403 memory_reference: None,
404 declaration_location_reference: None,
405 value_location_reference: None,
406 }],
407 );
408
409 client.on_request::<Variables, _>({
410 let variables = Arc::new(variables.clone());
411 move |_, args| {
412 Ok(dap::VariablesResponse {
413 variables: variables.get(&args.variables_reference).unwrap().clone(),
414 })
415 }
416 });
417
418 client
419 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
420 reason: dap::StoppedEventReason::Pause,
421 description: None,
422 thread_id: Some(1),
423 preserve_focus_hint: None,
424 text: None,
425 all_threads_stopped: None,
426 hit_breakpoint_ids: None,
427 }))
428 .await;
429
430 cx.run_until_parked();
431
432 let running_state =
433 active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
434 cx.focus_self(window);
435 item.running_state().clone()
436 });
437 cx.run_until_parked();
438
439 running_state.update(cx, |running_state, cx| {
440 let (stack_frame_list, stack_frame_id) =
441 running_state.stack_frame_list().update(cx, |list, _| {
442 (
443 list.flatten_entries(true, true),
444 list.opened_stack_frame_id(),
445 )
446 });
447
448 assert_eq!(Some(1), stack_frame_id);
449 assert_eq!(stack_frames, stack_frame_list);
450
451 running_state
452 .variable_list()
453 .update(cx, |variable_list, _| {
454 assert_eq!(2, variable_list.scopes().len());
455 assert_eq!(scopes, variable_list.scopes());
456 let variables_by_scope = variable_list.variables_per_scope();
457
458 // scope 1
459 assert_eq!(
460 vec![
461 variables.get(&2).unwrap()[0].clone(),
462 variables.get(&2).unwrap()[1].clone(),
463 ],
464 variables_by_scope[0].1
465 );
466
467 // scope 2
468 let empty_vec: Vec<dap::Variable> = vec![];
469 assert_eq!(empty_vec, variables_by_scope[1].1);
470
471 variable_list.assert_visual_entries(vec![
472 "v Scope 1",
473 " > variable1",
474 " > variable2",
475 "> Scope 2",
476 ]);
477 });
478 });
479}
480
481/// 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