1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum EditMode {
8 Emacs,
9 Vim,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum VimMode {
15 Normal,
16 Insert,
17}
18
19#[derive(Debug, Clone)]
23pub struct Editor {
24 buffer: String,
25 cursor: usize,
26 mode: EditMode,
27 vim: VimMode,
28}
29
30impl Editor {
31 #[must_use]
32 pub fn new(mode: EditMode) -> Self {
33 Self {
34 buffer: String::new(),
35 cursor: 0,
36 mode,
37 vim: VimMode::Insert,
38 }
39 }
40
41 #[must_use]
42 pub fn with_buffer(mode: EditMode, buffer: impl Into<String>) -> Self {
43 let buf: String = buffer.into();
44 let cursor = buf.chars().count();
45 Self {
46 buffer: buf,
47 cursor,
48 mode,
49 vim: VimMode::Insert,
50 }
51 }
52
53 #[must_use]
54 pub fn buffer(&self) -> &str {
55 &self.buffer
56 }
57
58 #[must_use]
59 pub fn cursor(&self) -> usize {
60 self.cursor
61 }
62
63 #[must_use]
64 pub fn mode(&self) -> EditMode {
65 self.mode
66 }
67
68 #[must_use]
69 pub fn vim_mode(&self) -> VimMode {
70 self.vim
71 }
72
73 pub fn set_mode(&mut self, mode: EditMode) {
74 self.mode = mode;
75 self.vim = VimMode::Insert;
76 }
77
78 pub fn replace_buffer(&mut self, new: impl Into<String>) {
80 let buf: String = new.into();
81 self.cursor = buf.chars().count();
82 self.buffer = buf;
83 }
84
85 fn chars(&self) -> Vec<char> {
86 self.buffer.chars().collect()
87 }
88
89 fn rebuild(&mut self, chars: &[char]) {
90 self.buffer = chars.iter().collect();
91 if self.cursor > chars.len() {
92 self.cursor = chars.len();
93 }
94 }
95
96 pub fn insert_char(&mut self, c: char) {
99 if self.mode == EditMode::Vim && self.vim == VimMode::Normal {
100 return;
101 }
102 let mut chars = self.chars();
103 chars.insert(self.cursor, c);
104 self.cursor += 1;
105 self.rebuild(&chars);
106 }
107
108 pub fn delete_backward(&mut self) {
109 if self.cursor == 0 {
110 return;
111 }
112 let mut chars = self.chars();
113 chars.remove(self.cursor - 1);
114 self.cursor -= 1;
115 self.rebuild(&chars);
116 }
117
118 pub fn delete_forward(&mut self) {
119 let mut chars = self.chars();
120 if self.cursor < chars.len() {
121 chars.remove(self.cursor);
122 self.rebuild(&chars);
123 }
124 }
125
126 pub fn move_left(&mut self) {
127 if self.cursor > 0 {
128 self.cursor -= 1;
129 }
130 }
131
132 pub fn move_right(&mut self) {
133 let len = self.chars().len();
134 if self.cursor < len {
135 self.cursor += 1;
136 }
137 }
138
139 pub fn move_home(&mut self) {
140 self.cursor = 0;
141 }
142
143 pub fn move_end(&mut self) {
144 self.cursor = self.chars().len();
145 }
146
147 pub fn kill_to_end(&mut self) {
149 let mut chars = self.chars();
150 chars.truncate(self.cursor);
151 self.rebuild(&chars);
152 }
153
154 pub fn kill_word_backward(&mut self) {
156 let mut chars = self.chars();
157 let mut i = self.cursor;
158 while i > 0 && chars[i - 1].is_whitespace() {
159 i -= 1;
160 }
161 while i > 0 && !chars[i - 1].is_whitespace() {
162 i -= 1;
163 }
164 chars.drain(i..self.cursor);
165 self.cursor = i;
166 self.rebuild(&chars);
167 }
168
169 pub fn enter_insert_mode(&mut self) {
170 self.vim = VimMode::Insert;
171 }
172
173 pub fn enter_normal_mode(&mut self) {
174 self.vim = VimMode::Normal;
175 }
176
177 pub fn vim_action(&mut self, action: VimAction) {
182 match action {
183 VimAction::MoveLeft => self.move_left(),
184 VimAction::MoveRight => self.move_right(),
185 VimAction::MoveHome => self.move_home(),
186 VimAction::MoveEnd => self.move_end(),
187 VimAction::WordForward => self.vim_word_forward(),
188 VimAction::WordBackward => self.vim_word_backward(),
189 VimAction::DeleteChar => self.delete_forward(),
190 VimAction::DeleteWordForward => self.vim_delete_word_forward(),
191 VimAction::DeleteWordBackward => self.kill_word_backward(),
192 VimAction::InsertAtCursor => self.enter_insert_mode(),
193 VimAction::InsertAfterCursor => {
194 self.move_right();
195 self.enter_insert_mode();
196 }
197 VimAction::InsertAtLineStart => {
198 self.move_home();
199 self.enter_insert_mode();
200 }
201 VimAction::InsertAtLineEnd => {
202 self.move_end();
203 self.enter_insert_mode();
204 }
205 }
206 }
207
208 fn vim_word_forward(&mut self) {
209 let chars = self.chars();
210 let mut i = self.cursor;
211 while i < chars.len() && !chars[i].is_whitespace() {
212 i += 1;
213 }
214 while i < chars.len() && chars[i].is_whitespace() {
215 i += 1;
216 }
217 self.cursor = i;
218 }
219
220 fn vim_word_backward(&mut self) {
221 let chars = self.chars();
222 let mut i = self.cursor;
223 while i > 0 && chars[i - 1].is_whitespace() {
224 i -= 1;
225 }
226 while i > 0 && !chars[i - 1].is_whitespace() {
227 i -= 1;
228 }
229 self.cursor = i;
230 }
231
232 fn vim_delete_word_forward(&mut self) {
233 let mut chars = self.chars();
234 let start = self.cursor;
235 let mut i = start;
236 while i < chars.len() && !chars[i].is_whitespace() {
237 i += 1;
238 }
239 while i < chars.len() && chars[i].is_whitespace() {
240 i += 1;
241 }
242 chars.drain(start..i);
243 self.rebuild(&chars);
244 }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum VimAction {
250 MoveLeft,
251 MoveRight,
252 MoveHome,
253 MoveEnd,
254 WordForward,
255 WordBackward,
256 DeleteChar,
257 DeleteWordForward,
258 DeleteWordBackward,
259 InsertAtCursor,
260 InsertAfterCursor,
261 InsertAtLineStart,
262 InsertAtLineEnd,
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn emacs_insert_appends_at_cursor() {
271 let mut e = Editor::new(EditMode::Emacs);
272 e.insert_char('a');
273 e.insert_char('b');
274 e.insert_char('c');
275 assert_eq!(e.buffer(), "abc");
276 assert_eq!(e.cursor(), 3);
277 }
278
279 #[test]
280 fn emacs_kill_to_end_truncates() {
281 let mut e = Editor::with_buffer(EditMode::Emacs, "hello world");
282 e.move_home();
283 for _ in 0..5 {
284 e.move_right();
285 }
286 e.kill_to_end();
287 assert_eq!(e.buffer(), "hello");
288 assert_eq!(e.cursor(), 5);
289 }
290
291 #[test]
292 fn emacs_kill_word_backward_drops_word_and_spaces() {
293 let mut e = Editor::with_buffer(EditMode::Emacs, "hello world");
294 e.move_end();
295 e.kill_word_backward();
296 assert_eq!(e.buffer(), "hello ");
297 }
298
299 #[test]
300 fn delete_backward_at_start_is_noop() {
301 let mut e = Editor::new(EditMode::Emacs);
302 e.delete_backward();
303 assert_eq!(e.buffer(), "");
304 assert_eq!(e.cursor(), 0);
305 }
306
307 #[test]
308 fn move_right_saturates_at_end() {
309 let mut e = Editor::with_buffer(EditMode::Emacs, "ab");
310 e.move_right();
311 e.move_right();
312 e.move_right();
313 assert_eq!(e.cursor(), 2);
314 }
315
316 #[test]
317 fn vim_normal_mode_ignores_insert_char() {
318 let mut e = Editor::new(EditMode::Vim);
319 e.enter_normal_mode();
320 e.insert_char('a');
321 assert_eq!(e.buffer(), "");
322 }
323
324 #[test]
325 fn vim_i_enters_insert_mode() {
326 let mut e = Editor::new(EditMode::Vim);
327 e.enter_normal_mode();
328 e.vim_action(VimAction::InsertAtCursor);
329 assert_eq!(e.vim_mode(), VimMode::Insert);
330 e.insert_char('x');
331 assert_eq!(e.buffer(), "x");
332 }
333
334 #[test]
335 fn vim_word_forward_skips_to_next_token() {
336 let mut e = Editor::with_buffer(EditMode::Vim, "hello world foo");
337 e.enter_normal_mode();
338 e.move_home();
339 e.vim_action(VimAction::WordForward);
340 assert_eq!(e.cursor(), 6);
341 e.vim_action(VimAction::WordForward);
342 assert_eq!(e.cursor(), 12);
343 }
344
345 #[test]
346 fn vim_word_backward_reverses_word_forward() {
347 let mut e = Editor::with_buffer(EditMode::Vim, "hello world foo");
348 e.enter_normal_mode();
349 e.move_end();
350 e.vim_action(VimAction::WordBackward);
351 assert_eq!(e.cursor(), 12);
352 e.vim_action(VimAction::WordBackward);
353 assert_eq!(e.cursor(), 6);
354 }
355
356 #[test]
357 fn vim_dw_deletes_word_forward() {
358 let mut e = Editor::with_buffer(EditMode::Vim, "hello world foo");
359 e.enter_normal_mode();
360 e.move_home();
361 e.vim_action(VimAction::DeleteWordForward);
362 assert_eq!(e.buffer(), "world foo");
363 }
364
365 #[test]
366 fn vim_capital_a_appends_at_eol_in_insert_mode() {
367 let mut e = Editor::with_buffer(EditMode::Vim, "ab");
368 e.enter_normal_mode();
369 e.vim_action(VimAction::InsertAtLineEnd);
370 assert_eq!(e.cursor(), 2);
371 assert_eq!(e.vim_mode(), VimMode::Insert);
372 e.insert_char('c');
373 assert_eq!(e.buffer(), "abc");
374 }
375
376 #[test]
377 fn switching_mode_resets_to_insert() {
378 let mut e = Editor::new(EditMode::Vim);
379 e.enter_normal_mode();
380 e.set_mode(EditMode::Emacs);
381 assert_eq!(e.vim_mode(), VimMode::Insert);
382 }
383}