diff options
-rw-r--r-- | Cargo.lock | 31 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/buffer.rs | 177 |
3 files changed, 193 insertions, 16 deletions
@@ -3,10 +3,29 @@ version = 3 [[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] name = "dwarvish" version = "0.0.1" dependencies = [ "libc", + "nix", "signal-hook", "termios", ] @@ -18,6 +37,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -14,5 +14,6 @@ path = "src/main.rs" [dependencies] libc = "0.2.153" +nix = { version = "0.29.0", features = ["signal"] } signal-hook = "0.3.17" termios = "0.3.3" 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; } }, } |