aboutsummaryrefslogtreecommitdiffstats
path: root/src/command/client/search/cursor.rs
diff options
context:
space:
mode:
authorConrad Ludgate <conradludgate@gmail.com>2022-09-11 16:24:16 +0100
committerGitHub <noreply@github.com>2022-09-11 16:24:16 +0100
commit702a644f68c687142c9a03b48cf451665ed41b62 (patch)
tree5621dc20001662f556532745d800ed5dc3607673 /src/command/client/search/cursor.rs
parentAdd index for interactive search (#493) (diff)
downloadatuin-702a644f68c687142c9a03b48cf451665ed41b62.zip
better cursor search (#473)
* improve cursor code * proper unicode support * refactor and test * fmt * clippy * move methods to state * refactor search modules
Diffstat (limited to 'src/command/client/search/cursor.rs')
-rw-r--r--src/command/client/search/cursor.rs156
1 files changed, 156 insertions, 0 deletions
diff --git a/src/command/client/search/cursor.rs b/src/command/client/search/cursor.rs
new file mode 100644
index 00000000..1d0e6b8e
--- /dev/null
+++ b/src/command/client/search/cursor.rs
@@ -0,0 +1,156 @@
+pub struct Cursor {
+ source: String,
+ index: usize,
+}
+
+impl From<String> for Cursor {
+ fn from(source: String) -> Self {
+ Self { source, index: 0 }
+ }
+}
+
+impl Cursor {
+ pub fn as_str(&self) -> &str {
+ self.source.as_str()
+ }
+
+ /// Returns the string before the cursor
+ pub fn substring(&self) -> &str {
+ &self.source[..self.index]
+ }
+
+ /// Returns the currently selected [`char`]
+ pub fn char(&self) -> Option<char> {
+ self.source[self.index..].chars().next()
+ }
+
+ pub fn right(&mut self) {
+ if self.index < self.source.len() {
+ loop {
+ self.index += 1;
+ if self.source.is_char_boundary(self.index) {
+ break;
+ }
+ }
+ }
+ }
+
+ pub fn left(&mut self) -> bool {
+ if self.index > 0 {
+ loop {
+ self.index -= 1;
+ if self.source.is_char_boundary(self.index) {
+ break true;
+ }
+ }
+ } else {
+ false
+ }
+ }
+
+ pub fn insert(&mut self, c: char) {
+ self.source.insert(self.index, c);
+ self.index += c.len_utf8();
+ }
+
+ pub fn remove(&mut self) -> char {
+ self.source.remove(self.index)
+ }
+
+ pub fn back(&mut self) -> Option<char> {
+ if self.left() {
+ Some(self.remove())
+ } else {
+ None
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.source.clear();
+ self.index = 0;
+ }
+
+ pub fn end(&mut self) {
+ self.index = self.source.len();
+ }
+
+ pub fn start(&mut self) {
+ self.index = 0;
+ }
+}
+
+#[cfg(test)]
+mod cursor_tests {
+ use super::Cursor;
+
+ #[test]
+ fn right() {
+ // ö is 2 bytes
+ let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
+ let indices = [0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18, 20, 20, 20, 20];
+ for i in indices {
+ assert_eq!(c.index, i);
+ c.right();
+ }
+ }
+
+ #[test]
+ fn left() {
+ // ö is 2 bytes
+ let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
+ c.end();
+ let indices = [20, 18, 17, 15, 14, 12, 11, 9, 8, 6, 5, 3, 2, 0, 0, 0, 0];
+ for i in indices {
+ assert_eq!(c.index, i);
+ c.left();
+ }
+ }
+
+ #[test]
+ fn pop() {
+ let mut s = String::from("öaöböcödöeöfö");
+ let mut c = Cursor::from(s.clone());
+ c.end();
+ while !s.is_empty() {
+ let c1 = s.pop();
+ let c2 = c.back();
+ assert_eq!(c1, c2);
+ assert_eq!(s.as_str(), c.substring());
+ }
+ let c1 = s.pop();
+ let c2 = c.back();
+ assert_eq!(c1, c2);
+ }
+
+ #[test]
+ fn back() {
+ let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
+ // move to ^
+ for _ in 0..4 {
+ c.right();
+ }
+ assert_eq!(c.substring(), "öaöb");
+ assert_eq!(c.back(), Some('b'));
+ assert_eq!(c.back(), Some('ö'));
+ assert_eq!(c.back(), Some('a'));
+ assert_eq!(c.back(), Some('ö'));
+ assert_eq!(c.back(), None);
+ assert_eq!(c.as_str(), "öcödöeöfö");
+ }
+
+ #[test]
+ fn insert() {
+ let mut c = Cursor::from(String::from("öaöböcödöeöfö"));
+ // move to ^
+ for _ in 0..4 {
+ c.right();
+ }
+ assert_eq!(c.substring(), "öaöb");
+ c.insert('ö');
+ c.insert('g');
+ c.insert('ö');
+ c.insert('h');
+ assert_eq!(c.substring(), "öaöbögöh");
+ assert_eq!(c.as_str(), "öaöbögöhöcödöeöfö");
+ }
+}