diff --git a/src/input/input.rs b/src/input/input.rs index df251ea..3d19414 100644 --- a/src/input/input.rs +++ b/src/input/input.rs @@ -1,10 +1,18 @@ -use std::{io::stdin, sync::Arc}; +use std::{ + io::{stdin, stdout, Write}, + sync::Arc, +}; use rustyline::{ + completion::{Completer, FilenameCompleter, Pair}, + highlight::Highlighter, + hint::{Hinter, HistoryHinter}, history::History, - Editor, + validate::Validator, + Editor, Helper, }; use termion::{ + clear, color, cursor, event::{Event, Key}, input::TermReadEventsAndRaw, }; @@ -16,12 +24,80 @@ use tokio::{ task, }; -pub async fn read_line<H>( - rl: Arc<Mutex<Editor<(), H>>>, +pub struct CompletionHelper { + completer: Option<FilenameCompleter>, + hinter: HistoryHinter, +} +impl Helper for CompletionHelper {} +impl Hinter for CompletionHelper { + type Hint = String; + fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> { + return self.hinter.hint(line, pos, ctx); + } +} +impl Highlighter for CompletionHelper {} +impl Validator for CompletionHelper {} +impl Completer for CompletionHelper { + type Candidate = Pair; + fn complete( + &self, // FIXME should be `&mut self` + line: &str, + pos: usize, + ctx: &rustyline::Context<'_>, + ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> { + return match &self.completer { + Some(completer) => completer.complete(line, pos, ctx), + None => Ok((0, Vec::new())), + }; + } +} + +impl CompletionHelper { + pub fn new() -> CompletionHelper { + let helper: CompletionHelper = CompletionHelper { + completer: Some(FilenameCompleter::new()), + hinter: HistoryHinter {}, + }; + return helper; + } + pub fn new_only_hinter() -> CompletionHelper { + let helper: CompletionHelper = CompletionHelper { + completer: None, + hinter: HistoryHinter {}, + }; + return helper; + } +} + +pub fn display_notification(text: String) { + let mut stdout = stdout(); + let notification = format!( + "{goto}{clear}{success}{text}{reset}", + goto = cursor::Goto(1, 1), + clear = clear::CurrentLine, + success = color::Fg(color::LightGreen), + reset = color::Fg(color::Reset), + ); + + // save cursor position + write!(stdout, "{}", cursor::Save).unwrap(); + stdout.flush().unwrap(); + + stdout.write_all(¬ification.as_bytes()).unwrap(); + stdout.flush().unwrap(); + + // restore cursor position + write!(stdout, "{}", cursor::Restore).unwrap(); + stdout.flush().unwrap(); +} + +pub async fn read_line<T, H>( + rl: Arc<Mutex<Editor<T, H>>>, prompt: Option<&str>, ) -> Result<String, RecvError> where H: History + Send + 'static, + T: Helper + Send + 'static, { let (tx, rx) = oneshot::channel::<String>(); let input_prompt = match prompt { diff --git a/src/main.rs b/src/main.rs index 7ba186c..6b8f102 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,19 +2,21 @@ use std::collections::HashMap; use std::env::{self, set_current_dir}; use std::sync::Arc; -use input::input::read_line; +use input::input::{read_line, CompletionHelper}; use menu::menu_list::clear; -use rustyline::DefaultEditor; +use rustyline::history::MemHistory; +use rustyline::{Config, CompletionType, Editor}; use std::process::{exit, Command}; use termion::raw::IntoRawMode; +use crate::input::input::display_notification; use crate::menu::menu_list; use crate::socket::{connection, listener}; use connection::{handle_new_shell, Handle}; use futures_util::pin_mut; use futures_util::stream::StreamExt; -use std::io::{stdout, Write}; -use termion::{self, clear, color, cursor}; +use std::io::stdout; +use termion::{self, color}; use tokio::sync::Mutex; mod input; @@ -45,7 +47,14 @@ fn input_loop( init_message: Option<String>, ) { tokio::spawn(async move { - let menu_rl = Arc::new(Mutex::new(DefaultEditor::new().unwrap())); + let history = MemHistory::new(); + let mut builder = Config::builder(); + builder = builder.completion_type(CompletionType::Circular); + let config = builder.build(); + let mut rl = Editor::with_history(config, history).unwrap(); + let helper = CompletionHelper::new(); + rl.set_helper(Some(helper)); + let menu_rl = Arc::new(Mutex::new(rl)); clear(); if init_message.is_some() { let msg = init_message.unwrap(); @@ -144,37 +153,12 @@ async fn main() { exit(1) } }; - // display notification, I know this is gross but it's the best I can do with rustyline getting in the way :( - let mut stdout = stdout(); - let notification = format!( - "{goto}{clear}{success}new shell received from {addr} !{reset}", - goto = cursor::Goto(1, 1), - clear = clear::CurrentLine, - success = color::Fg(color::LightGreen), - addr = soc.peer_addr().unwrap().to_string(), - reset = color::Fg(color::Reset), - ); - // save cursor position - stdout - .write_all(&"\x1B7".as_bytes()) - .unwrap(); - stdout.flush().unwrap(); - - stdout - .write_all( - ¬ification - .as_bytes(), - ) - .unwrap(); - stdout.flush().unwrap(); - - // restore cursor position - stdout - .write_all(&"\x1B8".as_bytes()) - .unwrap(); - stdout.flush().unwrap(); - - handle_new_shell(soc, connected_shells.clone(), None).await; + + let soc_added = handle_new_shell(soc, connected_shells.clone(), None).await; + + if soc_added { + display_notification(String::from("new shell received!")); + } } } diff --git a/src/socket/connection.rs b/src/socket/connection.rs index d361cfa..4a1b141 100644 --- a/src/socket/connection.rs +++ b/src/socket/connection.rs @@ -11,9 +11,11 @@ use tokio::net::TcpStream; use tokio::sync::Mutex; use tokio::time::sleep; +use crate::input::input::CompletionHelper; + #[derive(Clone)] pub struct Handle { - pub readline: Arc<Mutex<Editor<(), MemHistory>>>, + pub readline: Arc<Mutex<Editor<CompletionHelper, MemHistory>>>, pub read_stream: Arc<Mutex<OwnedReadHalf>>, pub write_stream: Arc<Mutex<OwnedWriteHalf>>, pub raw_mode: bool, @@ -25,8 +27,10 @@ impl Handle { let mut builder = Config::builder(); builder = builder.check_cursor_position(false); let config = builder.build(); + let mut rl = Editor::with_history(config, history).unwrap(); + rl.set_helper(Some(CompletionHelper::new_only_hinter())); let handle = Handle { - readline: Arc::new(Mutex::new(Editor::with_history(config, history).unwrap())), + readline: Arc::new(Mutex::new(rl)), read_stream: Arc::new(Mutex::new(read_stream)), write_stream: Arc::new(Mutex::new(write_stream)), raw_mode: false, @@ -66,7 +70,7 @@ pub async fn handle_new_shell( soc: TcpStream, connected_shells: Arc<Mutex<HashMap<String, Handle>>>, skip_validation: Option<bool>, -) { +) -> bool { let soc_addr = soc.peer_addr(); let (soc_read, soc_write) = soc.into_split(); let handle = Handle::new(soc_read, soc_write); @@ -90,11 +94,12 @@ pub async fn handle_new_shell( let mut shells = connected_shells.lock().await; shells.insert(soc_key, handle); } else { - return; + return false; } } - Err(_) => return, + Err(_) => return false, } + return true; } #[cfg(test)]