rustyline/
edit.rs

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