rustyline/
edit.rs

1//! Command processor
2
3use log::debug;
4use std::fmt;
5use unicode_segmentation::UnicodeSegmentation;
6use unicode_width::UnicodeWidthChar;
7
8use super::{Context, Helper, Result};
9use crate::error::ReadlineError;
10use crate::highlight::Highlighter;
11use crate::hint::Hint;
12use crate::history::SearchDirection;
13use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
14use crate::keymap::{InputState, Invoke, Refresher};
15use crate::layout::{Layout, Position};
16use crate::line_buffer::{
17    ChangeListener, DeleteListener, Direction, LineBuffer, NoListener, WordAction, MAX_LINE,
18};
19use crate::tty::{Renderer, Term, Terminal};
20use crate::undo::Changeset;
21use crate::validate::{ValidationContext, ValidationResult};
22use crate::KillRing;
23
24/// Represent the state during line editing.
25/// Implement rendering.
26pub struct State<'out, 'prompt, H: Helper> {
27    pub out: &'out mut <Terminal as Term>::Writer,
28    prompt: &'prompt str,  // Prompt to display (rl_prompt)
29    prompt_size: Position, // Prompt Unicode/visible width and height
30    pub line: LineBuffer,  // Edited line buffer
31    pub layout: Layout,
32    saved_line_for_history: LineBuffer, // Current edited line before history browsing
33    byte_buffer: [u8; 4],
34    pub changes: Changeset, // changes to line, for undo/redo
35    pub helper: Option<&'out H>,
36    pub ctx: Context<'out>,          // Give access to history for `hinter`
37    pub hint: Option<Box<dyn Hint>>, // last hint displayed
38    pub highlight_char: bool,        // `true` if a char has been highlighted
39    pub forced_refresh: bool,        // `true` if line is redraw without hint or highlight_char
40}
41
42enum Info<'m> {
43    NoHint,
44    Hint,
45    Msg(Option<&'m str>),
46}
47
48impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
49    pub fn new(
50        out: &'out mut <Terminal as Term>::Writer,
51        prompt: &'prompt str,
52        helper: Option<&'out H>,
53        ctx: Context<'out>,
54    ) -> State<'out, 'prompt, H> {
55        let prompt_size = out.calculate_position(prompt, Position::default());
56        State {
57            out,
58            prompt,
59            prompt_size,
60            line: LineBuffer::with_capacity(MAX_LINE).can_growth(true),
61            layout: Layout::default(),
62            saved_line_for_history: LineBuffer::with_capacity(MAX_LINE).can_growth(true),
63            byte_buffer: [0; 4],
64            changes: Changeset::new(),
65            helper,
66            ctx,
67            hint: None,
68            highlight_char: false,
69            forced_refresh: false,
70        }
71    }
72
73    pub fn highlighter(&self) -> Option<&dyn Highlighter> {
74        if self.out.colors_enabled() {
75            self.helper.map(|h| h as &dyn Highlighter)
76        } else {
77            None
78        }
79    }
80
81    pub fn next_cmd(
82        &mut self,
83        input_state: &mut InputState,
84        rdr: &mut <Terminal as Term>::Reader,
85        single_esc_abort: bool,
86        ignore_external_print: bool,
87    ) -> Result<Cmd> {
88        loop {
89            let rc = input_state.next_cmd(rdr, self, single_esc_abort, ignore_external_print);
90            if let Err(ReadlineError::WindowResized) = rc {
91                debug!(target: "rustyline", "SIGWINCH");
92                let old_cols = self.out.get_columns();
93                self.out.update_size();
94                let new_cols = self.out.get_columns();
95                if new_cols != old_cols
96                    && (self.layout.end.row > 0 || self.layout.end.col >= new_cols)
97                {
98                    self.prompt_size = self
99                        .out
100                        .calculate_position(self.prompt, Position::default());
101                    self.refresh_line()?;
102                }
103                continue;
104            }
105            if let Ok(Cmd::Replace(..)) = rc {
106                self.changes.begin();
107            }
108            return rc;
109        }
110    }
111
112    pub fn backup(&mut self) {
113        self.saved_line_for_history
114            .update(self.line.as_str(), self.line.pos(), &mut NoListener);
115    }
116
117    pub fn restore(&mut self) {
118        self.line.update(
119            self.saved_line_for_history.as_str(),
120            self.saved_line_for_history.pos(),
121            &mut self.changes,
122        );
123    }
124
125    pub fn move_cursor(&mut self) -> Result<()> {
126        // calculate the desired position of the cursor
127        let cursor = self
128            .out
129            .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
130        if self.layout.cursor == cursor {
131            return Ok(());
132        }
133        if self.highlight_char() {
134            let prompt_size = self.prompt_size;
135            self.refresh(self.prompt, prompt_size, true, Info::NoHint)?;
136        } else {
137            self.out.move_cursor(self.layout.cursor, cursor)?;
138            self.layout.prompt_size = self.prompt_size;
139            self.layout.cursor = cursor;
140            debug_assert!(self.layout.prompt_size <= self.layout.cursor);
141            debug_assert!(self.layout.cursor <= self.layout.end);
142        }
143        Ok(())
144    }
145
146    pub fn move_cursor_to_end(&mut self) -> Result<()> {
147        if self.layout.cursor == self.layout.end {
148            return Ok(());
149        }
150        self.out.move_cursor(self.layout.cursor, self.layout.end)?;
151        self.layout.cursor = self.layout.end;
152        Ok(())
153    }
154
155    pub fn move_cursor_at_leftmost(&mut self, rdr: &mut <Terminal as Term>::Reader) -> Result<()> {
156        self.out.move_cursor_at_leftmost(rdr)
157    }
158
159    fn refresh(
160        &mut self,
161        prompt: &str,
162        prompt_size: Position,
163        default_prompt: bool,
164        info: Info<'_>,
165    ) -> Result<()> {
166        let info = match info {
167            Info::NoHint => None,
168            Info::Hint => self.hint.as_ref().map(|h| h.display()),
169            Info::Msg(msg) => msg,
170        };
171        let highlighter = if self.out.colors_enabled() {
172            self.helper.map(|h| h as &dyn Highlighter)
173        } else {
174            None
175        };
176
177        let new_layout = self
178            .out
179            .compute_layout(prompt_size, default_prompt, &self.line, info);
180
181        debug!(target: "rustyline", "old layout: {:?}", self.layout);
182        debug!(target: "rustyline", "new layout: {:?}", new_layout);
183        self.out.refresh_line(
184            prompt,
185            &self.line,
186            info,
187            &self.layout,
188            &new_layout,
189            highlighter,
190        )?;
191        self.layout = new_layout;
192
193        Ok(())
194    }
195
196    pub fn hint(&mut self) {
197        if let Some(hinter) = self.helper {
198            let hint = hinter.hint(self.line.as_str(), self.line.pos(), &self.ctx);
199            self.hint = match hint {
200                Some(val) if !val.display().is_empty() => Some(Box::new(val) as Box<dyn Hint>),
201                _ => None,
202            };
203        } else {
204            self.hint = None;
205        }
206    }
207
208    fn highlight_char(&mut self) -> bool {
209        if let Some(highlighter) = self.highlighter() {
210            let highlight_char =
211                highlighter.highlight_char(&self.line, self.line.pos(), self.forced_refresh);
212            if highlight_char {
213                self.highlight_char = true;
214                true
215            } else if self.highlight_char {
216                // previously highlighted => force a full refresh
217                self.highlight_char = false;
218                true
219            } else {
220                false
221            }
222        } else {
223            false
224        }
225    }
226
227    pub fn is_default_prompt(&self) -> bool {
228        self.layout.default_prompt
229    }
230
231    pub fn validate(&mut self) -> Result<ValidationResult> {
232        if let Some(validator) = self.helper {
233            self.changes.begin();
234            let result = validator.validate(&mut ValidationContext::new(self))?;
235            let corrected = self.changes.end();
236            match result {
237                ValidationResult::Incomplete => {}
238                ValidationResult::Valid(ref msg) => {
239                    // Accept the line regardless of where the cursor is.
240                    if corrected || self.has_hint() || msg.is_some() {
241                        // Force a refresh without hints to leave the previous
242                        // line as the user typed it after a newline.
243                        self.refresh_line_with_msg(msg.as_deref())?;
244                    }
245                }
246                ValidationResult::Invalid(ref msg) => {
247                    if corrected || self.has_hint() || msg.is_some() {
248                        self.refresh_line_with_msg(msg.as_deref())?;
249                    }
250                }
251            }
252            Ok(result)
253        } else {
254            Ok(ValidationResult::Valid(None))
255        }
256    }
257}
258
259impl<'out, 'prompt, H: Helper> Invoke for State<'out, 'prompt, H> {
260    fn input(&self) -> &str {
261        self.line.as_str()
262    }
263}
264
265impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> {
266    fn refresh_line(&mut self) -> Result<()> {
267        let prompt_size = self.prompt_size;
268        self.hint();
269        self.highlight_char();
270        self.refresh(self.prompt, prompt_size, true, Info::Hint)
271    }
272
273    fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()> {
274        let prompt_size = self.prompt_size;
275        self.hint = None;
276        self.highlight_char();
277        self.refresh(self.prompt, prompt_size, true, Info::Msg(msg))
278    }
279
280    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
281        let prompt_size = self.out.calculate_position(prompt, Position::default());
282        self.hint();
283        self.highlight_char();
284        self.refresh(prompt, prompt_size, false, Info::Hint)
285    }
286
287    fn doing_insert(&mut self) {
288        self.changes.begin();
289    }
290
291    fn done_inserting(&mut self) {
292        self.changes.end();
293    }
294
295    fn last_insert(&self) -> Option<String> {
296        self.changes.last_insert()
297    }
298
299    fn is_cursor_at_end(&self) -> bool {
300        self.line.pos() == self.line.len()
301    }
302
303    fn has_hint(&self) -> bool {
304        self.hint.is_some()
305    }
306
307    fn hint_text(&self) -> Option<&str> {
308        self.hint.as_ref().and_then(|hint| hint.completion())
309    }
310
311    fn line(&self) -> &str {
312        self.line.as_str()
313    }
314
315    fn pos(&self) -> usize {
316        self.line.pos()
317    }
318
319    fn external_print(&mut self, msg: String) -> Result<()> {
320        self.out.clear_rows(&self.layout)?;
321        self.layout.end.row = 0;
322        self.layout.cursor.row = 0;
323        self.out.write_and_flush(msg.as_str())?;
324        if !msg.ends_with('\n') {
325            self.out.write_and_flush("\n")?;
326        }
327        self.refresh_line()
328    }
329}
330
331impl<'out, 'prompt, H: Helper> fmt::Debug for State<'out, 'prompt, H> {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        f.debug_struct("State")
334            .field("prompt", &self.prompt)
335            .field("prompt_size", &self.prompt_size)
336            .field("buf", &self.line)
337            .field("cols", &self.out.get_columns())
338            .field("layout", &self.layout)
339            .field("saved_line_for_history", &self.saved_line_for_history)
340            .finish()
341    }
342}
343
344impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
345    pub fn clear_screen(&mut self) -> Result<()> {
346        self.out.clear_screen()?;
347        self.layout.cursor = Position::default();
348        self.layout.end = Position::default();
349        Ok(())
350    }
351
352    /// Insert the character `ch` at cursor current position.
353    pub fn edit_insert(&mut self, ch: char, n: RepeatCount) -> Result<()> {
354        if let Some(push) = self.line.insert(ch, n, &mut self.changes) {
355            if push {
356                let prompt_size = self.prompt_size;
357                let no_previous_hint = self.hint.is_none();
358                self.hint();
359                let width = ch.width().unwrap_or(0);
360                if n == 1
361                    && width != 0 // Ctrl-V + \t or \n ...
362                    && self.layout.cursor.col + width < self.out.get_columns()
363                    && (self.hint.is_none() && no_previous_hint) // TODO refresh only current line
364                    && !self.highlight_char()
365                {
366                    // Avoid a full update of the line in the trivial case.
367                    self.layout.cursor.col += width;
368                    self.layout.end.col += width;
369                    debug_assert!(self.layout.prompt_size <= self.layout.cursor);
370                    debug_assert!(self.layout.cursor <= self.layout.end);
371                    let bits = ch.encode_utf8(&mut self.byte_buffer);
372                    self.out.write_and_flush(bits)
373                } else {
374                    self.refresh(self.prompt, prompt_size, true, Info::Hint)
375                }
376            } else {
377                self.refresh_line()
378            }
379        } else {
380            Ok(())
381        }
382    }
383
384    /// Replace a single (or n) character(s) under the cursor (Vi mode)
385    pub fn edit_replace_char(&mut self, ch: char, n: RepeatCount) -> Result<()> {
386        self.changes.begin();
387        let succeed = if let Some(chars) = self.line.delete(n, &mut self.changes) {
388            let count = chars.graphemes(true).count();
389            self.line.insert(ch, count, &mut self.changes);
390            self.line.move_backward(1);
391            true
392        } else {
393            false
394        };
395        self.changes.end();
396        if succeed {
397            self.refresh_line()
398        } else {
399            Ok(())
400        }
401    }
402
403    /// Overwrite the character under the cursor (Vi mode)
404    pub fn edit_overwrite_char(&mut self, ch: char) -> Result<()> {
405        if let Some(end) = self.line.next_pos(1) {
406            {
407                let text = ch.encode_utf8(&mut self.byte_buffer);
408                let start = self.line.pos();
409                self.line.replace(start..end, text, &mut self.changes);
410            }
411            self.refresh_line()
412        } else {
413            Ok(())
414        }
415    }
416
417    // Yank/paste `text` at current position.
418    pub fn edit_yank(
419        &mut self,
420        input_state: &InputState,
421        text: &str,
422        anchor: Anchor,
423        n: RepeatCount,
424    ) -> Result<()> {
425        if let Anchor::After = anchor {
426            self.line.move_forward(1);
427        }
428        if self.line.yank(text, n, &mut self.changes).is_some() {
429            if !input_state.is_emacs_mode() {
430                self.line.move_backward(1);
431            }
432            self.refresh_line()
433        } else {
434            Ok(())
435        }
436    }
437
438    // Delete previously yanked text and yank/paste `text` at current position.
439    pub fn edit_yank_pop(&mut self, yank_size: usize, text: &str) -> Result<()> {
440        self.changes.begin();
441        let result = if self
442            .line
443            .yank_pop(yank_size, text, &mut self.changes)
444            .is_some()
445        {
446            self.refresh_line()
447        } else {
448            Ok(())
449        };
450        self.changes.end();
451        result
452    }
453
454    /// Move cursor on the left.
455    pub fn edit_move_backward(&mut self, n: RepeatCount) -> Result<()> {
456        if self.line.move_backward(n) {
457            self.move_cursor()
458        } else {
459            Ok(())
460        }
461    }
462
463    /// Move cursor on the right.
464    pub fn edit_move_forward(&mut self, n: RepeatCount) -> Result<()> {
465        if self.line.move_forward(n) {
466            self.move_cursor()
467        } else {
468            Ok(())
469        }
470    }
471
472    /// Move cursor to the start of the line.
473    pub fn edit_move_home(&mut self) -> Result<()> {
474        if self.line.move_home() {
475            self.move_cursor()
476        } else {
477            Ok(())
478        }
479    }
480
481    /// Move cursor to the end of the line.
482    pub fn edit_move_end(&mut self) -> Result<()> {
483        if self.line.move_end() {
484            self.move_cursor()
485        } else {
486            Ok(())
487        }
488    }
489
490    /// Move cursor to the start of the buffer.
491    pub fn edit_move_buffer_start(&mut self) -> Result<()> {
492        if self.line.move_buffer_start() {
493            self.move_cursor()
494        } else {
495            Ok(())
496        }
497    }
498
499    /// Move cursor to the end of the buffer.
500    pub fn edit_move_buffer_end(&mut self) -> Result<()> {
501        if self.line.move_buffer_end() {
502            self.move_cursor()
503        } else {
504            Ok(())
505        }
506    }
507
508    pub fn edit_kill(&mut self, mvt: &Movement, kill_ring: &mut KillRing) -> Result<()> {
509        struct Proxy<'p>(&'p mut Changeset, &'p mut KillRing);
510        let mut proxy = Proxy(&mut self.changes, kill_ring);
511        impl DeleteListener for Proxy<'_> {
512            fn start_killing(&mut self) {
513                self.1.start_killing();
514            }
515
516            fn delete(&mut self, idx: usize, string: &str, dir: Direction) {
517                self.0.delete(idx, string);
518                self.1.delete(idx, string, dir);
519            }
520
521            fn stop_killing(&mut self) {
522                self.1.stop_killing()
523            }
524        }
525        impl ChangeListener for Proxy<'_> {
526            fn insert_char(&mut self, idx: usize, c: char) {
527                self.0.insert_char(idx, c)
528            }
529
530            fn insert_str(&mut self, idx: usize, string: &str) {
531                self.0.insert_str(idx, string)
532            }
533
534            fn replace(&mut self, idx: usize, old: &str, new: &str) {
535                self.0.replace(idx, old, new)
536            }
537        }
538        if self.line.kill(mvt, &mut proxy) {
539            self.refresh_line()
540        } else {
541            Ok(())
542        }
543    }
544
545    pub fn edit_insert_text(&mut self, text: &str) -> Result<()> {
546        if text.is_empty() {
547            return Ok(());
548        }
549        let cursor = self.line.pos();
550        self.line.insert_str(cursor, text, &mut self.changes);
551        self.refresh_line()
552    }
553
554    /// Exchange the char before cursor with the character at cursor.
555    pub fn edit_transpose_chars(&mut self) -> Result<()> {
556        self.changes.begin();
557        let succeed = self.line.transpose_chars(&mut self.changes);
558        self.changes.end();
559        if succeed {
560            self.refresh_line()
561        } else {
562            Ok(())
563        }
564    }
565
566    pub fn edit_move_to_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Result<()> {
567        if self.line.move_to_prev_word(word_def, n) {
568            self.move_cursor()
569        } else {
570            Ok(())
571        }
572    }
573
574    pub fn edit_move_to_next_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
575        if self.line.move_to_next_word(at, word_def, n) {
576            self.move_cursor()
577        } else {
578            Ok(())
579        }
580    }
581
582    /// Moves the cursor to the same column in the line above
583    pub fn edit_move_line_up(&mut self, n: RepeatCount) -> Result<bool> {
584        if self.line.move_to_line_up(n) {
585            self.move_cursor()?;
586            Ok(true)
587        } else {
588            Ok(false)
589        }
590    }
591
592    /// Moves the cursor to the same column in the line above
593    pub fn edit_move_line_down(&mut self, n: RepeatCount) -> Result<bool> {
594        if self.line.move_to_line_down(n) {
595            self.move_cursor()?;
596            Ok(true)
597        } else {
598            Ok(false)
599        }
600    }
601
602    pub fn edit_move_to(&mut self, cs: CharSearch, n: RepeatCount) -> Result<()> {
603        if self.line.move_to(cs, n) {
604            self.move_cursor()
605        } else {
606            Ok(())
607        }
608    }
609
610    pub fn edit_word(&mut self, a: WordAction) -> Result<()> {
611        self.changes.begin();
612        let succeed = self.line.edit_word(a, &mut self.changes);
613        self.changes.end();
614        if succeed {
615            self.refresh_line()
616        } else {
617            Ok(())
618        }
619    }
620
621    pub fn edit_transpose_words(&mut self, n: RepeatCount) -> Result<()> {
622        self.changes.begin();
623        let succeed = self.line.transpose_words(n, &mut self.changes);
624        self.changes.end();
625        if succeed {
626            self.refresh_line()
627        } else {
628            Ok(())
629        }
630    }
631
632    /// Substitute the currently edited line with the next or previous history
633    /// entry.
634    pub fn edit_history_next(&mut self, prev: bool) -> Result<()> {
635        let history = self.ctx.history;
636        if history.is_empty() {
637            return Ok(());
638        }
639        if self.ctx.history_index == history.len() {
640            if prev {
641                // Save the current edited line before overwriting it
642                self.backup();
643            } else {
644                return Ok(());
645            }
646        } else if self.ctx.history_index == 0 && prev {
647            return Ok(());
648        }
649        let (idx, dir) = if prev {
650            (self.ctx.history_index - 1, SearchDirection::Reverse)
651        } else {
652            self.ctx.history_index += 1;
653            (self.ctx.history_index, SearchDirection::Forward)
654        };
655        if idx < history.len() {
656            if let Some(r) = history.get(idx, dir)? {
657                let buf = r.entry;
658                self.ctx.history_index = r.idx;
659                self.changes.begin();
660                self.line.update(&buf, buf.len(), &mut self.changes);
661                self.changes.end();
662            } else {
663                return Ok(());
664            }
665        } else {
666            // Restore current edited line
667            self.restore();
668        }
669        self.refresh_line()
670    }
671
672    // Non-incremental, anchored search
673    pub fn edit_history_search(&mut self, dir: SearchDirection) -> Result<()> {
674        let history = self.ctx.history;
675        if history.is_empty() {
676            return self.out.beep();
677        }
678        if self.ctx.history_index == history.len() && dir == SearchDirection::Forward
679            || self.ctx.history_index == 0 && dir == SearchDirection::Reverse
680        {
681            return self.out.beep();
682        }
683        if dir == SearchDirection::Reverse {
684            self.ctx.history_index -= 1;
685        } else {
686            self.ctx.history_index += 1;
687        }
688        if let Some(sr) = history.starts_with(
689            &self.line.as_str()[..self.line.pos()],
690            self.ctx.history_index,
691            dir,
692        )? {
693            self.ctx.history_index = sr.idx;
694            self.changes.begin();
695            self.line.update(&sr.entry, sr.pos, &mut self.changes);
696            self.changes.end();
697            self.refresh_line()
698        } else {
699            self.out.beep()
700        }
701    }
702
703    /// Substitute the currently edited line with the first/last history entry.
704    pub fn edit_history(&mut self, first: bool) -> Result<()> {
705        let history = self.ctx.history;
706        if history.is_empty() {
707            return Ok(());
708        }
709        if self.ctx.history_index == history.len() {
710            if first {
711                // Save the current edited line before overwriting it
712                self.backup();
713            } else {
714                return Ok(());
715            }
716        } else if self.ctx.history_index == 0 && first {
717            return Ok(());
718        }
719        if first {
720            if let Some(r) = history.get(0, SearchDirection::Forward)? {
721                let buf = r.entry;
722                self.ctx.history_index = r.idx;
723                self.changes.begin();
724                self.line.update(&buf, buf.len(), &mut self.changes);
725                self.changes.end();
726            } else {
727                return Ok(());
728            }
729        } else {
730            self.ctx.history_index = history.len();
731            // Restore current edited line
732            self.restore();
733        }
734        self.refresh_line()
735    }
736
737    /// Change the indentation of the lines covered by movement
738    pub fn edit_indent(&mut self, mvt: &Movement, amount: usize, dedent: bool) -> Result<()> {
739        if self.line.indent(mvt, amount, dedent, &mut self.changes) {
740            self.refresh_line()
741        } else {
742            Ok(())
743        }
744    }
745}
746
747#[cfg(test)]
748pub fn init_state<'out, H: Helper>(
749    out: &'out mut <Terminal as Term>::Writer,
750    line: &str,
751    pos: usize,
752    helper: Option<&'out H>,
753    history: &'out crate::history::DefaultHistory,
754) -> State<'out, 'static, H> {
755    State {
756        out,
757        prompt: "",
758        prompt_size: Position::default(),
759        line: LineBuffer::init(line, pos),
760        layout: Layout::default(),
761        saved_line_for_history: LineBuffer::with_capacity(100),
762        byte_buffer: [0; 4],
763        changes: Changeset::new(),
764        helper,
765        ctx: Context::new(history),
766        hint: Some(Box::new("hint".to_owned())),
767        highlight_char: false,
768        forced_refresh: false,
769    }
770}
771
772#[cfg(test)]
773mod test {
774    use super::init_state;
775    use crate::history::{DefaultHistory, History};
776    use crate::tty::Sink;
777
778    #[test]
779    fn edit_history_next() {
780        let mut out = Sink::default();
781        let mut history = DefaultHistory::new();
782        history.add("line0").unwrap();
783        history.add("line1").unwrap();
784        let line = "current edited line";
785        let helper: Option<()> = None;
786        let mut s = init_state(&mut out, line, 6, helper.as_ref(), &history);
787        s.ctx.history_index = history.len();
788
789        for _ in 0..2 {
790            s.edit_history_next(false).unwrap();
791            assert_eq!(line, s.line.as_str());
792        }
793
794        s.edit_history_next(true).unwrap();
795        assert_eq!(line, s.saved_line_for_history.as_str());
796        assert_eq!(1, s.ctx.history_index);
797        assert_eq!("line1", s.line.as_str());
798
799        for _ in 0..2 {
800            s.edit_history_next(true).unwrap();
801            assert_eq!(line, s.saved_line_for_history.as_str());
802            assert_eq!(0, s.ctx.history_index);
803            assert_eq!("line0", s.line.as_str());
804        }
805
806        s.edit_history_next(false).unwrap();
807        assert_eq!(line, s.saved_line_for_history.as_str());
808        assert_eq!(1, s.ctx.history_index);
809        assert_eq!("line1", s.line.as_str());
810
811        s.edit_history_next(false).unwrap();
812        // assert_eq!(line, s.saved_line_for_history);
813        assert_eq!(2, s.ctx.history_index);
814        assert_eq!(line, s.line.as_str());
815    }
816}