diff --git a/crates/shell/src/completion.rs b/crates/shell/src/completion.rs index 7de7937..70d7a52 100644 --- a/crates/shell/src/completion.rs +++ b/crates/shell/src/completion.rs @@ -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; @@ -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) { - if let Ok(entries) = fs::read_dir(".") { +fn complete_filenames(_is_start: bool, word: &str, matches: &mut Vec) { + // 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) { +fn complete_shell_commands(is_start: bool, word: &str, matches: &mut Vec) { + if !is_start { + return; + } let shell_commands = ["ls", "cat", "cd", "pwd", "echo", "grep"]; for &cmd in &shell_commands { if cmd.starts_with(word) { @@ -68,7 +101,10 @@ fn complete_shell_commands(word: &str, matches: &mut Vec) { } } -fn complete_executables_in_path(word: &str, matches: &mut Vec) { +fn complete_executables_in_path(is_start: bool, word: &str, matches: &mut Vec) { + 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) {