#[derive(PartialEq, Copy, Clone)]
enum CharType {
Whitespace,
Alphanumeric,
Punctuation,
}
fn get_char_type(c: char) -> CharType {
if c.is_whitespace() {
CharType::Whitespace
} else if c.is_alphanumeric() {
CharType::Alphanumeric
} else {
CharType::Punctuation
}
}
fn last_non_whitespace_index(text: &str) -> Option<usize> {
let len = text.chars().count();
for (rev_idx, ch) in text.chars().rev().enumerate() {
if !ch.is_whitespace() {
return Some(len - 1 - rev_idx);
}
}
None
}
pub fn find_next_word_start(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 {
return 0;
}
let current_pos = current_pos.min(len);
if current_pos == len {
return current_pos;
}
let mut pos = current_pos;
let initial_type = match text.chars().nth(pos) {
Some(ch) => get_char_type(ch),
None => return pos,
};
let mut skipping_whitespace = false;
for ch in text.chars().skip(current_pos) {
let ch_type = get_char_type(ch);
if !skipping_whitespace {
if ch_type == initial_type {
pos += 1;
continue;
}
if ch_type == CharType::Whitespace {
skipping_whitespace = true;
pos += 1;
continue;
}
break;
} else if ch_type == CharType::Whitespace {
pos += 1;
} else {
break;
}
}
pos
}
pub fn find_word_end(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 {
return 0;
}
let mut pos = current_pos.min(len - 1);
let current_type = match text.chars().nth(pos) {
Some(ch) => get_char_type(ch),
None => return len.saturating_sub(1),
};
if current_type != CharType::Whitespace {
for ch in text.chars().skip(pos) {
if get_char_type(ch) == current_type {
pos += 1;
} else {
break;
}
}
return pos.saturating_sub(1);
}
pos = find_next_word_start(text, pos);
if pos >= len {
return len.saturating_sub(1);
}
let word_type = match text.chars().nth(pos) {
Some(ch) => get_char_type(ch),
None => return len.saturating_sub(1),
};
for ch in text.chars().skip(pos) {
if get_char_type(ch) == word_type {
pos += 1;
} else {
break;
}
}
pos.saturating_sub(1).min(len.saturating_sub(1))
}
pub fn find_prev_word_start(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 || current_pos == 0 {
return 0;
}
let start = current_pos.min(len).saturating_sub(1);
let mut pos = 0usize;
let mut ch_at_pos = '\0';
let mut found = false;
for (rev_idx, ch) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx > start {
continue;
}
if idx > 0 && ch.is_whitespace() {
continue;
}
pos = idx;
ch_at_pos = ch;
found = true;
break;
}
if !found {
return 0;
}
if !ch_at_pos.is_whitespace() {
let word_type = get_char_type(ch_at_pos);
for (rev_idx, prev_ch) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx >= pos {
continue;
}
if get_char_type(prev_ch) == word_type {
pos = idx;
} else {
break;
}
}
}
pos
}
pub fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 || current_pos == 0 {
return 0;
}
let mut in_word = false;
let mut current_word_type: Option<CharType> = None;
let mut last_end_before_current = 0usize;
let mut found = false;
for (i, ch) in text.chars().enumerate() {
let char_type = get_char_type(ch);
match char_type {
CharType::Whitespace => {
if in_word {
let end = i - 1;
if end < current_pos {
last_end_before_current = end;
found = true;
}
in_word = false;
current_word_type = None;
}
}
_ => {
if !in_word || current_word_type != Some(char_type) {
if in_word {
let end = i - 1;
if end < current_pos {
last_end_before_current = end;
found = true;
}
}
in_word = true;
current_word_type = Some(char_type);
}
}
}
}
if in_word {
let end = len - 1;
if end < current_pos {
last_end_before_current = end;
found = true;
}
}
if found { last_end_before_current } else { 0 }
}
pub fn find_next_big_word_start(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 || current_pos >= len {
return len;
}
let mut pos = current_pos;
let mut skipping_whitespace = false;
for ch in text.chars().skip(current_pos) {
if !skipping_whitespace {
if !ch.is_whitespace() {
pos += 1;
} else {
skipping_whitespace = true;
pos += 1;
}
} else if ch.is_whitespace() {
pos += 1;
} else {
break;
}
}
pos
}
pub fn find_prev_big_word_start(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 || current_pos == 0 {
return 0;
}
let start = current_pos.min(len).saturating_sub(1);
let mut pos = 0usize;
let mut found = false;
for (rev_idx, ch) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx > start {
continue;
}
if idx > 0 && ch.is_whitespace() {
continue;
}
pos = idx;
found = true;
break;
}
if !found {
return 0;
}
for (rev_idx, prev_ch) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx >= pos {
continue;
}
if !prev_ch.is_whitespace() {
pos = idx;
} else {
break;
}
}
pos
}
pub fn find_big_word_end(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 {
return 0;
}
let mut pos = current_pos;
let mut in_word = false;
for ch in text.chars().skip(current_pos) {
if !in_word {
if ch.is_whitespace() {
pos += 1;
} else {
in_word = true;
pos += 1;
}
} else if ch.is_whitespace() {
break;
} else {
pos += 1;
}
}
if !in_word {
return len;
}
pos.saturating_sub(1)
}
pub fn find_prev_big_word_end(text: &str, current_pos: usize) -> usize {
let len = text.chars().count();
if len == 0 || current_pos == 0 {
return 0;
}
let start = current_pos.min(len).saturating_sub(1);
let mut word_start_idx = 0usize;
let mut ch_at_pos = '\0';
let mut found = false;
for (rev_idx, ch) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx > start {
continue;
}
if idx > 0 && ch.is_whitespace() {
continue;
}
word_start_idx = idx;
ch_at_pos = ch;
found = true;
break;
}
if !found {
return 0;
}
if word_start_idx == 0 && ch_at_pos.is_whitespace() {
return 0;
}
for (rev_idx, prev_ch) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx >= word_start_idx {
continue;
}
if !prev_ch.is_whitespace() {
word_start_idx = idx;
} else {
break;
}
}
let mut pos = word_start_idx;
for ch in text.chars().skip(word_start_idx) {
if !ch.is_whitespace() {
pos += 1;
} else {
break;
}
}
pos.saturating_sub(1)
}
pub fn find_last_word_start_in_field(text: &str) -> usize {
let len = text.chars().count();
let mut pos = 0usize;
let mut ch_at_pos = '\0';
let mut found = false;
for (rev_idx, ch) in text.chars().rev().enumerate() {
if ch.is_whitespace() {
continue;
}
pos = len - 1 - rev_idx;
ch_at_pos = ch;
found = true;
break;
}
if !found {
return 0;
}
let char_type = get_char_type(ch_at_pos);
for (rev_idx, prev_char) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx >= pos {
continue;
}
if get_char_type(prev_char) == char_type {
pos = idx;
} else {
break;
}
}
pos
}
pub fn find_last_word_end_in_field(text: &str) -> usize {
last_non_whitespace_index(text).unwrap_or(0)
}
pub fn find_last_big_word_start_in_field(text: &str) -> usize {
let len = text.chars().count();
let mut pos = 0usize;
let mut found = false;
for (rev_idx, ch) in text.chars().rev().enumerate() {
if ch.is_whitespace() {
continue;
}
pos = len - 1 - rev_idx;
found = true;
break;
}
if !found {
return 0;
}
for (rev_idx, prev_char) in text.chars().rev().enumerate() {
let idx = len - 1 - rev_idx;
if idx >= pos {
continue;
}
if prev_char.is_whitespace() {
break;
}
pos = idx;
}
pos
}
pub fn find_last_big_word_end_in_field(text: &str) -> usize {
last_non_whitespace_index(text).unwrap_or(0)
}