rustyline/
edit.rs

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