1#![doc = include_str!("../README.md")]
2
3use commands::Command;
4
5pub mod commands;
7pub mod events;
9
10#[derive(Debug, Clone, Default)]
38pub struct StringEditor {
39 text: String,
40 cursor: usize,
41 }
43
44impl StringEditor {
45 pub fn new() -> Self {
47 Self {
48 text: String::new(),
49 cursor: 0,
50 }
52 }
53
54 pub fn with_string(text: &str) -> Self {
56 Self {
57 text: text.to_string(),
58 cursor: text.len(),
59 }
61 }
62
63 pub fn get_text(&self) -> &str {
65 &self.text
66 }
67
68 pub fn cursor_pos(&self) -> usize {
70 self.cursor
71 }
72}
73
74impl StringEditor {
75 pub fn execute(&mut self, command: Command) {
77 match command {
78 Command::Insert(c) => {
79 self.text.insert(self.cursor, c);
80 self.cursor += 1;
81 }
82 Command::Type(s) => {
83 self.text.insert_str(self.cursor, &s);
84 self.cursor += s.len();
85 }
86 Command::CursorLeft(amt) => self.cursor = self.cursor.saturating_sub(amt),
87 Command::CursorRight(amt) => {
88 self.cursor += amt;
89 if self.cursor > self.text.len() {
90 self.cursor = self.text.len();
91 }
92 }
93 Command::CursorToStartOfLine => self.cursor = 0,
94 Command::CursorToEndOfLine => self.cursor = self.text.len(),
95 Command::Delete => {
96 if self.cursor < self.text.len() {
97 self.text.remove(self.cursor);
98 }
99 }
100 Command::Backspace => {
101 if self.cursor > 0 {
102 self.text.remove(self.cursor - 1);
103 self.cursor -= 1;
104 }
105 }
106 Command::DeleteStartOfLineToCursor => {
107 self.text.replace_range(0..self.cursor, "");
108 self.cursor = 0;
109 }
110 Command::DeleteToEndOfLine => {
111 self.text.replace_range(self.cursor..self.text.len(), "");
112 }
113 Command::DeleteWordLeadingToCursor => {
114 if self.cursor > 0 {
115 let mut pos = self.cursor - 1;
116
117 while pos > 0
118 && !self.text[pos - 1..pos]
119 .chars()
120 .next()
121 .unwrap()
122 .is_whitespace()
123 && !matches!(
124 self.text[pos - 1..pos].chars().next().unwrap(),
125 '-' | '_'
126 | '+'
127 | '='
128 | ','
129 | '.'
130 | '/'
131 | '\\'
132 | ':'
133 | ';'
134 | '!'
135 | '?'
136 | '@'
137 | '#'
138 | '$'
139 | '%'
140 | '^'
141 | '&'
142 | '*'
143 | '('
144 | ')'
145 | '['
146 | ']'
147 | '{'
148 | '}'
149 )
150 {
151 pos -= 1;
152 }
153
154 while pos < self.cursor {
155 self.text.remove(pos);
156 self.cursor -= 1;
157 }
158 }
159 }
160 _ => todo!(),
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn basic_commands() {
171 let mut editor = StringEditor::new();
172
173 editor.execute(Command::Insert('a'));
175 editor.execute(Command::Insert('b'));
176 assert_eq!(editor.get_text(), "ab");
177
178 editor.execute(Command::CursorLeft(1));
180 editor.execute(Command::Insert('c'));
181 assert_eq!(editor.get_text(), "acb");
182 editor.execute(Command::CursorRight(1));
183 assert_eq!(editor.cursor_pos(), 3);
184 editor.execute(Command::CursorRight(1));
185 assert_eq!(editor.cursor_pos(), 3);
186 editor.execute(Command::Insert('d'));
187 assert_eq!(editor.get_text(), "acbd");
188
189 editor.execute(Command::CursorToStartOfLine);
191 editor.execute(Command::Insert('e'));
192 assert_eq!(editor.get_text(), "eacbd");
193
194 editor.execute(Command::CursorToEndOfLine);
196 editor.execute(Command::Insert('f'));
197 assert_eq!(editor.get_text(), "eacbdf");
198
199 editor.execute(Command::Delete);
201 assert_eq!(editor.get_text(), "eacbdf");
202 editor.execute(Command::Backspace);
203 assert_eq!(editor.get_text(), "eacbd");
204 editor.execute(Command::CursorLeft(1));
205 editor.execute(Command::Backspace);
206 assert_eq!(editor.get_text(), "eacd");
207 editor.execute(Command::Delete);
208 assert_eq!(editor.get_text(), "eac");
209 }
210
211 #[test]
212 fn test_larger_edits() {
213 let mut editor = StringEditor::with_string("Hello, world!");
214 editor.execute(Command::CursorLeft(6));
215 editor.execute(Command::DeleteStartOfLineToCursor);
216 assert_eq!(editor.get_text(), "world!");
217 editor.execute(Command::Type("Hello, ".to_string()));
218 assert_eq!(editor.get_text(), "Hello, world!");
219 editor.execute(Command::DeleteToEndOfLine);
220 assert_eq!(editor.get_text(), "Hello, ");
221 editor.execute(Command::Insert('w'));
222 assert_eq!(editor.get_text(), "Hello, w");
223 }
224}