Last active
May 10, 2016 09:20
-
-
Save osa1/25132d1b06ba0446d2f3ba01edee63ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // When highlighting nicks in messages, we search words in the `nicks` set. To | |
| // be able to highlight substrings, we need offsets of chars, but | |
| // `SplitWhitespace` doesn't provide that. Also, our separators are actually a | |
| // set of characters, like {'<', '(', etc} instead of a fixed character or | |
| // whitespace. | |
| struct WordIdxs<'s> { | |
| /// The whole thing, not a shrinking slice. | |
| str : &'s str, | |
| /// Current position. | |
| idx : usize, | |
| } | |
| impl<'s> WordIdxs<'s> { | |
| fn new(str : &str) -> WordIdxs { | |
| WordIdxs { | |
| str: str, | |
| idx: 0, | |
| } | |
| } | |
| } | |
| impl<'s> Iterator for WordIdxs<'s> { | |
| type Item = (usize, usize); | |
| fn next(&mut self) -> Option<(usize, usize)> { | |
| if self.idx >= self.str.len() { | |
| return None; | |
| } | |
| let slice = unsafe { self.str.slice_unchecked(self.idx, self.str.len()) }; | |
| if let Some(left_ws_idx) = find_word_left(slice) { | |
| if let Some(right_ws_idx) = find_word_right(unsafe { | |
| self.str.slice_unchecked(self.idx + left_ws_idx, self.str.len()) | |
| }) { | |
| let idx = self.idx; | |
| self.idx += left_ws_idx + right_ws_idx; | |
| return Some((idx + left_ws_idx, idx + left_ws_idx + right_ws_idx)); | |
| } else { | |
| let idx = self.idx; | |
| self.idx += self.str.len(); | |
| return Some((idx + left_ws_idx, idx + left_ws_idx + self.str.len())); | |
| } | |
| } | |
| None | |
| } | |
| } | |
| /// A word may start with these characters, in addition to alphanumerics. | |
| static LEFT_SEPS : [char; 8] = [ '(', '{', '[', '|', '<', '\'', '`', '"' ]; | |
| /// A word may end with these characters, in addition to whitespace. | |
| static RIGHT_SEPS : [char; 11] = [ ')', '}', ']', '|', '>', ',', ';', '.', '\'', '`', '"' ]; | |
| #[inline] | |
| fn is_left_sep(char : char) -> bool { | |
| char.is_whitespace() || in_slice(char, &LEFT_SEPS) | |
| } | |
| #[inline] | |
| fn is_right_sep(char : char) -> bool { | |
| char.is_whitespace() || in_slice(char, &RIGHT_SEPS) | |
| } | |
| fn find_word_left(str : &str) -> Option<usize> { | |
| let mut iter = str.char_indices(); | |
| while let Some((char_idx, char)) = iter.next() { | |
| if is_left_sep(char) { | |
| // consume consecutive separators | |
| while let Some((char_idx, char)) = iter.next() { | |
| if !is_left_sep(char) { | |
| return Some(char_idx); | |
| } | |
| } | |
| } else if char.is_alphanumeric() { | |
| return Some(char_idx); | |
| } | |
| } | |
| None | |
| } | |
| fn find_word_right(str : &str) -> Option<usize> { | |
| if str.is_empty() { | |
| return None; | |
| } | |
| // find_word_left should have consumed this | |
| assert!(!str.chars().nth(0).unwrap().is_whitespace()); | |
| let mut iter = str.char_indices(); | |
| while let Some((char_idx, char)) = iter.next() { | |
| if is_right_sep(char) { | |
| return Some(char_idx); | |
| } | |
| } | |
| Some(str.len()) | |
| } | |
| #[inline] | |
| fn in_slice<E : Eq>(e : E, slice : &[E]) -> bool { | |
| for e_ in slice.iter() { | |
| if e == *e_ { | |
| return true; | |
| } | |
| } | |
| false | |
| } | |
| #[test] | |
| fn test_left_ws() { | |
| assert_eq!(find_word_left("x y"), Some(0)); | |
| assert_eq!(find_word_left(" x y"), Some(1)); | |
| assert_eq!(find_word_left(" y"), Some(4)); | |
| assert_eq!(find_word_left("xy"), Some(0)); | |
| assert_eq!(find_word_left(""), None); | |
| assert_eq!(find_word_left(" "), None); | |
| assert_eq!(find_word_left(" "), None); | |
| assert_eq!(find_word_left("<xyz>"), Some(1)); | |
| assert_eq!(find_word_left("< xyz>"), Some(3)); | |
| assert_eq!(find_word_left(">"), None); | |
| } | |
| #[test] | |
| fn test_right_ws() { | |
| assert_eq!(find_word_right(""), None); | |
| assert_eq!(find_word_right("x"), Some(1)); | |
| assert_eq!(find_word_right("x y"), Some(1)); | |
| assert_eq!(find_word_right("asdf"), Some(4)); | |
| assert_eq!(find_word_right("xyz>"), Some(3)); | |
| assert_eq!(find_word_right("xyz,"), Some(3)); | |
| } | |
| #[test] | |
| fn test_word_idxs() { | |
| assert_eq!(WordIdxs::new("x").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 1)]); | |
| assert_eq!(WordIdxs::new("x y").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 1), (2, 3)]); | |
| assert_eq!(WordIdxs::new("x y z").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 1), (2, 3), (4, 5)]); | |
| assert_eq!(WordIdxs::new("xyz").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 3)]); | |
| assert_eq!(WordIdxs::new("xyz foo bar baz").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 3), (4, 7), (8, 11), (12, 15)]); | |
| assert_eq!(WordIdxs::new("<xyz>").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(1, 4)]); | |
| assert_eq!(WordIdxs::new("<xyz> (foo) [bar] {baz}").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(1, 4), (7, 10), (13, 16), (19, 22)]); | |
| assert_eq!(WordIdxs::new("foo, bar; baz: yada").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 3), (5, 8), (10, 14), (15, 19)]); | |
| assert_eq!(WordIdxs::new("tiny_test, hey").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 9), (11, 14)]); | |
| assert_eq!(WordIdxs::new("foo's bar`s \"baz\"").into_iter().collect::<Vec<(usize, usize)>>(), | |
| vec![(0, 3), (4, 5), (6, 9), (10, 11), (13, 16)]); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment