summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-09-21 21:05:31 -0600
committerRory Dudley2024-09-21 21:05:31 -0600
commitda7aa5dbeb5e1c98c83fba10648b68962b614353 (patch)
tree4d665e35b474b6f2f59de310ec8083d5d40be64a
parent2852f93b714cf8ea83ba7d9aa5d6b138f58ee7e6 (diff)
downloaddwarvish-da7aa5dbeb5e1c98c83fba10648b68962b614353.tar.gz
Expand filepath autocomplete
This patch expands upon the last one, by providing a filepath autcomplete routine, which works more similarly to that of zsh or bash. It works for: - Any paths containing the current directory (`.`) - Any paths containing the parent directory (`..`) - Any paths containing the root directory (`/`) - Any paths that don't contain any explicit directory (defaults to the current directory, i.e. `.`) In order to achieve this, the pwd (present working directory) was broken out from the autocomplete() function, and is now recomputed every time a character is typed (in the getline() function). pwd always starts off as the current directory, but may change based off of user input. If the current directory cannot be read (likely due to a permissions issue), dwvsh defaults to the user's home directory for now. The filepath autocomplete also works for paths with mutliple directories now (i.e. /etc/mail/xxx), will still allow you to autocomplete files and directories beginning with 'xxx' inside the '/etc/mail' directory. It is also possible to use the tilda character (`~`) with the filepath autocomplete now (it denotes the user's home directory, for instance, autocomplete works for '~/.config/'). Finally, some logic for Shift+Tab was added to autocomplete. It works the same as Tab, except runs backwards through the files and directories (as opposed to forwards).
Notes
Notes: Some commented out code was left in this commit, just in case it may have any use in future code (it was quite helpful in figuring out some of the intricaces for proper filepath autocomplete). Speaking of which, the autocomplete code should probably be documented (annotated) a bit better. It is somewhat complex, so it is definitely worth writing some unit tests for (ugh, dangling participle), as well.
-rw-r--r--src/buffer.rs149
1 files changed, 134 insertions, 15 deletions
diff --git a/src/buffer.rs b/src/buffer.rs
index 3ea70b9..d41f883 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -3,7 +3,7 @@ use nix::unistd::Pid;
use std::env::current_dir;
use std::fs;
use std::io::{self, Read, Write};
-// use std::path::PathBuf;
+use std::path::PathBuf;
use std::sync::{Arc, Mutex};
// STDIN is file descriptor (fd) 0 on Linux and other UN*X-likes
@@ -16,6 +16,7 @@ enum Key {
Right,
Left,
Tab,
+ ShiftTab,
Ctrlc,
Else(u8),
Ignored,
@@ -45,6 +46,9 @@ fn getchar() -> Key {
67 => return Key::Right,
68 => return Key::Left,
+ // Shift tab
+ 90 => return Key::ShiftTab,
+
// Everything else
_ => return Key::Ignored,
}
@@ -73,9 +77,10 @@ fn getchar() -> Key {
fn autocomplete(
buffer: &mut Arc<Mutex<Vec<u8>>>,
index: usize,
+ pwd: &PathBuf,
) -> Result<(String, usize), Box<dyn std::error::Error>> {
// Get the present working directory
- let pwd = current_dir()?;
+ // let pwd = current_dir()?;
let buffer = buffer.lock().unwrap();
let word = match buffer.last() {
@@ -84,7 +89,7 @@ fn autocomplete(
_ => {
let mut word: Vec<u8> = vec![];
for c in buffer.iter().rev() {
- if *c == b' ' {
+ if *c == b' ' || *c == b'/' {
break;
}
word.push(*c);
@@ -171,6 +176,7 @@ fn autocomplete(
/// 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 pwd = current_dir().unwrap_or(PathBuf::from(env!("HOME")));
let mut auindex = 0;
let mut aulen = 0;
@@ -204,7 +210,7 @@ pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) ->
*pos.lock().unwrap() -= 1;
aulen -= 1;
}
- let (path, len) = autocomplete(buffer, auindex).unwrap();
+ let (path, len) = autocomplete(buffer, auindex, &pwd).unwrap();
for c in path.into_bytes().iter() {
buffer.lock().unwrap().insert(*pos.lock().unwrap(), *c);
*pos.lock().unwrap() += 1;
@@ -215,6 +221,21 @@ pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) ->
auindex = 0;
}
}
+ Key::ShiftTab => {
+ while aulen > 0 {
+ buffer.lock().unwrap().pop();
+ print!("\u{8} \u{8}");
+ *pos.lock().unwrap() -= 1;
+ aulen -= 1;
+ }
+ let (path, len) = autocomplete(buffer, auindex, &pwd).unwrap();
+ for c in path.into_bytes().iter() {
+ buffer.lock().unwrap().insert(*pos.lock().unwrap(), *c);
+ *pos.lock().unwrap() += 1;
+ aulen += 1;
+ }
+ auindex = if auindex == 0 { len - 1 } else { auindex - 1 };
+ }
Key::Ctrlc => {
kill(Pid::from_raw(0 as i32), Signal::SIGINT).unwrap();
}
@@ -263,33 +284,131 @@ pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) ->
aulen = 0;
}
+ // forward slash
+ // b'/' => {
+ // let mut buffer = buffer.lock().unwrap();
+ //
+ // match buffer.last() {
+ // Some(c) if *c == b'/' => {}
+ // Some(_) | None => {
+ // buffer.insert(*pos.lock().unwrap(), b'/');
+ // *pos.lock().unwrap() += 1;
+ // print!("/");
+ // }
+ // }
+ //
+ // 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()
+ // }
+ // };
+ //
+ // // Check for the ~ character (used to represent $HOME)
+ // let word = if word.starts_with("~") {
+ // let home = env!("HOME");
+ // format!("{}{}", home, &word[1..]).to_string()
+ // } else {
+ // word
+ // };
+ //
+ // // Reset autocomplete variables
+ // pwd = PathBuf::from(word);
+ // auindex = 0;
+ // aulen = 0;
+ // }
+
// everything else
_ => {
+ let mut 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();
+ if word.starts_with(b"..")
+ && word.iter().filter(|c| *c == &b'/').count() == 1
+ {
+ word = vec![b'.', b'.']
+ }
+ loop {
+ match word.last() {
+ Some(c) if *c == b'/' || *c == b'.' => {
+ break;
+ }
+ Some(_) => {
+ word.pop();
+ }
+ None => {
+ break;
+ }
+ }
+ }
+ String::from_utf8_lossy(&mut word).to_string()
+ }
+ };
+
+ // Check for the ~ character (used to represent $HOME)
+ let word = if word.is_empty() {
+ current_dir()
+ .unwrap_or(PathBuf::from(env!("HOME")))
+ .to_string_lossy()
+ .to_string()
+ } else if word.starts_with("~") {
+ let home = env!("HOME");
+ format!("{}{}", home, &word[1..]).to_string()
+ } else {
+ word
+ };
+
+ // Reset autocomplete variables
+ pwd = PathBuf::from(word);
+ auindex = 0;
+ aulen = 0;
+
// Print out the character as the user is typing
- print!("{}", c as char);
+ match buffer.last() {
+ Some(last) if *last == b'/' && c == b'/' => {
+ buffer.pop();
+ *pos.lock().unwrap() -= 1;
+ }
+ Some(_) => print!("{}", c as char),
+ None => print!("{}", c as char),
+ }
// Insert the character onto the buffer at whatever *pos.lock().unwrap()ition the cursor is at
- buffer.lock().unwrap().insert(*pos.lock().unwrap(), c);
+ buffer.insert(*pos.lock().unwrap(), c);
// Increment our *pos.lock().unwrap()ition
*pos.lock().unwrap() += 1;
// Reprint the end of the buffer if inserting at the front or middle
- if *pos.lock().unwrap() != buffer.lock().unwrap().len() {
+ if *pos.lock().unwrap() != buffer.len() {
print!(
"{}",
- String::from_utf8_lossy(
- &buffer.lock().unwrap()[*pos.lock().unwrap()..]
- )
+ String::from_utf8_lossy(&buffer[*pos.lock().unwrap()..])
);
- for _ in *pos.lock().unwrap()..buffer.lock().unwrap().len() {
+ for _ in *pos.lock().unwrap()..buffer.len() {
print!("\u{8}");
}
}
-
- // Reset autocomplete variables
- auindex = 0;
- aulen = 0;
}
},
}