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#[derive(Clone, Copy, Debug)]
17pub enum ViPromptMode {
18 Normal,
19 Insert,
20}
21
22#[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
72pub struct Prompt {
93 pub prompt: String,
94 pub vi_status: Option<ViStatus>,
95}
96
97impl Prompt {
98 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum CursorPosition {
126 InWord(usize),
128
129 OnWordLeftEdge(usize),
132
133 OnWordRightEdge(usize),
136
137 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
170pub struct Editor<'a, W: io::Write> {
172 prompt: Prompt,
173 out: W,
174 context: &'a mut Context,
175
176 closure: Option<ColorClosure>,
179
180 cursor: usize,
184
185 new_buf: Buffer,
187
188 hist_buf: Buffer,
190 hist_buf_valid: bool,
191
192 cur_history_loc: Option<usize>,
194
195 term_cursor_line: usize,
199
200 show_completions_hint: Option<(Vec<String>, Option<usize>)>,
202
203 show_autosuggestions: bool,
205
206 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" ")?; }
269 out.write_all("\r \r".as_bytes())?; 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 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 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('\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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn insert_after_cursor(&mut self, c: char) -> io::Result<()> {
766 self.insert_chars_after_cursor(&[c])
767 }
768
769 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 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 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 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 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 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 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 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 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 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 pub fn move_cursor_to_start_of_line(&mut self) -> io::Result<()> {
887 self.cursor = 0;
888 self.display()
889 }
890
891 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 pub fn current_buffer(&self) -> &Buffer {
909 cur_buf!(self)
910 }
911
912 pub fn current_buffer_mut(&mut self) -> &mut Buffer {
915 cur_buf_mut!(self)
916 }
917
918 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 fn current_autosuggestion(&mut self) -> Option<Buffer> {
940 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 fn search_prompt(&mut self) -> (String, usize) {
967 if self.is_search() {
968 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 let buf_num_chars = buf.num_chars();
1020 if buf_num_chars < self.cursor {
1021 self.cursor = buf_num_chars;
1022 }
1023
1024 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 let buf_widths_to_cursor = match self.autosuggestion {
1035 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 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 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 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!(&mut self.context.buf, "{}", prompt).unwrap();
1076
1077 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 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 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 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 pub fn display(&mut self) -> io::Result<()> {
1190 if self.is_search() && self.buffer_changed {
1191 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 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 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}