[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 07/11] Add autocomplete


This patch adds a fairly rudimentary form of autocomplete. For starters,
it only works for filepaths, not for programs. Additionally, it
currently only works for the present working directory. You cannot yet
autocomplete even one more level deep.
---
 Cargo.lock    |  31 +++++++++
 Cargo.toml    |   1 +
 src/buffer.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++-----
 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
@@ -2,11 +2,30 @@
 # It is not intended for manual editing.
 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",
 ]
@@ -17,6 +36,18 @@ version = "0.2.158"
 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"
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;
                 }
             },
         }
-- 
2.46.2




References:
[PATCH 00/11] Add filepath autcompleteRory Dudley <rory@xxxxxxx>
Archive administrator: postmaster AT dwarvish DOT org