1mod engine;
4mod text;
5mod types;
6
7pub use engine::{Editor, HandleKeyOutcome, handle_key};
8pub use types::{VimMode, VimState};
9
10#[cfg(test)]
11mod tests {
12 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
13
14 use super::{Editor, VimState, handle_key};
15
16 #[derive(Debug)]
17 struct TestEditor {
18 content: String,
19 cursor: usize,
20 }
21
22 impl TestEditor {
23 fn new(content: &str, cursor: usize) -> Self {
24 Self {
25 content: content.to_string(),
26 cursor,
27 }
28 }
29 }
30
31 impl Editor for TestEditor {
32 fn content(&self) -> &str {
33 &self.content
34 }
35
36 fn cursor(&self) -> usize {
37 self.cursor
38 }
39
40 fn set_cursor(&mut self, pos: usize) {
41 self.cursor = pos.min(self.content.len());
42 }
43
44 fn move_left(&mut self) {
45 if self.cursor == 0 {
46 return;
47 }
48 let mut pos = self.cursor - 1;
49 while pos > 0 && !self.content.is_char_boundary(pos) {
50 pos -= 1;
51 }
52 self.cursor = pos;
53 }
54
55 fn move_right(&mut self) {
56 if self.cursor >= self.content.len() {
57 return;
58 }
59 let mut pos = self.cursor + 1;
60 while pos < self.content.len() && !self.content.is_char_boundary(pos) {
61 pos += 1;
62 }
63 self.cursor = pos;
64 }
65
66 fn delete_char_forward(&mut self) {
67 if self.cursor >= self.content.len() {
68 return;
69 }
70 let mut end = self.cursor + 1;
71 while end < self.content.len() && !self.content.is_char_boundary(end) {
72 end += 1;
73 }
74 self.content.drain(self.cursor..end);
75 }
76
77 fn insert_text(&mut self, text: &str) {
78 self.content.insert_str(self.cursor, text);
79 self.cursor += text.len();
80 }
81
82 fn replace(&mut self, content: String, cursor: usize) {
83 self.content = content;
84 self.cursor = cursor.min(self.content.len());
85 }
86 }
87
88 fn enable_normal_mode(state: &mut VimState, editor: &mut TestEditor, clipboard: &mut String) {
89 state.set_enabled(true);
90 let outcome = handle_key(
91 state,
92 editor,
93 clipboard,
94 &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE),
95 );
96 assert!(outcome.handled);
97 }
98
99 #[test]
100 fn control_shortcuts_remain_unhandled() {
101 let mut state = VimState::new(true);
102 let mut editor = TestEditor::new("hello", 5);
103 let mut clipboard = String::new();
104
105 let outcome = handle_key(
106 &mut state,
107 &mut editor,
108 &mut clipboard,
109 &KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL),
110 );
111
112 assert!(!outcome.handled);
113 assert_eq!(editor.content, "hello");
114 }
115
116 #[test]
117 fn dd_deletes_current_line() {
118 let mut state = VimState::new(false);
119 let mut editor = TestEditor::new("one\ntwo\nthree", 4);
120 let mut clipboard = String::new();
121 enable_normal_mode(&mut state, &mut editor, &mut clipboard);
122
123 assert!(
124 handle_key(
125 &mut state,
126 &mut editor,
127 &mut clipboard,
128 &KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE),
129 )
130 .handled
131 );
132 assert!(
133 handle_key(
134 &mut state,
135 &mut editor,
136 &mut clipboard,
137 &KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE),
138 )
139 .handled
140 );
141
142 assert_eq!(editor.content, "one\nthree");
143 assert_eq!(editor.cursor, 4);
144 }
145
146 #[test]
147 fn dot_repeats_change_word_edit() {
148 let mut state = VimState::new(false);
149 let mut editor = TestEditor::new("alpha beta", 0);
150 let mut clipboard = String::new();
151 enable_normal_mode(&mut state, &mut editor, &mut clipboard);
152
153 let _ = handle_key(
154 &mut state,
155 &mut editor,
156 &mut clipboard,
157 &KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE),
158 );
159 let _ = handle_key(
160 &mut state,
161 &mut editor,
162 &mut clipboard,
163 &KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE),
164 );
165 editor.insert_text("A");
166 let _ = handle_key(
167 &mut state,
168 &mut editor,
169 &mut clipboard,
170 &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE),
171 );
172
173 editor.set_cursor(1);
174 let _ = handle_key(
175 &mut state,
176 &mut editor,
177 &mut clipboard,
178 &KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE),
179 );
180
181 assert_eq!(editor.content, "AA");
182 }
183
184 #[test]
185 fn dot_repeats_change_line_edit() {
186 let mut state = VimState::new(false);
187 let mut editor = TestEditor::new("one\ntwo", 0);
188 let mut clipboard = String::new();
189 enable_normal_mode(&mut state, &mut editor, &mut clipboard);
190
191 let _ = handle_key(
192 &mut state,
193 &mut editor,
194 &mut clipboard,
195 &KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE),
196 );
197 let _ = handle_key(
198 &mut state,
199 &mut editor,
200 &mut clipboard,
201 &KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE),
202 );
203 editor.insert_text("ONE");
204 let _ = handle_key(
205 &mut state,
206 &mut editor,
207 &mut clipboard,
208 &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE),
209 );
210
211 editor.set_cursor(4);
212 let _ = handle_key(
213 &mut state,
214 &mut editor,
215 &mut clipboard,
216 &KeyEvent::new(KeyCode::Char('.'), KeyModifiers::NONE),
217 );
218
219 assert_eq!(editor.content, "ONE\nONE");
220 }
221
222 #[test]
223 fn vertical_motion_preserves_preferred_column() {
224 let mut state = VimState::new(false);
225 let mut editor = TestEditor::new("abcd\nxy\nabcd", 3);
226 let mut clipboard = String::new();
227 enable_normal_mode(&mut state, &mut editor, &mut clipboard);
228
229 let _ = handle_key(
230 &mut state,
231 &mut editor,
232 &mut clipboard,
233 &KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE),
234 );
235 assert_eq!(editor.cursor, 7);
236
237 let _ = handle_key(
238 &mut state,
239 &mut editor,
240 &mut clipboard,
241 &KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE),
242 );
243 assert_eq!(editor.cursor, 11);
244
245 let _ = handle_key(
246 &mut state,
247 &mut editor,
248 &mut clipboard,
249 &KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE),
250 );
251 assert_eq!(editor.cursor, 7);
252 }
253
254 #[test]
255 fn find_repeat_reuses_last_character_search() {
256 let mut state = VimState::new(false);
257 let mut editor = TestEditor::new("a b c b", 0);
258 let mut clipboard = String::new();
259 enable_normal_mode(&mut state, &mut editor, &mut clipboard);
260
261 let _ = handle_key(
262 &mut state,
263 &mut editor,
264 &mut clipboard,
265 &KeyEvent::new(KeyCode::Char('f'), KeyModifiers::NONE),
266 );
267 let _ = handle_key(
268 &mut state,
269 &mut editor,
270 &mut clipboard,
271 &KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE),
272 );
273 assert_eq!(editor.cursor, 2);
274
275 let _ = handle_key(
276 &mut state,
277 &mut editor,
278 &mut clipboard,
279 &KeyEvent::new(KeyCode::Char(';'), KeyModifiers::NONE),
280 );
281 assert_eq!(editor.cursor, 6);
282
283 let _ = handle_key(
284 &mut state,
285 &mut editor,
286 &mut clipboard,
287 &KeyEvent::new(KeyCode::Char(','), KeyModifiers::NONE),
288 );
289 assert_eq!(editor.cursor, 2);
290 }
291}