diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buffer.rs | 177 |
1 files changed, 161 insertions, 16 deletions
diff --git a/src/buffer.rs b/src/buffer.rs index 8668249..3ea70b9 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,9 @@ +use nix::sys::signal::{kill, Signal}; +use nix::unistd::Pid; +use std::env::current_dir; +use std::fs; use std::io::{self, Read, Write}; +// use std::path::PathBuf; use std::sync::{Arc, Mutex}; // STDIN is file descriptor (fd) 0 on Linux and other UN*X-likes @@ -10,6 +15,8 @@ enum Key { Down, Right, Left, + Tab, + Ctrlc, Else(u8), Ignored, } @@ -23,28 +30,134 @@ fn getchar() -> Key { io::stdin().read_exact(&mut b).unwrap(); // Might me an ASNI escape sequence - if b[0] == 27 { - io::stdin().read_exact(&mut b).unwrap(); - - if b[0] == 91 { + match b[0] { + // Escape sequences + 27 => { io::stdin().read_exact(&mut b).unwrap(); - match b[0] { - // Arrow keys - 65 => return Key::Up, - 66 => return Key::Down, - 67 => return Key::Right, - 68 => return Key::Left, + if b[0] == 91 { + io::stdin().read_exact(&mut b).unwrap(); + + match b[0] { + // Arrow keys + 65 => return Key::Up, + 66 => return Key::Down, + 67 => return Key::Right, + 68 => return Key::Left, + + // Everything else + _ => return Key::Ignored, + } + } + + return Key::Ignored; + } + + // Tab + 9 => return Key::Tab, - // Everything else - _ => return Key::Ignored, + // ctrlc + 3 => return Key::Ctrlc, + + // Everything else + _ => Key::Else(b[0]), + } +} + +/// Handles autocomplete functionality for file paths +/// +/// Currently, dwvsh does not implement zsh's full autocomplete +/// ecosystem (though there are plans to). For now, this simply adds a +/// builtin way to get autocomplete suggestions for file paths via the +/// <tab> key. +fn autocomplete( + buffer: &mut Arc<Mutex<Vec<u8>>>, + index: usize, +) -> Result<(String, usize), Box<dyn std::error::Error>> { + // Get the present working directory + let pwd = current_dir()?; + + let buffer = buffer.lock().unwrap(); + let word = match buffer.last() { + Some(c) if *c == b' ' => "".to_string(), + None => "".to_string(), + _ => { + let mut word: Vec<u8> = vec![]; + for c in buffer.iter().rev() { + if *c == b' ' { + break; + } + word.push(*c); } + word.reverse(); + String::from_utf8_lossy(&mut word).to_string() } + }; + + // Get a file listing + let paths = fs::read_dir(&pwd)?; + let paths = if word.is_empty() { + paths + .into_iter() + .filter(|path| { + !path + .as_ref() + .unwrap() + .file_name() + .to_string_lossy() + .starts_with(".") + }) + .collect::<Vec<_>>() + } else { + paths + .into_iter() + .filter(|path| { + path.as_ref() + .unwrap() + .file_name() + .to_string_lossy() + .starts_with(&word) + }) + .collect::<Vec<_>>() + }; - return Key::Ignored; + // Return nothing is paths is empty + if paths.is_empty() { + return Ok(("".to_string(), 0)); } - Key::Else(b[0]) + // Collect path into DirEntries + let mut paths = paths + .iter() + .map(|path| path.as_ref().unwrap()) + .collect::<Vec<_>>(); + + // Sort the paths + paths.sort_by(|a, b| { + a.file_name() + .to_ascii_lowercase() + .cmp(&b.file_name().to_ascii_lowercase()) + }); + + // Output the file listing at index on the prompt + // let path = paths[index].path(); + let path = paths[index].path(); + + let path = if path.is_dir() { + path.file_name().unwrap().to_str().unwrap().to_string() + "/" + } else { + path.file_name().unwrap().to_str().unwrap().to_string() + }; + + let path = if word.is_empty() { + path + } else { + path[word.len()..].to_string() + }; + + print!("{}", path); + + Ok((path, paths.len())) } /// Handle user input at the repl prompt @@ -57,10 +170,13 @@ fn getchar() -> Key { /// that (ICANON and ECHO) are off. See the beginning of [crate::repl] /// for more details. pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) -> usize { + // Keep track of index for autocomplete + let mut auindex = 0; + let mut aulen = 0; + // Loop over characters until there is a newline loop { - let c = getchar(); - match c { + match getchar() { Key::Up => { continue; } @@ -81,6 +197,27 @@ pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) -> print!("\u{8}"); *pos.lock().unwrap() -= 1; } + Key::Tab => { + while aulen > 0 { + buffer.lock().unwrap().pop(); + print!("\u{8} \u{8}"); + *pos.lock().unwrap() -= 1; + aulen -= 1; + } + let (path, len) = autocomplete(buffer, auindex).unwrap(); + for c in path.into_bytes().iter() { + buffer.lock().unwrap().insert(*pos.lock().unwrap(), *c); + *pos.lock().unwrap() += 1; + aulen += 1; + } + auindex += 1; + if auindex >= len { + auindex = 0; + } + } + Key::Ctrlc => { + kill(Pid::from_raw(0 as i32), Signal::SIGINT).unwrap(); + } Key::Ignored => { continue; } @@ -120,6 +257,10 @@ pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) -> print!("\u{8}"); } } + + // Reset autocomplete variables + auindex = 0; + aulen = 0; } // everything else @@ -145,6 +286,10 @@ pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) -> print!("\u{8}"); } } + + // Reset autocomplete variables + auindex = 0; + aulen = 0; } }, } |