| //! Line buffer with current cursor position |
| use std::ops::Deref; |
| use keymap::{At, Word}; |
| |
| /// Maximum buffer size for the line read |
| pub static MAX_LINE: usize = 4096; |
| |
| pub enum WordAction { |
| CAPITALIZE, |
| LOWERCASE, |
| UPPERCASE, |
| } |
| |
| #[derive(Debug)] |
| pub struct LineBuffer { |
| buf: String, // Edited line buffer |
| pos: usize, // Current cursor position (byte position) |
| } |
| |
| impl LineBuffer { |
| /// Create a new line buffer with the given maximum `capacity`. |
| pub fn with_capacity(capacity: usize) -> LineBuffer { |
| LineBuffer { |
| buf: String::with_capacity(capacity), |
| pos: 0, |
| } |
| } |
| |
| #[cfg(test)] |
| pub fn init(line: &str, pos: usize) -> LineBuffer { |
| LineBuffer { |
| buf: String::from(line), |
| pos: pos, |
| } |
| } |
| |
| /// Extracts a string slice containing the entire buffer. |
| pub fn as_str(&self) -> &str { |
| &self.buf |
| } |
| |
| /// Converts a buffer into a `String` without copying or allocating. |
| pub fn into_string(self) -> String { |
| self.buf |
| } |
| |
| /// Current cursor position (byte position) |
| pub fn pos(&self) -> usize { |
| self.pos |
| } |
| pub fn set_pos(&mut self, pos: usize) { |
| assert!(pos <= self.buf.len()); |
| self.pos = pos; |
| } |
| |
| /// Returns the length of this buffer, in bytes. |
| pub fn len(&self) -> usize { |
| self.buf.len() |
| } |
| /// Returns `true` if this buffer has a length of zero. |
| pub fn is_empty(&self) -> bool { |
| self.buf.is_empty() |
| } |
| |
| /// Set line content (`buf`) and cursor position (`pos`). |
| pub fn update(&mut self, buf: &str, pos: usize) { |
| assert!(pos <= buf.len()); |
| self.buf.clear(); |
| let max = self.buf.capacity(); |
| if buf.len() > max { |
| self.buf.push_str(&buf[..max]); |
| if pos > max { |
| self.pos = max; |
| } else { |
| self.pos = pos; |
| } |
| } else { |
| self.buf.push_str(buf); |
| self.pos = pos; |
| } |
| } |
| |
| /// Backup `src` |
| pub fn backup(&mut self, src: &LineBuffer) { |
| self.buf.clear(); |
| self.buf.push_str(&src.buf); |
| self.pos = src.pos; |
| } |
| |
| /// Returns the character at current cursor position. |
| fn char_at_cursor(&self) -> Option<char> { |
| if self.pos == self.buf.len() { |
| None |
| } else { |
| self.buf[self.pos..].chars().next() |
| } |
| } |
| /// Returns the character just before the current cursor position. |
| fn char_before_cursor(&self) -> Option<char> { |
| if self.pos == 0 { |
| None |
| } else { |
| self.buf[..self.pos].chars().next_back() |
| } |
| } |
| |
| /// Insert the character `ch` at current cursor position |
| /// and advance cursor position accordingly. |
| /// Return `None` when maximum buffer size has been reached, |
| /// `true` when the character has been appended to the end of the line. |
| pub fn insert(&mut self, ch: char) -> Option<bool> { |
| let shift = ch.len_utf8(); |
| if self.buf.len() + shift > self.buf.capacity() { |
| return None; |
| } |
| let push = self.pos == self.buf.len(); |
| if push { |
| self.buf.push(ch); |
| } else { |
| self.buf.insert(self.pos, ch); |
| } |
| self.pos += shift; |
| Some(push) |
| } |
| |
| /// Yank/paste `text` at current position. |
| /// Return `None` when maximum buffer size has been reached, |
| /// `true` when the character has been appended to the end of the line. |
| pub fn yank(&mut self, text: &str) -> Option<bool> { |
| let shift = text.len(); |
| if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { |
| return None; |
| } |
| let pos = self.pos; |
| let push = self.insert_str(pos, text); |
| self.pos += shift; |
| Some(push) |
| } |
| |
| /// Delete previously yanked text and yank/paste `text` at current position. |
| pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option<bool> { |
| self.buf.drain((self.pos - yank_size)..self.pos); |
| self.pos -= yank_size; |
| self.yank(text) |
| } |
| |
| /// Move cursor on the left. |
| pub fn move_left(&mut self) -> bool { |
| if let Some(ch) = self.char_before_cursor() { |
| self.pos -= ch.len_utf8(); |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Move cursor on the right. |
| pub fn move_right(&mut self) -> bool { |
| if let Some(ch) = self.char_at_cursor() { |
| self.pos += ch.len_utf8(); |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Move cursor to the start of the line. |
| pub fn move_home(&mut self) -> bool { |
| if self.pos > 0 { |
| self.pos = 0; |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Move cursor to the end of the line. |
| pub fn move_end(&mut self) -> bool { |
| if self.pos == self.buf.len() { |
| false |
| } else { |
| self.pos = self.buf.len(); |
| true |
| } |
| } |
| |
| /// Replace a single character under the cursor (Vi mode) |
| pub fn replace_char(&mut self, ch: char) -> Option<bool> { |
| if self.delete() { self.insert(ch) } else { None } |
| } |
| |
| /// Delete the character at the right of the cursor without altering the cursor |
| /// position. Basically this is what happens with the "Delete" keyboard key. |
| pub fn delete(&mut self) -> bool { |
| if !self.buf.is_empty() && self.pos < self.buf.len() { |
| self.buf.remove(self.pos); |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Delete the character at the left of the cursor. |
| /// Basically that is what happens with the "Backspace" keyboard key. |
| pub fn backspace(&mut self) -> bool { |
| if let Some(ch) = self.char_before_cursor() { |
| self.pos -= ch.len_utf8(); |
| self.buf.remove(self.pos); |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Kill all characters on the current line. |
| pub fn kill_whole_line(&mut self) -> Option<String> { |
| self.move_home(); |
| self.kill_line() |
| } |
| |
| /// Kill the text from point to the end of the line. |
| pub fn kill_line(&mut self) -> Option<String> { |
| if !self.buf.is_empty() && self.pos < self.buf.len() { |
| let text = self.buf.drain(self.pos..).collect(); |
| Some(text) |
| } else { |
| None |
| } |
| } |
| |
| /// Kill backward from point to the beginning of the line. |
| pub fn discard_line(&mut self) -> Option<String> { |
| if self.pos > 0 && !self.buf.is_empty() { |
| let text = self.buf.drain(..self.pos).collect(); |
| self.pos = 0; |
| Some(text) |
| } else { |
| None |
| } |
| } |
| |
| /// Exchange the char before cursor with the character at cursor. |
| pub fn transpose_chars(&mut self) -> bool { |
| if self.pos == 0 || self.buf.chars().count() < 2 { |
| return false; |
| } |
| if self.pos == self.buf.len() { |
| self.move_left(); |
| } |
| let ch = self.buf.remove(self.pos); |
| let size = ch.len_utf8(); |
| let other_ch = self.char_before_cursor().unwrap(); |
| let other_size = other_ch.len_utf8(); |
| self.buf.insert(self.pos - other_size, ch); |
| if self.pos != self.buf.len() - size { |
| self.pos += size; |
| } else if size >= other_size { |
| self.pos += size - other_size; |
| } else { |
| self.pos -= other_size - size; |
| } |
| true |
| } |
| |
| /// Go left until start of word |
| fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option<usize> { |
| if pos == 0 { |
| return None; |
| } |
| let test = is_break_char(word_def); |
| let mut pos = pos; |
| // eat any spaces on the left |
| pos -= self.buf[..pos] |
| .chars() |
| .rev() |
| .take_while(|ch| test(ch)) |
| .map(char::len_utf8) |
| .sum(); |
| if pos > 0 { |
| // eat any non-spaces on the left |
| pos -= self.buf[..pos] |
| .chars() |
| .rev() |
| .take_while(|ch| !test(ch)) |
| .map(char::len_utf8) |
| .sum(); |
| } |
| Some(pos) |
| } |
| |
| /// Moves the cursor to the beginning of previous word. |
| pub fn move_to_prev_word(&mut self, word_def: Word) -> bool { |
| if let Some(pos) = self.prev_word_pos(self.pos, word_def) { |
| self.pos = pos; |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Delete the previous word, maintaining the cursor at the start of the |
| /// current word. |
| pub fn delete_prev_word(&mut self, word_def: Word) -> Option<String> { |
| if let Some(pos) = self.prev_word_pos(self.pos, word_def) { |
| let word = self.buf.drain(pos..self.pos).collect(); |
| self.pos = pos; |
| Some(word) |
| } else { |
| None |
| } |
| } |
| |
| fn next_pos(&self, pos: usize, at: At, word_def: Word) -> Option<usize> { |
| match at { |
| At::End => { |
| match self.next_word_pos(pos, word_def) { |
| Some((_, end)) => Some(end), |
| _ => None, |
| } |
| } |
| At::Start => self.next_start_of_word_pos(pos, word_def), |
| } |
| } |
| |
| /// Go right until start of word |
| fn next_start_of_word_pos(&self, pos: usize, word_def: Word) -> Option<usize> { |
| if pos < self.buf.len() { |
| let test = is_break_char(word_def); |
| let mut pos = pos; |
| // eat any non-spaces |
| pos += self.buf[pos..] |
| .chars() |
| .take_while(|ch| !test(ch)) |
| .map(char::len_utf8) |
| .sum(); |
| if pos < self.buf.len() { |
| // eat any spaces |
| pos += self.buf[pos..] |
| .chars() |
| .take_while(test) |
| .map(char::len_utf8) |
| .sum(); |
| } |
| Some(pos) |
| } else { |
| None |
| } |
| } |
| |
| /// Go right until end of word |
| /// Returns the position (start, end) of the next word. |
| fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { |
| if pos < self.buf.len() { |
| let test = is_break_char(word_def); |
| let mut pos = pos; |
| // eat any spaces |
| pos += self.buf[pos..] |
| .chars() |
| .take_while(test) |
| .map(char::len_utf8) |
| .sum(); |
| let start = pos; |
| if pos < self.buf.len() { |
| // eat any non-spaces |
| pos += self.buf[pos..] |
| .chars() |
| .take_while(|ch| !test(ch)) |
| .map(char::len_utf8) |
| .sum(); |
| } |
| Some((start, pos)) |
| } else { |
| None |
| } |
| } |
| |
| /// Moves the cursor to the end of next word. |
| pub fn move_to_next_word(&mut self, at: At, word_def: Word) -> bool { |
| if let Some(pos) = self.next_pos(self.pos, at, word_def) { |
| self.pos = pos; |
| true |
| } else { |
| false |
| } |
| } |
| |
| // TODO move_to (Vi mode: f|F|t|T) |
| |
| /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. |
| pub fn delete_word(&mut self, at: At, word_def: Word) -> Option<String> { |
| if let Some(pos) = self.next_pos(self.pos, at, word_def) { |
| let word = self.buf.drain(self.pos..pos).collect(); |
| Some(word) |
| } else { |
| None |
| } |
| } |
| |
| /// Alter the next word. |
| pub fn edit_word(&mut self, a: WordAction) -> bool { |
| if let Some((start, end)) = self.next_word_pos(self.pos, Word::Word) { |
| if start == end { |
| return false; |
| } |
| let word = self.buf.drain(start..end).collect::<String>(); |
| let result = match a { |
| WordAction::CAPITALIZE => { |
| if let Some(ch) = word.chars().next() { |
| let cap = ch.to_uppercase().collect::<String>(); |
| cap + &word[ch.len_utf8()..].to_lowercase() |
| } else { |
| word |
| } |
| } |
| WordAction::LOWERCASE => word.to_lowercase(), |
| WordAction::UPPERCASE => word.to_uppercase(), |
| }; |
| self.insert_str(start, &result); |
| self.pos = start + result.len(); |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Transpose two words |
| pub fn transpose_words(&mut self) -> bool { |
| // prevword___oneword__ |
| // ^ ^ ^ |
| // prev_start start self.pos/end |
| let word_def = Word::Word; |
| if let Some(start) = self.prev_word_pos(self.pos, word_def) { |
| if let Some(prev_start) = self.prev_word_pos(start, word_def) { |
| let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); |
| if prev_end >= start { |
| return false; |
| } |
| let (_, mut end) = self.next_word_pos(start, word_def).unwrap(); |
| if end < self.pos { |
| if self.pos < self.buf.len() { |
| let (s, _) = self.next_word_pos(self.pos, word_def).unwrap(); |
| end = s; |
| } else { |
| end = self.pos; |
| } |
| } |
| |
| let oneword = self.buf.drain(start..end).collect::<String>(); |
| let sep = self.buf.drain(prev_end..start).collect::<String>(); |
| let prevword = self.buf.drain(prev_start..prev_end).collect::<String>(); |
| |
| let mut idx = prev_start; |
| self.insert_str(idx, &oneword); |
| idx += oneword.len(); |
| self.insert_str(idx, &sep); |
| idx += sep.len(); |
| self.insert_str(idx, &prevword); |
| |
| self.pos = idx + prevword.len(); |
| return true; |
| } |
| } |
| false |
| } |
| |
| /// Replaces the content between [`start`..`end`] with `text` and positions the cursor to the end of text. |
| pub fn replace(&mut self, start: usize, end: usize, text: &str) { |
| self.buf.drain(start..end); |
| self.insert_str(start, text); |
| self.pos = start + text.len(); |
| } |
| |
| fn insert_str(&mut self, idx: usize, s: &str) -> bool { |
| if idx == self.buf.len() { |
| self.buf.push_str(s); |
| true |
| } else { |
| insert_str(&mut self.buf, idx, s); |
| false |
| } |
| } |
| } |
| |
| impl Deref for LineBuffer { |
| type Target = str; |
| |
| fn deref(&self) -> &str { |
| self.as_str() |
| } |
| } |
| |
| fn insert_str(buf: &mut String, idx: usize, s: &str) { |
| use std::ptr; |
| |
| let len = buf.len(); |
| assert!(idx <= len); |
| assert!(buf.is_char_boundary(idx)); |
| let amt = s.len(); |
| buf.reserve(amt); |
| |
| unsafe { |
| let v = buf.as_mut_vec(); |
| ptr::copy(v.as_ptr().offset(idx as isize), |
| v.as_mut_ptr().offset((idx + amt) as isize), |
| len - idx); |
| ptr::copy_nonoverlapping(s.as_ptr(), v.as_mut_ptr().offset(idx as isize), amt); |
| v.set_len(len + amt); |
| } |
| } |
| |
| fn is_break_char(word_def: Word) -> fn(&char) -> bool { |
| match word_def { |
| Word::Word => is_not_alphanumeric, |
| Word::ViWord => is_not_alphanumeric_and_underscore, |
| Word::BigWord => is_whitespace, |
| } |
| } |
| |
| fn is_not_alphanumeric(ch: &char) -> bool { |
| !ch.is_alphanumeric() |
| } |
| fn is_not_alphanumeric_and_underscore(ch: &char) -> bool { |
| !ch.is_alphanumeric() && *ch != '_' |
| } |
| fn is_whitespace(ch: &char) -> bool { |
| ch.is_whitespace() |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use keymap::{At, Word}; |
| use super::{LineBuffer, MAX_LINE, WordAction}; |
| |
| #[test] |
| fn insert() { |
| let mut s = LineBuffer::with_capacity(MAX_LINE); |
| let push = s.insert('α').unwrap(); |
| assert_eq!("α", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(true, push); |
| |
| let push = s.insert('ß').unwrap(); |
| assert_eq!("αß", s.buf); |
| assert_eq!(4, s.pos); |
| assert_eq!(true, push); |
| |
| s.pos = 0; |
| let push = s.insert('γ').unwrap(); |
| assert_eq!("γαß", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(false, push); |
| } |
| |
| #[test] |
| fn moves() { |
| let mut s = LineBuffer::init("αß", 4); |
| let ok = s.move_left(); |
| assert_eq!("αß", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(true, ok); |
| |
| let ok = s.move_right(); |
| assert_eq!("αß", s.buf); |
| assert_eq!(4, s.pos); |
| assert_eq!(true, ok); |
| |
| let ok = s.move_home(); |
| assert_eq!("αß", s.buf); |
| assert_eq!(0, s.pos); |
| assert_eq!(true, ok); |
| |
| let ok = s.move_end(); |
| assert_eq!("αß", s.buf); |
| assert_eq!(4, s.pos); |
| assert_eq!(true, ok); |
| } |
| |
| #[test] |
| fn delete() { |
| let mut s = LineBuffer::init("αß", 2); |
| let ok = s.delete(); |
| assert_eq!("α", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(true, ok); |
| |
| let ok = s.backspace(); |
| assert_eq!("", s.buf); |
| assert_eq!(0, s.pos); |
| assert_eq!(true, ok); |
| } |
| |
| #[test] |
| fn kill() { |
| let mut s = LineBuffer::init("αßγδε", 6); |
| let text = s.kill_line(); |
| assert_eq!("αßγ", s.buf); |
| assert_eq!(6, s.pos); |
| assert_eq!(Some("δε".to_string()), text); |
| |
| s.pos = 4; |
| let text = s.discard_line(); |
| assert_eq!("γ", s.buf); |
| assert_eq!(0, s.pos); |
| assert_eq!(Some("αß".to_string()), text); |
| } |
| |
| #[test] |
| fn transpose() { |
| let mut s = LineBuffer::init("aßc", 1); |
| let ok = s.transpose_chars(); |
| assert_eq!("ßac", s.buf); |
| assert_eq!(3, s.pos); |
| assert_eq!(true, ok); |
| |
| s.buf = String::from("aßc"); |
| s.pos = 3; |
| let ok = s.transpose_chars(); |
| assert_eq!("acß", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(true, ok); |
| |
| s.buf = String::from("aßc"); |
| s.pos = 4; |
| let ok = s.transpose_chars(); |
| assert_eq!("acß", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(true, ok); |
| } |
| |
| #[test] |
| fn move_to_prev_word() { |
| let mut s = LineBuffer::init("a ß c", 6); |
| let ok = s.move_to_prev_word(Word::Word); |
| assert_eq!("a ß c", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(true, ok); |
| } |
| |
| #[test] |
| fn delete_prev_word() { |
| let mut s = LineBuffer::init("a ß c", 6); |
| let text = s.delete_prev_word(Word::BigWord); |
| assert_eq!("a c", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(Some("ß ".to_string()), text); |
| } |
| |
| #[test] |
| fn move_to_next_word() { |
| let mut s = LineBuffer::init("a ß c", 1); |
| let ok = s.move_to_next_word(At::End, Word::Word); |
| assert_eq!("a ß c", s.buf); |
| assert_eq!(4, s.pos); |
| assert_eq!(true, ok); |
| } |
| |
| #[test] |
| fn move_to_start_of_word() { |
| let mut s = LineBuffer::init("a ß c", 2); |
| let ok = s.move_to_next_word(At::Start, Word::Word); |
| assert_eq!("a ß c", s.buf); |
| assert_eq!(6, s.pos); |
| assert_eq!(true, ok); |
| } |
| |
| #[test] |
| fn delete_word() { |
| let mut s = LineBuffer::init("a ß c", 1); |
| let text = s.delete_word(At::End, Word::Word); |
| assert_eq!("a c", s.buf); |
| assert_eq!(1, s.pos); |
| assert_eq!(Some(" ß".to_string()), text); |
| } |
| |
| #[test] |
| fn delete_til_start_of_word() { |
| let mut s = LineBuffer::init("a ß c", 2); |
| let text = s.delete_word(At::Start, Word::Word); |
| assert_eq!("a c", s.buf); |
| assert_eq!(2, s.pos); |
| assert_eq!(Some("ß ".to_string()), text); |
| } |
| |
| #[test] |
| fn edit_word() { |
| let mut s = LineBuffer::init("a ßeta c", 1); |
| assert!(s.edit_word(WordAction::UPPERCASE)); |
| assert_eq!("a SSETA c", s.buf); |
| assert_eq!(7, s.pos); |
| |
| let mut s = LineBuffer::init("a ßetA c", 1); |
| assert!(s.edit_word(WordAction::LOWERCASE)); |
| assert_eq!("a ßeta c", s.buf); |
| assert_eq!(7, s.pos); |
| |
| let mut s = LineBuffer::init("a ßETA c", 1); |
| assert!(s.edit_word(WordAction::CAPITALIZE)); |
| assert_eq!("a SSeta c", s.buf); |
| assert_eq!(7, s.pos); |
| } |
| |
| #[test] |
| fn transpose_words() { |
| let mut s = LineBuffer::init("ßeta / δelta__", 15); |
| assert!(s.transpose_words()); |
| assert_eq!("δelta__ / ßeta", s.buf); |
| assert_eq!(16, s.pos); |
| |
| let mut s = LineBuffer::init("ßeta / δelta", 14); |
| assert!(s.transpose_words()); |
| assert_eq!("δelta / ßeta", s.buf); |
| assert_eq!(14, s.pos); |
| |
| let mut s = LineBuffer::init(" / δelta", 8); |
| assert!(!s.transpose_words()); |
| |
| let mut s = LineBuffer::init("ßeta / __", 9); |
| assert!(!s.transpose_words()); |
| } |
| } |