ui.rs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. use rustyline::completion::Completer;
  2. use rustyline::config::OutputStreamType;
  3. use rustyline::error::ReadlineError;
  4. use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
  5. use rustyline::hint::{Hinter, HistoryHinter};
  6. use rustyline::line_buffer::LineBuffer;
  7. use rustyline::validate::{self, MatchingBracketValidator, Validator};
  8. use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyEvent};
  9. use rustyline_derive::Helper;
  10. use std::borrow::Cow::{self, Borrowed, Owned};
  11. #[cfg(feature = "tv")]
  12. use std::ffi::CString;
  13. use std::io::{stdout, Write};
  14. #[cfg(feature = "tv")]
  15. use std::os::raw::c_char;
  16. use std::process::exit;
  17. #[cfg(feature = "tv")]
  18. extern "C" {
  19. fn ui_main(line: *const c_char);
  20. }
  21. #[cfg(feature = "tv")]
  22. #[cfg_attr(tarpaulin, ignore)]
  23. pub fn run_tv() {
  24. let line = CString::new("I'm calling TV!").expect("Failed to create string");
  25. unsafe {
  26. ui_main(line.as_ptr());
  27. }
  28. println!("Hello, world!");
  29. }
  30. struct CatCompleter<'a> {
  31. completions: &'a [&'a String],
  32. }
  33. impl Completer for CatCompleter<'_> {
  34. type Candidate = String;
  35. fn complete(
  36. &self,
  37. line: &str,
  38. _pos: usize,
  39. _ctx: &Context<'_>,
  40. ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
  41. let results: Vec<String> = self
  42. .completions
  43. .iter()
  44. .filter(|comp| comp.contains(line))
  45. .map(|s| s.to_string())
  46. .collect();
  47. Ok((0, results))
  48. }
  49. fn update(&self, _line: &mut LineBuffer, _start: usize, _elected: &str) {}
  50. }
  51. impl<'a> CatCompleter<'a> {
  52. pub fn new(completions: &'a [&'a String]) -> Self {
  53. log::debug!("Completions: {:#?}", completions);
  54. Self { completions }
  55. }
  56. }
  57. #[derive(Helper)]
  58. struct CatHelper<'a> {
  59. completer: CatCompleter<'a>,
  60. highlighter: MatchingBracketHighlighter,
  61. validator: MatchingBracketValidator,
  62. hinter: HistoryHinter,
  63. colored_prompt: String,
  64. }
  65. impl Completer for CatHelper<'_> {
  66. type Candidate = String;
  67. fn complete(
  68. &self,
  69. line: &str,
  70. pos: usize,
  71. ctx: &Context<'_>,
  72. ) -> Result<(usize, Vec<Self::Candidate>), ReadlineError> {
  73. self.completer.complete(line, pos, ctx)
  74. }
  75. }
  76. impl Hinter for CatHelper<'_> {
  77. type Hint = String;
  78. fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String> {
  79. self.hinter.hint(line, pos, ctx)
  80. }
  81. }
  82. impl Highlighter for CatHelper<'_> {
  83. fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
  84. &'s self,
  85. prompt: &'p str,
  86. default: bool,
  87. ) -> Cow<'b, str> {
  88. if default {
  89. Borrowed(&self.colored_prompt)
  90. } else {
  91. Borrowed(prompt)
  92. }
  93. }
  94. fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
  95. Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
  96. }
  97. fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
  98. self.highlighter.highlight(line, pos)
  99. }
  100. fn highlight_char(&self, line: &str, pos: usize) -> bool {
  101. self.highlighter.highlight_char(line, pos)
  102. }
  103. }
  104. impl Validator for CatHelper<'_> {
  105. fn validate(
  106. &self,
  107. ctx: &mut validate::ValidationContext,
  108. ) -> rustyline::Result<validate::ValidationResult> {
  109. self.validator.validate(ctx)
  110. }
  111. fn validate_while_typing(&self) -> bool {
  112. self.validator.validate_while_typing()
  113. }
  114. }
  115. pub fn input_category(item: &str, cat: &str, cats: &[&String]) -> String {
  116. let _ = stdout().flush();
  117. let config = Config::builder()
  118. .history_ignore_space(true)
  119. .check_cursor_position(true)
  120. .completion_type(CompletionType::List)
  121. .edit_mode(EditMode::Emacs)
  122. .output_stream(OutputStreamType::Stdout)
  123. .build();
  124. let h = CatHelper {
  125. completer: CatCompleter::new(cats),
  126. highlighter: MatchingBracketHighlighter::new(),
  127. hinter: HistoryHinter {},
  128. colored_prompt: "".to_owned(),
  129. validator: MatchingBracketValidator::new(),
  130. };
  131. let mut rl = Editor::with_config(config);
  132. rl.set_helper(Some(h));
  133. rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward);
  134. rl.bind_sequence(KeyEvent::alt('p'), Cmd::HistorySearchBackward);
  135. if rl.load_history("history.txt").is_err() {
  136. log::debug!("No previous history.");
  137. }
  138. let mut result = String::new();
  139. let p = format!("{} ({})> ", item, cat);
  140. rl.helper_mut().expect("No helper").colored_prompt =
  141. format!("\x1b[1;33m{} \x1b[1;32m({})\x1b[0m\x1b[1;37m> ", item, cat);
  142. let readline = rl.readline(&p);
  143. match readline {
  144. Ok(line) => {
  145. rl.add_history_entry(line.as_str());
  146. result = line;
  147. }
  148. Err(ReadlineError::Interrupted) => {
  149. println!("Interrupted");
  150. exit(1);
  151. }
  152. Err(ReadlineError::Eof) => {
  153. println!("Encountered Eof");
  154. }
  155. Err(err) => {
  156. println!("Error: {:?}", err);
  157. }
  158. }
  159. print!("\x1b[1;0m");
  160. String::from(result.trim_end_matches('\n'))
  161. }