Skip to content

Commit

Permalink
feat: improve tab completion (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolfv authored Sep 6, 2024
1 parent 69ea52d commit ddaeaf3
Showing 1 changed file with 48 additions and 12 deletions.
60 changes: 48 additions & 12 deletions crates/shell/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use rustyline::{Context, Helper};
use std::borrow::Cow::{self, Owned};
use std::env;
use std::fs;
use std::path::Path;

pub struct ShellCompleter;

Expand All @@ -22,41 +23,73 @@ impl Completer for ShellCompleter {
let mut matches = Vec::new();
let (start, word) = extract_word(line, pos);

let is_start = start == 0;
// Complete filenames
complete_filenames(word, &mut matches);
complete_filenames(is_start, word, &mut matches);

// Complete shell commands
complete_shell_commands(word, &mut matches);
complete_shell_commands(is_start, word, &mut matches);

// Complete executables in PATH
complete_executables_in_path(word, &mut matches);
complete_executables_in_path(is_start, word, &mut matches);

Ok((start, matches))
}
}

fn extract_word(line: &str, pos: usize) -> (usize, &str) {
if line.ends_with(" ") {
return (pos, "");
}
let words: Vec<_> = line[..pos].split_whitespace().collect();
let word_start = words.last().map_or(0, |w| line.rfind(w).unwrap());
(word_start, &line[word_start..pos])
}

fn complete_filenames(word: &str, matches: &mut Vec<Pair>) {
if let Ok(entries) = fs::read_dir(".") {
fn complete_filenames(_is_start: bool, word: &str, matches: &mut Vec<Pair>) {
// Split the word into directory path and partial filename
let (dir_path, partial_name) = match word.rfind('/') {
Some(last_slash) => (&word[..=last_slash], &word[last_slash + 1..]),
None => ("", word),
};

// Determine the full directory path to search
let search_dir = if dir_path.starts_with('/') {
dir_path.to_string()
} else {
format!("./{}", dir_path)
};

if let Ok(entries) = fs::read_dir(Path::new(&search_dir)) {
for entry in entries.flatten() {
if let Ok(name) = entry.file_name().into_string() {
if name.starts_with(word) {
matches.push(Pair {
display: name.clone(),
replacement: name,
});
if name.starts_with(partial_name) {
let full_path = format!("{}{}", dir_path, name);
match entry.file_type() {
Ok(file_type) if file_type.is_dir() => {
matches.push(Pair {
display: full_path.clone() + "/",
replacement: full_path + "/",
});
}
Ok(_) => {
matches.push(Pair {
display: full_path.clone(),
replacement: full_path,
});
}
Err(_) => {}
}
}
}
}
}
}

fn complete_shell_commands(word: &str, matches: &mut Vec<Pair>) {
fn complete_shell_commands(is_start: bool, word: &str, matches: &mut Vec<Pair>) {
if !is_start {
return;
}
let shell_commands = ["ls", "cat", "cd", "pwd", "echo", "grep"];
for &cmd in &shell_commands {
if cmd.starts_with(word) {
Expand All @@ -68,7 +101,10 @@ fn complete_shell_commands(word: &str, matches: &mut Vec<Pair>) {
}
}

fn complete_executables_in_path(word: &str, matches: &mut Vec<Pair>) {
fn complete_executables_in_path(is_start: bool, word: &str, matches: &mut Vec<Pair>) {
if !is_start {
return;
}
if let Ok(paths) = env::var("PATH") {
for path in env::split_paths(&paths) {
if let Ok(entries) = fs::read_dir(path) {
Expand Down

0 comments on commit ddaeaf3

Please sign in to comment.