From 89e027f61eda0d918ee2c2622f6ad370a76c5754 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Tue, 17 Mar 2026 15:32:30 +0100 Subject: completion when using path in place of command --- src/completion.rs | 22 ++++++++++++++++++++-- src/parse/mod.rs | 10 +++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index c7a183f..d3e4704 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -2,6 +2,7 @@ use crate::parse::{self, CompletionContext}; use crate::{BString, Session}; use std::collections::HashMap; use std::ffi::OsStr; +use std::fs::DirEntry; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; @@ -12,7 +13,10 @@ pub struct Suggestion { pub delta: BString, } -fn _path_completion(mut prefix: BString) -> std::io::Result> { +fn _path_completion( + mut prefix: BString, + filter: &dyn Fn(&DirEntry) -> bool, +) -> std::io::Result> { let mut partial_entry = BString::new(); while let Some(c) = prefix.last().cloned() { if c == b'/' { @@ -31,6 +35,9 @@ fn _path_completion(mut prefix: BString) -> std::io::Result> { for entry in fs::read_dir(OsStr::from_bytes(&prefix))? { let entry = entry?; + if !filter(&entry) { + continue; + } let name = entry.file_name().as_bytes().to_vec(); if name.starts_with(&partial_entry) { let mut delta = name[partial_entry.len()..].to_vec(); @@ -53,7 +60,17 @@ fn _path_completion(mut prefix: BString) -> std::io::Result> { } pub fn path_completion(prefix: BString) -> Vec { - match _path_completion(prefix) { + match _path_completion(prefix, &|_| true) { + Ok(suggestions) => suggestions, + Err(err) => { + println!("path completion failed: {err:?}\r"); + Vec::new() + } + } +} + +pub fn path_exe_completion(prefix: BString) -> Vec { + match _path_completion(prefix, &|d| is_executable(&d.path())) { Ok(suggestions) => suggestions, Err(err) => { println!("path completion failed: {err:?}\r"); @@ -165,6 +182,7 @@ pub fn completion(session: Arc>, cmd: &[u8]) -> CompletionResult let mut suggestions = match comp.kind { parse::CompletionKind::Command => command_completion(session.clone(), comp.partial), + parse::CompletionKind::PathCommand => path_exe_completion(comp.partial), parse::CompletionKind::Argument => path_completion(comp.partial), parse::CompletionKind::Variable => variable_completion(session.clone(), comp.partial), parse::CompletionKind::None => return CompletionResult::empty(), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index e085f34..006fce2 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use crate::BString; +use crate::{BString, run::CommandKind}; #[cfg(test)] mod test; @@ -797,9 +797,10 @@ pub fn do_parse(x: &[u8]) -> Res, (ParseError, &[u8])> { } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum CompletionKind { Command, + PathCommand, Argument, Variable, None, @@ -840,7 +841,7 @@ impl Ast { } impl ExpString { - fn completion(&self, e: &mut E, kind: CompletionKind) -> CompletionContext { + fn completion(&self, e: &mut E, mut kind: CompletionKind) -> CompletionContext { if let Some(StringPart::Var(var)) = self.parts.last() && !var.already_complete { @@ -853,6 +854,9 @@ impl ExpString { { cmd.cmd.completion(e) } else if let Ok(s) = self.clone().expand(e) { + if s.contains(&b'/') && kind == CompletionKind::Command { + kind = CompletionKind::PathCommand; + } CompletionContext { kind, partial: s } } else { CompletionContext::none() -- cgit v1.2.3