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