1use super::*;
4
5impl DebugAdapter {
6 pub(super) fn handle_stack_trace(
8 &self,
9 seq: i64,
10 request_seq: i64,
11 arguments: Option<Value>,
12 ) -> DapMessage {
13 let args: Option<StackTraceArguments> =
14 arguments.and_then(|v| serde_json::from_value(v).ok());
15 let start_frame =
16 args.as_ref().and_then(|value| value.start_frame).unwrap_or(0).max(0) as usize;
17 let levels = args.as_ref().and_then(|value| value.levels).unwrap_or(0);
18 let requested_count = if levels <= 0 { None } else { Some(levels as usize) };
19 let mut framed_output_lines = None;
20
21 if let Some(ref mut session) = *lock_or_recover(&self.session, "debug_adapter.session")
23 && let Some(stdin) = session.process.stdin.as_mut()
24 {
25 let commands = vec!["T".to_string()];
26 match self.send_framed_debugger_commands(stdin, &commands) {
27 Ok((begin, end)) => {
28 framed_output_lines = self.capture_framed_debugger_output(
29 &begin,
30 &end,
31 DEBUGGER_QUERY_WAIT_MS * 8,
32 );
33 }
34 Err(error) => {
35 tracing::warn!(%error, "Failed to send framed stackTrace command, falling back");
36 let _ = stdin.write_all(b"T\n");
37 let _ = stdin.flush();
38 Self::wait_for_debugger_output_window(DEBUGGER_QUERY_WAIT_MS as u32);
39 }
40 }
41 }
42
43 let parsed_frames = if let Some(lines) = framed_output_lines.as_ref() {
44 let output = lines.join("\n");
45 let framed_frames =
46 Self::filter_user_visible_frames(Self::parse_stack_frames_from_text(&output));
47 if framed_frames.is_empty() {
48 Vec::new()
63 } else {
64 framed_frames
65 }
66 } else {
67 Vec::new()
74 };
75
76 let stack_frames = if !parsed_frames.is_empty() {
77 if let Some(ref mut session) = *lock_or_recover(&self.session, "debug_adapter.session")
79 {
80 session.stack_frames = parsed_frames.clone();
81 }
82 parsed_frames
83 } else if let Some(ref session) = *lock_or_recover(&self.session, "debug_adapter.session") {
84 Self::filter_user_visible_frames(session.stack_frames.clone())
85 } else if let Some(pid) = *lock_or_recover(&self.attached_pid, "debug_adapter.attached_pid")
86 {
87 vec![StackFrame {
88 id: Self::i64_to_i32_saturating(i64::from(pid)),
89 name: format!("attached::process::{pid}"),
90 source: Source {
91 name: Some(format!("pid:{pid}")),
92 path: format!("pid://{pid}"),
93 source_reference: None,
94 },
95 line: 1,
96 column: 1,
97 end_line: None,
98 end_column: None,
99 }]
100 } else {
101 Vec::new()
103 };
104 let total_frames = stack_frames.len();
108 let stack_frames = Self::paginate_stack_frames(stack_frames, start_frame, requested_count);
109
110 DapMessage::Response {
111 seq,
112 request_seq,
113 success: true,
114 command: "stackTrace".to_string(),
115 body: Some(json!({
116 "stackFrames": stack_frames,
117 "totalFrames": total_frames
118 })),
119 message: None,
120 }
121 }
122
123 pub fn handle_scopes(
125 &self,
126 seq: i64,
127 request_seq: i64,
128 arguments: Option<Value>,
129 ) -> DapMessage {
130 let args: ScopesArguments = match arguments.and_then(|v| serde_json::from_value(v).ok()) {
131 Some(a) => a,
132 None => {
133 return DapMessage::Response {
134 seq,
135 request_seq,
136 success: false,
137 command: "scopes".to_string(),
138 body: None,
139 message: Some("Missing frameId".to_string()),
140 };
141 }
142 };
143
144 let frame_id = Self::i64_to_i32_saturating(args.frame_id);
145
146 use crate::debug_adapter::var_ref::{ScopeKind, VariableReference};
149 let locals_ref =
150 VariableReference::Scope { frame_id, kind: ScopeKind::Locals }.encode().unwrap_or(0);
151 let package_ref =
152 VariableReference::Scope { frame_id, kind: ScopeKind::Package }.encode().unwrap_or(0);
153 let globals_ref =
154 VariableReference::Scope { frame_id, kind: ScopeKind::Globals }.encode().unwrap_or(0);
155
156 let scopes_body = ScopesResponseBody {
157 scopes: vec![
158 Scope {
159 name: "Locals".to_string(),
160 presentation_hint: Some("locals".to_string()),
161 variables_reference: i64::from(locals_ref),
162 expensive: false,
163 named_variables: None,
164 indexed_variables: None,
165 },
166 Scope {
167 name: "Package".to_string(),
168 presentation_hint: None,
169 variables_reference: i64::from(package_ref),
170 expensive: true,
171 named_variables: None,
172 indexed_variables: None,
173 },
174 Scope {
175 name: "Globals".to_string(),
176 presentation_hint: None,
177 variables_reference: i64::from(globals_ref),
178 expensive: true,
179 named_variables: None,
180 indexed_variables: None,
181 },
182 ],
183 };
184
185 DapMessage::Response {
186 seq,
187 request_seq,
188 success: true,
189 command: "scopes".to_string(),
190 body: serde_json::to_value(&scopes_body).ok(),
191 message: None,
192 }
193 }
194}
195
196impl DebugAdapter {
197 fn paginate_stack_frames(
198 stack_frames: Vec<StackFrame>,
199 start_frame: usize,
200 levels: Option<usize>,
201 ) -> Vec<StackFrame> {
202 let iter = stack_frames.into_iter().skip(start_frame);
203 match levels {
204 Some(limit) => iter.take(limit).collect(),
205 None => iter.collect(),
206 }
207 }
208}
209
210#[cfg(test)]
211mod pagination_tests {
212 use super::*;
213
214 fn make_frame(id: i32, name: &str) -> StackFrame {
215 StackFrame {
216 id,
217 name: name.to_string(),
218 source: Source {
219 name: Some("test.pl".to_string()),
220 path: "/tmp/test.pl".to_string(),
221 source_reference: None,
222 },
223 line: id,
224 column: 1,
225 end_line: None,
226 end_column: None,
227 }
228 }
229
230 #[test]
235 fn total_frames_is_pre_pagination_length() -> Result<(), Box<dyn std::error::Error>> {
236 let all_frames: Vec<StackFrame> = (1..=5).map(|i| make_frame(i, "main::step")).collect();
237 let total_before = all_frames.len();
238
239 let paginated = DebugAdapter::paginate_stack_frames(all_frames, 0, Some(2));
241
242 assert_eq!(paginated.len(), 2, "paginated window should be 2");
243 assert_eq!(total_before, 5, "total_frames must be full depth (5)");
244 assert!(
245 total_before >= paginated.len(),
246 "total_frames ({total_before}) must be >= paginated len ({})",
247 paginated.len()
248 );
249 Ok(())
250 }
251
252 #[test]
255 fn total_frames_with_start_frame_beyond_depth() -> Result<(), Box<dyn std::error::Error>> {
256 let all_frames: Vec<StackFrame> = (1..=3).map(|i| make_frame(i, "main::step")).collect();
257 let total_before = all_frames.len();
258
259 let paginated = DebugAdapter::paginate_stack_frames(all_frames, 10, Some(2));
260
261 assert_eq!(paginated.len(), 0, "paginated slice beyond depth should be empty");
262 assert_eq!(total_before, 3, "total_frames must still report full depth when start > depth");
263 Ok(())
264 }
265
266 #[test]
268 fn total_frames_no_pagination_unchanged() -> Result<(), Box<dyn std::error::Error>> {
269 let all_frames: Vec<StackFrame> = (1..=4).map(|i| make_frame(i, "main::step")).collect();
270 let total_before = all_frames.len();
271
272 let paginated = DebugAdapter::paginate_stack_frames(all_frames, 0, None);
273
274 assert_eq!(paginated.len(), total_before, "no pagination: total == paginated");
275 Ok(())
276 }
277}