Skip to content

Commit

Permalink
Support completion of dirs (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
reubeno authored May 13, 2024
1 parent 62f9451 commit de3c243
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 21 deletions.
2 changes: 1 addition & 1 deletion shell/src/builtins/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl BuiltinCommand for HelpCommand {
"The following commands are implemented as shell built-ins:"
)?;

let builtin_names: Vec<_> = crate::builtins::get_all_builtin_names();
let builtin_names = context.shell.get_builtin_names();

const COLUMN_COUNT: usize = 3;

Expand Down
29 changes: 20 additions & 9 deletions shell/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
path::{Path, PathBuf},
};

use crate::{builtins, env, error, patterns, variables::ShellValueLiteral, Shell};
use crate::{env, error, patterns, variables::ShellValueLiteral, Shell};

#[derive(Clone, Debug, ValueEnum)]
pub enum CompleteAction {
Expand Down Expand Up @@ -186,12 +186,13 @@ impl CompletionSpec {
CompleteAction::ArrayVar => tracing::debug!("UNIMPLEMENTED: complete -A arrayvar"),
CompleteAction::Binding => tracing::debug!("UNIMPLEMENTED: complete -A binding"),
CompleteAction::Builtin => {
let mut builtin_names = builtins::get_all_builtin_names();
let mut builtin_names = shell.get_builtin_names();
candidates.append(&mut builtin_names);
}
CompleteAction::Command => tracing::debug!("UNIMPLEMENTED: complete -A command"),
CompleteAction::Directory => {
tracing::debug!("UNIMPLEMENTED: complete -A directory");
let mut file_completions = get_file_completions(shell, context, true);
candidates.append(&mut file_completions);
}
CompleteAction::Disabled => tracing::debug!("UNIMPLEMENTED: complete -A disabled"),
CompleteAction::Enabled => tracing::debug!("UNIMPLEMENTED: complete -A enabled"),
Expand All @@ -203,7 +204,7 @@ impl CompletionSpec {
}
}
CompleteAction::File => {
let mut file_completions = get_file_completions(shell, context);
let mut file_completions = get_file_completions(shell, context, false);
candidates.append(&mut file_completions);
}
CompleteAction::Function => {
Expand Down Expand Up @@ -527,13 +528,23 @@ impl CompletionConfig {
}
}

fn get_file_completions(shell: &Shell, context: &CompletionContext) -> Vec<String> {
fn get_file_completions(
shell: &Shell,
context: &CompletionContext,
must_be_dir: bool,
) -> Vec<String> {
let glob = std::format!("{}*", context.token_to_complete);

let metadata_filter = |metadata: Option<&std::fs::Metadata>| {
!must_be_dir || metadata.is_some_and(|md| md.is_dir())
};

// TODO: Pass through quoting.
if let Ok(mut candidates) = patterns::Pattern::from(glob)
.expand(shell.working_dir.as_path(), shell.options.extended_globbing)
{
if let Ok(mut candidates) = patterns::Pattern::from(glob).expand(
shell.working_dir.as_path(),
shell.options.extended_globbing,
Some(&metadata_filter),
) {
for candidate in &mut candidates {
if Path::new(candidate.as_str()).is_dir() {
candidate.push('/');
Expand All @@ -549,7 +560,7 @@ fn get_completions_using_basic_lookup(
shell: &Shell,
context: &CompletionContext,
) -> CompletionResult {
let mut candidates = get_file_completions(shell, context);
let mut candidates = get_file_completions(shell, context, false);

// TODO: Contextually generate different completions.
// If this appears to be the command token (and if there's *some* prefix without
Expand Down
1 change: 1 addition & 0 deletions shell/src/expansion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ impl<'a> WordExpander<'a> {
.expand(
self.shell.working_dir.as_path(),
self.parser_options.enable_extended_globbing,
Some(&patterns::Pattern::accept_all_expand_filter),
)
.map_or_else(
|_err| vec![],
Expand Down
38 changes: 31 additions & 7 deletions shell/src/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,20 @@ impl Pattern {
self.pieces.iter().all(|p| p.as_str().is_empty())
}

pub(crate) fn expand(
pub(crate) fn accept_all_expand_filter(_metadata: Option<&std::fs::Metadata>) -> bool {
true
}

#[allow(clippy::too_many_lines)]
pub(crate) fn expand<MF>(
&self,
working_dir: &Path,
enable_extended_globbing: bool,
) -> Result<Vec<String>, error::Error> {
metadata_filter: Option<&MF>,
) -> Result<Vec<String>, error::Error>
where
MF: Fn(Option<&std::fs::Metadata>) -> bool,
{
if self.is_empty() {
return Ok(vec![]);
} else if !self.pieces.iter().any(|piece| {
Expand Down Expand Up @@ -140,16 +149,31 @@ impl Pattern {
for current_path in current_paths {
let subpattern = Pattern::from(&component);
let regex = subpattern.to_regex(true, true, enable_extended_globbing)?;

let matches_criteria = |dir_entry: &std::fs::DirEntry| {
if !regex
.is_match(dir_entry.file_name().to_string_lossy().as_ref())
.unwrap_or(false)
{
return false;
}

if let Some(filter) = &metadata_filter {
let metadata_result = dir_entry.metadata();
if !filter(metadata_result.as_ref().ok()) {
return false;
}
}

true
};

let mut matching_paths_in_dir: Vec<_> = current_path
.read_dir()
.map_or_else(|_| vec![], |dir| dir.into_iter().collect())
.into_iter()
.filter_map(|result| result.ok())
.filter(|entry| {
regex
.is_match(entry.file_name().to_string_lossy().as_ref())
.unwrap_or(false)
})
.filter(matches_criteria)
.map(|entry| entry.path())
.collect();

Expand Down
15 changes: 11 additions & 4 deletions shell/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;

use crate::env::{EnvironmentLookup, EnvironmentScope, ShellEnvironment};
use crate::error;
use crate::expansion;
use crate::interp::{self, Execute, ExecutionParameters, ExecutionResult};
use crate::jobs;
use crate::openfiles;
use crate::options::RuntimeOptions;
use crate::prompt::expand_prompt;
use crate::variables::{self, ShellValue, ShellVariable};
use crate::{builtins, error};
use crate::{commands, patterns};
use crate::{completion, users};
use crate::{context, env};
Expand Down Expand Up @@ -709,9 +709,11 @@ impl Shell {
for dir_str in self.env.get_str("PATH").unwrap_or_default().split(':') {
let pattern = std::format!("{dir_str}/{required_glob_pattern}");
// TODO: Pass through quoting.
if let Ok(entries) = patterns::Pattern::from(pattern)
.expand(&self.working_dir, self.options.extended_globbing)
{
if let Ok(entries) = patterns::Pattern::from(pattern).expand(
&self.working_dir,
self.options.extended_globbing,
Some(&patterns::Pattern::accept_all_expand_filter),
) {
for entry in entries {
let path = Path::new(&entry);
if path.executable() {
Expand Down Expand Up @@ -808,4 +810,9 @@ impl Shell {

writeln!(self.stderr(), "{prefix}{}", command.as_ref())
}

#[allow(clippy::unused_self)]
pub fn get_builtin_names(&self) -> Vec<String> {
builtins::get_all_builtin_names()
}
}

0 comments on commit de3c243

Please sign in to comment.