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