diff --git a/Cargo.toml b/Cargo.toml index ee2f541..ef67dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] rustyline = "9.0.0" +rustyline-derive = "*" +env_logger= "*" serde = "1.0.130" serialport = "4.0.1" [target.'cfg(windows)'.dependencies] diff --git a/src/main.rs b/src/main.rs index 8a07cec..e2abe0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,85 @@ use std::io::{self, Write}; use std::time::Duration; use wmi::{COMLibrary, WMIConnection, WMIError}; +use std::borrow::Cow::{self, Borrowed, Owned}; + +use rustyline::completion::{Completer, FilenameCompleter, Pair}; +use rustyline::config::OutputStreamType; use rustyline::error::ReadlineError; -use rustyline::Editor; +use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; +use rustyline::hint::{Hinter, HistoryHinter}; +use rustyline::validate::{self, MatchingBracketValidator, Validator}; +use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyEvent}; +use rustyline_derive::Helper; + +#[derive(Helper)] +struct MyHelper { + completer: FilenameCompleter, + highlighter: MatchingBracketHighlighter, + validator: MatchingBracketValidator, + hinter: HistoryHinter, + colored_prompt: String, +} + +impl Completer for MyHelper { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> Result<(usize, Vec), ReadlineError> { + self.completer.complete(line, pos, ctx) + } +} + +impl Hinter for MyHelper { + type Hint = String; + + fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + self.hinter.hint(line, pos, ctx) + } +} + +impl Highlighter for MyHelper { + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + default: bool, + ) -> Cow<'b, str> { + if default { + Borrowed(&self.colored_prompt) + } else { + Borrowed(prompt) + } + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Owned("\x1b[1m".to_owned() + hint + "\x1b[m") + } + + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.highlighter.highlight(line, pos) + } + + fn highlight_char(&self, line: &str, pos: usize) -> bool { + self.highlighter.highlight_char(line, pos) + } +} + +impl Validator for MyHelper { + fn validate( + &self, + ctx: &mut validate::ValidationContext, + ) -> rustyline::Result { + self.validator.validate(ctx) + } + + fn validate_while_typing(&self) -> bool { + self.validator.validate_while_typing() + } +} #[cfg(windows)] #[allow(non_camel_case_types)] @@ -123,13 +200,32 @@ fn main() { //TODO:Add auto complete with AT command history //TODO:Colors - // `()` can be used when no completer is required - let mut rl = Editor::<()>::new(); + env_logger::init(); + let config = Config::builder() + .history_ignore_space(true) + .completion_type(CompletionType::List) + .edit_mode(EditMode::Emacs) + .output_stream(OutputStreamType::Stdout) + .build(); + let h = MyHelper { + completer: FilenameCompleter::new(), + highlighter: MatchingBracketHighlighter::new(), + hinter: HistoryHinter {}, + colored_prompt: "".to_owned(), + validator: MatchingBracketValidator::new(), + }; + let mut rl = Editor::with_config(config); + rl.set_helper(Some(h)); + rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward); + rl.bind_sequence(KeyEvent::alt('r'), Cmd::HistorySearchBackward); + if rl.load_history("history.txt").is_err() { println!("No previous history."); } + + rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}❯\x1b[0m", port_name); loop { - let readline = rl.readline("❯❯ "); + let readline = rl.readline(format!("{}❯", port_name).as_str()); match readline { Ok(line) => { let line = line + "\r";