#![doc = include_str!("../README.md")]
use commands::Command;
pub mod commands;
pub mod events;
#[derive(Debug, Clone, Default)]
pub struct StringEditor {
text: String,
cursor: usize,
}
impl StringEditor {
pub fn new() -> Self {
Self {
text: String::new(),
cursor: 0,
}
}
pub fn with_string(text: &str) -> Self {
Self {
text: text.to_string(),
cursor: text.len(),
}
}
pub fn get_text(&self) -> &str {
&self.text
}
pub fn cursor_pos(&self) -> usize {
self.cursor
}
}
impl StringEditor {
pub fn execute(&mut self, command: Command) {
match command {
Command::Insert(c) => {
self.text.insert(self.cursor, c);
self.cursor += 1;
}
Command::Type(s) => {
self.text.insert_str(self.cursor, &s);
self.cursor += s.len();
}
Command::CursorLeft(amt) => self.cursor = self.cursor.saturating_sub(amt),
Command::CursorRight(amt) => {
self.cursor += amt;
if self.cursor > self.text.len() {
self.cursor = self.text.len();
}
}
Command::CursorToStartOfLine => self.cursor = 0,
Command::CursorToEndOfLine => self.cursor = self.text.len(),
Command::Delete => {
if self.cursor < self.text.len() {
self.text.remove(self.cursor);
}
}
Command::Backspace => {
if self.cursor > 0 {
self.text.remove(self.cursor - 1);
self.cursor -= 1;
}
}
Command::DeleteStartOfLineToCursor => {
self.text.replace_range(0..self.cursor, "");
self.cursor = 0;
}
Command::DeleteToEndOfLine => {
self.text.replace_range(self.cursor..self.text.len(), "");
}
Command::DeleteWordLeadingToCursor => {
if self.cursor > 0 {
let mut pos = self.cursor - 1;
while pos > 0
&& !self.text[pos - 1..pos]
.chars()
.next()
.unwrap()
.is_whitespace()
&& !matches!(
self.text[pos - 1..pos].chars().next().unwrap(),
'-' | '_'
| '+'
| '='
| ','
| '.'
| '/'
| '\\'
| ':'
| ';'
| '!'
| '?'
| '@'
| '#'
| '$'
| '%'
| '^'
| '&'
| '*'
| '('
| ')'
| '['
| ']'
| '{'
| '}'
)
{
pos -= 1;
}
while pos < self.cursor {
self.text.remove(pos);
self.cursor -= 1;
}
}
}
_ => todo!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_commands() {
let mut editor = StringEditor::new();
editor.execute(Command::Insert('a'));
editor.execute(Command::Insert('b'));
assert_eq!(editor.get_text(), "ab");
editor.execute(Command::CursorLeft(1));
editor.execute(Command::Insert('c'));
assert_eq!(editor.get_text(), "acb");
editor.execute(Command::CursorRight(1));
assert_eq!(editor.cursor_pos(), 3);
editor.execute(Command::CursorRight(1));
assert_eq!(editor.cursor_pos(), 3);
editor.execute(Command::Insert('d'));
assert_eq!(editor.get_text(), "acbd");
editor.execute(Command::CursorToStartOfLine);
editor.execute(Command::Insert('e'));
assert_eq!(editor.get_text(), "eacbd");
editor.execute(Command::CursorToEndOfLine);
editor.execute(Command::Insert('f'));
assert_eq!(editor.get_text(), "eacbdf");
editor.execute(Command::Delete);
assert_eq!(editor.get_text(), "eacbdf");
editor.execute(Command::Backspace);
assert_eq!(editor.get_text(), "eacbd");
editor.execute(Command::CursorLeft(1));
editor.execute(Command::Backspace);
assert_eq!(editor.get_text(), "eacd");
editor.execute(Command::Delete);
assert_eq!(editor.get_text(), "eac");
}
#[test]
fn test_larger_edits() {
let mut editor = StringEditor::with_string("Hello, world!");
editor.execute(Command::CursorLeft(6));
editor.execute(Command::DeleteStartOfLineToCursor);
assert_eq!(editor.get_text(), "world!");
editor.execute(Command::Type("Hello, ".to_string()));
assert_eq!(editor.get_text(), "Hello, world!");
editor.execute(Command::DeleteToEndOfLine);
assert_eq!(editor.get_text(), "Hello, ");
editor.execute(Command::Insert('w'));
assert_eq!(editor.get_text(), "Hello, w");
}
}