pub fn move_left(buffer: &str, cursor: usize) -> usize {
buffer[..cursor]
.char_indices()
.next_back()
.map(|(i, _)| i)
.unwrap_or(0)
}
pub fn move_right(buffer: &str, cursor: usize) -> usize {
buffer[cursor..]
.char_indices()
.nth(1)
.map(|(i, _)| cursor + i)
.unwrap_or(buffer.len())
}
pub fn move_word_left(buffer: &str, cursor: usize) -> usize {
let before = &buffer[..cursor];
let trimmed = before.trim_end();
if trimmed.is_empty() {
return 0;
}
for (i, ch) in trimmed.char_indices().rev() {
if ch.is_whitespace() {
return i + ch.len_utf8();
}
}
0
}
pub fn move_word_right(buffer: &str, cursor: usize) -> usize {
let after = &buffer[cursor..];
let mut chars = after.char_indices().peekable();
while let Some(&(_, ch)) = chars.peek() {
if ch.is_whitespace() {
break;
}
chars.next();
}
for (i, ch) in chars {
if !ch.is_whitespace() {
return cursor + i;
}
}
buffer.len()
}
pub fn backspace(buffer: &str, cursor: usize) -> Option<(String, usize)> {
if cursor == 0 {
return None;
}
let new_cursor = move_left(buffer, cursor);
let mut new_buffer = String::with_capacity(buffer.len());
new_buffer.push_str(&buffer[..new_cursor]);
new_buffer.push_str(&buffer[cursor..]);
Some((new_buffer, new_cursor))
}
pub fn delete_at(buffer: &str, cursor: usize) -> Option<String> {
if cursor >= buffer.len() {
return None;
}
let next = move_right(buffer, cursor);
let mut new_buffer = String::with_capacity(buffer.len());
new_buffer.push_str(&buffer[..cursor]);
new_buffer.push_str(&buffer[next..]);
Some(new_buffer)
}
pub fn delete_word_back(buffer: &str, cursor: usize) -> Option<(String, usize)> {
if cursor == 0 {
return None;
}
let word_start = move_word_left(buffer, cursor);
let mut new_buffer = String::with_capacity(buffer.len());
new_buffer.push_str(&buffer[..word_start]);
new_buffer.push_str(&buffer[cursor..]);
Some((new_buffer, word_start))
}
pub fn delete_word_forward(buffer: &str, cursor: usize) -> Option<String> {
if cursor >= buffer.len() {
return None;
}
let word_end = move_word_right(buffer, cursor);
let mut new_buffer = String::with_capacity(buffer.len());
new_buffer.push_str(&buffer[..cursor]);
new_buffer.push_str(&buffer[word_end..]);
Some(new_buffer)
}
pub fn insert_char(buffer: &str, cursor: usize, ch: char) -> (String, usize) {
let mut new_buffer = String::with_capacity(buffer.len() + ch.len_utf8());
new_buffer.push_str(&buffer[..cursor]);
new_buffer.push(ch);
new_buffer.push_str(&buffer[cursor..]);
let new_cursor = cursor + ch.len_utf8();
(new_buffer, new_cursor)
}
pub fn insert_str(buffer: &str, cursor: usize, text: &str) -> (String, usize) {
let clean: String = text.chars().filter(|c| *c != '\n' && *c != '\r').collect();
let mut new_buffer = String::with_capacity(buffer.len() + clean.len());
new_buffer.push_str(&buffer[..cursor]);
new_buffer.push_str(&clean);
new_buffer.push_str(&buffer[cursor..]);
let new_cursor = cursor + clean.len();
(new_buffer, new_cursor)
}
#[cfg(test)]
fn char_display_width(ch: char) -> usize {
unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_move_left() {
assert_eq!(move_left("hello", 3), 2);
assert_eq!(move_left("hello", 0), 0);
}
#[test]
fn test_move_left_multibyte() {
assert_eq!(move_left("a世b", 4), 1); }
#[test]
fn test_move_right() {
assert_eq!(move_right("hello", 2), 3);
assert_eq!(move_right("hello", 5), 5);
}
#[test]
fn test_move_right_multibyte() {
assert_eq!(move_right("a世b", 1), 4); }
#[test]
fn test_move_word_left() {
assert_eq!(move_word_left("hello world", 11), 6);
assert_eq!(move_word_left("hello world", 6), 0);
assert_eq!(move_word_left("hello world", 0), 0);
}
#[test]
fn test_move_word_left_multiple_spaces() {
assert_eq!(move_word_left("hello world", 13), 8);
}
#[test]
fn test_move_word_right() {
assert_eq!(move_word_right("hello world", 0), 6);
assert_eq!(move_word_right("hello world", 6), 11);
}
#[test]
fn test_move_word_right_at_end() {
assert_eq!(move_word_right("hello", 5), 5);
}
#[test]
fn test_backspace() {
let (buf, cur) = backspace("hello", 3).unwrap();
assert_eq!(buf, "helo");
assert_eq!(cur, 2);
}
#[test]
fn test_backspace_at_start() {
assert!(backspace("hello", 0).is_none());
}
#[test]
fn test_delete_at() {
let buf = delete_at("hello", 2).unwrap();
assert_eq!(buf, "helo");
}
#[test]
fn test_delete_at_end() {
assert!(delete_at("hello", 5).is_none());
}
#[test]
fn test_delete_word_back() {
let (buf, cur) = delete_word_back("hello world", 11).unwrap();
assert_eq!(buf, "hello ");
assert_eq!(cur, 6);
}
#[test]
fn test_delete_word_forward() {
let buf = delete_word_forward("hello world", 5).unwrap();
assert_eq!(buf, "helloworld");
}
#[test]
fn test_insert_char() {
let (buf, cur) = insert_char("hllo", 1, 'e');
assert_eq!(buf, "hello");
assert_eq!(cur, 2);
}
#[test]
fn test_insert_str_strips_newlines() {
let (buf, cur) = insert_str("ab", 1, "x\ny\nz");
assert_eq!(buf, "axyzb");
assert_eq!(cur, 4);
}
#[test]
fn test_char_display_width() {
assert_eq!(char_display_width('a'), 1);
assert_eq!(char_display_width('世'), 2);
}
}