summaryrefslogtreecommitdiffstats
path: root/src/buffer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer.rs')
-rw-r--r--src/buffer.rs177
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;
}
},
}