1use std::{
32 borrow::Cow,
33 fmt,
34 io::{self, StderrLock, Write},
35 num::NonZeroU16,
36 rc::Rc,
37 sync::Arc,
38};
39
40use crossterm::{
41 cursor,
42 event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
43 style,
44 terminal::{self, ClearType},
45 QueueableCommand,
46};
47use nucleo::{
48 pattern::{CaseMatching, Normalization, Pattern},
49 Config, Matcher, Nucleo, Snapshot, Utf32Str,
50};
51
52pub use crossterm::style::{Attribute, Attributes, Color, ContentStyle, StyledContent, Stylize};
53use unicode_segmentation::UnicodeSegmentation;
54
55pub type Result<T, E = Error> = std::result::Result<T, E>;
56
57#[derive(Debug, onlyerror::Error)]
58pub enum Error {
59 #[error("No options to select from")]
60 NoOptions,
61
62 #[error("Selection cancelled")]
63 Cancelled,
64
65 #[error("Selected index is out of bounds")]
66 InvalidSelection,
67
68 #[error("Cannot run with the terminal in non-interactive mode (not a tty)")]
69 NonInteractive,
70
71 #[error("Failed to interacto with the terminal: {0}")]
72 IoError(#[from] io::Error),
73}
74
75pub trait Select {
88 fn search_content(&self) -> &str;
90
91 fn render_before_content(&self) -> Option<impl fmt::Display + '_>;
94
95 fn render_after_content(&self) -> Option<impl fmt::Display + '_>;
98}
99
100#[allow(clippy::struct_excessive_bools)]
101#[derive(Clone, Debug)]
103pub struct FuzzySelect<T> {
104 prompt: Option<String>,
105 options: Vec<T>,
106 initial_selection: u32,
107 query: Option<String>,
108 filter: bool,
109 highlight: bool,
110 page_size: Option<NonZeroU16>,
111 color: Option<bool>,
112 theme: Theme,
113 alternate_screen: bool,
114 select1: bool,
115}
116
117impl<T> Default for FuzzySelect<T> {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl<T> FuzzySelect<T> {
124 #[must_use]
125 pub fn new() -> Self {
126 Self {
127 prompt: None,
128 options: Vec::new(),
129 initial_selection: 0,
130 query: None,
131 filter: true,
132 highlight: true,
133 page_size: None,
134 color: None,
135 theme: Theme::default(),
136 alternate_screen: true,
137 select1: false,
138 }
139 }
140
141 #[must_use]
142 pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
143 self.prompt = Some(prompt.into());
144 self
145 }
146
147 #[must_use]
148 pub fn without_prompt(mut self) -> Self {
149 self.prompt = None;
150 self
151 }
152
153 #[must_use]
154 pub fn set_prompt<P: Into<String>>(mut self, prompt: impl Into<Option<P>>) -> Self {
155 self.prompt = prompt.into().map(Into::into);
156 self
157 }
158
159 #[must_use]
160 pub fn with_options(mut self, options: Vec<T>) -> Self {
161 self.options = options;
162 self
163 }
164
165 #[must_use]
166 pub fn with_options_from_iter(mut self, options: impl IntoIterator<Item = T>) -> Self {
167 self.options.extend(options);
168 self
169 }
170
171 #[must_use]
172 pub fn with_options_from_slice(mut self, options: &[T]) -> Self
173 where
174 T: Clone,
175 {
176 self.options.extend_from_slice(options);
177 self
178 }
179
180 #[must_use]
181 pub fn add_option(mut self, option: T) -> Self {
182 self.options.push(option);
183 self
184 }
185
186 #[must_use]
187 pub fn add_options(mut self, options: impl IntoIterator<Item = T>) -> Self {
188 self.options.extend(options);
189 self
190 }
191
192 #[must_use]
193 pub fn with_initial_selection(mut self, initial_selection: u32) -> Self {
194 self.initial_selection = initial_selection;
195 self
196 }
197
198 #[must_use]
199 pub fn with_query(mut self, query: impl Into<String>) -> Self {
200 self.query = Some(query.into());
201 self
202 }
203
204 #[must_use]
205 pub fn without_query(mut self) -> Self {
206 self.query = None;
207 self
208 }
209
210 #[must_use]
211 pub fn set_query<Q: Into<String>>(mut self, query: impl Into<Option<Q>>) -> Self {
212 self.query = query.into().map(Into::into);
213 self
214 }
215
216 #[must_use]
217 pub fn with_filter(mut self) -> Self {
218 self.filter = true;
219 self
220 }
221
222 #[must_use]
223 pub fn without_filter(mut self) -> Self {
224 self.filter = false;
225 self
226 }
227
228 #[must_use]
229 pub fn set_filter(mut self, filter: bool) -> Self {
230 self.filter = filter;
231 self
232 }
233
234 #[must_use]
235 pub fn with_select1(mut self) -> Self {
236 self.select1 = true;
237 self
238 }
239
240 #[must_use]
241 pub fn without_select1(mut self) -> Self {
242 self.select1 = false;
243 self
244 }
245
246 #[must_use]
247 pub fn set_select1(mut self, select1: bool) -> Self {
248 self.select1 = select1;
249 self
250 }
251
252 #[must_use]
253 pub fn without_highlight(mut self) -> Self {
254 self.highlight = false;
255 self
256 }
257
258 #[must_use]
259 pub fn set_highlight(mut self, highlight: bool) -> Self {
260 self.highlight = highlight;
261 self
262 }
263
264 #[must_use]
265 pub fn with_page_size(mut self, page_size: u16) -> Self {
266 self.page_size = NonZeroU16::new(page_size);
267 self
268 }
269
270 #[must_use]
271 pub fn with_default_page_size(mut self) -> Self {
272 self.page_size = None;
273 self
274 }
275
276 #[must_use]
277 pub fn with_color(mut self, color: bool) -> Self {
278 self.color = Some(color);
279 self
280 }
281
282 #[must_use]
283 pub fn with_default_color(mut self) -> Self {
284 self.color = None;
285 self
286 }
287
288 #[must_use]
289 pub fn set_color(mut self, color: impl Into<Option<bool>>) -> Self {
290 self.color = color.into();
291 self
292 }
293
294 #[must_use]
295 pub fn with_theme(mut self, theme: Theme) -> Self {
296 self.theme = theme;
297 self
298 }
299
300 #[must_use]
301 pub fn with_default_theme(mut self) -> Self {
302 self.theme = Theme::default();
303 self
304 }
305
306 #[must_use]
307 pub fn without_alternate_screen(mut self) -> Self {
308 self.alternate_screen = false;
309 self
310 }
311
312 #[must_use]
313 pub fn with_alternate_screen(mut self) -> Self {
314 self.alternate_screen = true;
315 self
316 }
317
318 #[must_use]
319 pub fn set_alternate_screen(mut self, alternate_screen: bool) -> Self {
320 self.alternate_screen = alternate_screen;
321 self
322 }
323
324 pub fn select(mut self) -> Result<T>
338 where
339 T: Select,
340 {
341 if self.options.is_empty() {
342 return Err(Error::NoOptions);
343 }
344
345 let mut engine = self.init_engine();
346
347 if self.query.is_some() && self.select1 {
348 let _status = engine.tick(10);
349 let snap = engine.snapshot();
350 if snap.matched_item_count() == 1 {
351 if let Some(index) = snap.get_matched_item(0) {
352 return Ok(self.options.swap_remove(*index.data));
353 }
354 }
355 }
356
357 let mut prompt = self.init_prompt()?;
358 prompt.initial_prompt(self.prompt.as_deref());
359
360 let mut redraw = Redraw::Selection;
361 let selected = loop {
362 let res = prompt.tick(&self.theme, &mut engine, &self.options, redraw)?;
363
364 match res {
365 Ok(r) => redraw = r,
366 Err(Stop::Quit) => return Err(Error::Cancelled),
367 Err(Stop::Selected(selected)) => break selected,
368 }
369 };
370 drop(prompt);
371
372 let item = *engine
373 .snapshot()
374 .get_matched_item(selected)
375 .ok_or(Error::InvalidSelection)?
376 .data;
377 let item = self.options.swap_remove(item);
378
379 Ok(item)
380 }
381
382 pub fn select_opt(self) -> Result<Option<T>>
391 where
392 T: Select,
393 {
394 match self.select() {
395 Ok(res) => Ok(Some(res)),
396 Err(Error::NoOptions | Error::Cancelled | Error::InvalidSelection) => Ok(None),
397 Err(e) => Err(e),
398 }
399 }
400
401 fn init_engine(&self) -> Nucleo<usize>
402 where
403 T: Select,
404 {
405 let mut engine = Nucleo::<usize>::new(Config::DEFAULT, Arc::new(|| {}), None, 1);
406
407 let injector = engine.injector();
408
409 for (idx, item) in self.options.iter().enumerate() {
410 let _ = injector.push(idx, move |_, cols| {
411 cols[0] = item.search_content().into();
412 });
413 }
414
415 if let Some(query) = self.query.as_deref() {
416 engine
417 .pattern
418 .reparse(0, query, CaseMatching::Smart, Normalization::Smart, true);
419 }
420
421 engine
422 }
423
424 fn init_prompt(&mut self) -> Result<Prompt> {
425 let term = Terminal::new(self.color, self.alternate_screen)?;
426 let page_size = self
427 .page_size
428 .map(NonZeroU16::get)
429 .or_else(|| terminal::size().ok().map(|(_, h)| h.saturating_sub(1)))
430 .unwrap_or(10);
431 let highlighter = Highlighter::new(self.highlight && self.color.unwrap_or(true));
432 let query = self.query.take().unwrap_or_default();
433 let input = Input::new(query);
434 let selected = self.initial_selection;
435 if selected as usize >= self.options.len() {
436 return Err(Error::InvalidSelection);
437 }
438
439 Ok(Prompt {
440 term,
441 scroll_offset: 0,
442 selected,
443 height: u32::from(page_size),
444 active: true,
445 filter: self.filter,
446 number_of_matches: u32::MAX,
447 highlighter,
448 input,
449 })
450 }
451}
452
453#[derive(Copy, Clone, Debug)]
455pub struct Theme {
456 pub selected_indicator: StyledContent<char>,
459
460 pub indicator: StyledContent<char>,
463
464 pub selected_text: ContentStyle,
467
468 pub text: ContentStyle,
471
472 pub selected_highlight: ContentStyle,
476
477 pub highlight: ContentStyle,
481}
482
483impl Default for Theme {
484 fn default() -> Self {
485 Self {
486 selected_indicator: ContentStyle::new().red().on_black().apply('>'),
487 indicator: ContentStyle::new().on_black().apply(' '),
488 selected_text: ContentStyle::new().white().on_black(),
489 text: ContentStyle::new(),
490 selected_highlight: ContentStyle::new().dark_cyan().on_black(),
491 highlight: ContentStyle::new().cyan(),
492 }
493 }
494}
495
496impl Select for String {
497 fn search_content(&self) -> &str {
498 self.as_str()
499 }
500
501 fn render_before_content(&self) -> Option<impl fmt::Display + '_> {
502 None::<Self>
503 }
504
505 fn render_after_content(&self) -> Option<impl fmt::Display + '_> {
506 None::<Self>
507 }
508}
509
510impl Select for &str {
511 fn search_content(&self) -> &str {
512 self
513 }
514
515 fn render_before_content(&self) -> Option<impl fmt::Display + '_> {
516 None::<Self>
517 }
518
519 fn render_after_content(&self) -> Option<impl fmt::Display + '_> {
520 None::<Self>
521 }
522}
523
524impl Select for Box<str> {
525 fn search_content(&self) -> &str {
526 self
527 }
528
529 fn render_before_content(&self) -> Option<impl fmt::Display + '_> {
530 None::<Self>
531 }
532
533 fn render_after_content(&self) -> Option<impl fmt::Display + '_> {
534 None::<Self>
535 }
536}
537
538impl Select for Rc<str> {
539 fn search_content(&self) -> &str {
540 self
541 }
542
543 fn render_before_content(&self) -> Option<impl fmt::Display + '_> {
544 None::<Self>
545 }
546
547 fn render_after_content(&self) -> Option<impl fmt::Display + '_> {
548 None::<Self>
549 }
550}
551
552impl Select for Arc<str> {
553 fn search_content(&self) -> &str {
554 self
555 }
556
557 fn render_before_content(&self) -> Option<impl fmt::Display + '_> {
558 None::<Self>
559 }
560
561 fn render_after_content(&self) -> Option<impl fmt::Display + '_> {
562 None::<Self>
563 }
564}
565
566impl Select for Cow<'_, str> {
567 fn search_content(&self) -> &str {
568 self
569 }
570
571 fn render_before_content(&self) -> Option<impl fmt::Display + '_> {
572 None::<Self>
573 }
574
575 fn render_after_content(&self) -> Option<impl fmt::Display + '_> {
576 None::<Self>
577 }
578}
579
580struct Prompt {
581 term: Terminal,
582 input: Input,
583 scroll_offset: u32,
584 selected: u32,
585 height: u32,
586 active: bool,
587 filter: bool,
588 number_of_matches: u32,
589 highlighter: Highlighter,
590}
591
592impl Prompt {
593 fn initial_prompt(&mut self, prompt: Option<&str>) {
594 self.term
595 .queue(cursor::MoveTo(0, 0))
596 .queue_(terminal::Clear(ClearType::All));
597
598 if let Some(prompt) = prompt {
599 self.term.queue_(style::Print(prompt));
600
601 if self.filter {
602 self.term.queue_(style::Print(" "));
603 } else {
604 self.term.queue_(cursor::Hide);
605 }
606 }
607
608 self.term.queue_(cursor::SavePosition);
609 }
610
611 fn tick<T: Select>(
612 &mut self,
613 theme: &Theme,
614 nucleo: &mut Nucleo<usize>,
615 items: &[T],
616 redraw: Redraw,
617 ) -> Result<Result<Redraw, Stop>> {
618 if self.active {
619 let status = nucleo.tick(10);
620 let snap = nucleo.snapshot();
621
622 if status.changed {
623 self.scroll_offset = 0;
624 self.selected = self
625 .selected
626 .min(snap.matched_item_count().saturating_sub(1));
627 }
628
629 self.render_items(theme, snap, items, redraw)?;
630 }
631
632 let key = crossterm::event::read()?;
633 let changed = self.handle_event(&key);
634
635 let redraw = match changed {
636 Changed::Nothing => Redraw::Nothing,
637 Changed::Cursor => Redraw::Cursor,
638 Changed::Selection => Redraw::Selection,
639 Changed::Content => {
640 nucleo.pattern.reparse(
641 0,
642 self.input.content(),
643 CaseMatching::Smart,
644 Normalization::Smart,
645 self.input.appending(),
646 );
647
648 Redraw::Content
649 }
650 Changed::Stop(stop) => return Ok(Err(stop)),
651 };
652
653 Ok(Ok(redraw))
654 }
655
656 fn render_items<T: Select>(
657 &mut self,
658 theme: &Theme,
659 snapshot: &Snapshot<usize>,
660 items: &[T],
661 redraw: Redraw,
662 ) -> Result<()> {
663 self.number_of_matches = snapshot.matched_item_count();
664
665 if redraw >= Redraw::Content {
666 let end = self
667 .scroll_offset
668 .saturating_add(self.height)
669 .min(snapshot.matched_item_count());
670 let start = self.scroll_offset.min(end.saturating_sub(1));
671
672 let matched_items = snapshot.matched_items(start..end).enumerate();
673
674 for (idx, item) in matched_items {
675 #[allow(clippy::cast_possible_truncation)]
676 let idx = idx as u32 + start;
677 let entry = &items[*item.data];
678
679 let (indicator, text, hl) = if idx == self.selected {
680 (
681 theme.selected_indicator,
682 &theme.selected_text,
683 &theme.selected_highlight,
684 )
685 } else {
686 (theme.indicator, &theme.text, &theme.highlight)
687 };
688
689 self.term
690 .queue(cursor::MoveToNextLine(1))
691 .queue(terminal::Clear(ClearType::CurrentLine))
692 .queue(style::PrintStyledContent(indicator))
693 .queue_(style::PrintStyledContent(text.apply(" ")));
694
695 if let Some(content) = entry.render_before_content() {
696 let _ = self
697 .term
698 .queue(style::PrintStyledContent(text.apply(content)));
699 }
700
701 self.highlighter.render(
702 *text,
703 *hl,
704 &mut self.term,
705 snapshot.pattern().column_pattern(0),
706 item.matcher_columns[0].slice(..),
707 );
708
709 if let Some(content) = entry.render_after_content() {
710 self.term
711 .queue_(style::PrintStyledContent(text.apply(content)));
712 }
713 }
714 }
715
716 if redraw >= Redraw::Content {
717 self.term
718 .queue(terminal::Clear(ClearType::FromCursorDown))
719 .queue(cursor::RestorePosition)
720 .queue(terminal::Clear(ClearType::UntilNewLine))
721 .queue_(style::PrintStyledContent(self.input.content().bold()));
722
723 let offset = self.input.cursor_offset_from_end();
724 if offset > 0 {
725 self.term.queue_(cursor::MoveLeft(offset));
726 }
727 } else if redraw == Redraw::Cursor {
728 self.term.queue_(cursor::RestorePosition);
729
730 let offset = self.input.cursor_offset_from_start();
731 if offset > 0 {
732 self.term.queue_(cursor::MoveRight(offset));
733 }
734 }
735
736 if redraw > Redraw::Nothing {
737 self.term.flush()?;
738 }
739
740 Ok(())
741 }
742
743 fn handle_event(&mut self, event: &Event) -> Changed {
744 match event {
745 Event::Key(KeyEvent {
746 code,
747 modifiers,
748 kind: KeyEventKind::Press,
749 ..
750 }) => return self.handle_key_event(*code, *modifiers),
751 Event::FocusLost => self.active = false,
752 Event::FocusGained => self.active = true,
753 Event::Resize(_, h) => {
754 self.height = u32::from(h.saturating_sub(1));
755 return Changed::Selection;
756 }
757 Event::Mouse(_) | Event::Paste(_) | Event::Key(_) => {}
758 };
759
760 Changed::Nothing
761 }
762
763 fn handle_key_event(&mut self, code: KeyCode, modifiers: KeyModifiers) -> Changed {
764 match (code, modifiers) {
765 (KeyCode::Esc, _) => match self.input.clear() {
766 Changed::Nothing => Changed::Stop(Stop::Quit),
767 otherwise => otherwise,
768 },
769 (KeyCode::Char('c'), KeyModifiers::CONTROL) => Changed::Stop(Stop::Quit),
770 (KeyCode::Enter | KeyCode::Char('\n' | '\r'), _) => {
771 Changed::Stop(Stop::Selected(self.selected))
772 }
773 (KeyCode::Backspace, _) => self.input.delete_left(),
774 (KeyCode::Delete, _) => self.input.delete_right(),
775 (KeyCode::Home, _) => self.input.move_to_start(),
776 (KeyCode::End, _) => self.input.move_to_end(),
777 (KeyCode::Left, m) => self
778 .input
779 .move_by(isize::from(m.contains(KeyModifiers::SHIFT)) * -9 - 1),
780 (KeyCode::Right, m) => self
781 .input
782 .move_by(isize::from(m.contains(KeyModifiers::SHIFT)) * 9 + 1),
783 (KeyCode::Up, m) => {
784 self.move_selection(i32::from(m.contains(KeyModifiers::SHIFT)) * -9 - 1)
785 }
786 (KeyCode::Down, m) => {
787 self.move_selection(i32::from(m.contains(KeyModifiers::SHIFT)) * 9 + 1)
788 }
789 (KeyCode::PageUp, _) => {
790 self.scroll_offset = self.scroll_offset.saturating_sub(self.height);
791 Changed::Selection
792 }
793 (KeyCode::PageDown, _) => {
794 self.scroll_offset = self.scroll_offset.saturating_add(self.height);
795 Changed::Selection
796 }
797 (KeyCode::Char(c), _) if self.filter => self.input.insert(c),
798 _ => Changed::Nothing,
799 }
800 }
801
802 fn move_selection(&mut self, diff: i32) -> Changed {
803 self.selected = self
804 .selected
805 .saturating_add_signed(diff)
806 .min(self.number_of_matches.saturating_sub(1));
807
808 if self.selected < self.scroll_offset {
809 self.scroll_offset = self.selected;
810 }
811
812 if self.selected >= self.scroll_offset.saturating_add(self.height) {
813 self.scroll_offset = self.selected.saturating_sub(self.height).saturating_add(1);
814 }
815
816 Changed::Selection
817 }
818}
819
820#[derive(Clone, Debug)]
821struct Input {
822 content: String,
823 boundaries: Vec<usize>,
824 cursor: usize,
825 appending: bool,
826}
827
828impl Input {
829 fn new(content: impl Into<String>) -> Self {
830 let content = content.into();
831 let boundaries = content
832 .grapheme_indices(true)
833 .map(|(idx, _)| idx)
834 .collect::<Vec<_>>();
835 let cursor = boundaries.len();
836 Self {
837 content,
838 boundaries,
839 cursor,
840 appending: true,
841 }
842 }
843
844 fn content(&self) -> &str {
845 &self.content
846 }
847
848 fn appending(&self) -> bool {
849 self.appending
850 }
851
852 fn cursor_offset_from_start(&self) -> u16 {
853 #[allow(clippy::cast_possible_truncation)]
854 let cursor = self.cursor.min(usize::from(u16::MAX)) as u16;
855
856 cursor
857 }
858
859 fn cursor_offset_from_end(&self) -> u16 {
860 #[allow(clippy::cast_possible_truncation)]
861 let cursor_offset = self
862 .boundaries
863 .len()
864 .saturating_sub(self.cursor)
865 .min(usize::from(u16::MAX)) as u16;
866
867 cursor_offset
868 }
869
870 fn clear(&mut self) -> Changed {
871 if self.content.is_empty() {
872 self.appending = true;
873 Changed::Nothing
874 } else {
875 self.content.clear();
876 self.boundaries.clear();
877 self.cursor = 0;
878 self.appending = false;
879 Changed::Content
880 }
881 }
882
883 fn insert(&mut self, c: char) -> Changed {
884 if self.cursor >= self.boundaries.len() {
885 self.appending = true;
886 self.boundaries.push(self.content.len());
887 self.cursor = self.boundaries.len();
888 self.content.push(c);
889 } else {
890 self.appending = false;
891 self.content.insert(self.boundaries[self.cursor], c);
892 self.boundaries
893 .insert(self.cursor, self.boundaries[self.cursor]);
894 let len = c.len_utf8();
895 self.boundaries[self.cursor + 1..]
896 .iter_mut()
897 .for_each(|b| *b += len);
898 self.cursor += 1;
899 }
900 Changed::Content
901 }
902
903 fn delete_left(&mut self) -> Changed {
904 if let Some(pos) = self.cursor.checked_sub(1) {
905 let indexes = self.boundaries[pos]..self.index_at_cursor();
906 self.content.replace_range(indexes, "");
907 let _ = self.boundaries.remove(pos);
908 self.cursor = pos;
909 self.appending = false;
910 Changed::Content
911 } else {
912 Changed::Nothing
913 }
914 }
915
916 fn delete_right(&mut self) -> Changed {
917 if self.cursor < self.boundaries.len() {
918 let start = self.boundaries.remove(self.cursor);
919 let indexes = start..self.index_at_cursor();
920 self.content.replace_range(indexes, "");
921 self.appending = false;
922 Changed::Content
923 } else {
924 Changed::Nothing
925 }
926 }
927
928 fn move_by(&mut self, diff: isize) -> Changed {
929 self.move_to(self.cursor.saturating_add_signed(diff))
930 }
931
932 fn move_to(&mut self, cursor: usize) -> Changed {
933 let changed = cursor != self.cursor;
934 self.cursor = cursor.min(self.boundaries.len());
935 if changed {
936 Changed::Cursor
937 } else {
938 Changed::Nothing
939 }
940 }
941
942 fn move_to_start(&mut self) -> Changed {
943 self.move_to(0)
944 }
945
946 fn move_to_end(&mut self) -> Changed {
947 self.move_to(self.boundaries.len())
948 }
949
950 fn index_at_cursor(&self) -> usize {
951 self.boundaries
952 .get(self.cursor)
953 .map_or_else(|| self.content.len(), |&i| i)
954 }
955}
956
957#[derive(Copy, Clone, Debug, PartialEq, Eq)]
958enum Changed {
959 Nothing,
960 Cursor,
961 Content,
962 Selection,
963 Stop(Stop),
964}
965
966#[derive(Copy, Clone, Debug, PartialEq, Eq)]
967enum Stop {
968 Quit,
969 Selected(u32),
970}
971
972#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
973enum Redraw {
974 Nothing,
975 Cursor,
976 Content,
977 Selection,
978}
979
980struct Highlighter {
981 highlight: bool,
982 indices: Vec<u32>,
983 content: String,
984 matcher: Matcher,
985}
986
987impl Highlighter {
988 fn new(highlight: bool) -> Self {
989 Self {
990 highlight,
991 indices: Vec::new(),
992 content: String::new(),
993 matcher: Matcher::new(Config::DEFAULT),
994 }
995 }
996
997 fn render(
998 &mut self,
999 text: ContentStyle,
1000 hl: ContentStyle,
1001 term: &mut Terminal,
1002 pattern: &Pattern,
1003 matched_item: Utf32Str<'_>,
1004 ) {
1005 if self.highlight {
1006 let _score = pattern.indices(matched_item, &mut self.matcher, &mut self.indices);
1007 self.indices.sort_unstable();
1008 self.indices.dedup();
1009
1010 let mut indices = self.indices.drain(..).map(|i| i as usize);
1011 let mut match_idx = indices.next().unwrap_or(usize::MAX);
1012
1013 for (grapheme_idx, grapheme) in matched_item.chars().enumerate() {
1014 if grapheme_idx == match_idx {
1015 if !self.content.is_empty() {
1016 term.queue_(style::PrintStyledContent(text.apply(self.content.as_str())));
1017 self.content.clear();
1018 }
1019
1020 term.queue_(style::PrintStyledContent(hl.apply(grapheme)));
1021 match_idx = indices.next().unwrap_or(usize::MAX);
1022 } else {
1023 self.content.push(grapheme);
1024 }
1025 }
1026
1027 if !self.content.is_empty() {
1028 term.queue_(style::PrintStyledContent(text.apply(self.content.as_str())));
1029 self.content.clear();
1030 }
1031 } else {
1032 term.queue_(style::PrintStyledContent(text.apply(matched_item)));
1033 }
1034 }
1035}
1036
1037struct Terminal {
1038 io: StderrLock<'static>,
1039 err: Option<Error>,
1040 alternate_screen: bool,
1041}
1042
1043impl Terminal {
1044 fn new(color: Option<bool>, alternate_screen: bool) -> Result<Self> {
1045 terminal::enable_raw_mode().map_err(|e| match e.raw_os_error() {
1046 Some(25 | 6) => Error::NonInteractive,
1047 _ => e.into(),
1048 })?;
1049
1050 if let Some(color) = color {
1051 style::force_color_output(color);
1052 }
1053
1054 let mut io = io::stderr().lock();
1055 if alternate_screen {
1056 let _ = io.queue(terminal::EnterAlternateScreen)?;
1057 }
1058
1059 Ok(Self {
1060 io,
1061 err: None,
1062 alternate_screen,
1063 })
1064 }
1065
1066 fn queue(&mut self, cmd: impl crossterm::Command) -> &mut Self {
1067 if let Err(e) = self.io.queue(cmd) {
1068 if self.err.is_none() {
1069 self.err = Some(e.into());
1070 }
1071 };
1072 self
1073 }
1074
1075 fn queue_(&mut self, cmd: impl crossterm::Command) {
1076 let _ = self.queue(cmd);
1077 }
1078
1079 fn flush(&mut self) -> Result<()> {
1080 if let Some(e) = self.err.take() {
1081 return Err(e);
1082 }
1083
1084 self.io.flush()?;
1085
1086 Ok(())
1087 }
1088}
1089
1090impl Drop for Terminal {
1091 fn drop(&mut self) {
1092 self.queue_(cursor::Show);
1093 if self.alternate_screen {
1094 let _ = self.queue(terminal::LeaveAlternateScreen).flush();
1095 }
1096 let _ = terminal::disable_raw_mode();
1097 }
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 use super::*;
1103
1104 #[test]
1105 fn input_push_at_end() {
1106 let mut input = Input::new("foo");
1107
1108 assert_eq!(input.content(), "foo");
1109 assert_eq!(input.cursor_offset_from_start(), 3);
1110 assert_eq!(input.cursor_offset_from_end(), 0);
1111 assert!(input.appending());
1112
1113 let changed = input.insert('b');
1114 assert_eq!(input.content(), "foob");
1115 assert_eq!(input.cursor_offset_from_start(), 4);
1116 assert_eq!(input.cursor_offset_from_end(), 0);
1117 assert!(input.appending());
1118
1119 assert_eq!(changed, Changed::Content);
1120 }
1121
1122 #[test]
1123 fn input_push_at_middle() {
1124 let mut input = Input::new("foo");
1125 let changed = input.move_by(-1);
1126 assert_eq!(changed, Changed::Cursor);
1127
1128 assert_eq!(input.content(), "foo");
1129 assert_eq!(input.cursor_offset_from_start(), 2);
1130 assert_eq!(input.cursor_offset_from_end(), 1);
1131 assert!(input.appending());
1132
1133 let changed = input.insert('b');
1134 assert_eq!(input.content(), "fobo");
1135 assert_eq!(input.cursor_offset_from_start(), 3);
1136 assert_eq!(input.cursor_offset_from_end(), 1);
1137 assert!(!input.appending());
1138
1139 assert_eq!(changed, Changed::Content);
1140 }
1141
1142 #[test]
1143 fn input_delete_left() {
1144 let mut input = Input::new("foo");
1145
1146 assert_eq!(input.content(), "foo");
1147 assert_eq!(input.cursor_offset_from_start(), 3);
1148 assert_eq!(input.cursor_offset_from_end(), 0);
1149 assert!(input.appending());
1150
1151 let changed = input.delete_left();
1152 assert_eq!(input.content(), "fo");
1153 assert_eq!(input.cursor_offset_from_start(), 2);
1154 assert_eq!(input.cursor_offset_from_end(), 0);
1155 assert!(!input.appending());
1156
1157 assert_eq!(changed, Changed::Content);
1158 }
1159
1160 #[test]
1161 fn input_delete_left_from_middle() {
1162 let mut input = Input::new("fob");
1163 let changed = input.move_by(-1);
1164 assert_eq!(changed, Changed::Cursor);
1165
1166 assert_eq!(input.content(), "fob");
1167 assert_eq!(input.cursor_offset_from_start(), 2);
1168 assert_eq!(input.cursor_offset_from_end(), 1);
1169 assert!(input.appending());
1170
1171 let changed = input.delete_left();
1172 assert_eq!(input.content(), "fb");
1173 assert_eq!(input.cursor_offset_from_start(), 1);
1174 assert_eq!(input.cursor_offset_from_end(), 1);
1175 assert!(!input.appending());
1176
1177 assert_eq!(changed, Changed::Content);
1178 }
1179
1180 #[test]
1181 fn input_delete_left_from_start() {
1182 let mut input = Input::new("fob");
1183 let changed = input.move_to(0);
1184 assert_eq!(changed, Changed::Cursor);
1185
1186 assert_eq!(input.content(), "fob");
1187 assert_eq!(input.cursor_offset_from_start(), 0);
1188 assert_eq!(input.cursor_offset_from_end(), 3);
1189 assert!(input.appending());
1190
1191 let changed = input.delete_left();
1192 assert_eq!(input.content(), "fob");
1193 assert_eq!(input.cursor_offset_from_start(), 0);
1194 assert_eq!(input.cursor_offset_from_end(), 3);
1195 assert!(input.appending());
1196
1197 assert_eq!(changed, Changed::Nothing);
1198 }
1199
1200 #[test]
1201 fn input_delete_right() {
1202 let mut input = Input::new("foo");
1203
1204 assert_eq!(input.content(), "foo");
1205 assert_eq!(input.cursor_offset_from_start(), 3);
1206 assert_eq!(input.cursor_offset_from_end(), 0);
1207 assert!(input.appending());
1208
1209 let changed = input.delete_right();
1210 assert_eq!(input.content(), "foo");
1211 assert_eq!(input.cursor_offset_from_start(), 3);
1212 assert_eq!(input.cursor_offset_from_end(), 0);
1213 assert!(input.appending());
1214
1215 assert_eq!(changed, Changed::Nothing);
1216 }
1217
1218 #[test]
1219 fn input_delete_right_from_middle() {
1220 let mut input = Input::new("fob");
1221 let changed = input.move_by(-1);
1222 assert_eq!(changed, Changed::Cursor);
1223
1224 assert_eq!(input.content(), "fob");
1225 assert_eq!(input.cursor_offset_from_start(), 2);
1226 assert_eq!(input.cursor_offset_from_end(), 1);
1227 assert!(input.appending());
1228
1229 let changed = input.delete_right();
1230 assert_eq!(input.content(), "fo");
1231 assert_eq!(input.cursor_offset_from_start(), 2);
1232 assert_eq!(input.cursor_offset_from_end(), 0);
1233 assert!(!input.appending());
1234
1235 assert_eq!(changed, Changed::Content);
1236 }
1237
1238 #[test]
1239 fn input_delete_right_from_start() {
1240 let mut input = Input::new("fob");
1241 let changed = input.move_to(0);
1242 assert_eq!(changed, Changed::Cursor);
1243
1244 assert_eq!(input.content(), "fob");
1245 assert_eq!(input.cursor_offset_from_start(), 0);
1246 assert_eq!(input.cursor_offset_from_end(), 3);
1247 assert!(input.appending());
1248
1249 let changed = input.delete_right();
1250 assert_eq!(input.content(), "ob");
1251 assert_eq!(input.cursor_offset_from_start(), 0);
1252 assert_eq!(input.cursor_offset_from_end(), 2);
1253 assert!(!input.appending());
1254
1255 assert_eq!(changed, Changed::Content);
1256 }
1257
1258 #[test]
1259 fn input_move_by() {
1260 let mut input = Input::new("foo");
1261
1262 assert_eq!(input.content(), "foo");
1263 assert_eq!(input.cursor_offset_from_start(), 3);
1264 assert_eq!(input.cursor_offset_from_end(), 0);
1265 assert!(input.appending());
1266
1267 let changed = input.move_by(-1);
1268 assert_eq!(input.content(), "foo");
1269 assert_eq!(input.cursor_offset_from_start(), 2);
1270 assert_eq!(input.cursor_offset_from_end(), 1);
1271 assert!(input.appending());
1272
1273 assert_eq!(changed, Changed::Cursor);
1274
1275 let changed = input.move_by(1);
1276 assert_eq!(input.content(), "foo");
1277 assert_eq!(input.cursor_offset_from_start(), 3);
1278 assert_eq!(input.cursor_offset_from_end(), 0);
1279 assert!(input.appending());
1280
1281 assert_eq!(changed, Changed::Cursor);
1282 }
1283
1284 #[test]
1285 fn input_move_to() {
1286 let mut input = Input::new("foo");
1287
1288 assert_eq!(input.content(), "foo");
1289 assert_eq!(input.cursor_offset_from_start(), 3);
1290 assert_eq!(input.cursor_offset_from_end(), 0);
1291 assert!(input.appending());
1292
1293 let changed = input.move_to(1);
1294 assert_eq!(input.content(), "foo");
1295 assert_eq!(input.cursor_offset_from_start(), 1);
1296 assert_eq!(input.cursor_offset_from_end(), 2);
1297 assert!(input.appending());
1298
1299 assert_eq!(changed, Changed::Cursor);
1300
1301 let changed = input.move_to(3);
1302 assert_eq!(input.content(), "foo");
1303 assert_eq!(input.cursor_offset_from_start(), 3);
1304 assert_eq!(input.cursor_offset_from_end(), 0);
1305 assert!(input.appending());
1306
1307 assert_eq!(changed, Changed::Cursor);
1308 }
1309
1310 #[test]
1311 fn input_move_to_start_and_end() {
1312 let mut input = Input::new("foo");
1313
1314 assert_eq!(input.content(), "foo");
1315 assert_eq!(input.cursor_offset_from_start(), 3);
1316 assert_eq!(input.cursor_offset_from_end(), 0);
1317 assert!(input.appending());
1318
1319 let changed = input.move_to_start();
1320 assert_eq!(input.content(), "foo");
1321 assert_eq!(input.cursor_offset_from_start(), 0);
1322 assert_eq!(input.cursor_offset_from_end(), 3);
1323 assert!(input.appending());
1324
1325 assert_eq!(changed, Changed::Cursor);
1326
1327 let changed = input.move_to_end();
1328 assert_eq!(input.content(), "foo");
1329 assert_eq!(input.cursor_offset_from_start(), 3);
1330 assert_eq!(input.cursor_offset_from_end(), 0);
1331 assert!(input.appending());
1332
1333 assert_eq!(changed, Changed::Cursor);
1334 }
1335
1336 #[test]
1337 fn input_clear() {
1338 let mut input = Input::new("foo");
1339
1340 assert_eq!(input.content(), "foo");
1341 assert_eq!(input.cursor_offset_from_start(), 3);
1342 assert_eq!(input.cursor_offset_from_end(), 0);
1343 assert!(input.appending());
1344
1345 let changed = input.clear();
1346 assert_eq!(input.content(), "");
1347 assert_eq!(input.cursor_offset_from_start(), 0);
1348 assert_eq!(input.cursor_offset_from_end(), 0);
1349 assert!(!input.appending());
1350
1351 assert_eq!(changed, Changed::Content);
1352
1353 let changed = input.clear();
1354 assert_eq!(input.content(), "");
1355 assert_eq!(input.cursor_offset_from_start(), 0);
1356 assert_eq!(input.cursor_offset_from_end(), 0);
1357 assert!(input.appending());
1358
1359 assert_eq!(changed, Changed::Nothing);
1360 }
1361}