1use gpui::SharedString;
2
3#[derive(Clone, PartialEq, Debug)]
4pub enum MaskToken {
5 Digit,
9 Letter,
11 LetterOrDigit,
13 Sep(char),
15 Any,
17}
18
19#[allow(unused)]
20impl MaskToken {
21 pub fn is_any(&self) -> bool {
23 matches!(self, MaskToken::Any)
24 }
25
26 fn is_match(&self, ch: char) -> bool {
30 match self {
31 MaskToken::Digit => ch.is_ascii_digit(),
32 MaskToken::Letter => ch.is_ascii_alphabetic(),
33 MaskToken::LetterOrDigit => ch.is_ascii_alphanumeric(),
34 MaskToken::Any => true,
35 MaskToken::Sep(c) => *c == ch,
36 }
37 }
38
39 fn is_sep(&self) -> bool {
41 matches!(self, MaskToken::Sep(_))
42 }
43
44 pub fn is_number(&self) -> bool {
46 matches!(self, MaskToken::Digit)
47 }
48
49 pub fn placeholder(&self) -> char {
50 match self {
51 MaskToken::Sep(c) => *c,
52 _ => '_',
53 }
54 }
55
56 fn mask_char(&self, ch: char) -> char {
57 match self {
58 MaskToken::Digit | MaskToken::LetterOrDigit | MaskToken::Letter => ch,
59 MaskToken::Sep(c) => *c,
60 MaskToken::Any => ch,
61 }
62 }
63
64 fn unmask_char(&self, ch: char) -> Option<char> {
65 match self {
66 MaskToken::Digit => Some(ch),
67 MaskToken::Letter => Some(ch),
68 MaskToken::LetterOrDigit => Some(ch),
69 MaskToken::Any => Some(ch),
70 _ => None,
71 }
72 }
73}
74
75#[derive(Clone, Default)]
76pub enum MaskPattern {
77 #[default]
78 None,
79 Pattern {
80 pattern: SharedString,
81 tokens: Vec<MaskToken>,
82 },
83 Number {
84 separator: Option<char>,
86 fraction: Option<usize>,
88 },
89}
90
91impl From<&str> for MaskPattern {
92 fn from(pattern: &str) -> Self {
93 Self::new(pattern)
94 }
95}
96
97impl MaskPattern {
98 pub fn new(pattern: &str) -> Self {
113 let tokens = pattern
114 .chars()
115 .map(|ch| match ch {
116 '9' => MaskToken::Digit,
118 'A' => MaskToken::Letter,
119 '#' => MaskToken::LetterOrDigit,
120 '*' => MaskToken::Any,
121 _ => MaskToken::Sep(ch),
122 })
123 .collect();
124
125 Self::Pattern {
126 pattern: pattern.to_owned().into(),
127 tokens,
128 }
129 }
130
131 #[allow(unused)]
132 fn tokens(&self) -> Option<&Vec<MaskToken>> {
133 match self {
134 Self::Pattern { tokens, .. } => Some(tokens),
135 Self::Number { .. } => None,
136 Self::None => None,
137 }
138 }
139
140 pub fn number(sep: Option<char>) -> Self {
142 Self::Number {
143 separator: sep,
144 fraction: None,
145 }
146 }
147
148 pub fn placeholder(&self) -> Option<String> {
149 match self {
150 Self::Pattern { tokens, .. } => {
151 Some(tokens.iter().map(|token| token.placeholder()).collect())
152 }
153 Self::Number { .. } => None,
154 Self::None => None,
155 }
156 }
157
158 pub fn is_none(&self) -> bool {
160 match self {
161 Self::Pattern { tokens, .. } => tokens.is_empty(),
162 Self::Number { .. } => false,
163 Self::None => true,
164 }
165 }
166
167 pub fn is_valid(&self, mask_text: &str) -> bool {
171 if self.is_none() {
172 return true;
173 }
174
175 let mut text_index = 0;
176 let mask_text_chars: Vec<char> = mask_text.chars().collect();
177 match self {
178 Self::Pattern { tokens, .. } => {
179 for token in tokens {
180 if text_index >= mask_text_chars.len() {
181 break;
182 }
183
184 let ch = mask_text_chars[text_index];
185 if token.is_match(ch) {
186 text_index += 1;
187 }
188 }
189 text_index == mask_text.len()
190 }
191 Self::Number { separator, .. } => {
192 if mask_text.is_empty() {
193 return true;
194 }
195
196 let mut parts = mask_text.split('.');
198 let int_part = parts.next().unwrap_or("");
199 let frac_part = parts.next();
200
201 if int_part.is_empty() {
202 return false;
203 }
204
205 let sign_positions: Vec<usize> = int_part
206 .chars()
207 .enumerate()
208 .filter_map(|(i, ch)| match is_sign(&ch) {
209 true => Some(i),
210 false => None,
211 })
212 .collect();
213
214 if sign_positions.len() > 1 || sign_positions.first() > Some(&0) {
217 return false;
218 }
219
220 if !int_part.chars().enumerate().all(|(i, ch)| {
222 ch.is_ascii_digit() || is_sign(&ch) && i == 0 || Some(ch) == *separator
223 }) {
224 return false;
225 }
226
227 if let Some(frac) = frac_part {
229 if !frac
230 .chars()
231 .all(|ch| ch.is_ascii_digit() || Some(ch) == *separator)
232 {
233 return false;
234 }
235 }
236
237 true
238 }
239 Self::None => true,
240 }
241 }
242
243 pub fn is_valid_at(&self, ch: char, pos: usize) -> bool {
245 if self.is_none() {
246 return true;
247 }
248
249 match self {
250 Self::Pattern { tokens, .. } => {
251 if let Some(token) = tokens.get(pos) {
252 if token.is_match(ch) {
253 return true;
254 }
255
256 if token.is_sep() {
257 if let Some(next_token) = tokens.get(pos + 1) {
259 if next_token.is_match(ch) {
260 return true;
261 }
262 }
263 }
264 }
265
266 false
267 }
268 Self::Number { .. } => true,
269 Self::None => true,
270 }
271 }
272
273 pub fn mask(&self, text: &str) -> SharedString {
281 if self.is_none() {
282 return text.to_owned().into();
283 }
284
285 match self {
286 Self::Number {
287 separator,
288 fraction,
289 } => {
290 if let Some(sep) = *separator {
291 let text = text.replace(sep, "");
293
294 let mut parts = text.split('.');
295 let int_part = parts.next().unwrap_or("");
296
297 let frac_part = parts.next().map(|part| {
299 part.chars()
300 .take(fraction.unwrap_or(usize::MAX))
301 .collect::<String>()
302 });
303
304 let mut chars: Vec<char> = int_part.chars().rev().collect();
306
307 let maybe_signed = if let Some(pos) = chars.iter().position(is_sign) {
309 Some(chars.remove(pos))
310 } else {
311 None
312 };
313
314 let mut result = String::new();
315 for (i, ch) in chars.iter().enumerate() {
316 if i > 0 && i % 3 == 0 {
317 result.push(sep);
318 }
319 result.push(*ch);
320 }
321 let int_with_sep: String = result.chars().rev().collect();
322
323 let final_str = if let Some(frac) = frac_part {
324 if fraction == &Some(0) {
325 int_with_sep
326 } else {
327 format!("{}.{}", int_with_sep, frac)
328 }
329 } else {
330 int_with_sep
331 };
332
333 let final_str = if let Some(sign) = maybe_signed {
334 format!("{}{}", sign, final_str)
335 } else {
336 final_str
337 };
338
339 return final_str.into();
340 }
341
342 text.to_owned().into()
343 }
344 Self::Pattern { tokens, .. } => {
345 let mut result = String::new();
346 let mut text_index = 0;
347 let text_chars: Vec<char> = text.chars().collect();
348 for (pos, token) in tokens.iter().enumerate() {
349 if text_index >= text_chars.len() {
350 break;
351 }
352 let ch = text_chars[text_index];
353 if !token.is_sep() && !self.is_valid_at(ch, pos) {
355 break;
356 }
357 let mask_ch = token.mask_char(ch);
358 result.push(mask_ch);
359 if ch == mask_ch {
360 text_index += 1;
361 continue;
362 }
363 }
364 result.into()
365 }
366 Self::None => text.to_owned().into(),
367 }
368 }
369
370 pub fn unmask(&self, mask_text: &str) -> String {
372 match self {
373 Self::Number { separator, .. } => {
374 if let Some(sep) = *separator {
375 let mut result = String::new();
376 for ch in mask_text.chars() {
377 if ch == sep {
378 continue;
379 }
380 result.push(ch);
381 }
382
383 if result.contains('.') {
384 result = result.trim_end_matches('0').to_string();
385 }
386 return result;
387 }
388
389 return mask_text.to_owned();
390 }
391 Self::Pattern { tokens, .. } => {
392 let mut result = String::new();
393 let mask_text_chars: Vec<char> = mask_text.chars().collect();
394 for (text_index, token) in tokens.iter().enumerate() {
395 if text_index >= mask_text_chars.len() {
396 break;
397 }
398 let ch = mask_text_chars[text_index];
399 let unmask_ch = token.unmask_char(ch);
400 if let Some(ch) = unmask_ch {
401 result.push(ch);
402 }
403 }
404 result
405 }
406 Self::None => mask_text.to_owned(),
407 }
408 }
409}
410
411#[inline]
412fn is_sign(ch: &char) -> bool {
413 matches!(ch, '+' | '-')
414}
415
416#[cfg(test)]
417mod tests {
418 use crate::input::mask_pattern::{MaskPattern, MaskToken};
419
420 #[test]
421 fn test_is_match() {
422 assert_eq!(MaskToken::Sep('(').is_match('('), true);
423 assert_eq!(MaskToken::Sep('-').is_match('('), false);
424 assert_eq!(MaskToken::Sep('-').is_match('3'), false);
425
426 assert_eq!(MaskToken::Digit.is_match('0'), true);
427 assert_eq!(MaskToken::Digit.is_match('9'), true);
428 assert_eq!(MaskToken::Digit.is_match('a'), false);
429 assert_eq!(MaskToken::Digit.is_match('C'), false);
430
431 assert_eq!(MaskToken::Letter.is_match('a'), true);
432 assert_eq!(MaskToken::Letter.is_match('Z'), true);
433 assert_eq!(MaskToken::Letter.is_match('3'), false);
434 assert_eq!(MaskToken::Letter.is_match('-'), false);
435
436 assert_eq!(MaskToken::LetterOrDigit.is_match('0'), true);
437 assert_eq!(MaskToken::LetterOrDigit.is_match('9'), true);
438 assert_eq!(MaskToken::LetterOrDigit.is_match('a'), true);
439 assert_eq!(MaskToken::LetterOrDigit.is_match('Z'), true);
440 assert_eq!(MaskToken::LetterOrDigit.is_match('3'), true);
441
442 assert_eq!(MaskToken::Any.is_match('a'), true);
443 assert_eq!(MaskToken::Any.is_match('3'), true);
444 assert_eq!(MaskToken::Any.is_match('-'), true);
445 assert_eq!(MaskToken::Any.is_match(' '), true);
446 }
447
448 #[test]
449 fn test_mask_none() {
450 let mask = MaskPattern::None;
451 assert_eq!(mask.is_none(), true);
452 assert_eq!(mask.is_valid("1124124ASLDJKljk"), true);
453 assert_eq!(mask.mask("hello-world"), "hello-world");
454 assert_eq!(mask.unmask("hello-world"), "hello-world");
455 }
456
457 #[test]
458 fn test_mask_pattern1() {
459 let mask = MaskPattern::new("(AA)999-999");
460 assert_eq!(
461 mask.tokens(),
462 Some(&vec![
463 MaskToken::Sep('('),
464 MaskToken::Letter,
465 MaskToken::Letter,
466 MaskToken::Sep(')'),
467 MaskToken::Digit,
468 MaskToken::Digit,
469 MaskToken::Digit,
470 MaskToken::Sep('-'),
471 MaskToken::Digit,
472 MaskToken::Digit,
473 MaskToken::Digit,
474 ])
475 );
476
477 assert_eq!(mask.is_valid_at('(', 0), true);
478 assert_eq!(mask.is_valid_at('H', 0), true);
479 assert_eq!(mask.is_valid_at('3', 0), false);
480 assert_eq!(mask.is_valid_at('-', 0), false);
481 assert_eq!(mask.is_valid_at(')', 1), false);
482 assert_eq!(mask.is_valid_at('H', 1), true);
483 assert_eq!(mask.is_valid_at('1', 1), false);
484 assert_eq!(mask.is_valid_at('e', 2), true);
485 assert_eq!(mask.is_valid_at(')', 3), true);
486 assert_eq!(mask.is_valid_at('1', 3), true);
487 assert_eq!(mask.is_valid_at('2', 4), true);
488
489 assert_eq!(mask.is_valid("(AB)123-456"), true);
490
491 assert_eq!(mask.mask("AB123456"), "(AB)123-456");
492 assert_eq!(mask.mask("(AB)123-456"), "(AB)123-456");
493 assert_eq!(mask.mask("(AB123456"), "(AB)123-456");
494 assert_eq!(mask.mask("AB123-456"), "(AB)123-456");
495 assert_eq!(mask.mask("AB123-"), "(AB)123-");
496 assert_eq!(mask.mask("AB123--"), "(AB)123-");
497 assert_eq!(mask.mask("AB123-4"), "(AB)123-4");
498
499 let unmasked_text = mask.unmask("(AB)123-456");
500 assert_eq!(unmasked_text, "AB123456");
501
502 assert_eq!(mask.is_valid("12AB345"), false);
503 assert_eq!(mask.is_valid("(11)123-456"), false);
504 assert_eq!(mask.is_valid("##"), false);
505 assert_eq!(mask.is_valid("(AB)123456"), true);
506 }
507
508 #[test]
509 fn test_mask_pattern2() {
510 let mask = MaskPattern::new("999-999-******");
511 assert_eq!(
512 mask.tokens(),
513 Some(&vec![
514 MaskToken::Digit,
515 MaskToken::Digit,
516 MaskToken::Digit,
517 MaskToken::Sep('-'),
518 MaskToken::Digit,
519 MaskToken::Digit,
520 MaskToken::Digit,
521 MaskToken::Sep('-'),
522 MaskToken::Any,
523 MaskToken::Any,
524 MaskToken::Any,
525 MaskToken::Any,
526 MaskToken::Any,
527 MaskToken::Any,
528 ])
529 );
530
531 let text = "123456A(111)";
532 let masked_text = mask.mask(text);
533 assert_eq!(masked_text, "123-456-A(111)");
534 let unmasked_text = mask.unmask(&masked_text);
535 assert_eq!(unmasked_text, "123456A(111)");
536 assert_eq!(mask.is_valid(&masked_text), true);
537 }
538
539 #[test]
540 fn test_number_with_group_separator() {
541 let mask = MaskPattern::number(Some(','));
543 assert_eq!(mask.mask("1234567"), "1,234,567");
544 assert_eq!(mask.mask("1,234,567"), "1,234,567");
545 assert_eq!(mask.unmask("1,234,567"), "1234567");
546 let mask = MaskPattern::number(Some(','));
547 assert_eq!(mask.mask("1234567.89"), "1,234,567.89");
548 assert_eq!(mask.unmask("1,234,567.89"), "1234567.89");
549
550 let mask = MaskPattern::number(Some(' '));
552 assert_eq!(mask.mask("1234567"), "1 234 567");
553 assert_eq!(mask.unmask("1 234 567"), "1234567");
554 let mask = MaskPattern::number(Some(' '));
555 assert_eq!(mask.mask("1234567.89"), "1 234 567.89");
556 assert_eq!(mask.unmask("1 234 567.89"), "1234567.89");
557
558 let mask = MaskPattern::number(None);
560 assert_eq!(mask.mask("1234567"), "1234567");
561 assert_eq!(mask.unmask("1234567"), "1234567");
562 let mask = MaskPattern::number(None);
563 assert_eq!(mask.mask("1234567.89"), "1234567.89");
564 assert_eq!(mask.unmask("1234567.89"), "1234567.89");
565 }
566
567 #[test]
568 fn test_number_with_fraction_digits() {
569 let mask = MaskPattern::Number {
570 separator: Some(','),
571 fraction: Some(4),
572 };
573
574 assert_eq!(mask.mask("1234567"), "1,234,567");
575 assert_eq!(mask.unmask("1,234,567"), "1234567");
576 assert_eq!(mask.mask("1234567."), "1,234,567.");
577 assert_eq!(mask.mask("1234567.89"), "1,234,567.89");
578 assert_eq!(mask.unmask("1,234,567.890"), "1234567.89");
579 assert_eq!(mask.mask("1234567.891"), "1,234,567.891");
580 assert_eq!(mask.mask("1234567.891234"), "1,234,567.8912");
581
582 let mask = MaskPattern::Number {
583 separator: Some(','),
584 fraction: None,
585 };
586
587 assert_eq!(mask.mask("1234567.1234567"), "1,234,567.1234567");
588
589 let mask = MaskPattern::Number {
590 separator: Some(','),
591 fraction: Some(0),
592 };
593
594 assert_eq!(mask.mask("1234567.1234567"), "1,234,567");
595 }
596
597 #[test]
598 fn test_signed_number_numbers() {
599 let mask = MaskPattern::Number {
600 separator: Some(','),
601 fraction: Some(2),
602 };
603
604 assert_eq!(mask.is_valid("-"), true);
605 assert_eq!(mask.is_valid("-1234567"), true);
606 assert_eq!(mask.is_valid("-1,234,567"), true);
607 assert_eq!(mask.is_valid("-1234567."), true);
608 assert_eq!(mask.is_valid("-1234567.89"), true);
609
610 assert_eq!(mask.is_valid("+"), true);
611 assert_eq!(mask.is_valid("+1234567"), true);
612 assert_eq!(mask.is_valid("+1,234,567"), true);
613 assert_eq!(mask.is_valid("+1234567."), true);
614 assert_eq!(mask.is_valid("+1234567.89"), true);
615
616 assert_eq!(mask.is_valid("+-"), false);
618 assert_eq!(mask.is_valid("-+"), false);
619 assert_eq!(mask.is_valid("+-1234567"), false);
620
621 assert_eq!(mask.is_valid("1,-234,567"), false);
623 assert_eq!(mask.is_valid("12-34567.89"), false);
624
625 assert_eq!(mask.is_valid("+1234567.-"), false);
627
628 assert_eq!(mask.mask("-123"), "-123");
630
631 assert_eq!(mask.mask("-1234567"), "-1,234,567");
632 assert_eq!(mask.mask("+1234567"), "+1,234,567");
633 assert_eq!(mask.unmask("-1,234,567"), "-1234567");
634 assert_eq!(mask.mask("-1234567."), "-1,234,567.");
635 assert_eq!(mask.mask("-1234567.89"), "-1,234,567.89");
636 }
637}