summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock31
-rw-r--r--Cargo.toml1
-rw-r--r--src/buffer.rs177
3 files changed, 193 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cb0161a..5f2722c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 93c090a..3aed5a3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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;
}
},
}