1 //! Tools to work with rustyline readline() library. 2 3 use futures::Future; 4 5 use rustyline::completion::Completer; 6 use rustyline::error::ReadlineError; 7 use rustyline::highlight::Highlighter; 8 use rustyline::hint::Hinter; 9 use rustyline::validate::Validator; 10 use rustyline::{CompletionType, Config, Editor}; 11 use rustyline_derive::Helper; 12 13 use std::collections::HashSet; 14 use std::pin::Pin; 15 use std::sync::{Arc, Mutex}; 16 use std::task::{Context, Poll}; 17 18 use crate::console_blue; 19 use crate::ClientContext; 20 21 #[derive(Helper)] 22 struct BtHelper { 23 // Command rules must follow below format: 24 // cmd arg1 arg2 arg3 ... 25 // where each argument could have multiple options separated by a single '|' 26 // 27 // It is not required to put an argument in angle brackets. 28 // 29 // "address" in options is a keyword, which will be matched by any of the founded 30 // and bonded devices. 31 // 32 // Example: 33 // list <found|bonded> <address> 34 // This will match 35 // list found any-cached-address 36 // and 37 // list bond any-cached-address 38 command_rules: Vec<String>, 39 client_context: Arc<Mutex<ClientContext>>, 40 } 41 42 #[derive(Hash, Eq, PartialEq)] 43 struct CommandCandidate { 44 suggest_word: String, 45 matched_len: usize, 46 } 47 48 impl Completer for BtHelper { 49 type Candidate = String; 50 complete( &self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>, ) -> Result<(usize, Vec<String>), ReadlineError>51 fn complete( 52 &self, 53 line: &str, 54 pos: usize, 55 _ctx: &rustyline::Context<'_>, 56 ) -> Result<(usize, Vec<String>), ReadlineError> { 57 let slice = &line[..pos]; 58 let candidates = self.get_candidates(slice.to_string().clone()); 59 let mut completions = 60 candidates.iter().map(|c| c.suggest_word.clone() + " ").collect::<Vec<String>>(); 61 62 completions.sort(); 63 64 // |start| points to the starting position of the current token 65 let start = match slice.rfind(' ') { 66 Some(x) => x + 1, 67 None => 0, 68 }; 69 70 Ok((start, completions)) 71 } 72 } 73 74 impl Hinter for BtHelper { 75 type Hint = String; 76 } 77 78 impl Highlighter for BtHelper {} 79 80 impl Validator for BtHelper {} 81 82 impl BtHelper { get_candidates(&self, cmd: String) -> HashSet<CommandCandidate>83 fn get_candidates(&self, cmd: String) -> HashSet<CommandCandidate> { 84 let mut result = HashSet::<CommandCandidate>::new(); 85 86 for rule in self.command_rules.iter() { 87 let n_splits = cmd.split(" ").count(); 88 // The tokens should have empty strings removed from them, except the last one. 89 let tokens = cmd 90 .split(" ") 91 .enumerate() 92 .filter_map(|(i, token)| (i == n_splits - 1 || token != "").then(|| token)); 93 94 let n_cmd = tokens.clone().count(); 95 for (i, (rule_token, cmd_token)) in rule.split(" ").zip(tokens).enumerate() { 96 let mut candidates = Vec::<String>::new(); 97 let mut match_some = false; 98 99 for opt in rule_token.replace("<", "").replace(">", "").split("|") { 100 if opt.eq("address") { 101 let devices = self.client_context.lock().unwrap().get_devices(); 102 candidates.extend(devices); 103 } else { 104 candidates.push(opt.to_string()); 105 } 106 } 107 108 if cmd_token.len() == 0 { 109 candidates.iter().for_each(|s| { 110 result.insert(CommandCandidate { suggest_word: s.clone(), matched_len: 0 }); 111 }); 112 break; 113 } 114 115 for opt in candidates { 116 if opt.starts_with(cmd_token) { 117 match_some = true; 118 if i == n_cmd - 1 { 119 // we add candidates only if it's the last word 120 result.insert(CommandCandidate { 121 suggest_word: opt.clone(), 122 matched_len: cmd_token.len(), 123 }); 124 } 125 } 126 } 127 128 if !match_some { 129 break; 130 } 131 } 132 } 133 result 134 } 135 } 136 137 /// A future that does async readline(). 138 /// 139 /// async readline() is implemented by spawning a thread for the blocking readline(). While this 140 /// readline() thread is blocked, it yields back to executor and will wake the executor up when the 141 /// blocked thread has proceeded and got input from readline(). 142 pub struct AsyncReadline { 143 rl: Arc<Mutex<Editor<BtHelper>>>, 144 result: Arc<Mutex<Option<rustyline::Result<String>>>>, 145 } 146 147 impl Future for AsyncReadline { 148 type Output = rustyline::Result<String>; 149 poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<rustyline::Result<String>>150 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<rustyline::Result<String>> { 151 let option = self.result.lock().unwrap().take(); 152 if let Some(res) = option { 153 return Poll::Ready(res); 154 } 155 156 let waker = cx.waker().clone(); 157 let result_clone = self.result.clone(); 158 let rl = self.rl.clone(); 159 std::thread::spawn(move || { 160 let readline = rl.lock().unwrap().readline(console_blue!("bluetooth> ")); 161 *result_clone.lock().unwrap() = Some(readline); 162 waker.wake(); 163 }); 164 165 Poll::Pending 166 } 167 } 168 169 /// Wrapper of rustyline editor that supports async readline(). 170 pub struct AsyncEditor { 171 rl: Arc<Mutex<Editor<BtHelper>>>, 172 } 173 174 impl AsyncEditor { 175 /// Creates new async rustyline editor. 176 /// 177 /// * `commands` - List of commands for autocomplete. new( command_rules: Vec<String>, client_context: Arc<Mutex<ClientContext>>, ) -> rustyline::Result<AsyncEditor>178 pub(crate) fn new( 179 command_rules: Vec<String>, 180 client_context: Arc<Mutex<ClientContext>>, 181 ) -> rustyline::Result<AsyncEditor> { 182 let builder = Config::builder() 183 .auto_add_history(true) 184 .history_ignore_dups(true) 185 .completion_type(CompletionType::List); 186 let config = builder.build(); 187 let mut rl = rustyline::Editor::with_config(config)?; 188 let helper = BtHelper { command_rules, client_context }; 189 rl.set_helper(Some(helper)); 190 Ok(AsyncEditor { rl: Arc::new(Mutex::new(rl)) }) 191 } 192 193 /// Does async readline(). 194 /// 195 /// Returns a future that will do the readline() when await-ed. This does not block the thread 196 /// but rather yields to the executor while waiting for a command to be entered. readline(&self) -> AsyncReadline197 pub fn readline(&self) -> AsyncReadline { 198 AsyncReadline { rl: self.rl.clone(), result: Arc::new(Mutex::new(None)) } 199 } 200 } 201