1
use super::SPACES_PER_TAB;
2
use crate::LineNumber;
3
use crate::{
4
    AnsiPosition, Console, ConsoleSize, Document, Editor, Mode, Operation, OperationType, Position,
5
    Row, RowIndex,
6
};
7
use std::fmt;
8
use std::fs;
9
use std::io::Error;
10
use std::io::Write;
11
use std::path::PathBuf;
12
use tempfile::{tempdir, NamedTempFile};
13
use termion::color;
14
use termion::event::{Event, Key, MouseEvent};
15

            
16
48
#[derive(Default)]
17
struct MockConsole {}
18

            
19
impl Console for MockConsole {
20
    fn read_event(&mut self) -> Result<Event, Error> {
21
        Ok(Event::Key(Key::Char('r')))
22
    }
23

            
24
    fn clear_screen(&self) {}
25

            
26
    fn clear_current_line(&self) {}
27

            
28
    /// # Errors
29
    ///
30
    /// Returns an error if stdout can't be flushed
31
    fn flush(&self) -> Result<(), std::io::Error> {
32
        Ok(())
33
    }
34

            
35
    fn hide_cursor(&self) {}
36

            
37
    fn show_cursor(&self) {}
38

            
39
    fn set_bg_color(&self, _color: color::Rgb) {}
40

            
41
    fn reset_bg_color(&self) {}
42

            
43
    fn set_fg_color(&self, _color: color::Rgb) {}
44

            
45
    fn reset_fg_color(&self) {}
46

            
47
    fn to_alternate_screen(&self) {}
48

            
49
    fn to_main_screen(&self) {}
50

            
51
    fn clear_all(&self) {}
52

            
53
17
    fn set_cursor_as_steady_bar(&self) {}
54

            
55
11
    fn set_cursor_as_steady_block(&self) {}
56

            
57
109
    fn size(&self) -> ConsoleSize {
58
109
        ConsoleSize::default()
59
109
    }
60

            
61
92
    fn text_area_size(&self) -> ConsoleSize {
62
92
        ConsoleSize {
63
            height: 78,
64
            width: 120,
65
        }
66
184
    }
67

            
68
46
    fn middle_of_screen_line_number(&self) -> LineNumber {
69
46
        LineNumber::new(self.text_area_size().height as usize / 2)
70
46
    }
71

            
72
46
    fn bottom_of_screen_line_number(&self) -> LineNumber {
73
46
        LineNumber::new(self.text_area_size().height as usize)
74
46
    }
75

            
76
    fn set_cursor_position_in_text_area(&self, _position: &Position, _row_prefix_length: u8) {}
77

            
78
    fn set_cursor_position_anywhere(&self, _position: &Position) {}
79

            
80
    #[must_use]
81
    fn get_cursor_index_from_mouse_event(
82
        &self,
83
        _mouse_event: MouseEvent,
84
        _x_offset: u8,
85
    ) -> Position {
86
        Position::default()
87
    }
88
}
89

            
90
impl fmt::Debug for MockConsole {
91
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92
        f.debug_struct("Terminal").finish()
93
    }
94
}
95

            
96
41
fn get_short_document() -> Document {
97
41
    let lines: Vec<&str> = vec!["Hellö world", "Hello world!", "Hello world!!"];
98
41
    let mut rows: Vec<Row> = vec![];
99
164
    for line in lines {
100
246
        rows.push(Row::from(line));
101
    }
102
41
    Document::new(rows, PathBuf::from("test"))
103
41
}
104

            
105
4
fn get_long_document() -> Document {
106
4
    let mut rows: Vec<Row> = vec![];
107
804
    for _ in 0..200 {
108
800
        rows.push(Row::from("Some line"));
109
    }
110
4
    Document::new(rows, PathBuf::from("test"))
111
4
}
112

            
113
41
fn get_test_editor() -> Editor {
114
41
    let console = Box::new(MockConsole::default());
115
41
    let mut editor = Editor::new(None, console);
116
41
    editor.document = get_short_document();
117
41
    editor.last_saved_hash = editor.document.hashed();
118
    editor
119
41
}
120

            
121
4
fn get_test_editor_with_long_document() -> Editor {
122
4
    let console = Box::new(MockConsole::default());
123
4
    let mut editor = Editor::new(None, console);
124
4
    editor.document = get_long_document();
125
4
    editor.last_saved_hash = editor.document.hashed();
126
    editor
127
4
}
128

            
129
61
fn assert_position_is(editor: &Editor, x: usize, y: usize) {
130
61
    assert_eq!(editor.cursor_position, Position { x, y });
131
61
}
132

            
133
14
fn assert_nth_row_is(editor: &Editor, n: usize, s: &str) {
134
14
    assert_eq!(
135
14
        editor.document.get_row(RowIndex::new(n)).unwrap().string,
136
14
        String::from(s)
137
    );
138
14
}
139

            
140
3
fn assert_current_line_is(editor: &Editor, s: &str) {
141
3
    assert_eq!(editor.current_row().string, String::from(s));
142
3
}
143

            
144
16
fn process_keystrokes(editor: &mut Editor, keys: Vec<char>) {
145
69
    for c in keys {
146
106
        editor.process_keystroke(Key::Char(c));
147
    }
148
16
}
149

            
150
17
fn process_command(editor: &mut Editor, command: &str) {
151
17
    let mut command = String::from(command);
152
17
    command.push('\n');
153
131
    for c in command.chars() {
154
228
        editor.process_keystroke(Key::Char(c));
155
    }
156
17
}
157

            
158
3
fn process_command_no_enter(editor: &mut Editor, command: &str) {
159
3
    let command = String::from(command);
160
11
    for c in command.chars() {
161
16
        editor.process_keystroke(Key::Char(c));
162
    }
163
3
}
164

            
165
#[test]
166
2
fn test_editor_enter_mode() {
167
1
    let mut editor = get_test_editor();
168
1
    assert_eq!(editor.mode, Mode::Normal); // default mode
169
1
    editor.enter_insert_mode();
170
1
    assert_eq!(editor.mode, Mode::Insert);
171
1
    editor.enter_normal_mode();
172
1
    assert_eq!(editor.mode, Mode::Normal);
173
2
}
174

            
175
#[test]
176
2
fn test_editor_command_buffer() {
177
1
    let mut editor = get_test_editor();
178
1
    assert!(!editor.is_receiving_command());
179
1
    editor.start_receiving_command();
180
1
    editor.command_buffer.push_str("help");
181
1
    assert!(editor.is_receiving_command());
182
1
    editor.stop_receiving_command();
183
1
    assert!(!editor.is_receiving_command());
184
2
}
185

            
186
#[test]
187
2
fn test_editor_pop_normal_command_repetitions() {
188
1
    let mut editor = get_test_editor();
189
1
    editor.normal_command_buffer.push("123".to_string());
190
1
    let times = editor.pop_normal_command_repetitions();
191
1
    assert_eq!(times, 123);
192
1
    assert!(editor.normal_command_buffer.is_empty());
193
2
}
194

            
195
#[test]
196
2
fn test_editor_process_keystroke_command() {
197
1
    let mut editor = get_test_editor();
198

            
199
1
    assert!(!editor.is_receiving_command());
200
1
    editor.process_keystroke(Key::Char(':'));
201
1
    assert!(editor.is_receiving_command());
202
2
}
203

            
204
#[test]
205
2
fn test_editor_process_keystroke_navigation() {
206
1
    let mut editor = get_test_editor();
207

            
208
1
    assert_position_is(&editor, 0, 0);
209

            
210
1
    editor.process_keystroke(Key::Char('j'));
211
1
    assert_position_is(&editor, 0, 1);
212

            
213
1
    editor.process_keystroke(Key::Char('k'));
214
1
    assert_position_is(&editor, 0, 0);
215

            
216
1
    editor.process_keystroke(Key::Char('l'));
217
1
    assert_position_is(&editor, 1, 0);
218

            
219
1
    editor.process_keystroke(Key::Char('h'));
220
1
    assert_position_is(&editor, 0, 0);
221

            
222
1
    process_keystrokes(&mut editor, vec!['2', 'j']);
223
1
    assert_position_is(&editor, 0, 2);
224
2
}
225

            
226
#[test]
227
2
fn test_editor_change_x_position_when_moving_down_or_up() {
228
1
    let mut editor = get_test_editor();
229

            
230
1
    assert_position_is(&editor, 0, 0);
231

            
232
    // move to 3rd line "Hello world!!"
233
1
    process_command(&mut editor, ":3");
234
    // move at the end of the line
235
1
    editor.process_keystroke(Key::Char('$'));
236
1
    assert_position_is(&editor, 12, 2);
237

            
238
    // move up to 2nd line "Hello world!", shorter than "Hello world!".
239
    // After having moved, the cursor should be on the last "!" and not after it
240
1
    editor.process_keystroke(Key::Char('k'));
241
1
    assert_position_is(&editor, 11, 1);
242
2
}
243

            
244
#[test]
245
2
fn test_editor_help_command() {
246
1
    let mut editor = get_test_editor();
247

            
248
1
    assert!(!editor.alternate_screen);
249
1
    process_command(&mut editor, ":help");
250
1
    assert!(editor.alternate_screen);
251
1
    editor.process_keystroke(Key::Char('q'));
252
1
    assert!(!editor.alternate_screen);
253
2
}
254

            
255
#[test]
256
2
fn test_editor_goto_line() {
257
1
    let mut editor = get_test_editor();
258

            
259
1
    assert_position_is(&editor, 0, 0);
260
1
    process_command(&mut editor, ":2");
261
1
    assert_position_is(&editor, 0, 1);
262
1
    assert_eq!(editor.current_line_number(), LineNumber::new(2));
263
2
}
264

            
265
#[test]
266
2
fn test_editor_search() {
267
1
    let mut editor = get_test_editor();
268

            
269
1
    assert!(editor.search_matches.is_empty());
270
1
    process_command(&mut editor, "/world");
271
1
    assert_eq!(editor.search_matches.len(), 3);
272
2
    assert_eq!(
273
        editor.search_matches,
274
1
        vec![
275
1
            (Position { x: 7, y: 1 }, Position { x: 13, y: 1 }),
276
1
            (Position { x: 6, y: 2 }, Position { x: 12, y: 2 }),
277
1
            (Position { x: 6, y: 3 }, Position { x: 12, y: 3 })
278
        ]
279
    );
280
1
    assert_eq!(editor.message, "Match 1/3");
281
1
    assert_eq!(editor.current_search_match_index, 0);
282

            
283
1
    editor.process_keystroke(Key::Char('n'));
284
1
    assert_eq!(editor.current_search_match_index, 1);
285
1
    assert_position_is(&editor, 6, 1);
286

            
287
1
    editor.process_keystroke(Key::Char('n'));
288
1
    assert_eq!(editor.current_search_match_index, 2);
289
1
    assert_position_is(&editor, 6, 2);
290

            
291
1
    editor.process_keystroke(Key::Char('n'));
292
1
    assert_eq!(editor.current_search_match_index, 0);
293
1
    assert_position_is(&editor, 7, 0);
294

            
295
1
    editor.process_keystroke(Key::Char('N'));
296
1
    assert_eq!(editor.current_search_match_index, 2);
297
1
    assert_position_is(&editor, 6, 2);
298

            
299
1
    editor.process_keystroke(Key::Esc);
300
1
    assert!(editor.search_matches.is_empty());
301
1
    assert_eq!(editor.current_search_match_index, 0);
302
2
}
303

            
304
#[test]
305
2
fn test_editor_unknown_command() {
306
1
    let mut editor = get_test_editor();
307

            
308
1
    process_command(&mut editor, ":derp");
309
1
    assert_eq!(
310
        editor.message,
311
        "\u{1b}[38;5;1mUnknown command 'derp'\u{1b}[39m"
312
    );
313
2
}
314

            
315
#[test]
316
2
fn test_editor_navigation() {
317
1
    let mut editor = get_test_editor();
318

            
319
1
    assert_position_is(&editor, 0, 0);
320

            
321
1
    editor.process_keystroke(Key::Char('G'));
322
1
    assert_position_is(&editor, 0, 2);
323

            
324
1
    editor.process_keystroke(Key::Char('g'));
325
1
    assert_position_is(&editor, 0, 0);
326

            
327
1
    editor.process_keystroke(Key::Char('$'));
328
1
    assert_position_is(&editor, 10, 0);
329

            
330
1
    editor.process_keystroke(Key::Char('^'));
331
1
    assert_position_is(&editor, 0, 0);
332

            
333
1
    editor.process_keystroke(Key::Char('w'));
334
1
    assert_position_is(&editor, 6, 0);
335

            
336
1
    editor.process_keystroke(Key::Char('b'));
337
1
    assert_position_is(&editor, 0, 0);
338

            
339
1
    process_keystrokes(&mut editor, vec!['2', 'w']);
340
1
    assert_position_is(&editor, 10, 0);
341

            
342
1
    process_keystrokes(&mut editor, vec!['2', 'b']);
343
1
    assert_position_is(&editor, 0, 0);
344
2
}
345

            
346
#[test]
347
2
fn test_editor_deletion() {
348
1
    let mut editor = get_test_editor();
349

            
350
1
    editor.goto_x_y(1, RowIndex::new(1));
351
1
    editor.process_keystroke(Key::Char('i'));
352
1
    editor.process_keystroke(Key::Backspace);
353
1
    assert_eq!(editor.document.num_rows(), 3);
354
1
    assert_eq!(
355
1
        editor.document.get_row(RowIndex::new(1)).unwrap().string,
356
        "ello world!"
357
    );
358
1
    editor.goto_x_y(0, RowIndex::new(1));
359
1
    editor.process_keystroke(Key::Backspace);
360
1
    assert_eq!(editor.document.num_rows(), 2);
361
1
    assert_eq!(
362
1
        editor.document.get_row(RowIndex::new(0)).unwrap().string,
363
        "Hellö worldello world!"
364
    );
365
1
    assert_eq!(
366
1
        editor.document.get_row(RowIndex::new(1)).unwrap().string,
367
        "Hello world!!"
368
    );
369
2
}
370

            
371
#[test]
372
2
fn test_editor_edition() {
373
1
    let mut editor = get_test_editor();
374

            
375
1
    assert_eq!(editor.document.num_rows(), 3);
376
1
    editor.process_keystroke(Key::Char('o'));
377
1
    assert_position_is(&editor, 0, 1);
378
1
    assert_eq!(editor.document.num_rows(), 4);
379
1
    assert_nth_row_is(&editor, 1, "");
380

            
381
1
    editor.process_keystroke(Key::Esc);
382
1
    editor.process_keystroke(Key::Char('O'));
383
1
    assert_position_is(&editor, 0, 1);
384
1
    assert_eq!(editor.document.num_rows(), 5);
385
1
    assert_nth_row_is(&editor, 1, "");
386
1
    assert_nth_row_is(&editor, 2, "");
387

            
388
1
    editor.process_keystroke(Key::Esc);
389
1
    assert_eq!(editor.document.num_rows(), 5);
390
1
    editor.process_keystroke(Key::Char('d'));
391
1
    assert_eq!(editor.document.num_rows(), 4);
392

            
393
1
    editor.goto_x_y(0, RowIndex::new(1));
394
1
    editor.process_keystroke(Key::Char('i'));
395
1
    assert_eq!(editor.mode, Mode::Insert);
396
1
    process_keystrokes(&mut editor, vec!['b', 'o', 'o', 'p']);
397
1
    assert_nth_row_is(&editor, 1, "boop");
398
1
    editor.process_keystroke(Key::Backspace);
399
1
    assert_nth_row_is(&editor, 1, "boo");
400

            
401
1
    editor.process_keystroke(Key::Esc);
402
1
    assert_eq!(editor.mode, Mode::Normal);
403
1
    process_keystrokes(&mut editor, vec!['^', 'i']);
404
1
    assert_eq!(editor.mode, Mode::Insert);
405
1
    assert_eq!(editor.document.num_rows(), 4);
406
1
    editor.process_keystroke(Key::Backspace);
407
1
    assert_eq!(editor.document.num_rows(), 3);
408
1
    assert_nth_row_is(&editor, 0, "Hellö worldboo");
409

            
410
1
    editor.goto_x_y(11, RowIndex::new(0));
411
1
    assert_position_is(&editor, 11, 0);
412
1
    assert_eq!(editor.document.num_rows(), 3);
413
1
    editor.process_keystroke(Key::Char('\n'));
414
1
    assert_eq!(editor.document.num_rows(), 4);
415
1
    assert_nth_row_is(&editor, 0, "Hellö world");
416
1
    assert_nth_row_is(&editor, 1, "boo");
417
1
    assert_position_is(&editor, 0, 1);
418

            
419
1
    editor.goto_x_y(0, RowIndex::new(0));
420
1
    editor.process_keystroke(Key::Esc);
421
1
    editor.process_keystroke(Key::Char('x'));
422
1
    assert_nth_row_is(&editor, 0, "ellö world");
423

            
424
1
    editor.process_keystroke(Key::Char('A'));
425
1
    assert_eq!(editor.mode, Mode::Insert);
426
1
    assert_position_is(&editor, 10, 0);
427
2
}
428

            
429
#[test]
430
2
fn test_editor_insert_spaces_for_tab() {
431
1
    let mut editor = get_test_editor();
432

            
433
1
    process_keystrokes(&mut editor, vec!['i', '\t']);
434
1
    assert_position_is(&editor, SPACES_PER_TAB, 0);
435
1
    assert_nth_row_is(&editor, 0, "    Hellö world");
436
2
}
437

            
438
#[test]
439
2
fn test_editor_move_cursor_to_position_x() {
440
1
    let mut editor = get_test_editor();
441

            
442
1
    assert_position_is(&editor, 0, 0);
443
1
    editor.move_cursor_to_position_x(1);
444
1
    assert_position_is(&editor, 1, 0);
445
1
    assert_eq!(editor.offset.columns, 0);
446

            
447
1
    editor.move_cursor_to_position_x(140);
448
1
    assert_position_is(&editor, 119, 0);
449
1
    assert_eq!(editor.offset.columns, 21);
450
2
}
451

            
452
#[test]
453
2
fn test_editor_move_cursor_to_position_y() {
454
1
    let mut editor = get_test_editor_with_long_document();
455

            
456
1
    assert_position_is(&editor, 0, 0);
457
1
    assert_eq!(editor.offset.rows, 0);
458

            
459
1
    editor.move_cursor_to_position_y(RowIndex::new(10));
460
1
    assert_position_is(&editor, 0, 10);
461
1
    assert_eq!(editor.current_line_number(), LineNumber::new(11));
462
1
    assert_eq!(editor.offset.rows, 0);
463

            
464
1
    editor.move_cursor_to_position_y(RowIndex::new(199));
465
1
    assert_eq!(editor.current_line_number(), LineNumber::new(200));
466
1
    assert_eq!(editor.current_row_index(), RowIndex::new(199));
467
    // The editor is 78 lines high, and line 78 <--> row 77
468
    // and offset 122 + row 77 = row 199
469
1
    assert_position_is(&editor, 0, 77);
470
1
    assert_eq!(editor.offset.rows, 122);
471

            
472
1
    editor.move_cursor_to_position_y(RowIndex::new(110));
473
1
    assert_eq!(editor.current_line_number(), LineNumber::new(111));
474
    // The editor is 78 lines high, and its middle line is L39,
475
    // which means row 38, and row 38 + offset 72 = row 110
476
1
    assert_position_is(&editor, 0, 38);
477
1
    assert_eq!(editor.offset.rows, 72);
478

            
479
    // We stay in the same view
480
1
    editor.move_cursor_to_position_y(RowIndex::new(112));
481
1
    assert_position_is(&editor, 0, 40);
482
1
    assert_eq!(editor.offset.rows, 72);
483

            
484
    // We move to the last view
485
1
    editor.move_cursor_to_position_y(RowIndex::new(180));
486
1
    assert_eq!(editor.current_line_number(), LineNumber::new(181));
487
    // we see the last 78 lines, meaning the lines we see start at line
488
    // 200 - 78 = 122, and end at line 200.
489
    // Moving to line 181 means that we are located at the position
490
    // y = 181 - 122 - 1 = 58 (-1 because y is a rowindex)
491
1
    assert_position_is(&editor, 0, 58);
492
1
    assert_eq!(editor.offset.rows, 122);
493

            
494
    // We move to the first half view from the last
495
1
    editor.move_cursor_to_position_y(RowIndex::new(10));
496
1
    assert_position_is(&editor, 0, 10);
497
1
    assert_eq!(editor.current_line_number(), LineNumber::new(11));
498
1
    assert_eq!(editor.offset.rows, 0);
499
2
}
500

            
501
#[test]
502
2
fn test_editor_goto_percentage_in_document() {
503
1
    let mut editor = get_test_editor_with_long_document();
504

            
505
1
    process_keystrokes(&mut editor, vec!['1', '0', '%']);
506
1
    assert_position_is(&editor, 0, 19); // line 20
507
2
}
508

            
509
#[test]
510
2
fn test_editor_navigate_long_document() {
511
1
    let mut editor = get_test_editor_with_long_document();
512

            
513
1
    editor.move_cursor_to_position_y(RowIndex::new(110));
514
    // The terminal is 78 lines high, middle line = 39, so middle
515
    // row = 38
516
1
    assert_position_is(&editor, 0, 38);
517
1
    assert_eq!(editor.offset.rows, 72);
518

            
519
1
    editor.process_keystroke(Key::Char('H'));
520
1
    assert_position_is(&editor, 0, 0);
521
1
    assert_eq!(editor.offset.rows, 72);
522

            
523
1
    editor.process_keystroke(Key::Char('M'));
524
1
    assert_position_is(&editor, 0, 38);
525
1
    assert_eq!(editor.offset.rows, 72);
526

            
527
1
    editor.process_keystroke(Key::Char('L'));
528
1
    assert_position_is(&editor, 0, 77);
529
1
    assert_eq!(editor.offset.rows, 72);
530
2
}
531

            
532
#[test]
533
2
fn test_editor_simple_utilities() {
534
1
    let editor = get_test_editor();
535
1
    assert_eq!(editor.current_row_index(), RowIndex::new(0));
536
1
    assert_eq!(editor.current_line_number(), LineNumber::new(1));
537
1
    assert_eq!(editor.current_x_position(), 0);
538
1
    assert_eq!(editor.current_grapheme(), "H");
539
1
    assert_eq!(editor.current_row().string, "Hellö world");
540
2
}
541

            
542
#[test]
543
2
fn test_editor_status() {
544
1
    let mut editor = get_test_editor();
545

            
546
1
    assert_eq!(
547
1
        editor.generate_status(),
548
1
        format!("[test] NORMAL{}Ln 1, Col 1\r", " ".repeat(96))
549
    );
550

            
551
    // insert new characters
552
1
    process_keystrokes(&mut editor, vec!['i', 'o']);
553

            
554
1
    assert_eq!(
555
1
        editor.generate_status(),
556
1
        format!("[test] + INSERT{}Ln 1, Col 2\r", " ".repeat(94))
557
    );
558

            
559
1
    editor.process_keystroke(Key::Esc);
560

            
561
1
    assert_eq!(
562
1
        editor.generate_status(),
563
1
        format!("[test] + NORMAL{}Ln 1, Col 2\r", " ".repeat(94))
564
    );
565

            
566
1
    editor.cursor_position.x = 1;
567
1
    editor.cursor_position.y = 2;
568
1
    assert_eq!(
569
1
        editor.generate_status(),
570
1
        format!("[test] + NORMAL{}Ln 3, Col 2\r", " ".repeat(94))
571
    );
572
1
    editor.cursor_position.x = 0;
573
1
    editor.cursor_position.y = 0;
574

            
575
1
    editor.config.display_stats = true;
576
1
    assert_eq!(
577
1
        editor.generate_status(),
578
1
        format!("[test] + NORMAL{}[3L/6W] Ln 1, Col 1\r", " ".repeat(86))
579
    );
580
2
}
581

            
582
#[test]
583
2
fn test_editor_quit() {
584
1
    let mut editor = get_test_editor();
585
1
    assert!(!editor.should_quit);
586
1
    assert!(!editor.is_dirty());
587
1
    editor.quit(false);
588
1
    assert!(editor.should_quit);
589

            
590
1
    editor.should_quit = false;
591
    // insert new characters
592
1
    process_keystrokes(&mut editor, vec!['i', 'o']);
593

            
594
1
    assert!(!editor.should_quit);
595
1
    editor.quit(false);
596
1
    assert!(!editor.should_quit);
597
1
    assert_eq!(
598
        editor.message,
599
        "\u{1b}[38;5;1mUnsaved changes! Run :q! to override\u{1b}[39m"
600
    );
601

            
602
1
    editor.quit(true);
603
1
    assert!(editor.should_quit);
604
2
}
605

            
606
#[test]
607
2
fn test_editor_join_lines() {
608
1
    let mut editor = get_test_editor();
609
    // Go to end of line and join it with the next one
610
1
    process_keystrokes(&mut editor, vec!['$', 'J']);
611
1
    assert_nth_row_is(&editor, 0, "Hellö world Hello world!");
612
1
    assert_eq!(editor.document.num_rows(), 2);
613
2
}
614

            
615
#[test]
616
2
fn test_editor_edit_long_document() {
617
1
    let mut editor = get_test_editor_with_long_document();
618
1
    assert_eq!(editor.document.num_rows(), 200);
619
1
    assert_eq!(editor.mode, Mode::Normal);
620
1
    editor.move_cursor_to_position_y(RowIndex::new(110));
621
    // line 111
622
    // terminal height is 78, and we're positioned at line 39, meaning
623
    // row 38, offset is 110 - 38 = 72
624
1
    assert_position_is(&editor, 0, 38);
625
1
    assert_eq!(editor.offset.rows, 72);
626

            
627
    // Go to Insert mode and append a new line
628
1
    editor.process_keystroke(Key::Char('o'));
629
1
    assert_eq!(editor.mode, Mode::Insert);
630
1
    assert_eq!(editor.document.num_rows(), 201);
631
1
    assert_position_is(&editor, 0, 39);
632
1
    assert_eq!(editor.offset.rows, 72);
633

            
634
    // write some text
635
1
    process_keystrokes(&mut editor, vec!['d', 'e', 'r', 'p']);
636
1
    assert_eq!(editor.document.num_rows(), 201);
637
1
    assert_current_line_is(&editor, "derp");
638
1
    assert_position_is(&editor, 4, 39);
639
1
    assert_eq!(editor.offset.rows, 72);
640

            
641
    // enter newline
642
1
    editor.process_keystroke(Key::Char('\n'));
643
1
    assert_eq!(editor.document.num_rows(), 202);
644
1
    assert_position_is(&editor, 0, 40);
645
1
    assert_eq!(editor.offset.rows, 72);
646
1
    assert_current_line_is(&editor, "");
647

            
648
    // delete line
649
1
    editor.process_keystroke(Key::Backspace);
650
1
    assert_position_is(&editor, 4, 39);
651
1
    assert_current_line_is(&editor, "derp");
652
2
}
653

            
654
#[test]
655
2
fn test_position_from_ansiposition() {
656
1
    let ap = AnsiPosition { x: 10, y: 8 }; // 1-indexed
657
2
    let p = Position::from(ap); // 0-indexed
658
1
    assert_eq!(p.x, 9);
659
1
    assert_eq!(p.y, 7);
660
2
}
661

            
662
#[test]
663
2
fn test_editor_serialize() {
664
1
    let editor = get_test_editor();
665
1
    let serialized_editor = serde_json::to_string_pretty(&editor).unwrap();
666
1
    assert_eq!(
667
        serialized_editor,
668
        r#"{
669
  "cursor_position": {
670
    "x": 0,
671
    "y": 0
672
  },
673
  "offset": {
674
    "rows": 0,
675
    "columns": 0
676
  },
677
  "mode": "NORMAL",
678
  "command_buffer": "",
679
  "normal_command_buffer": [],
680
  "search_matches": [],
681
  "current_search_match_index": 0,
682
  "unsaved_edits": 0,
683
  "last_saved_hash": 6051608862860543045,
684
  "row_prefix_length": 0,
685
  "document": {
686
    "rows": [
687
      {
688
        "string": "Hellö world"
689
      },
690
      {
691
        "string": "Hello world!"
692
      },
693
      {
694
        "string": "Hello world!!"
695
      }
696
    ],
697
    "filename": "test"
698
  },
699
  "command_suggestions": [],
700
  "current_autocompletion_index": 0
701
}"#
702
    );
703
2
}
704

            
705
#[test]
706
2
fn test_open_existing_file() {
707
1
    let console = Box::new(MockConsole::default());
708
1
    let mut f = NamedTempFile::new().unwrap();
709
1
    f.write_all("Hello\nHello!\nHello!!\n".as_bytes()).unwrap();
710
1
    let f_name_pathbuf: PathBuf = f.path().to_path_buf();
711
1
    let f_name_str: String = f_name_pathbuf.to_str().unwrap().to_string(); // gawd
712
1
    let editor = Editor::new(Some(f_name_str), console);
713
1
    assert_eq!(editor.document.filename, Some(f_name_pathbuf));
714
2
}
715

            
716
#[test]
717
2
fn test_stop_receiving_command_after_processing_esc_key() {
718
1
    let mut editor = get_test_editor();
719
1
    editor.process_keystroke(Key::Char(':'));
720
1
    assert!(editor.is_receiving_command());
721
1
    editor.process_keystroke(Key::Esc);
722
1
    assert!(!editor.is_receiving_command());
723
2
}
724

            
725
#[test]
726
2
fn test_process_backspace_mid_receiving_command() {
727
1
    let mut editor = get_test_editor();
728
1
    process_keystrokes(&mut editor, vec![':', 'o']);
729
1
    assert!(editor.is_receiving_command());
730
1
    assert_eq!(editor.command_buffer, String::from(":o"));
731
1
    editor.process_keystroke(Key::Backspace);
732
1
    assert!(editor.is_receiving_command());
733
1
    assert_eq!(editor.command_buffer, String::from(":"));
734
2
}
735

            
736
#[test]
737
2
fn test_open_non_existing_file() {
738
1
    let mut editor = get_test_editor();
739
1
    process_command(&mut editor, ":o nope.txt");
740
    // the file will be opened but unsaved
741
1
    assert_eq!(editor.document.filename, Some(PathBuf::from("nope.txt")));
742
2
}
743

            
744
#[test]
745
2
fn test_new_file() {
746
1
    let mut editor = get_test_editor();
747
1
    process_command(&mut editor, ":new nope.txt");
748
    // the file will be opened but unsaved
749
1
    assert_eq!(editor.document.filename, Some(PathBuf::from("nope.txt")));
750
2
}
751

            
752
#[test]
753
2
fn test_save_file() {
754
1
    let console = Box::new(MockConsole::default());
755
1
    let f = NamedTempFile::new().unwrap();
756
1
    let f_name_pathbuf: PathBuf = f.path().to_path_buf();
757
1
    let f_name_str: String = f_name_pathbuf.to_str().unwrap().to_string(); // gawd
758
1
    let mut editor = Editor::new(Some(f_name_str), console);
759

            
760
1
    process_keystrokes(&mut editor, vec!['i', 'h', 'e', 'l', 'l', 'o']);
761
1
    editor.process_keystroke(Key::Esc);
762
1
    process_command(&mut editor, ":w");
763
1
    assert_eq!(editor.unsaved_edits, 0);
764

            
765
1
    let content = fs::read_to_string(f).unwrap();
766
1
    assert_eq!(content, "hello\n");
767
2
}
768

            
769
#[test]
770
2
fn test_save_file_trim_whitespaces() {
771
1
    let console = Box::new(MockConsole::default());
772
1
    let f = NamedTempFile::new().unwrap();
773
1
    let f_name_pathbuf: PathBuf = f.path().to_path_buf();
774
1
    let f_name_str: String = f_name_pathbuf.to_str().unwrap().to_string(); // gawd
775
1
    let mut editor = Editor::new(Some(f_name_str), console);
776

            
777
1
    process_keystrokes(&mut editor, vec!['i', ' ', 'h', 'e', 'l', 'l', 'o', ' ']);
778
1
    editor.process_keystroke(Key::Esc);
779
1
    process_command(&mut editor, ":w");
780
1
    assert_eq!(editor.unsaved_edits, 0);
781

            
782
1
    let content = fs::read_to_string(f).unwrap();
783
1
    assert_eq!(content, " hello\n"); // trailing whitespace has been removed
784
2
}
785

            
786
#[test]
787
2
fn test_display_line_numbers() {
788
1
    let mut editor = get_test_editor();
789
1
    assert!(!editor.config.display_line_numbers);
790
1
    process_command(&mut editor, ":ln");
791
1
    assert!(editor.config.display_line_numbers);
792
1
    process_command(&mut editor, ":ln");
793
1
    assert!(!editor.config.display_line_numbers);
794
2
}
795

            
796
#[test]
797
2
fn test_display_stats() {
798
1
    let mut editor = get_test_editor();
799
1
    assert!(!editor.config.display_stats);
800
1
    process_command(&mut editor, ":stats");
801
1
    assert!(editor.config.display_stats);
802
1
    process_command(&mut editor, ":stats");
803
1
    assert!(!editor.config.display_stats);
804
2
}
805

            
806
#[test]
807
2
fn test_go_to_start_of_line() {
808
1
    let mut editor = get_test_editor();
809
1
    editor.process_keystroke(Key::Char('w'));
810
1
    assert_position_is(&editor, 6, 0);
811
1
    editor.process_keystroke(Key::Char('0'));
812
1
    assert_position_is(&editor, 0, 0);
813
2
}
814

            
815
#[test]
816
2
fn test_goto_matching_closing_symbol() {
817
1
    let mut editor = get_test_editor();
818
1
    editor.process_keystroke(Key::Char('A'));
819
1
    process_keystrokes(&mut editor, vec!['(', 'o', 'h', ')']);
820
1
    let first_line_content = editor
821
        .document
822
1
        .get_row(RowIndex::new(0))
823
        .unwrap()
824
        .string
825
        .clone();
826
1
    assert_eq!(first_line_content.chars().nth(11), Some('('));
827
1
    assert_eq!(first_line_content.chars().nth(14), Some(')'));
828
1
    editor.cursor_position = Position { x: 11, y: 0 }; // first paren
829
1
    editor.process_keystroke(Key::Esc);
830
1
    editor.process_keystroke(Key::Char('m'));
831
1
    assert_position_is(&editor, 14, 0);
832
2
}
833

            
834
#[test]
835
2
fn test_move_by_paragraph() {
836
1
    let mut editor = get_test_editor();
837
1
    assert_position_is(&editor, 0, 0);
838
1
    editor.process_keystroke(Key::Char('}'));
839
1
    assert_position_is(&editor, 0, 2);
840
1
    editor.process_keystroke(Key::Char('{'));
841
1
    assert_position_is(&editor, 0, 0);
842
2
}
843

            
844
#[test]
845
2
fn test_delete_last_line() {
846
1
    let mut editor = get_test_editor();
847
1
    assert_eq!(editor.document.num_rows(), 3);
848
1
    editor.process_keystroke(Key::Char('G'));
849
1
    assert_position_is(&editor, 0, 2);
850
1
    editor.process_keystroke(Key::Char('d'));
851
1
    assert_eq!(editor.document.num_rows(), 2);
852
1
    assert_position_is(&editor, 0, 1);
853
2
}
854

            
855
#[test]
856
2
fn test_delete_line_longer_than_previous_one() {
857
1
    let mut editor = get_test_editor();
858
1
    editor.process_keystroke(Key::Char('j')); // go down a line
859
1
    editor.process_keystroke(Key::Char('j')); // go down a line
860
1
    editor.process_keystroke(Key::Char('A')); // go to last character
861
1
    editor.process_keystroke(Key::Esc); // go to last character
862
1
    assert_position_is(&editor, 13, 2);
863
1
    editor.process_keystroke(Key::Char('d'));
864
1
    assert_position_is(&editor, 12, 1);
865
2
}
866

            
867
#[test]
868
2
fn test_process_command_not_found() {
869
1
    let mut editor = get_test_editor();
870
1
    process_command(&mut editor, ":nope");
871
1
    assert_eq!(editor.message, r#"Unknown command 'nope'"#);
872
2
}
873

            
874
#[test]
875
2
fn test_save_and_quit() {
876
1
    let dir = tempdir().unwrap();
877
2
    if std::env::set_current_dir(&dir).is_ok() {
878
1
        let mut editor = get_test_editor();
879
1
        process_keystrokes(&mut editor, vec!['G', 'o', 'd', 'e', 'r', 'p']);
880
1
        editor.process_keystroke(Key::Esc);
881
1
        assert_eq!(editor.unsaved_edits, 4);
882
1
        assert!(!editor.should_quit);
883
1
        process_command(&mut editor, ":wq");
884
1
        assert_eq!(editor.unsaved_edits, 0);
885
1
        assert!(editor.should_quit);
886
1
    };
887
2
}
888

            
889
#[test]
890
2
fn test_process_command_autocompletions() {
891
1
    let dir = tempdir().unwrap();
892
2
    if std::env::set_current_dir(&dir).is_ok() {
893
1
        let mut editor = get_test_editor();
894
1
        process_command_no_enter(&mut editor, ":w"); // this could be expanded into :w or :wq
895
1
        assert_eq!(editor.current_autocompletion_index, 0);
896
1
        assert!(!editor.is_autocompleting_command());
897

            
898
        // trigger an autocompletion
899
1
        editor.process_keystroke(Key::Char('\t'));
900
1
        assert!(editor.is_autocompleting_command());
901
1
        assert_eq!(editor.command_suggestions, vec!["w", "wq"]);
902
1
        assert_eq!(editor.current_autocompletion_index, 0);
903

            
904
        // Cycle through the completion suggestions
905
1
        editor.process_keystroke(Key::Char('\t'));
906
1
        assert_eq!(editor.current_autocompletion_index, 1);
907
1
        editor.process_keystroke(Key::Char('\t'));
908
1
        assert_eq!(editor.current_autocompletion_index, 0);
909

            
910
        // Hit Enter once the proper suggestion is selected
911
1
        editor.process_keystroke(Key::Char('\n'));
912
1
        assert_eq!(editor.command_buffer, "");
913
1
        assert_eq!(editor.current_autocompletion_index, 0);
914
1
        assert!(editor.command_suggestions.is_empty());
915
1
        assert!(!editor.is_autocompleting_command());
916
1
    }
917
2
}
918

            
919
#[test]
920
2
fn test_process_command_autocompletions_and_keep_typing() {
921
1
    let mut editor = get_test_editor();
922
1
    process_command_no_enter(&mut editor, ":w"); // this could be expanded into :w or :wq
923
1
    assert_eq!(editor.current_autocompletion_index, 0);
924
1
    assert!(!editor.is_autocompleting_command());
925

            
926
    // trigger an autocompletion
927
1
    editor.process_keystroke(Key::Char('\t'));
928
1
    assert!(editor.is_autocompleting_command());
929
1
    assert_eq!(editor.command_suggestions, vec!["w", "wq"]);
930
1
    assert_eq!(editor.current_autocompletion_index, 0);
931

            
932
    // Ignore completions and keep typing
933
1
    editor.process_keystroke(Key::Char('a'));
934
1
    assert!(!editor.is_autocompleting_command());
935
1
    assert!(editor.command_suggestions.is_empty());
936
1
    assert_eq!(editor.command_buffer, ":wa");
937
2
}
938

            
939
#[test]
940
2
fn test_autocompletion_single_suggestion() {
941
1
    let mut editor = get_test_editor();
942
1
    process_command_no_enter(&mut editor, ":deb"); // this could be expanded into :w or :wq
943
1
    assert_eq!(editor.current_autocompletion_index, 0);
944
1
    assert!(!editor.is_autocompleting_command());
945

            
946
    // trigger an autocompletion
947
1
    editor.process_keystroke(Key::Char('\t'));
948
1
    assert!(!editor.is_autocompleting_command());
949
1
    assert!(editor.command_suggestions.is_empty());
950
1
    assert_eq!(editor.current_autocompletion_index, 0);
951
1
    assert_eq!(editor.command_buffer, ":debug");
952
2
}
953

            
954
#[test]
955
2
fn test_undo_insert() {
956
1
    let mut editor = get_test_editor();
957

            
958
    // We simulate that the 3 lines were written in 2 separate operations
959
1
    editor.history.operations.push_back(Operation {
960
1
        op_type: OperationType::Insert,
961
1
        content: String::from("Hello world\n"),
962
1
        start_position: Position { x: 0, y: 0 },
963
    });
964
1
    editor.history.operations.push_back(Operation {
965
1
        op_type: OperationType::Insert,
966
1
        content: String::from("Hello world!\nHello world!!"),
967
1
        start_position: Position { x: 0, y: 1 },
968
    });
969
1
    assert_eq!(editor.document.num_rows(), 3);
970
1
    assert_eq!(editor.history.operations.len(), 2);
971

            
972
    // undo last insertion
973
1
    editor.process_keystroke(Key::Char('u'));
974
1
    assert_eq!(editor.history.operations.len(), 1);
975
1
    assert_eq!(editor.document.num_rows(), 2); // L1 = "Hello world" and L2 = ""
976
1
    assert_position_is(&editor, 0, 1);
977
2
}
978

            
979
#[test]
980
2
fn test_undo_delete() {
981
1
    let mut editor = get_test_editor();
982

            
983
    // We simulate that the 3 lines were written in 2 separate operations
984
1
    editor.history.operations.push_back(Operation {
985
1
        op_type: OperationType::Insert,
986
1
        content: String::from("Hello world\n"),
987
1
        start_position: Position { x: 0, y: 0 },
988
    });
989
1
    editor.history.operations.push_back(Operation {
990
1
        op_type: OperationType::Insert,
991
1
        content: String::from("Hello world!\nHello world!!"),
992
1
        start_position: Position { x: 0, y: 1 },
993
    });
994

            
995
    // We now simulate that we deleted the " world!!" at the end of the last line
996
1
    editor.history.operations.push_back(Operation {
997
1
        op_type: OperationType::Delete,
998
1
        content: String::from("!!dlrow "),
999
1
        start_position: Position { x: 14, y: 2 },
    });
1
    editor.document.delete_row(RowIndex::new(2));
1
    editor
        .document
1
        .insert_string("\nHello", 11, RowIndex::new(1));
    // at that point the 3rd row contains the string "Hello"
1
    assert_eq!(editor.document.num_rows(), 3);
1
    assert_eq!(editor.history.operations.len(), 3);
    // undo last deletion
1
    editor.process_keystroke(Key::Char('u'));
1
    assert_eq!(editor.history.operations.len(), 2);
1
    assert_eq!(
1
        editor.document.get_row(RowIndex::new(2)).unwrap().string,
        "Hello world!!"
    );
2
}
#[test]
2
fn test_delete_line_then_undo() {
1
    let mut editor = get_test_editor();
1
    editor.process_keystroke(Key::Char('j'));
1
    assert_nth_row_is(&editor, 1, "Hello world!");
1
    editor.process_keystroke(Key::Char('d'));
1
    assert_nth_row_is(&editor, 1, "Hello world!!");
1
    assert_eq!(
1
        editor.history.operations.back().unwrap().content,
        "!dlrow olleH\n"
    );
1
    editor.process_keystroke(Key::Char('u'));
1
    assert_nth_row_is(&editor, 1, "Hello world!");
2
}
#[test]
2
fn test_repoen_same_file() {
1
    let mut editor = get_test_editor();
1
    process_command(&mut editor, ":open test");
1
    assert_eq!(editor.message, "test is already opened");
2
}
#[test]
2
fn test_reset_history_at_open() {
1
    let mut editor = get_test_editor();
    // We simulate that the 3 lines were written in one operation
1
    editor.history.operations.push_back(Operation {
1
        op_type: OperationType::Insert,
1
        content: String::from("Hello world\nHello world!\nHello world!!"),
1
        start_position: Position { x: 0, y: 0 },
    });
1
    process_command(&mut editor, ":open newfile");
1
    assert!(editor.history.operations.is_empty());
2
}