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