1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use super::*;
impl DebugAdapter {
/// Handle setBreakpoints request
pub(in crate::debug_adapter) fn handle_set_breakpoints(
&mut self,
seq: i64,
request_seq: i64,
arguments: Option<Value>,
) -> DapMessage {
let Some(args_value) = arguments else {
return DapMessage::Response {
seq,
request_seq,
success: false,
command: "setBreakpoints".to_string(),
body: None,
message: Some("Missing arguments".to_string()),
};
};
let args: crate::protocol::SetBreakpointsArguments =
match serde_json::from_value(args_value) {
Ok(a) => a,
Err(e) => {
return DapMessage::Response {
seq,
request_seq,
success: false,
command: "setBreakpoints".to_string(),
body: None,
message: Some(format!("Invalid arguments: {}", e)),
};
}
};
// Snapshot old breakpoints for this file before replacing them,
// so we can clear only per-file breakpoints instead of global `B *`.
let old_breakpoints = if let Some(ref source_path) = args.source.path {
self.breakpoints.get_breakpoints(source_path)
} else {
Vec::new()
};
// AC7: AST-based breakpoint validation via BreakpointStore
let verified_breakpoints = self.breakpoints.set_breakpoints(&args);
let new_breakpoint_records = if let Some(ref source_path) = args.source.path {
self.breakpoints.get_breakpoints(source_path)
} else {
Vec::new()
};
let condition_by_id: HashMap<i64, Option<String>> = new_breakpoint_records
.into_iter()
.map(|record| (record.id, record.condition))
.collect();
// If a session is active, also sync the breakpoints to the Perl debugger
if let Ok(mut guard) = self.session.lock()
&& let Some(ref mut session) = *guard
{
if let Some(stdin) = session.process.stdin.as_mut() {
let mut command_batch = String::new();
// Clear only the old breakpoints for this specific file
for old_bp in &old_breakpoints {
if old_bp.verified {
command_batch.push_str(&format!("B {}\n", old_bp.line));
}
}
// Set new breakpoints that were successfully verified
for bp in &verified_breakpoints {
if bp.verified {
// Retrieve original condition (if present) from records produced by this call.
let cmd = if let Some(Some(cond)) = condition_by_id.get(&bp.id) {
format!("b {} {}\n", bp.line, cond)
} else {
format!("b {}\n", bp.line)
};
command_batch.push_str(&cmd);
}
}
if !command_batch.is_empty() {
let _ = stdin.write_all(command_batch.as_bytes());
let _ = stdin.flush();
}
}
}
// Keep function breakpoints active after line-breakpoint synchronization.
self.apply_stored_function_breakpoints();
DapMessage::Response {
seq,
request_seq,
success: true,
command: "setBreakpoints".to_string(),
body: Some(json!({
"breakpoints": verified_breakpoints
})),
message: None,
}
}
}