zsh/zle/
compmatch_port.rs1#[derive(Debug, Clone)]
19pub struct CompMatcher {
20 pub line_pattern: String,
21 pub word_pattern: String,
22 pub flags: MatchFlags,
23}
24
25#[derive(Debug, Clone, Copy, Default)]
27pub struct MatchFlags {
28 pub case_insensitive: bool,
29 pub partial_word: bool,
30 pub anchor_start: bool,
31 pub anchor_end: bool,
32 pub substring: bool,
33}
34
35#[derive(Debug, Clone)]
37pub struct CompLine {
38 pub prefix: String,
39 pub line: String,
40 pub suffix: String,
41 pub word: String,
42 pub matched: bool,
43}
44
45impl CompLine {
46 pub fn new() -> Self {
47 CompLine {
48 prefix: String::new(),
49 line: String::new(),
50 suffix: String::new(),
51 word: String::new(),
52 matched: false,
53 }
54 }
55
56 pub fn sublen(&self) -> usize {
58 self.prefix.len() + self.line.len() + self.suffix.len()
59 }
60
61 pub fn setlens(&mut self) {
63 }
65}
66
67impl Default for CompLine {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73pub fn cpatterns_same(a: &[CompMatcher], b: &[CompMatcher]) -> bool {
75 if a.len() != b.len() {
76 return false;
77 }
78 a.iter()
79 .zip(b.iter())
80 .all(|(ma, mb)| ma.line_pattern == mb.line_pattern && ma.word_pattern == mb.word_pattern)
81}
82
83pub fn cmatchers_same(a: &[CompMatcher], b: &[CompMatcher]) -> bool {
85 cpatterns_same(a, b)
86}
87
88pub fn match_str(
90 line: &str,
91 word: &str,
92 matchers: &[CompMatcher],
93 flags: &MatchFlags,
94) -> Option<Vec<CompLine>> {
95 if flags.case_insensitive {
96 if line.to_lowercase().starts_with(&word.to_lowercase()) {
97 return Some(vec![CompLine {
98 line: line.to_string(),
99 word: word.to_string(),
100 matched: true,
101 ..Default::default()
102 }]);
103 }
104 } else if line.starts_with(word) {
105 return Some(vec![CompLine {
106 line: line.to_string(),
107 word: word.to_string(),
108 matched: true,
109 ..Default::default()
110 }]);
111 }
112
113 for matcher in matchers {
115 if try_matcher(line, word, matcher) {
116 return Some(vec![CompLine {
117 line: line.to_string(),
118 word: word.to_string(),
119 matched: true,
120 ..Default::default()
121 }]);
122 }
123 }
124
125 None
126}
127
128fn try_matcher(line: &str, word: &str, matcher: &CompMatcher) -> bool {
129 if matcher.flags.case_insensitive {
130 line.to_lowercase().contains(&word.to_lowercase())
131 } else if matcher.flags.substring {
132 line.contains(word)
133 } else if matcher.flags.partial_word {
134 let mut li = line.chars().peekable();
136 let mut wi = word.chars();
137 let mut wc = wi.next();
138
139 while let Some(lc) = li.next() {
140 if let Some(w) = wc {
141 if lc.eq_ignore_ascii_case(&w) {
142 wc = wi.next();
143 }
144 } else {
145 return true;
146 }
147 }
148 wc.is_none()
149 } else {
150 false
151 }
152}
153
154pub fn match_parts(line: &str, word: &str, flags: &MatchFlags) -> Vec<(usize, usize)> {
156 let mut parts = Vec::new();
157 let line_lower = if flags.case_insensitive {
158 line.to_lowercase()
159 } else {
160 line.to_string()
161 };
162 let word_lower = if flags.case_insensitive {
163 word.to_lowercase()
164 } else {
165 word.to_string()
166 };
167
168 let mut pos = 0;
169 for wc in word_lower.chars() {
170 if let Some(found) = line_lower[pos..].find(wc) {
171 let abs_pos = pos + found;
172 parts.push((abs_pos, abs_pos + wc.len_utf8()));
173 pos = abs_pos + wc.len_utf8();
174 }
175 }
176 parts
177}
178
179pub fn comp_match(line: &str, word: &str, flags: &MatchFlags) -> bool {
181 match_str(line, word, &[], flags).is_some()
182}
183
184pub fn start_match() -> Vec<CompLine> {
186 Vec::new()
187}
188
189pub fn abort_match(_lines: Vec<CompLine>) {
191 }
193
194pub fn cp_cline(line: &CompLine) -> CompLine {
196 line.clone()
197}
198
199pub fn free_cline(_line: CompLine) {}
201
202pub fn revert_cline(line: &mut CompLine) {
204 line.matched = false;
205}
206
207pub fn cline_matched(line: &CompLine) -> bool {
209 line.matched
210}
211
212pub fn pattern_match_equivalence(a: char, b: char, case_insensitive: bool) -> bool {
214 if case_insensitive {
215 a.eq_ignore_ascii_case(&b)
216 } else {
217 a == b
218 }
219}
220
221pub fn parse_matcher_spec(spec: &str) -> Vec<CompMatcher> {
224 let mut matchers = Vec::new();
225
226 for part in spec.split_whitespace() {
227 let flags = MatchFlags {
228 case_insensitive: part.starts_with("m:"),
229 partial_word: part.starts_with("r:") || part.starts_with("l:"),
230 anchor_start: part.starts_with("l:"),
231 anchor_end: part.starts_with("r:"),
232 substring: part.starts_with("M:"),
233 };
234
235 if let Some((line_pat, word_pat)) = part.split_once('=') {
236 let line_pat = line_pat.split(':').last().unwrap_or("");
237 matchers.push(CompMatcher {
238 line_pattern: line_pat.to_string(),
239 word_pattern: word_pat.to_string(),
240 flags,
241 });
242 }
243 }
244
245 matchers
246}
247
248pub fn update_bmatchers(matchers: &mut Vec<CompMatcher>, new: Vec<CompMatcher>) {
250 *matchers = new;
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_match_str_exact() {
259 let flags = MatchFlags::default();
260 assert!(match_str("foobar", "foo", &[], &flags).is_some());
261 assert!(match_str("foobar", "baz", &[], &flags).is_none());
262 }
263
264 #[test]
265 fn test_match_str_case_insensitive() {
266 let flags = MatchFlags {
267 case_insensitive: true,
268 ..Default::default()
269 };
270 assert!(match_str("FooBar", "foo", &[], &flags).is_some());
271 }
272
273 #[test]
274 fn test_match_parts() {
275 let flags = MatchFlags::default();
276 let parts = match_parts("foobar", "fbr", &flags);
277 assert_eq!(parts.len(), 3);
278 }
279
280 #[test]
281 fn test_pattern_match_equivalence() {
282 assert!(pattern_match_equivalence('a', 'A', true));
283 assert!(!pattern_match_equivalence('a', 'A', false));
284 }
285
286 #[test]
287 fn test_parse_matcher_spec() {
288 let matchers = parse_matcher_spec("m:{[:lower:]}={[:upper:]}");
289 assert_eq!(matchers.len(), 1);
290 assert!(matchers[0].flags.case_insensitive);
291 }
292
293 #[test]
294 fn test_comp_line() {
295 let mut cl = CompLine::new();
296 cl.prefix = "pre".to_string();
297 cl.line = "middle".to_string();
298 cl.suffix = "suf".to_string();
299 assert_eq!(cl.sublen(), 12);
300 }
301}