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(&notification.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(
-                &notification
-                .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)]