liner/
editor.rs

1use std::cmp;
2use std::fmt::{self, Write};
3use std::io;
4use strip_ansi_escapes::strip;
5use termion::{self, clear, color, cursor};
6
7use super::complete::Completer;
8use crate::context::ColorClosure;
9use crate::event::*;
10use crate::util;
11use crate::Buffer;
12use crate::Context;
13use itertools::Itertools;
14
15/// Indicates the mode that should be currently displayed in the propmpt.
16#[derive(Clone, Copy, Debug)]
17pub enum ViPromptMode {
18    Normal,
19    Insert,
20}
21
22/// Holds the current mode and the indicators for all modes.
23#[derive(Debug)]
24pub struct ViStatus {
25    pub mode: ViPromptMode,
26    normal: String,
27    insert: String,
28}
29
30impl ViStatus {
31    pub fn new<N, I>(mode: ViPromptMode, normal: N, insert: I) -> Self
32    where
33        N: Into<String>,
34        I: Into<String>,
35    {
36        Self {
37            mode,
38            normal: normal.into(),
39            insert: insert.into(),
40        }
41    }
42
43    pub fn as_str(&self) -> &str {
44        use ViPromptMode::*;
45        match self.mode {
46            Normal => &self.normal,
47            Insert => &self.insert,
48        }
49    }
50}
51
52impl Default for ViStatus {
53    fn default() -> Self {
54        ViStatus {
55            mode: ViPromptMode::Insert,
56            normal: String::from("[N] "),
57            insert: String::from("[I] "),
58        }
59    }
60}
61
62impl fmt::Display for ViStatus {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        use ViPromptMode::*;
65        match self.mode {
66            Normal => write!(f, "{}", self.normal),
67            Insert => write!(f, "{}", self.insert),
68        }
69    }
70}
71
72/// User-defined prompt.
73///
74/// # Examples
75///
76/// For Emacs mode, you simply define a static prompt that holds a string.
77/// ```
78/// # use liner::Prompt;
79/// let prompt = Prompt::from("prompt$ ");
80/// assert_eq!(&prompt.to_string(), "prompt$ ");
81/// ```
82///
83/// You can also display Vi mode indicator in your prompt.
84/// ```
85/// # use liner::{Prompt, ViStatus, ViPromptMode};
86/// let prompt = Prompt {
87///     prompt: "prompt$ ".into(),
88///     vi_status: Some(ViStatus::default()),
89/// };
90/// assert_eq!(&prompt.to_string(), "[I] prompt$ ");
91/// ```
92pub struct Prompt {
93    pub prompt: String,
94    pub vi_status: Option<ViStatus>,
95}
96
97impl Prompt {
98    /// Constructs a static prompt.
99    pub fn from<P: Into<String>>(prompt: P) -> Self {
100        Prompt {
101            prompt: prompt.into(),
102            vi_status: None,
103        }
104    }
105
106    pub fn prefix(&self) -> &str {
107        match &self.vi_status {
108            Some(status) => status.as_str(),
109            None => "",
110        }
111    }
112}
113
114impl fmt::Display for Prompt {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        if let Some(status) = &self.vi_status {
117            write!(f, "{}", status)?
118        }
119        write!(f, "{}", self.prompt)
120    }
121}
122
123/// Represents the position of the cursor relative to words in the buffer.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum CursorPosition {
126    /// The cursor is in the word with the specified index.
127    InWord(usize),
128
129    /// The cursor is on the left edge of the word with the specified index.
130    /// For example: `abc |hi`, where `|` is the cursor.
131    OnWordLeftEdge(usize),
132
133    /// The cursor is on the right edge of the word with the specified index.
134    /// For example: `abc| hi`, where `|` is the cursor.
135    OnWordRightEdge(usize),
136
137    /// The cursor is not in contact with any word. Each `Option<usize>` specifies the index of the
138    /// closest word to the left and right, respectively, or `None` if there is no word on that side.
139    InSpace(Option<usize>, Option<usize>),
140}
141
142impl CursorPosition {
143    pub fn get(cursor: usize, words: &[(usize, usize)]) -> CursorPosition {
144        use CursorPosition::*;
145
146        if words.is_empty() {
147            return InSpace(None, None);
148        } else if cursor == words[0].0 {
149            return OnWordLeftEdge(0);
150        } else if cursor < words[0].0 {
151            return InSpace(None, Some(0));
152        }
153
154        for (i, &(start, end)) in words.iter().enumerate() {
155            if start == cursor {
156                return OnWordLeftEdge(i);
157            } else if end == cursor {
158                return OnWordRightEdge(i);
159            } else if start < cursor && cursor < end {
160                return InWord(i);
161            } else if cursor < start {
162                return InSpace(Some(i - 1), Some(i));
163            }
164        }
165
166        InSpace(Some(words.len() - 1), None)
167    }
168}
169
170/// The core line editor. Displays and provides editing for history and the new buffer.
171pub struct Editor<'a, W: io::Write> {
172    prompt: Prompt,
173    out: W,
174    context: &'a mut Context,
175
176    // A closure that is evaluated just before we write to out.
177    // This allows us to do custom syntax highlighting and other fun stuff.
178    closure: Option<ColorClosure>,
179
180    // The location of the cursor. Note that the cursor does not lie on a char, but between chars.
181    // So, if `cursor == 0` then the cursor is before the first char,
182    // and if `cursor == 1` ten the cursor is after the first char and before the second char.
183    cursor: usize,
184
185    // Buffer for the new line (ie. not from editing history)
186    new_buf: Buffer,
187
188    // Buffer to use when editing history so we do not overwrite it.
189    hist_buf: Buffer,
190    hist_buf_valid: bool,
191
192    // None if we're on the new buffer, else the index of history
193    cur_history_loc: Option<usize>,
194
195    // The line of the cursor relative to the prompt. 1-indexed.
196    // So if the cursor is on the same line as the prompt, `term_cursor_line == 1`.
197    // If the cursor is on the line below the prompt, `term_cursor_line == 2`.
198    term_cursor_line: usize,
199
200    // The next completion to suggest, or none
201    show_completions_hint: Option<(Vec<String>, Option<usize>)>,
202
203    // Show autosuggestions based on history
204    show_autosuggestions: bool,
205
206    // if set, the cursor will not be allow to move one past the end of the line, this is necessary
207    // for Vi's normal mode.
208    pub no_eol: bool,
209
210    reverse_search: bool,
211    forward_search: bool,
212    buffer_changed: bool,
213
214    history_subset_index: Vec<usize>,
215    history_subset_loc: Option<usize>,
216
217    autosuggestion: Option<Buffer>,
218
219    history_fresh: bool,
220}
221
222macro_rules! cur_buf_mut {
223    ($s:expr) => {{
224        $s.buffer_changed = true;
225        match $s.cur_history_loc {
226            Some(i) => {
227                if !$s.hist_buf_valid {
228                    $s.hist_buf.copy_buffer(&$s.context.history[i]);
229                    $s.hist_buf_valid = true;
230                }
231                &mut $s.hist_buf
232            }
233            _ => &mut $s.new_buf,
234        }
235    }};
236}
237
238macro_rules! cur_buf {
239    ($s:expr) => {
240        match $s.cur_history_loc {
241            Some(_) if $s.hist_buf_valid => &$s.hist_buf,
242            Some(i) => &$s.context.history[i],
243            _ => &$s.new_buf,
244        }
245    };
246}
247
248impl<'a, W: io::Write> Editor<'a, W> {
249    pub fn new(
250        out: W,
251        prompt: Prompt,
252        f: Option<ColorClosure>,
253        context: &'a mut Context,
254    ) -> io::Result<Self> {
255        Editor::new_with_init_buffer(out, prompt, f, context, Buffer::new())
256    }
257
258    pub fn new_with_init_buffer<B: Into<Buffer>>(
259        mut out: W,
260        prompt: Prompt,
261        f: Option<ColorClosure>,
262        context: &'a mut Context,
263        buffer: B,
264    ) -> io::Result<Self> {
265        out.write_all("⏎".as_bytes())?;
266        for _ in 0..(util::terminal_width().unwrap_or(80) - 1) {
267            out.write_all(b" ")?; // if the line is not empty, overflow on next line
268        }
269        out.write_all("\r \r".as_bytes())?; // Erase the "⏎" if nothing overwrites it
270        let Prompt {
271            mut prompt,
272            vi_status,
273        } = prompt;
274        out.write_all(prompt.split('\n').join("\r\n").as_bytes())?;
275        if let Some(index) = prompt.rfind('\n') {
276            prompt = prompt.split_at(index + 1).1.into()
277        }
278        let prompt = Prompt { prompt, vi_status };
279        let mut ed = Editor {
280            prompt,
281            cursor: 0,
282            out,
283            closure: f,
284            new_buf: buffer.into(),
285            hist_buf: Buffer::new(),
286            hist_buf_valid: false,
287            cur_history_loc: None,
288            context,
289            show_completions_hint: None,
290            show_autosuggestions: true,
291            term_cursor_line: 1,
292            no_eol: false,
293            reverse_search: false,
294            forward_search: false,
295            buffer_changed: false,
296            history_subset_index: vec![],
297            history_subset_loc: None,
298            autosuggestion: None,
299            history_fresh: false,
300        };
301
302        if !ed.new_buf.is_empty() {
303            ed.move_cursor_to_end_of_line()?;
304        }
305        ed.display()?;
306        Ok(ed)
307    }
308
309    fn is_search(&self) -> bool {
310        self.reverse_search || self.forward_search
311    }
312
313    fn clear_search(&mut self) {
314        self.reverse_search = false;
315        self.forward_search = false;
316        self.history_subset_loc = None;
317        self.history_subset_index.clear();
318    }
319
320    /// None if we're on the new buffer, else the index of history
321    pub fn current_history_location(&self) -> Option<usize> {
322        self.cur_history_loc
323    }
324
325    pub fn get_words_and_cursor_position(&self) -> (Vec<(usize, usize)>, CursorPosition) {
326        let word_fn = &self.context.word_divider_fn;
327        let words = word_fn(cur_buf!(self));
328        let pos = CursorPosition::get(self.cursor, &words);
329        (words, pos)
330    }
331
332    pub fn set_prompt(&mut self, mut prompt: Prompt) {
333        if let Some(passed_status) = &mut prompt.vi_status {
334            if let Some(old_status) = &self.prompt.vi_status {
335                passed_status.mode = old_status.mode;
336            }
337        }
338        self.prompt = prompt;
339    }
340
341    pub fn context(&mut self) -> &mut Context {
342        self.context
343    }
344
345    pub fn cursor(&self) -> usize {
346        self.cursor
347    }
348
349    // XXX: Returning a bool to indicate doneness is a bit awkward, maybe change it
350    pub fn handle_newline(&mut self) -> io::Result<bool> {
351        self.history_fresh = false;
352        if self.is_search() {
353            self.accept_autosuggestion()?;
354        }
355        self.clear_search();
356        if self.show_completions_hint.is_some() {
357            self.show_completions_hint = None;
358            return Ok(false);
359        }
360
361        let char_before_cursor = cur_buf!(self).char_before(self.cursor);
362        if char_before_cursor == Some('\\') {
363            // self.insert_after_cursor('\r')?;
364            self.insert_after_cursor('\n')?;
365            Ok(false)
366        } else {
367            self.cursor = cur_buf!(self).num_chars();
368            self._display(false)?;
369            self.out.write_all(b"\r\n")?;
370            self.show_completions_hint = None;
371            Ok(true)
372        }
373    }
374
375    fn search_history_loc(&self) -> Option<usize> {
376        self.history_subset_loc
377            .and_then(|i| self.history_subset_index.get(i).cloned())
378    }
379
380    fn freshen_history(&mut self) {
381        if self.context.history.share && !self.history_fresh {
382            let _ = self.context.history.load_history(false);
383            self.history_fresh = true;
384        }
385    }
386
387    /// Refresh incremental search, either when started or when the buffer changes.
388    fn refresh_search(&mut self, forward: bool) {
389        let search_history_loc = self.search_history_loc();
390        self.history_subset_index = self.context.history.search_index(&self.new_buf);
391        if !self.history_subset_index.is_empty() {
392            self.history_subset_loc = if forward {
393                Some(0)
394            } else {
395                Some(self.history_subset_index.len() - 1)
396            };
397            if let Some(target_loc) = search_history_loc {
398                for (i, history_loc) in self.history_subset_index.iter().enumerate() {
399                    if target_loc <= *history_loc {
400                        if forward || target_loc == *history_loc || i == 0 {
401                            self.history_subset_loc = Some(i);
402                        } else {
403                            self.history_subset_loc = Some(i - 1);
404                        }
405                        break;
406                    }
407                }
408            }
409        } else {
410            self.history_subset_loc = None;
411        }
412
413        self.reverse_search = !forward;
414        self.forward_search = forward;
415        self.cur_history_loc = None;
416        self.hist_buf_valid = false;
417        self.buffer_changed = false;
418    }
419
420    /// Begin or continue a search through history.  If forward is true then start at top (or
421    /// current_history_loc if set). If started with forward true then incremental search goes
422    /// forward (top to bottom) other wise reverse (bottom to top).  It is valid to continue a
423    /// search with forward changed (i.e. reverse search direction for one result).
424    pub fn search(&mut self, forward: bool) -> io::Result<()> {
425        if !self.is_search() {
426            self.freshen_history();
427            self.refresh_search(forward);
428        } else if !self.history_subset_index.is_empty() {
429            self.history_subset_loc = if let Some(p) = self.history_subset_loc {
430                if forward {
431                    if p < self.history_subset_index.len() - 1 {
432                        Some(p + 1)
433                    } else {
434                        Some(0)
435                    }
436                } else if p > 0 {
437                    Some(p - 1)
438                } else {
439                    Some(self.history_subset_index.len() - 1)
440                }
441            } else {
442                None
443            };
444        }
445        self.display()?;
446        Ok(())
447    }
448
449    pub fn flush(&mut self) -> io::Result<()> {
450        self.out.flush()
451    }
452
453    /// Attempts to undo an action on the current buffer.
454    ///
455    /// Returns `Ok(true)` if an action was undone.
456    /// Returns `Ok(false)` if there was no action to undo.
457    pub fn undo(&mut self) -> io::Result<bool> {
458        let did = cur_buf_mut!(self).undo();
459        if did {
460            self.move_cursor_to_end_of_line()?;
461        } else {
462            self.display()?;
463        }
464        Ok(did)
465    }
466
467    pub fn redo(&mut self) -> io::Result<bool> {
468        let did = cur_buf_mut!(self).redo();
469        if did {
470            self.move_cursor_to_end_of_line()?;
471        } else {
472            self.display()?;
473        }
474        Ok(did)
475    }
476
477    pub fn revert(&mut self) -> io::Result<bool> {
478        let did = cur_buf_mut!(self).revert();
479        if did {
480            self.move_cursor_to_end_of_line()?;
481        } else {
482            self.display()?;
483        }
484        Ok(did)
485    }
486
487    fn print_completion_list(
488        completions: &[String],
489        highlighted: Option<usize>,
490        output_buf: &mut String,
491    ) -> io::Result<usize> {
492        use std::cmp::max;
493
494        let (w, _) = termion::terminal_size()?;
495
496        // XXX wide character support
497        let max_word_size = completions.iter().fold(1, |m, x| max(m, x.chars().count()));
498        let cols = max(1, w as usize / (max_word_size));
499        let col_width = 2 + w as usize / cols;
500        let cols = max(1, w as usize / col_width);
501
502        let lines = completions.len() / cols;
503
504        let mut i = 0;
505        for (index, com) in completions.iter().enumerate() {
506            if i == cols {
507                output_buf.push_str("\r\n");
508                i = 0;
509            } else if i > cols {
510                unreachable!()
511            }
512
513            if Some(index) == highlighted {
514                write!(
515                    output_buf,
516                    "{}{}",
517                    color::Black.fg_str(),
518                    color::White.bg_str()
519                )
520                .unwrap();
521            }
522            write!(output_buf, "{:<1$}", com, col_width).unwrap();
523            if Some(index) == highlighted {
524                write!(
525                    output_buf,
526                    "{}{}",
527                    color::Reset.bg_str(),
528                    color::Reset.fg_str()
529                )
530                .unwrap();
531            }
532
533            i += 1;
534        }
535
536        Ok(lines)
537    }
538
539    pub fn skip_completions_hint(&mut self) {
540        self.show_completions_hint = None;
541    }
542
543    pub fn complete<T: Completer>(&mut self, handler: &mut T) -> io::Result<()> {
544        handler.on_event(Event::new(self, EventKind::BeforeComplete));
545
546        if let Some((completions, i_in)) = self.show_completions_hint.take() {
547            let i = i_in.map_or(0, |i| (i + 1) % completions.len());
548
549            match i_in {
550                Some(x) if cur_buf!(self) == &Buffer::from(&completions[x][..]) => {
551                    cur_buf_mut!(self).truncate(0);
552                    self.cursor = 0;
553                }
554                _ => self.delete_word_before_cursor(false)?,
555            }
556            self.insert_str_after_cursor(&completions[i])?;
557
558            self.show_completions_hint = Some((completions, Some(i)));
559        }
560        if self.show_completions_hint.is_some() {
561            self.display()?;
562            return Ok(());
563        }
564
565        let (word, completions) = {
566            let word_range = self.get_word_before_cursor(false);
567            let buf = cur_buf_mut!(self);
568
569            let word = match word_range {
570                Some((start, end)) => buf.range(start, end),
571                None => "".into(),
572            };
573
574            let mut completions = handler.completions(word.as_ref());
575            completions.sort();
576            completions.dedup();
577            (word, completions)
578        };
579
580        if completions.is_empty() {
581            // Do nothing.
582            self.show_completions_hint = None;
583            Ok(())
584        } else if completions.len() == 1 {
585            self.show_completions_hint = None;
586            self.delete_word_before_cursor(false)?;
587            self.insert_str_after_cursor(completions[0].as_ref())
588        } else {
589            let common_prefix = util::find_longest_common_prefix(
590                &completions
591                    .iter()
592                    .map(|x| x.chars().collect())
593                    .collect::<Vec<Vec<char>>>()[..],
594            );
595
596            if let Some(p) = common_prefix {
597                let s = p.iter().cloned().collect::<String>();
598
599                if s.len() > word.len() && s.starts_with(&word[..]) {
600                    self.delete_word_before_cursor(false)?;
601                    return self.insert_str_after_cursor(s.as_ref());
602                }
603            }
604
605            self.show_completions_hint = Some((completions, None));
606            self.display()?;
607
608            Ok(())
609        }
610    }
611
612    fn get_word_before_cursor(&self, ignore_space_before_cursor: bool) -> Option<(usize, usize)> {
613        let (words, pos) = self.get_words_and_cursor_position();
614        match pos {
615            CursorPosition::InWord(i) => Some(words[i]),
616            CursorPosition::InSpace(Some(i), _) => {
617                if ignore_space_before_cursor {
618                    Some(words[i])
619                } else {
620                    None
621                }
622            }
623            CursorPosition::InSpace(None, _) => None,
624            CursorPosition::OnWordLeftEdge(i) => {
625                if ignore_space_before_cursor && i > 0 {
626                    Some(words[i - 1])
627                } else {
628                    None
629                }
630            }
631            CursorPosition::OnWordRightEdge(i) => Some(words[i]),
632        }
633    }
634
635    /// Deletes the word preceding the cursor.
636    /// If `ignore_space_before_cursor` is true and there is space directly before the cursor,
637    /// this method ignores that space until it finds a word.
638    /// If `ignore_space_before_cursor` is false and there is space directly before the cursor,
639    /// nothing is deleted.
640    pub fn delete_word_before_cursor(
641        &mut self,
642        ignore_space_before_cursor: bool,
643    ) -> io::Result<()> {
644        if let Some((start, _)) = self.get_word_before_cursor(ignore_space_before_cursor) {
645            let moved = cur_buf_mut!(self).remove(start, self.cursor);
646            self.cursor -= moved;
647        }
648        self.display()
649    }
650
651    /// Clears the screen then prints the prompt and current buffer.
652    pub fn clear(&mut self) -> io::Result<()> {
653        write!(
654            &mut self.context.buf,
655            "{}{}",
656            clear::All,
657            cursor::Goto(1, 1)
658        )
659        .unwrap();
660
661        self.term_cursor_line = 1;
662        self.clear_search();
663        self.display()
664    }
665
666    /// Move up (backwards) in history.
667    pub fn move_up(&mut self) -> io::Result<()> {
668        if self.is_search() {
669            self.search(false)
670        } else {
671            self.hist_buf_valid = false;
672            self.freshen_history();
673            if self.new_buf.num_chars() > 0 {
674                match self.history_subset_loc {
675                    Some(i) if i > 0 => {
676                        self.history_subset_loc = Some(i - 1);
677                        self.cur_history_loc = Some(self.history_subset_index[i - 1]);
678                    }
679                    None => {
680                        self.history_subset_index =
681                            self.context.history.get_history_subset(&self.new_buf);
682                        if !self.history_subset_index.is_empty() {
683                            self.history_subset_loc = Some(self.history_subset_index.len() - 1);
684                            self.cur_history_loc = Some(
685                                self.history_subset_index[self.history_subset_index.len() - 1],
686                            );
687                        }
688                    }
689                    _ => (),
690                }
691            } else {
692                match self.cur_history_loc {
693                    Some(i) if i > 0 => self.cur_history_loc = Some(i - 1),
694                    None if !self.context.history.is_empty() => {
695                        self.cur_history_loc = Some(self.context.history.len() - 1)
696                    }
697                    _ => (),
698                }
699            }
700            self.move_cursor_to_end_of_line()
701        }
702    }
703
704    /// Move down (forwards) in history, or to the new buffer if we reach the end of history.
705    pub fn move_down(&mut self) -> io::Result<()> {
706        if self.is_search() {
707            self.search(true)
708        } else {
709            self.hist_buf_valid = false;
710            if self.new_buf.num_chars() > 0 {
711                if let Some(i) = self.history_subset_loc {
712                    if i < self.history_subset_index.len() - 1 {
713                        self.history_subset_loc = Some(i + 1);
714                        self.cur_history_loc = Some(self.history_subset_index[i + 1]);
715                    } else {
716                        self.cur_history_loc = None;
717                        self.history_subset_loc = None;
718                        self.history_subset_index.clear();
719                        self.history_fresh = false;
720                    }
721                }
722            } else {
723                match self.cur_history_loc.take() {
724                    Some(i) if i < self.context.history.len() - 1 => {
725                        self.cur_history_loc = Some(i + 1)
726                    }
727                    _ => self.history_fresh = false,
728                }
729            }
730            self.move_cursor_to_end_of_line()
731        }
732    }
733
734    /// Moves to the start of history (ie. the earliest history entry).
735    pub fn move_to_start_of_history(&mut self) -> io::Result<()> {
736        self.hist_buf_valid = false;
737        if self.context.history.is_empty() {
738            self.cur_history_loc = None;
739            self.display()
740        } else {
741            self.cur_history_loc = Some(0);
742            self.move_cursor_to_end_of_line()
743        }
744    }
745
746    /// Moves to the end of history (ie. the new buffer).
747    pub fn move_to_end_of_history(&mut self) -> io::Result<()> {
748        self.hist_buf_valid = false;
749        if self.cur_history_loc.is_some() {
750            self.cur_history_loc = None;
751            self.move_cursor_to_end_of_line()
752        } else {
753            self.display()
754        }
755    }
756
757    /// Inserts a string directly after the cursor, moving the cursor to the right.
758    ///
759    /// Note: it is more efficient to call `insert_chars_after_cursor()` directly.
760    pub fn insert_str_after_cursor(&mut self, s: &str) -> io::Result<()> {
761        self.insert_chars_after_cursor(&s.chars().collect::<Vec<char>>()[..])
762    }
763
764    /// Inserts a character directly after the cursor, moving the cursor to the right.
765    pub fn insert_after_cursor(&mut self, c: char) -> io::Result<()> {
766        self.insert_chars_after_cursor(&[c])
767    }
768
769    /// Inserts characters directly after the cursor, moving the cursor to the right.
770    pub fn insert_chars_after_cursor(&mut self, cs: &[char]) -> io::Result<()> {
771        {
772            let buf = cur_buf_mut!(self);
773            buf.insert(self.cursor, cs);
774        }
775
776        self.cursor += cs.len();
777        self.display()
778    }
779
780    /// Deletes the character directly before the cursor, moving the cursor to the left.
781    /// If the cursor is at the start of the line, nothing happens.
782    pub fn delete_before_cursor(&mut self) -> io::Result<()> {
783        if self.cursor > 0 {
784            let buf = cur_buf_mut!(self);
785            buf.remove(self.cursor - 1, self.cursor);
786            self.cursor -= 1;
787        }
788
789        self.display()
790    }
791
792    /// Deletes the character directly after the cursor. The cursor does not move.
793    /// If the cursor is at the end of the line, nothing happens.
794    pub fn delete_after_cursor(&mut self) -> io::Result<()> {
795        {
796            let buf = cur_buf_mut!(self);
797
798            if self.cursor < buf.num_chars() {
799                buf.remove(self.cursor, self.cursor + 1);
800            }
801        }
802        self.display()
803    }
804
805    /// Deletes every character preceding the cursor until the beginning of the line.
806    pub fn delete_all_before_cursor(&mut self) -> io::Result<()> {
807        cur_buf_mut!(self).remove(0, self.cursor);
808        self.cursor = 0;
809        self.display()
810    }
811
812    /// Deletes every character after the cursor until the end of the line.
813    pub fn delete_all_after_cursor(&mut self) -> io::Result<()> {
814        {
815            let buf = cur_buf_mut!(self);
816            buf.truncate(self.cursor);
817        }
818        self.display()
819    }
820
821    /// Deletes every character from the cursor until the given position.
822    pub fn delete_until(&mut self, position: usize) -> io::Result<()> {
823        {
824            let buf = cur_buf_mut!(self);
825            buf.remove(
826                cmp::min(self.cursor, position),
827                cmp::max(self.cursor, position),
828            );
829            self.cursor = cmp::min(self.cursor, position);
830        }
831        self.display()
832    }
833
834    /// Deletes every character from the cursor until the given position, inclusive.
835    pub fn delete_until_inclusive(&mut self, position: usize) -> io::Result<()> {
836        {
837            let buf = cur_buf_mut!(self);
838            buf.remove(
839                cmp::min(self.cursor, position),
840                cmp::max(self.cursor + 1, position + 1),
841            );
842            self.cursor = cmp::min(self.cursor, position);
843        }
844        self.display()
845    }
846
847    /// Moves the cursor to the left by `count` characters.
848    /// The cursor will not go past the start of the buffer.
849    pub fn move_cursor_left(&mut self, mut count: usize) -> io::Result<()> {
850        if count > self.cursor {
851            count = self.cursor;
852        }
853
854        self.cursor -= count;
855
856        self.display()
857    }
858
859    /// Moves the cursor to the right by `count` characters.
860    /// The cursor will not go past the end of the buffer.
861    pub fn move_cursor_right(&mut self, mut count: usize) -> io::Result<()> {
862        {
863            let buf = cur_buf!(self);
864
865            if count > buf.num_chars() - self.cursor {
866                count = buf.num_chars() - self.cursor;
867            }
868
869            self.cursor += count;
870        }
871
872        self.display()
873    }
874
875    /// Moves the cursor to `pos`. If `pos` is past the end of the buffer, it will be clamped.
876    pub fn move_cursor_to(&mut self, pos: usize) -> io::Result<()> {
877        self.cursor = pos;
878        let buf_len = cur_buf!(self).num_chars();
879        if self.cursor > buf_len {
880            self.cursor = buf_len;
881        }
882        self.display()
883    }
884
885    /// Moves the cursor to the start of the line.
886    pub fn move_cursor_to_start_of_line(&mut self) -> io::Result<()> {
887        self.cursor = 0;
888        self.display()
889    }
890
891    /// Moves the cursor to the end of the line.
892    pub fn move_cursor_to_end_of_line(&mut self) -> io::Result<()> {
893        self.cursor = cur_buf!(self).num_chars();
894        self.display()
895    }
896
897    pub fn cursor_is_at_end_of_line(&self) -> bool {
898        let num_chars = cur_buf!(self).num_chars();
899        if self.no_eol {
900            self.cursor == num_chars - 1
901        } else {
902            self.cursor == num_chars
903        }
904    }
905
906    ///  Returns a reference to the current buffer being edited.
907    /// This may be the new buffer or a buffer from history.
908    pub fn current_buffer(&self) -> &Buffer {
909        cur_buf!(self)
910    }
911
912    ///  Returns a mutable reference to the current buffer being edited.
913    /// This may be the new buffer or a buffer from history.
914    pub fn current_buffer_mut(&mut self) -> &mut Buffer {
915        cur_buf_mut!(self)
916    }
917
918    /// Accept autosuggestion and copy its content into current buffer
919    pub fn accept_autosuggestion(&mut self) -> io::Result<()> {
920        if self.show_autosuggestions {
921            {
922                let autosuggestion = self.autosuggestion.clone();
923                let search = self.is_search();
924                let buf = self.current_buffer_mut();
925                match autosuggestion {
926                    Some(ref x) if search => buf.copy_buffer(x),
927                    Some(ref x) => buf.insert_from_buffer(x),
928                    None => (),
929                }
930            }
931        }
932        self.clear_search();
933        self.move_cursor_to_end_of_line()
934    }
935
936    /// Returns current auto suggestion, for history search this is the current match if not
937    /// searching the first history entry to start with current text (reverse order).
938    /// Return None if nothing found.
939    fn current_autosuggestion(&mut self) -> Option<Buffer> {
940        // If we are editing a previous history item no autosuggestion.
941        if self.hist_buf_valid {
942            return None;
943        }
944        let context_history = &self.context.history;
945        let autosuggestion = if self.is_search() {
946            self.search_history_loc().map(|i| &context_history[i])
947        } else if self.show_autosuggestions {
948            self.cur_history_loc
949                .map(|i| &context_history[i])
950                .or_else(|| {
951                    context_history
952                        .get_newest_match(Some(context_history.len()), &self.new_buf)
953                        .map(|i| &context_history[i])
954                })
955        } else {
956            None
957        };
958        autosuggestion.cloned()
959    }
960
961    pub fn is_currently_showing_autosuggestion(&self) -> bool {
962        self.autosuggestion.is_some()
963    }
964
965    /// Override the prompt for incremental search if needed.
966    fn search_prompt(&mut self) -> (String, usize) {
967        if self.is_search() {
968            // If we are searching override prompt to search prompt.
969            let (hplace, color) = if self.history_subset_index.is_empty() {
970                (0, color::Red.fg_str())
971            } else {
972                (
973                    self.history_subset_loc.unwrap_or(0) + 1,
974                    color::Green.fg_str(),
975                )
976            };
977            let prefix = self.prompt.prefix();
978            (
979                format!(
980                    "{}(search)'{}{}{}` ({}/{}): ",
981                    &prefix,
982                    color,
983                    self.current_buffer(),
984                    color::Reset.fg_str(),
985                    hplace,
986                    self.history_subset_index.len()
987                ),
988                strip(prefix).len() + 9,
989            )
990        } else {
991            (self.prompt.to_string(), 0)
992        }
993    }
994
995    fn _display(&mut self, show_autosuggest: bool) -> io::Result<()> {
996        fn calc_width(prompt_width: usize, buf_widths: &[usize], terminal_width: usize) -> usize {
997            let mut total = 0;
998
999            for line in buf_widths {
1000                if total % terminal_width != 0 {
1001                    total = ((total / terminal_width) + 1) * terminal_width;
1002                }
1003
1004                total += prompt_width + line;
1005            }
1006
1007            total
1008        }
1009
1010        let (prompt, rev_prompt_width) = self.search_prompt();
1011
1012        let terminal_width = util::terminal_width()?;
1013        let prompt_width = util::last_prompt_line_width(&prompt);
1014
1015        let buf = cur_buf!(self);
1016        let buf_width = buf.width();
1017
1018        // Don't let the cursor go over the end!
1019        let buf_num_chars = buf.num_chars();
1020        if buf_num_chars < self.cursor {
1021            self.cursor = buf_num_chars;
1022        }
1023
1024        // Can't move past the last character in vi normal mode
1025        if self.no_eol && self.cursor != 0 && self.cursor == buf_num_chars {
1026            self.cursor -= 1;
1027        }
1028
1029        let buf_widths = match self.autosuggestion {
1030            Some(ref suggestion) => suggestion.width(),
1031            None => buf_width,
1032        };
1033        // Width of the current buffer lines (including autosuggestion) from the start to the cursor
1034        let buf_widths_to_cursor = match self.autosuggestion {
1035            // Cursor might overrun autosuggestion with history search.
1036            Some(ref suggestion) if self.cursor < suggestion.num_chars() => {
1037                suggestion.range_width(0, self.cursor)
1038            }
1039            _ => buf.range_width(0, self.cursor),
1040        };
1041
1042        // Total number of terminal spaces taken up by prompt and buffer
1043        let new_total_width = calc_width(prompt_width, &buf_widths, terminal_width);
1044        let new_total_width_to_cursor = if self.is_search() {
1045            calc_width(rev_prompt_width, &buf_widths_to_cursor, terminal_width)
1046        } else {
1047            calc_width(prompt_width, &buf_widths_to_cursor, terminal_width)
1048        };
1049
1050        let new_num_lines = (new_total_width + terminal_width) / terminal_width;
1051
1052        self.context.buf.push_str("\x1B[?1000l\x1B[?1l");
1053
1054        // Move the term cursor to the same line as the prompt.
1055        if self.term_cursor_line > 1 {
1056            write!(
1057                &mut self.context.buf,
1058                "{}",
1059                cursor::Up(self.term_cursor_line as u16 - 1)
1060            )
1061            .unwrap();
1062        }
1063
1064        write!(&mut self.context.buf, "\r{}", clear::AfterCursor).unwrap();
1065
1066        // If we're cycling through completions, show those
1067        let mut completion_lines = 0;
1068        if let Some((completions, i)) = self.show_completions_hint.as_ref() {
1069            completion_lines =
1070                1 + Self::print_completion_list(completions, *i, &mut self.context.buf)?;
1071            self.context.buf.push_str("\r\n");
1072        }
1073
1074        // Write the prompt
1075        write!(&mut self.context.buf, "{}", prompt).unwrap();
1076
1077        // If we have an autosuggestion, we make the autosuggestion the buffer we print out.
1078        // We get the number of bytes in the buffer (but NOT the autosuggestion).
1079        // Then, we loop and subtract from that number until it's 0, in which case we are printing
1080        // the autosuggestion from here on (in a different color).
1081        let lines = match self.autosuggestion {
1082            Some(ref suggestion) if show_autosuggest => suggestion.lines(),
1083            _ => buf.lines(),
1084        };
1085        let mut buf_num_remaining_bytes = buf.num_bytes();
1086
1087        let lines_len = lines.len();
1088        for (i, line) in lines.into_iter().enumerate() {
1089            if i > 0 {
1090                write!(
1091                    &mut self.context.buf,
1092                    "{}",
1093                    cursor::Right(prompt_width as u16)
1094                )
1095                .unwrap();
1096            }
1097
1098            if buf_num_remaining_bytes == 0 {
1099                self.context.buf.push_str(&line);
1100            } else if line.len() > buf_num_remaining_bytes {
1101                let start = &line[..buf_num_remaining_bytes];
1102                let start = match self.closure {
1103                    Some(ref f) => f(start),
1104                    None => start.to_owned(),
1105                };
1106                if self.is_search() {
1107                    write!(&mut self.context.buf, "{}", color::Yellow.fg_str()).unwrap();
1108                }
1109                write!(&mut self.context.buf, "{}", start).unwrap();
1110                if !self.is_search() {
1111                    write!(&mut self.context.buf, "{}", color::Yellow.fg_str()).unwrap();
1112                }
1113                self.context.buf.push_str(&line[buf_num_remaining_bytes..]);
1114                buf_num_remaining_bytes = 0;
1115            } else {
1116                buf_num_remaining_bytes -= line.len();
1117                let written_line = match self.closure {
1118                    Some(ref f) => f(&line),
1119                    None => line,
1120                };
1121                if self.is_search() {
1122                    write!(&mut self.context.buf, "{}", color::Yellow.fg_str()).unwrap();
1123                }
1124                self.context.buf.push_str(&written_line);
1125            }
1126
1127            if i + 1 < lines_len {
1128                self.context.buf.push_str("\r\n");
1129            }
1130        }
1131
1132        if self.is_currently_showing_autosuggestion() || self.is_search() {
1133            write!(&mut self.context.buf, "{}", color::Reset.fg_str()).unwrap();
1134        }
1135
1136        // at the end of the line, move the cursor down a line
1137        if new_total_width % terminal_width == 0 {
1138            self.context.buf.push_str("\r\n");
1139        }
1140
1141        self.term_cursor_line = (new_total_width_to_cursor + terminal_width) / terminal_width;
1142
1143        // The term cursor is now on the bottom line. We may need to move the term cursor up
1144        // to the line where the true cursor is.
1145        let cursor_line_diff = new_num_lines as isize - self.term_cursor_line as isize;
1146        if cursor_line_diff > 0 {
1147            write!(
1148                &mut self.context.buf,
1149                "{}",
1150                cursor::Up(cursor_line_diff as u16)
1151            )
1152            .unwrap();
1153        } else if cursor_line_diff < 0 {
1154            unreachable!();
1155        }
1156
1157        // Now that we are on the right line, we must move the term cursor left or right
1158        // to match the true cursor.
1159        let cursor_col_diff = new_total_width as isize
1160            - new_total_width_to_cursor as isize
1161            - cursor_line_diff * terminal_width as isize;
1162        if cursor_col_diff > 0 {
1163            write!(
1164                &mut self.context.buf,
1165                "{}",
1166                cursor::Left(cursor_col_diff as u16)
1167            )
1168            .unwrap();
1169        } else if cursor_col_diff < 0 {
1170            write!(
1171                &mut self.context.buf,
1172                "{}",
1173                cursor::Right((-cursor_col_diff) as u16)
1174            )
1175            .unwrap();
1176        }
1177
1178        self.term_cursor_line += completion_lines;
1179
1180        {
1181            let out = &mut self.out;
1182            out.write_all(self.context.buf.as_bytes())?;
1183            self.context.buf.clear();
1184            out.flush()
1185        }
1186    }
1187
1188    /// Deletes the displayed prompt and buffer, replacing them with the current prompt and buffer
1189    pub fn display(&mut self) -> io::Result<()> {
1190        if self.is_search() && self.buffer_changed {
1191            // Refresh incremental search.
1192            let forward = self.forward_search;
1193            self.refresh_search(forward);
1194        }
1195        self.autosuggestion = self.current_autosuggestion();
1196
1197        self._display(true)
1198    }
1199
1200    /// Modifies the prompt to reflect the Vi mode.
1201    ///
1202    /// This operation will be ignored if a static prompt is used, as mode changes will have no
1203    /// side effect.
1204    pub fn set_vi_mode(&mut self, mode: ViPromptMode) {
1205        if let Some(status) = &mut self.prompt.vi_status {
1206            status.mode = mode;
1207        }
1208    }
1209}
1210
1211impl<'a, W: io::Write> From<Editor<'a, W>> for String {
1212    fn from(ed: Editor<'a, W>) -> String {
1213        match ed.cur_history_loc {
1214            Some(i) => {
1215                if ed.hist_buf_valid {
1216                    ed.hist_buf
1217                } else {
1218                    ed.context.history[i].clone()
1219                }
1220            }
1221            _ => ed.new_buf,
1222        }
1223        .into()
1224    }
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229    use super::*;
1230    use Context;
1231
1232    #[test]
1233    /// test undoing delete_all_after_cursor
1234    fn delete_all_after_cursor_undo() {
1235        let mut context = Context::new();
1236        let out = Vec::new();
1237        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1238        ed.insert_str_after_cursor("delete all of this").unwrap();
1239        ed.move_cursor_to_start_of_line().unwrap();
1240        ed.delete_all_after_cursor().unwrap();
1241        ed.undo().unwrap();
1242        assert_eq!(String::from(ed), "delete all of this");
1243    }
1244
1245    #[test]
1246    fn move_cursor_left() {
1247        let mut context = Context::new();
1248        let out = Vec::new();
1249        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1250        ed.insert_str_after_cursor("let").unwrap();
1251        assert_eq!(ed.cursor, 3);
1252
1253        ed.move_cursor_left(1).unwrap();
1254        assert_eq!(ed.cursor, 2);
1255
1256        ed.insert_after_cursor('f').unwrap();
1257        assert_eq!(ed.cursor, 3);
1258        assert_eq!(String::from(ed), "left");
1259    }
1260
1261    #[test]
1262    fn cursor_movement() {
1263        let mut context = Context::new();
1264        let out = Vec::new();
1265        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1266        ed.insert_str_after_cursor("right").unwrap();
1267        assert_eq!(ed.cursor, 5);
1268
1269        ed.move_cursor_left(2).unwrap();
1270        ed.move_cursor_right(1).unwrap();
1271        assert_eq!(ed.cursor, 4);
1272    }
1273
1274    #[test]
1275    fn delete_until_backwards() {
1276        let mut context = Context::new();
1277        let out = Vec::new();
1278        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1279        ed.insert_str_after_cursor("right").unwrap();
1280        assert_eq!(ed.cursor, 5);
1281
1282        ed.delete_until(0).unwrap();
1283        assert_eq!(ed.cursor, 0);
1284        assert_eq!(String::from(ed), "");
1285    }
1286
1287    #[test]
1288    fn delete_until_forwards() {
1289        let mut context = Context::new();
1290        let out = Vec::new();
1291        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1292        ed.insert_str_after_cursor("right").unwrap();
1293        ed.cursor = 0;
1294
1295        ed.delete_until(5).unwrap();
1296        assert_eq!(ed.cursor, 0);
1297        assert_eq!(String::from(ed), "");
1298    }
1299
1300    #[test]
1301    fn delete_until() {
1302        let mut context = Context::new();
1303        let out = Vec::new();
1304        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1305        ed.insert_str_after_cursor("right").unwrap();
1306        ed.cursor = 4;
1307
1308        ed.delete_until(1).unwrap();
1309        assert_eq!(ed.cursor, 1);
1310        assert_eq!(String::from(ed), "rt");
1311    }
1312
1313    #[test]
1314    fn delete_until_inclusive() {
1315        let mut context = Context::new();
1316        let out = Vec::new();
1317        let mut ed = Editor::new(out, Prompt::from("prompt"), None, &mut context).unwrap();
1318        ed.insert_str_after_cursor("right").unwrap();
1319        ed.cursor = 4;
1320
1321        ed.delete_until_inclusive(1).unwrap();
1322        assert_eq!(ed.cursor, 1);
1323        assert_eq!(String::from(ed), "r");
1324    }
1325}