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