From fb443270b14ad4d404f6b401f83b323a9769e667 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Tue, 10 Mar 2026 17:03:16 +0100 Subject: command-name completion based on PATH --- src/completion.rs | 34 +++++++++++++++++++++++++++++++++- src/main.rs | 5 +++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/completion.rs b/src/completion.rs index 2137b6f..d10aabd 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,4 +1,5 @@ use crate::*; +use std::path::{Path, PathBuf}; use std::{env, fs}; pub struct Suggestion { @@ -80,10 +81,41 @@ pub fn variable_completion(session: Arc>, prefix: BString) -> Vec out } +#[derive(Default)] +pub struct PathCache { + binaries: HashMap, +} + +fn is_executable(path: &Path) -> bool { + use std::os::unix::fs::PermissionsExt; + fs::metadata(path) + .map(|m| m.permissions().mode() & 0o111 != 0) + .unwrap_or(false) +} + +pub fn populate_path_cache(session: Arc>) { + let path_var = env::var_os("PATH").unwrap(); + + let mut binaries = HashMap::new(); + + for dir in env::split_paths(&path_var) { + if let Ok(entries) = fs::read_dir(&dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() && is_executable(&path) { + binaries.insert(path.file_name().unwrap().as_bytes().to_vec(), path); + } + } + } + } + + session.lock().unwrap().path_cache = PathCache { binaries }; +} + pub fn command_completion(session: Arc>, prefix: BString) -> Vec { let se = session.lock().unwrap(); let mut out = Vec::new(); - for fun in se.funs.keys().chain(se.builtins.keys()) { + for fun in se.funs.keys().chain(se.builtins.keys()).chain(se.path_cache.binaries.keys()) { if fun.starts_with(&prefix) { out.push(Suggestion { display: fun.to_vec(), diff --git a/src/main.rs b/src/main.rs index 5e7d40a..7a8d864 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,7 @@ pub mod serialization; use linebuf::LineBuf; use raw::*; +use crate::completion::PathCache; use crate::cursor::{Direction, move_cursor}; use crate::history::HistoryEntry; use crate::parse::{Ast, PreExpansion}; @@ -74,6 +75,7 @@ pub struct Session { vars: HashMap, funs: HashMap>, socket_running: Option, + path_cache: PathCache, /// n before end of history.len() /// 0 == not checking history @@ -270,11 +272,14 @@ fn event_loop() { socket_running: None, vars: HashMap::new(), funs: HashMap::new(), + path_cache: Default::default(), }; print!("{}", se.prompt()); let session = Arc::new(Mutex::new(se)); + completion::populate_path_cache(session.clone()); + let _sock_dropper = export_fun::listen(session.clone()); loop { -- cgit v1.2.3