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