pub enum Key {
Char(char),
Backspace,
Enter,
Up,
Down,
Left,
Right,
}
pub struct Text {
lines: Vec<String>,
cursor: (usize, usize),
multi_line: bool,
preferred_column: usize,
}
impl Text {
pub fn new(multi_line: bool) -> Self {
Self {
lines: vec![String::new()],
cursor: (0, 0),
multi_line,
preferred_column: 0,
}
}
pub fn from(value: &str, cursor: (usize, usize), multi_line: bool) -> Self {
let mut lines = if multi_line {
value.lines().map(|line| line.to_string()).collect()
} else {
vec![value.replace("\n", "").replace("\r", "")]
};
if lines.is_empty() || value.ends_with("\n") || value.ends_with("\r\n") {
lines.push(String::new());
}
let mut text = Self {
lines,
cursor: (0, 0),
multi_line,
preferred_column: 0,
};
text.set_cursor(cursor);
text
}
pub fn cursor(&self) -> (usize, usize) {
self.cursor
}
pub fn value(&self) -> String {
self.lines.join("\n")
}
pub fn lines(&self) -> &Vec<String> {
&self.lines
}
pub fn set_cursor(&mut self, position: (usize, usize)) {
self.cursor = position;
if self.cursor.1 >= self.lines.len() {
self.cursor.1 = self.lines.len() - 1;
}
let line_length = self.get_line_length(self.cursor.1);
if self.cursor.0 > line_length {
self.cursor.0 = line_length;
}
self.preferred_column = self.cursor.0;
}
pub fn handle_input(&mut self, input: Key) {
match input {
Key::Char(ch) => self.insert_character(ch),
Key::Backspace => self.backspace_character(),
Key::Enter => self.insert_newline(),
Key::Up => self.move_up(),
Key::Down => self.move_down(),
Key::Left => self.move_left(),
Key::Right => self.move_right(),
}
}
fn insert_character(&mut self, ch: char) {
self.lines[self.cursor.1].insert(self.cursor.0, ch);
self.cursor.0 += 1;
self.preferred_column = self.cursor.0;
}
fn backspace_character(&mut self) {
let at_start_of_line = self.cursor.0 == 0;
if at_start_of_line {
let on_first_line = self.cursor.1 == 0;
if !on_first_line {
let line = self.lines.remove(self.cursor.1);
let prior_line_index = self.cursor.1 - 1;
self.cursor = (self.get_line_length(prior_line_index), prior_line_index);
self.lines[self.cursor.1].push_str(&line);
}
} else {
self.cursor.0 -= 1;
self.lines[self.cursor.1].remove(self.cursor.0);
}
self.preferred_column = self.cursor.0;
}
fn insert_newline(&mut self) {
if !self.multi_line {
return;
}
let (prefix, suffix) = self.lines[self.cursor.1].split_at(self.cursor.0).to_owned();
let (prefix, suffix) = (prefix.to_string(), suffix.to_string());
self.lines[self.cursor.1] = prefix;
let new_line_index = self.cursor.1 + 1;
self.lines.insert(new_line_index, suffix);
self.cursor = (0, new_line_index);
if self.lines[self.cursor.1 - 1].starts_with(" - ") {
self.lines[new_line_index].insert_str(0, " - ");
self.cursor.0 += 3;
}
self.preferred_column = self.cursor.0;
}
fn move_up(&mut self) {
if !self.multi_line {
return;
}
let on_first_line = self.cursor.1 == 0;
if !on_first_line {
let previous_line = self.cursor.1 - 1;
let desired_column = std::cmp::max(self.cursor.0, self.preferred_column);
let new_column = std::cmp::min(desired_column, self.get_line_length(previous_line));
self.cursor = (new_column, previous_line);
}
}
fn move_down(&mut self) {
if !self.multi_line {
return;
}
let next_line = self.cursor.1 + 1;
let is_last_line = next_line == self.lines.len();
if !is_last_line {
let desired_column = std::cmp::max(self.cursor.0, self.preferred_column);
let new_column = std::cmp::min(desired_column, self.get_line_length(next_line));
self.cursor = (new_column, self.cursor.1 + 1);
}
}
fn move_left(&mut self) {
let at_start_of_line = self.cursor.0 == 0;
let on_first_line = self.cursor.1 == 0;
if !at_start_of_line {
self.cursor.0 -= 1;
} else if !on_first_line {
let previous_line = self.cursor.1 - 1;
self.cursor = (self.get_line_length(previous_line), previous_line);
}
self.preferred_column = self.cursor.0;
}
fn move_right(&mut self) {
let at_end_of_line = self.cursor.0 == self.get_line_length(self.cursor.1);
let on_last_line = self.cursor.1 + 1 == self.lines.len();
if !at_end_of_line {
self.cursor.0 += 1;
} else if !on_last_line {
self.cursor = (0, self.cursor.1 + 1);
}
self.preferred_column = self.cursor.0;
}
fn get_line_length(&self, line_index: usize) -> usize {
self.lines[line_index].len()
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! svec {
($($x:expr),*) => (vec![$($x.to_string()),*]);
}
macro_rules! assert_text {
($text: ident, $cursor: expr, $value: expr, $lines: expr) => {
assert_eq!($cursor, $text.cursor());
assert_eq!($value, $text.value());
assert_eq!(&$lines, $text.lines());
};
}
#[test]
fn new() {
let text = Text::new(false);
assert_text!(text, (0, 0), "", svec![""]);
}
#[test]
fn from() {
let text = Text::from("a\nbc", (1, 1), true);
assert_text!(text, (1, 1), "a\nbc", svec!["a", "bc"]);
}
#[test]
fn from_clamp_cursor() {
let text = Text::from("a\nbc", (5, 5), true);
assert_text!(text, (2, 1), "a\nbc", svec!["a", "bc"]);
}
#[test]
fn from_collapse_single_line() {
let text = Text::from("a\n\nbc", (1, 0), false);
assert_text!(text, (1, 0), "abc", svec!["abc"]);
}
#[test]
fn from_clamp_cursor_single_line() {
let text = Text::from("a\n\nbc", (3, 0), false);
assert_text!(text, (3, 0), "abc", svec!["abc"]);
}
#[test]
fn blank_lines() {
let text = Text::from("abc\n\r\n", (0, 1), true);
assert_text!(text, (0, 1), "abc\n\n", svec!["abc", "", ""]);
}
#[test]
fn set_cursor() {
let mut text = Text::from("a\nbc", (0, 0), true);
assert_eq!((0, 0), text.cursor());
text.set_cursor((1, 1));
assert_eq!((1, 1), text.cursor());
}
#[test]
fn set_cursor_clamping() {
let mut text = Text::from("a\nbc", (0, 0), true);
assert_eq!((0, 0), text.cursor());
text.set_cursor((5, 5));
assert_eq!((2, 1), text.cursor());
}
#[test]
fn handle_input() {
let mut text = Text::from("abc\ndef", (2, 1), true);
assert_text!(text, (2, 1), "abc\ndef", svec!["abc", "def"]);
text.handle_input(Key::Char('X'));
assert_text!(text, (3, 1), "abc\ndeXf", svec!["abc", "deXf"]);
text.handle_input(Key::Left);
assert_text!(text, (2, 1), "abc\ndeXf", svec!["abc", "deXf"]);
text.handle_input(Key::Backspace);
assert_text!(text, (1, 1), "abc\ndXf", svec!["abc", "dXf"]);
text.handle_input(Key::Right);
text.handle_input(Key::Right);
assert_text!(text, (3, 1), "abc\ndXf", svec!["abc", "dXf"]);
text.handle_input(Key::Char('g'));
text.handle_input(Key::Char('h'));
text.handle_input(Key::Char('i'));
assert_text!(text, (6, 1), "abc\ndXfghi", svec!["abc", "dXfghi"]);
text.handle_input(Key::Up);
assert_text!(text, (3, 0), "abc\ndXfghi", svec!["abc", "dXfghi"]);
text.handle_input(Key::Left);
text.handle_input(Key::Left);
assert_text!(text, (1, 0), "abc\ndXfghi", svec!["abc", "dXfghi"]);
text.handle_input(Key::Down);
assert_text!(text, (1, 1), "abc\ndXfghi", svec!["abc", "dXfghi"]);
text.handle_input(Key::Backspace);
text.handle_input(Key::Backspace);
assert_text!(text, (3, 0), "abcXfghi", svec!["abcXfghi"]);
text.handle_input(Key::Right);
text.handle_input(Key::Right);
assert_text!(text, (5, 0), "abcXfghi", svec!["abcXfghi"]);
text.handle_input(Key::Enter);
assert_text!(text, (0, 1), "abcXf\nghi", svec!["abcXf", "ghi"]);
text.handle_input(Key::Left);
assert_text!(text, (5, 0), "abcXf\nghi", svec!["abcXf", "ghi"]);
text.handle_input(Key::Down);
assert_text!(text, (3, 1), "abcXf\nghi", svec!["abcXf", "ghi"]);
}
#[test]
fn handle_input_single_line() {
let mut text = Text::from("abcdef", (3, 0), false);
assert_text!(text, (3, 0), "abcdef", svec!["abcdef"]);
text.handle_input(Key::Char('X'));
assert_text!(text, (4, 0), "abcXdef", svec!["abcXdef"]);
text.handle_input(Key::Enter);
assert_text!(text, (4, 0), "abcXdef", svec!["abcXdef"]);
text.handle_input(Key::Up);
assert_text!(text, (4, 0), "abcXdef", svec!["abcXdef"]);
text.handle_input(Key::Down);
assert_text!(text, (4, 0), "abcXdef", svec!["abcXdef"]);
text.handle_input(Key::Backspace);
assert_text!(text, (3, 0), "abcdef", svec!["abcdef"]);
text.set_cursor((0, 0));
text.move_left();
assert_text!(text, (0, 0), "abcdef", svec!["abcdef"]);
text.set_cursor((6, 0));
text.move_right();
assert_text!(text, (6, 0), "abcdef", svec!["abcdef"]);
}
#[test]
fn insert_character_end_line() {
let mut text = Text::new(true);
text.insert_character('a');
text.insert_character('b');
text.insert_character('c');
assert_text!(text, (3, 0), "abc", svec!["abc"]);
}
#[test]
fn insert_character_mid_line() {
let mut text = Text::from("abc", (1, 0), true);
text.insert_character('X');
assert_text!(text, (2, 0), "aXbc", svec!["aXbc"]);
}
#[test]
fn insert_character_start_line() {
let mut text = Text::from("abc", (0, 0), true);
text.insert_character('X');
assert_text!(text, (1, 0), "Xabc", svec!["Xabc"]);
}
#[test]
fn backspace_character_all() {
let mut text = Text::from("abc", (3, 0), true);
text.backspace_character();
text.backspace_character();
text.backspace_character();
assert_text!(text, (0, 0), "", svec![""]);
}
#[test]
fn backspace_character_mid_line() {
let mut text = Text::from("abc", (2, 0), true);
text.backspace_character();
text.backspace_character();
text.backspace_character();
assert_text!(text, (0, 0), "c", svec!["c"]);
}
#[test]
fn backspace_character_start_line() {
let mut text = Text::from("abc", (0, 0), true);
text.backspace_character();
text.backspace_character();
text.backspace_character();
assert_text!(text, (0, 0), "abc", svec!["abc"]);
}
#[test]
fn backspace_character_multi_line() {
let mut text = Text::from("abc\ndef", (0, 1), true);
text.backspace_character();
assert_text!(text, (3, 0), "abcdef", svec!["abcdef"]);
}
#[test]
fn insert_newline_end_line() {
let mut text = Text::from("abc", (3, 0), true);
text.insert_newline();
assert_text!(text, (0, 1), "abc\n", svec!["abc", ""]);
}
#[test]
fn insert_newline_start_line() {
let mut text = Text::from("abc", (0, 0), true);
text.insert_newline();
assert_text!(text, (0, 1), "\nabc", svec!["", "abc"]);
}
#[test]
fn insert_newline_mid_line() {
let mut text = Text::from("abc", (1, 0), true);
text.insert_newline();
assert_text!(text, (0, 1), "a\nbc", svec!["a", "bc"]);
}
#[test]
fn insert_newline_empty() {
let mut text = Text::from("", (0, 0), true);
text.insert_newline();
assert_text!(text, (0, 1), "\n", svec!["", ""]);
}
#[test]
fn insert_newline_single_line() {
let mut text = Text::from("abcdef", (3, 0), false);
text.insert_newline();
assert_text!(text, (3, 0), "abcdef", svec!["abcdef"]);
}
#[test]
fn move_up_start_line() {
let mut text = Text::from("abc\ndef", (0, 1), true);
text.move_up();
assert_text!(text, (0, 0), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_up_mid_line() {
let mut text = Text::from("abc\ndef", (2, 1), true);
text.move_up();
assert_text!(text, (2, 0), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_up_end_line() {
let mut text = Text::from("abc\ndef", (3, 1), true);
text.move_up();
assert_text!(text, (3, 0), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_up_shorter_line() {
let mut text = Text::from("a\ndef", (2, 1), true);
text.move_up();
assert_text!(text, (1, 0), "a\ndef", svec!["a", "def"]);
}
#[test]
fn move_up_single_line() {
let mut text = Text::from("abcdef", (3, 0), true);
text.move_up();
assert_text!(text, (3, 0), "abcdef", svec!["abcdef"]);
}
#[test]
fn move_down_start_line() {
let mut text = Text::from("abc\ndef", (0, 0), true);
text.move_down();
assert_text!(text, (0, 1), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_down_mid_line() {
let mut text = Text::from("abc\ndef", (2, 0), true);
text.move_down();
assert_text!(text, (2, 1), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_down_end_line() {
let mut text = Text::from("abc\ndef", (3, 0), true);
text.move_down();
assert_text!(text, (3, 1), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_down_shorter_line() {
let mut text = Text::from("a\ndef", (2, 0), true);
text.move_down();
assert_text!(text, (1, 1), "a\ndef", svec!["a", "def"]);
}
#[test]
fn move_down_single_line() {
let mut text = Text::from("abcdef", (3, 0), false);
text.move_down();
assert_text!(text, (3, 0), "abcdef", svec!["abcdef"]);
}
#[test]
fn move_left_mid_line() {
let mut text = Text::from("abc", (2, 0), true);
text.move_left();
assert_text!(text, (1, 0), "abc", svec!["abc"]);
}
#[test]
fn move_left_end_line() {
let mut text = Text::from("abc", (3, 0), true);
text.move_left();
assert_text!(text, (2, 0), "abc", svec!["abc"]);
}
#[test]
fn move_left_start_value() {
let mut text = Text::from("abc", (0, 0), true);
text.move_left();
assert_text!(text, (0, 0), "abc", svec!["abc"]);
}
#[test]
fn move_left_wrap_up() {
let mut text = Text::from("abc\ndef", (0, 1), true);
text.move_left();
assert_text!(text, (3, 0), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_left_wrap_up_empty_line() {
let mut text = Text::from("abc\n\ndef", (0, 2), true);
text.move_left();
assert_text!(text, (0, 1), "abc\n\ndef", svec!["abc", "", "def"]);
}
#[test]
fn move_left_single_line() {
let mut text = Text::from("abcdef", (0, 0), false);
text.move_left();
assert_text!(text, (0, 0), "abcdef", svec!["abcdef"]);
}
#[test]
fn move_right_mid_line() {
let mut text = Text::from("abc", (1, 0), true);
text.move_right();
assert_text!(text, (2, 0), "abc", svec!["abc"]);
}
#[test]
fn move_right_start_line() {
let mut text = Text::from("abc", (0, 0), true);
text.move_right();
assert_text!(text, (1, 0), "abc", svec!["abc"]);
}
#[test]
fn move_right_end_value() {
let mut text = Text::from("abc", (3, 0), true);
text.move_right();
assert_text!(text, (3, 0), "abc", svec!["abc"]);
}
#[test]
fn move_right_wrap_down() {
let mut text = Text::from("abc\ndef", (3, 0), true);
text.move_right();
assert_text!(text, (0, 1), "abc\ndef", svec!["abc", "def"]);
}
#[test]
fn move_right_wrap_down_empty_line() {
let mut text = Text::from("abc\n\ndef", (3, 0), true);
text.move_right();
assert_text!(text, (0, 1), "abc\n\ndef", svec!["abc", "", "def"]);
}
#[test]
fn move_right_single_line() {
let mut text = Text::from("abcdef", (6, 0), false);
text.move_right();
assert_text!(text, (6, 0), "abcdef", svec!["abcdef"]);
}
}