perl_dap_breakpoint/
suggestion.rs1use crate::validator::{AstBreakpointValidator, BreakpointValidator};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum SearchDirection {
11 Forward,
13 Backward,
15 Both,
17}
18
19pub fn find_nearest_valid_line(
32 validator: &AstBreakpointValidator,
33 line: i64,
34 direction: SearchDirection,
35 max_distance: Option<usize>,
36) -> Option<i64> {
37 let max_dist = max_distance.unwrap_or(usize::MAX);
38
39 match direction {
40 SearchDirection::Forward => find_forward(validator, line, max_dist),
41 SearchDirection::Backward => find_backward(validator, line, max_dist),
42 SearchDirection::Both => {
43 let forward = find_forward(validator, line, max_dist);
44 let backward = find_backward(validator, line, max_dist);
45
46 match (forward, backward) {
47 (Some(f), Some(b)) => {
48 let f_dist = (f - line).unsigned_abs();
49 let b_dist = (line - b).unsigned_abs();
50 if f_dist <= b_dist { Some(f) } else { Some(b) }
51 }
52 (Some(f), None) => Some(f),
53 (None, Some(b)) => Some(b),
54 (None, None) => None,
55 }
56 }
57 }
58}
59
60fn find_forward(
61 validator: &AstBreakpointValidator,
62 start_line: i64,
63 max_distance: usize,
64) -> Option<i64> {
65 for offset in 1..=max_distance {
66 let line = start_line + offset as i64;
67 if validator.is_executable_line(line) {
68 return Some(line);
69 }
70 let result = validator.validate(line);
72 if result.reason == Some(crate::validator::ValidationReason::LineOutOfRange) {
73 break;
74 }
75 }
76 None
77}
78
79fn find_backward(
80 validator: &AstBreakpointValidator,
81 start_line: i64,
82 max_distance: usize,
83) -> Option<i64> {
84 for offset in 1..=max_distance {
85 let line = start_line - offset as i64;
86 if line < 1 {
87 break;
88 }
89 if validator.is_executable_line(line) {
90 return Some(line);
91 }
92 }
93 None
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use perl_tdd_support::must;
100
101 #[test]
102 fn test_find_nearest_forward() {
103 let source = "# comment\n# comment\nmy $x = 1;\n";
104 let validator = must(AstBreakpointValidator::new(source));
105
106 let result = find_nearest_valid_line(&validator, 1, SearchDirection::Forward, None);
107 assert_eq!(result, Some(3));
108 }
109
110 #[test]
111 fn test_find_nearest_backward() {
112 let source = "my $x = 1;\n# comment\n# comment\n";
113 let validator = must(AstBreakpointValidator::new(source));
114
115 let result = find_nearest_valid_line(&validator, 3, SearchDirection::Backward, None);
116 assert_eq!(result, Some(1));
117 }
118
119 #[test]
120 fn test_find_nearest_both_prefers_closer() {
121 let source = "my $x = 1;\n# comment\n# comment\n# comment\nmy $y = 2;\n";
122 let validator = must(AstBreakpointValidator::new(source));
123
124 let result = find_nearest_valid_line(&validator, 2, SearchDirection::Both, None);
126 assert_eq!(result, Some(1));
127
128 let result = find_nearest_valid_line(&validator, 4, SearchDirection::Both, None);
130 assert_eq!(result, Some(5));
131 }
132
133 #[test]
134 fn test_find_nearest_with_max_distance() {
135 let source = "# comment\n# comment\n# comment\nmy $x = 1;\n";
136 let validator = must(AstBreakpointValidator::new(source));
137
138 let result = find_nearest_valid_line(&validator, 1, SearchDirection::Forward, Some(2));
140 assert_eq!(result, None);
141
142 let result = find_nearest_valid_line(&validator, 1, SearchDirection::Forward, Some(3));
144 assert_eq!(result, Some(4));
145 }
146
147 #[test]
148 fn test_find_nearest_no_valid_lines() {
149 let source = "# all comments\n# more comments\n";
150 let validator = must(AstBreakpointValidator::new(source));
151
152 let result = find_nearest_valid_line(&validator, 1, SearchDirection::Both, None);
153 assert_eq!(result, None);
154 }
155}