Skip to content

Instantly share code, notes, and snippets.

@osa1
Last active May 10, 2016 09:20
Show Gist options
  • Select an option

  • Save osa1/25132d1b06ba0446d2f3ba01edee63ac to your computer and use it in GitHub Desktop.

Select an option

Save osa1/25132d1b06ba0446d2f3ba01edee63ac to your computer and use it in GitHub Desktop.
// 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