--- /dev/null
+use unicode_width::UnicodeWidthChar;
+
+use super::tui::{OurKey, OurKey::*};
+
+struct EditorCore {
+ text: String,
+ paste_buffer: String,
+ point: usize,
+}
+
+impl EditorCore {
+ fn is_char_boundary(&self, pos: usize) -> bool {
+ if !self.text.is_char_boundary(pos) {
+ false
+ } else {
+ match self.text[pos..].chars().next() {
+ None => true, // end of string
+
+ // not just before a combining character
+ Some(c) => UnicodeWidthChar::width(c).unwrap_or(0) > 0,
+ }
+ }
+ }
+
+ fn is_word_boundary(&self, pos: usize) -> bool {
+ if !self.is_char_boundary(pos) {
+ false
+ } else {
+ match self.text[pos..].chars().next() {
+ None => true, // end of string
+
+ // not just before a space
+ Some(c) => c != ' '
+ }
+ }
+ }
+
+ fn forward(&mut self) -> bool {
+ let len = self.text.len();
+ if self.point >= len {
+ false
+ } else {
+ self.point += 1;
+ while !self.is_char_boundary(self.point) {
+ self.point += 1;
+ }
+ true
+ }
+ }
+
+ fn backward(&mut self) -> bool {
+ if self.point == 0 {
+ false
+ } else {
+ self.point -= 1;
+ while !self.is_char_boundary(self.point) {
+ self.point -= 1;
+ }
+ true
+ }
+ }
+
+ fn delete_backward(&mut self) {
+ let prev_point = self.point;
+ if self.backward() {
+ self.text = self.text[..self.point].to_owned() +
+ &self.text[prev_point..];
+ }
+ }
+
+ fn delete_forward(&mut self) {
+ let prev_point = self.point;
+ if self.forward() {
+ self.text = self.text[..prev_point].to_owned() +
+ &self.text[self.point..];
+ self.point = prev_point;
+ }
+ }
+
+ fn forward_word(&mut self) -> bool {
+ let len = self.text.len();
+ if self.point >= len {
+ false
+ } else {
+ self.point += 1;
+ while !self.is_word_boundary(self.point) {
+ self.point += 1;
+ }
+ true
+ }
+ }
+
+ fn backward_word(&mut self) -> bool {
+ if self.point == 0 {
+ false
+ } else {
+ self.point -= 1;
+ while !self.is_word_boundary(self.point) {
+ self.point -= 1;
+ }
+ true
+ }
+ }
+
+ fn insert(&mut self, text: &str) {
+ self.text = self.text[..self.point].to_owned() + text +
+ &self.text[self.point..];
+ self.point += text.len();
+ }
+
+ fn paste(&mut self) {
+ self.text = self.text[..self.point].to_owned() + &self.paste_buffer +
+ &self.text[self.point..];
+ self.point += self.paste_buffer.len();
+ }
+
+ fn handle_keypress(&mut self, key: OurKey) {
+ match key {
+ Left | Ctrl('B') => { self.backward(); },
+ Right | Ctrl('F') => { self.forward(); },
+ Backspace => { self.delete_backward(); },
+ Del | Ctrl('D') => { self.delete_forward(); },
+ Ctrl('W') => { self.backward_word(); },
+ Ctrl('T') => { self.forward_word(); },
+ Ctrl('Y') => { self.paste(); },
+ Pr(c) => { self.insert(&c.to_string()); },
+ _ => (),
+ }
+ }
+}
+
+#[test]
+fn test_forward_backward() {
+ let mut ec = EditorCore {
+ text: "héllo, wørld".to_owned(),
+ point: 0,
+ paste_buffer: "".to_owned(),
+ };
+
+ assert_eq!(ec.forward(), true);
+ assert_eq!(ec.point, 1);
+ assert_eq!(ec.forward(), true);
+ assert_eq!(ec.point, 3);
+ assert_eq!(ec.forward(), true);
+ assert_eq!(ec.point, 4);
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, 3);
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, 1);
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, 0);
+ assert_eq!(ec.backward(), false);
+
+ ec.point = ec.text.len() - 2;
+ assert_eq!(ec.forward(), true);
+ assert_eq!(ec.point, ec.text.len() - 1);
+ assert_eq!(ec.forward(), true);
+ assert_eq!(ec.point, ec.text.len());
+ assert_eq!(ec.forward(), false);
+ assert_eq!(ec.point, ec.text.len());
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, ec.text.len() - 1);
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, ec.text.len() - 2);
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, ec.text.len() - 3);
+ assert_eq!(ec.backward(), true);
+ assert_eq!(ec.point, ec.text.len() - 5);
+ assert_eq!(ec.backward(), true);
+}
+
+#[test]
+fn test_delete() {
+ let mut ec = EditorCore {
+ text: "hélło".to_owned(),
+ point: 3,
+ paste_buffer: "".to_owned(),
+ };
+
+ ec.delete_forward();
+ assert_eq!(&ec.text, "héło");
+ assert_eq!(ec.point, 3);
+ ec.delete_forward();
+ assert_eq!(&ec.text, "héo");
+ assert_eq!(ec.point, 3);
+ ec.delete_backward();
+ assert_eq!(&ec.text, "ho");
+ assert_eq!(ec.point, 1);
+ ec.delete_backward();
+ assert_eq!(&ec.text, "o");
+ assert_eq!(ec.point, 0);
+ ec.delete_backward();
+ assert_eq!(&ec.text, "o");
+ assert_eq!(ec.point, 0);
+ ec.delete_forward();
+ assert_eq!(&ec.text, "");
+ assert_eq!(ec.point, 0);
+ ec.delete_forward();
+ assert_eq!(&ec.text, "");
+ assert_eq!(ec.point, 0);
+}
+
+#[test]
+fn test_insert() {
+ let mut ec = EditorCore {
+ text: "hélło".to_owned(),
+ point: 3,
+ paste_buffer: "".to_owned(),
+ };
+
+ ec.insert("PÏNG");
+ assert_eq!(&ec.text, "héPÏNGlło");
+ assert_eq!(ec.point, 8);
+
+ // We don't let you move _to_ a position before a combining char,
+ // but you can insert one in a way that makes your previous position
+ // no longer valid
+ ec.text = "Beszel".to_string();
+ ec.point = 4;
+ ec.insert("\u{301}");
+ assert_eq!(&ec.text, "Besźel");
+ assert_eq!(ec.point, 6);
+
+ ec.paste_buffer = "PASTE".to_owned();
+ ec.paste();
+ assert_eq!(&ec.text, "BesźPASTEel");
+ assert_eq!(ec.point, 11);
+}