1use std::borrow::Cow;
2
3use gpui::SharedString;
4
5#[derive(Clone, PartialEq, Debug)]
6pub enum MaskToken {
7 Digit,
11 Letter,
13 LetterOrDigit,
15 Sep(char),
17 Any,
19}
20
21#[allow(unused)]
22impl MaskToken {
23 pub fn is_any(&self) -> bool {
25 matches!(self, MaskToken::Any)
26 }
27
28 fn is_match(&self, ch: char) -> bool {
32 match self {
33 MaskToken::Digit => ch.is_ascii_digit(),
34 MaskToken::Letter => ch.is_ascii_alphabetic(),
35 MaskToken::LetterOrDigit => ch.is_ascii_alphanumeric(),
36 MaskToken::Any => true,
37 MaskToken::Sep(c) => *c == ch,
38 }
39 }
40
41 fn is_sep(&self) -> bool {
43 matches!(self, MaskToken::Sep(_))
44 }
45
46 pub fn is_number(&self) -> bool {
48 matches!(self, MaskToken::Digit)
49 }
50
51 pub fn placeholder(&self) -> char {
52 match self {
53 MaskToken::Sep(c) => *c,
54 _ => '_',
55 }
56 }
57
58 fn mask_char(&self, ch: char) -> char {
59 match self {
60 MaskToken::Digit | MaskToken::LetterOrDigit | MaskToken::Letter => ch,
61 MaskToken::Sep(c) => *c,
62 MaskToken::Any => ch,
63 }
64 }
65
66 fn unmask_char(&self, ch: char) -> Option<char> {
67 match self {
68 MaskToken::Digit => Some(ch),
69 MaskToken::Letter => Some(ch),
70 MaskToken::LetterOrDigit => Some(ch),
71 MaskToken::Any => Some(ch),
72 _ => None,
73 }
74 }
75}
76
77#[derive(Clone, Default)]
78pub enum MaskPattern {
79 #[default]
80 None,
81 Pattern {
82 pattern: SharedString,
83 tokens: Vec<MaskToken>,
84 },
85 Number {
86 separator: Option<char>,
88 fraction: Option<usize>,
90 },
91}
92
93impl From<&str> for MaskPattern {
94 fn from(pattern: &str) -> Self {
95 Self::new(pattern)
96 }
97}
98
99impl MaskPattern {
100 pub fn new(pattern: &str) -> Self {
115 let tokens = pattern
116 .chars()
117 .map(|ch| match ch {
118 '9' => MaskToken::Digit,
120 'A' => MaskToken::Letter,
121 '#' => MaskToken::LetterOrDigit,
122 '*' => MaskToken::Any,
123 _ => MaskToken::Sep(ch),
124 })
125 .collect();
126
127 Self::Pattern {
128 pattern: pattern.to_owned().into(),
129 tokens,
130 }
131 }
132
133 #[allow(unused)]
134 fn tokens(&self) -> Option<&Vec<MaskToken>> {
135 match self {
136 Self::Pattern { tokens, .. } => Some(tokens),
137 Self::Number { .. } => None,
138 Self::None => None,
139 }
140 }
141
142 pub fn number(sep: Option<char>) -> Self {
144 Self::Number {
145 separator: sep,
146 fraction: None,
147 }
148 }
149
150 pub fn placeholder(&self) -> Option<String> {
151 match self {
152 Self::Pattern { tokens, .. } => {
153 Some(tokens.iter().map(|token| token.placeholder()).collect())
154 }
155 Self::Number { .. } => None,
156 Self::None => None,
157 }
158 }
159
160 pub fn is_none(&self) -> bool {
162 match self {
163 Self::Pattern { tokens, .. } => tokens.is_empty(),
164 Self::Number { .. } => false,
165 Self::None => true,
166 }
167 }
168
169 pub fn is_valid(&self, mask_text: &str) -> bool {
173 if self.is_none() {
174 return true;
175 }
176
177 let mut text_index = 0;
178 let mask_text_chars: Vec<char> = mask_text.chars().collect();
179 match self {
180 Self::Pattern { tokens, .. } => {
181 for token in tokens {
182 if text_index >= mask_text_chars.len() {
183 break;
184 }
185
186 let ch = mask_text_chars[text_index];
187 if token.is_match(ch) {
188 text_index += 1;
189 }
190 }
191 text_index == mask_text.len()
192 }
193 Self::Number { separator, .. } => {
194 if mask_text.is_empty() {
195 return true;
196 }
197
198 let mut parts = mask_text.split('.');
200 let int_part = parts.next().unwrap_or("");
201 let frac_part = parts.next();
202
203 if parts.next().is_some() {
205 return false;
206 }
207
208 let sign_positions: Vec<usize> = int_part
209 .chars()
210 .enumerate()
211 .filter_map(|(i, ch)| match is_sign(&ch) {
212 true => Some(i),
213 false => None,
214 })
215 .collect();
216
217 if sign_positions.len() > 1 || sign_positions.first() > Some(&0) {
220 return false;
221 }
222
223 if !int_part.chars().enumerate().all(|(i, ch)| {
225 ch.is_ascii_digit() || is_sign(&ch) && i == 0 || Some(ch) == *separator
226 }) {
227 return false;
228 }
229
230 if let Some(frac) = frac_part {
232 if !frac
233 .chars()
234 .all(|ch| ch.is_ascii_digit() || Some(ch) == *separator)
235 {
236 return false;
237 }
238 }
239
240 true
241 }
242 Self::None => true,
243 }
244 }
245
246 pub fn is_valid_at(&self, ch: char, pos: usize) -> bool {
248 if self.is_none() {
249 return true;
250 }
251
252 match self {
253 Self::Pattern { tokens, .. } => {
254 if let Some(token) = tokens.get(pos) {
255 if token.is_match(ch) {
256 return true;
257 }
258
259 if token.is_sep() {
260 if let Some(next_token) = tokens.get(pos + 1) {
262 if next_token.is_match(ch) {
263 return true;
264 }
265 }
266 }
267 }
268
269 false
270 }
271 Self::Number { .. } => true,
272 Self::None => true,
273 }
274 }
275
276 pub fn mask(&self, text: &str) -> SharedString {
284 if self.is_none() {
285 return text.to_owned().into();
286 }
287
288 match self {
289 Self::Number {
290 separator,
291 fraction,
292 } => {
293 if let Some(sep) = *separator {
294 let text = text.replace(sep, "");
296
297 let mut parts = text.split('.');
298 let int_part = parts.next().unwrap_or("");
299
300 let frac_part = parts.next().map(|part| {
302 part.chars()
303 .take(fraction.unwrap_or(usize::MAX))
304 .collect::<String>()
305 });
306
307 let mut chars: Vec<char> = int_part.chars().rev().collect();
309
310 let maybe_signed = if let Some(pos) = chars.iter().position(is_sign) {
312 Some(chars.remove(pos))
313 } else {
314 None
315 };
316
317 let mut result = String::new();
318 for (i, ch) in chars.iter().enumerate() {
319 if i > 0 && i % 3 == 0 {
320 result.push(sep);
321 }
322 result.push(*ch);
323 }
324 let int_with_sep: String = result.chars().rev().collect();
325
326 let final_str = if let Some(frac) = frac_part {
327 if fraction == &Some(0) {
328 int_with_sep
329 } else {
330 format!("{}.{}", int_with_sep, frac)
331 }
332 } else {
333 int_with_sep
334 };
335
336 let final_str = if let Some(sign) = maybe_signed {
337 format!("{}{}", sign, final_str)
338 } else {
339 final_str
340 };
341
342 return final_str.into();
343 }
344
345 text.to_owned().into()
346 }
347 Self::Pattern { tokens, .. } => {
348 let mut result = String::new();
349 let mut text_index = 0;
350 let text_chars: Vec<char> = text.chars().collect();
351 for (pos, token) in tokens.iter().enumerate() {
352 if text_index >= text_chars.len() {
353 break;
354 }
355 let ch = text_chars[text_index];
356 if !token.is_sep() && !self.is_valid_at(ch, pos) {
358 break;
359 }
360 let mask_ch = token.mask_char(ch);
361 result.push(mask_ch);
362 if ch == mask_ch {
363 text_index += 1;
364 continue;
365 }
366 }
367 result.into()
368 }
369 Self::None => text.to_owned().into(),
370 }
371 }
372
373 pub fn unmask(&self, mask_text: &str) -> String {
375 match self {
376 Self::Number { separator, .. } => {
377 if let Some(sep) = *separator {
378 let mut result = String::new();
379 for ch in mask_text.chars() {
380 if ch == sep {
381 continue;
382 }
383 result.push(ch);
384 }
385
386 if result.contains('.') {
387 result = result.trim_end_matches('0').to_string();
388 }
389 return result;
390 }
391
392 return mask_text.to_owned();
393 }
394 Self::Pattern { tokens, .. } => {
395 let mut result = String::new();
396 let mask_text_chars: Vec<char> = mask_text.chars().collect();
397 for (text_index, token) in tokens.iter().enumerate() {
398 if text_index >= mask_text_chars.len() {
399 break;
400 }
401 let ch = mask_text_chars[text_index];
402 let unmask_ch = token.unmask_char(ch);
403 if let Some(ch) = unmask_ch {
404 result.push(ch);
405 }
406 }
407 result
408 }
409 Self::None => mask_text.to_owned(),
410 }
411 }
412}
413
414#[inline]
415fn is_sign(ch: &char) -> bool {
416 matches!(ch, '+' | '-')
417}
418
419#[doc(hidden)]
428pub(super) fn normalize_number_input(text: &str) -> Cow<'_, str> {
429 #[inline]
430 fn normalize_char(ch: char) -> Option<char> {
431 match ch {
432 '\u{FF10}'..='\u{FF19}' => char::from_u32(ch as u32 - 0xFF10 + '0' as u32),
434 '\u{FF0B}' => Some('+'),
436 '\u{FF0D}' | '\u{2212}' => Some('-'),
438 '\u{FF0E}' | '\u{3002}' => Some('.'),
440 '\u{FF0C}' => Some(','),
442 _ => None,
443 }
444 }
445
446 if !text.chars().any(|ch| normalize_char(ch).is_some()) {
447 return Cow::Borrowed(text);
448 }
449
450 Cow::Owned(
451 text.chars()
452 .map(|ch| normalize_char(ch).unwrap_or(ch))
453 .collect(),
454 )
455}
456
457#[cfg(test)]
458mod tests {
459 use super::{MaskPattern, MaskToken};
460
461 #[test]
462 fn test_is_match() {
463 assert_eq!(MaskToken::Sep('(').is_match('('), true);
464 assert_eq!(MaskToken::Sep('-').is_match('('), false);
465 assert_eq!(MaskToken::Sep('-').is_match('3'), false);
466
467 assert_eq!(MaskToken::Digit.is_match('0'), true);
468 assert_eq!(MaskToken::Digit.is_match('9'), true);
469 assert_eq!(MaskToken::Digit.is_match('a'), false);
470 assert_eq!(MaskToken::Digit.is_match('C'), false);
471
472 assert_eq!(MaskToken::Letter.is_match('a'), true);
473 assert_eq!(MaskToken::Letter.is_match('Z'), true);
474 assert_eq!(MaskToken::Letter.is_match('3'), false);
475 assert_eq!(MaskToken::Letter.is_match('-'), false);
476
477 assert_eq!(MaskToken::LetterOrDigit.is_match('0'), true);
478 assert_eq!(MaskToken::LetterOrDigit.is_match('9'), true);
479 assert_eq!(MaskToken::LetterOrDigit.is_match('a'), true);
480 assert_eq!(MaskToken::LetterOrDigit.is_match('Z'), true);
481 assert_eq!(MaskToken::LetterOrDigit.is_match('3'), true);
482
483 assert_eq!(MaskToken::Any.is_match('a'), true);
484 assert_eq!(MaskToken::Any.is_match('3'), true);
485 assert_eq!(MaskToken::Any.is_match('-'), true);
486 assert_eq!(MaskToken::Any.is_match(' '), true);
487 }
488
489 #[test]
490 fn test_mask_none() {
491 let mask = MaskPattern::None;
492 assert_eq!(mask.is_none(), true);
493 assert_eq!(mask.is_valid("1124124ASLDJKljk"), true);
494 assert_eq!(mask.mask("hello-world"), "hello-world");
495 assert_eq!(mask.unmask("hello-world"), "hello-world");
496 }
497
498 #[test]
499 fn test_mask_pattern1() {
500 let mask = MaskPattern::new("(AA)999-999");
501 assert_eq!(
502 mask.tokens(),
503 Some(&vec![
504 MaskToken::Sep('('),
505 MaskToken::Letter,
506 MaskToken::Letter,
507 MaskToken::Sep(')'),
508 MaskToken::Digit,
509 MaskToken::Digit,
510 MaskToken::Digit,
511 MaskToken::Sep('-'),
512 MaskToken::Digit,
513 MaskToken::Digit,
514 MaskToken::Digit,
515 ])
516 );
517
518 assert_eq!(mask.is_valid_at('(', 0), true);
519 assert_eq!(mask.is_valid_at('H', 0), true);
520 assert_eq!(mask.is_valid_at('3', 0), false);
521 assert_eq!(mask.is_valid_at('-', 0), false);
522 assert_eq!(mask.is_valid_at(')', 1), false);
523 assert_eq!(mask.is_valid_at('H', 1), true);
524 assert_eq!(mask.is_valid_at('1', 1), false);
525 assert_eq!(mask.is_valid_at('e', 2), true);
526 assert_eq!(mask.is_valid_at(')', 3), true);
527 assert_eq!(mask.is_valid_at('1', 3), true);
528 assert_eq!(mask.is_valid_at('2', 4), true);
529
530 assert_eq!(mask.is_valid("(AB)123-456"), true);
531
532 assert_eq!(mask.mask("AB123456"), "(AB)123-456");
533 assert_eq!(mask.mask("(AB)123-456"), "(AB)123-456");
534 assert_eq!(mask.mask("(AB123456"), "(AB)123-456");
535 assert_eq!(mask.mask("AB123-456"), "(AB)123-456");
536 assert_eq!(mask.mask("AB123-"), "(AB)123-");
537 assert_eq!(mask.mask("AB123--"), "(AB)123-");
538 assert_eq!(mask.mask("AB123-4"), "(AB)123-4");
539
540 let unmasked_text = mask.unmask("(AB)123-456");
541 assert_eq!(unmasked_text, "AB123456");
542
543 assert_eq!(mask.is_valid("12AB345"), false);
544 assert_eq!(mask.is_valid("(11)123-456"), false);
545 assert_eq!(mask.is_valid("##"), false);
546 assert_eq!(mask.is_valid("(AB)123456"), true);
547 }
548
549 #[test]
550 fn test_mask_pattern2() {
551 let mask = MaskPattern::new("999-999-******");
552 assert_eq!(
553 mask.tokens(),
554 Some(&vec![
555 MaskToken::Digit,
556 MaskToken::Digit,
557 MaskToken::Digit,
558 MaskToken::Sep('-'),
559 MaskToken::Digit,
560 MaskToken::Digit,
561 MaskToken::Digit,
562 MaskToken::Sep('-'),
563 MaskToken::Any,
564 MaskToken::Any,
565 MaskToken::Any,
566 MaskToken::Any,
567 MaskToken::Any,
568 MaskToken::Any,
569 ])
570 );
571
572 let text = "123456A(111)";
573 let masked_text = mask.mask(text);
574 assert_eq!(masked_text, "123-456-A(111)");
575 let unmasked_text = mask.unmask(&masked_text);
576 assert_eq!(unmasked_text, "123456A(111)");
577 assert_eq!(mask.is_valid(&masked_text), true);
578 }
579
580 #[test]
581 fn test_number_with_group_separator() {
582 let mask = MaskPattern::number(Some(','));
584 assert_eq!(mask.mask("1234567"), "1,234,567");
585 assert_eq!(mask.mask("1,234,567"), "1,234,567");
586 assert_eq!(mask.unmask("1,234,567"), "1234567");
587 let mask = MaskPattern::number(Some(','));
588 assert_eq!(mask.mask("1234567.89"), "1,234,567.89");
589 assert_eq!(mask.unmask("1,234,567.89"), "1234567.89");
590
591 let mask = MaskPattern::number(Some(' '));
593 assert_eq!(mask.mask("1234567"), "1 234 567");
594 assert_eq!(mask.unmask("1 234 567"), "1234567");
595 let mask = MaskPattern::number(Some(' '));
596 assert_eq!(mask.mask("1234567.89"), "1 234 567.89");
597 assert_eq!(mask.unmask("1 234 567.89"), "1234567.89");
598
599 let mask = MaskPattern::number(None);
601 assert_eq!(mask.mask("1234567"), "1234567");
602 assert_eq!(mask.unmask("1234567"), "1234567");
603 let mask = MaskPattern::number(None);
604 assert_eq!(mask.mask("1234567.89"), "1234567.89");
605 assert_eq!(mask.unmask("1234567.89"), "1234567.89");
606 }
607
608 #[test]
609 fn test_number_with_fraction_digits() {
610 let mask = MaskPattern::Number {
611 separator: Some(','),
612 fraction: Some(4),
613 };
614
615 assert_eq!(mask.mask("1234567"), "1,234,567");
616 assert_eq!(mask.unmask("1,234,567"), "1234567");
617 assert_eq!(mask.mask("1234567."), "1,234,567.");
618 assert_eq!(mask.mask("1234567.89"), "1,234,567.89");
619 assert_eq!(mask.unmask("1,234,567.890"), "1234567.89");
620 assert_eq!(mask.mask("1234567.891"), "1,234,567.891");
621 assert_eq!(mask.mask("1234567.891234"), "1,234,567.8912");
622
623 let mask = MaskPattern::Number {
624 separator: Some(','),
625 fraction: None,
626 };
627
628 assert_eq!(mask.mask("1234567.1234567"), "1,234,567.1234567");
629
630 let mask = MaskPattern::Number {
631 separator: Some(','),
632 fraction: Some(0),
633 };
634
635 assert_eq!(mask.mask("1234567.1234567"), "1,234,567");
636 }
637
638 #[test]
639 fn test_signed_number_numbers() {
640 let mask = MaskPattern::Number {
641 separator: Some(','),
642 fraction: Some(2),
643 };
644
645 assert_eq!(mask.is_valid("-"), true);
646 assert_eq!(mask.is_valid("-1234567"), true);
647 assert_eq!(mask.is_valid("-1,234,567"), true);
648 assert_eq!(mask.is_valid("-1234567."), true);
649 assert_eq!(mask.is_valid("-1234567.89"), true);
650
651 assert_eq!(mask.is_valid("+"), true);
652 assert_eq!(mask.is_valid("+1234567"), true);
653 assert_eq!(mask.is_valid("+1,234,567"), true);
654 assert_eq!(mask.is_valid("+1234567."), true);
655 assert_eq!(mask.is_valid("+1234567.89"), true);
656
657 assert_eq!(mask.is_valid("+-"), false);
659 assert_eq!(mask.is_valid("-+"), false);
660 assert_eq!(mask.is_valid("+-1234567"), false);
661
662 assert_eq!(mask.is_valid("1,-234,567"), false);
664 assert_eq!(mask.is_valid("12-34567.89"), false);
665
666 assert_eq!(mask.is_valid("+1234567.-"), false);
668
669 assert_eq!(mask.mask("-123"), "-123");
671
672 assert_eq!(mask.mask("-1234567"), "-1,234,567");
673 assert_eq!(mask.mask("+1234567"), "+1,234,567");
674 assert_eq!(mask.unmask("-1,234,567"), "-1234567");
675 assert_eq!(mask.mask("-1234567."), "-1,234,567.");
676 assert_eq!(mask.mask("-1234567.89"), "-1,234,567.89");
677 }
678
679 #[test]
680 fn test_number_leading_dot() {
681 let mask = MaskPattern::number(None);
682 assert_eq!(mask.is_valid("."), true);
683 assert_eq!(mask.is_valid(".5"), true);
684 assert_eq!(mask.is_valid("-."), true);
685 assert_eq!(mask.is_valid("-.5"), true);
686 assert_eq!(mask.is_valid("1.2.3"), false);
687 assert_eq!(mask.is_valid("1.."), false);
688
689 assert_eq!(mask.mask("."), ".");
692 assert_eq!(mask.mask(".5"), ".5");
693 assert_eq!(mask.mask("-.5"), "-.5");
694 assert_eq!(mask.mask("+.5"), "+.5");
695
696 let mask = MaskPattern::number(Some(','));
697 assert_eq!(mask.mask(".5"), ".5");
698 assert_eq!(mask.mask("-.5"), "-.5");
699 }
700
701 #[test]
702 fn test_normalize_number_input() {
703 use std::borrow::Cow;
704
705 use super::normalize_number_input;
706
707 assert!(matches!(
709 normalize_number_input("-1,234.5"),
710 Cow::Borrowed(_)
711 ));
712
713 assert_eq!(normalize_number_input("0123456789"), "0123456789");
715 assert_eq!(normalize_number_input("+1.5"), "+1.5");
717 assert_eq!(normalize_number_input("-1,234"), "-1,234");
718 assert_eq!(normalize_number_input("−1.5"), "-1.5");
720 assert_eq!(normalize_number_input("12。5"), "12.5");
722 assert_eq!(normalize_number_input("ab 中 1"), "ab 中 1");
724 }
725}