diff --git a/src/lib/parser/assignments/keys.rs b/src/lib/parser/assignments/keys.rs index 8da6ae3d0c455af1a43a72b9333cbcc4147806d0..1bdc12394e008aaf662e53b192fc6d13a0801f6b 100644 --- a/src/lib/parser/assignments/keys.rs +++ b/src/lib/parser/assignments/keys.rs @@ -208,35 +208,35 @@ mod tests { Ok(Key { name: "a", kind: Primitive::Integer, - }) + },) ); assert_eq!( parser.next().unwrap(), Ok(Key { name: "b", kind: Primitive::AnyArray, - }) + },) ); assert_eq!( parser.next().unwrap(), Ok(Key { name: "c", kind: Primitive::Boolean, - }) + },) ); assert_eq!( parser.next().unwrap(), Ok(Key { name: "d", kind: Primitive::Any, - }) + },) ); assert_eq!( parser.next().unwrap(), Ok(Key { name: "e", kind: Primitive::IntegerArray, - }) + },) ); assert_eq!(parser.next().unwrap(), Err(TypeError::Invalid("a"))); } diff --git a/src/lib/parser/shell_expand/ranges.rs b/src/lib/parser/shell_expand/ranges.rs index 78536368d11b4ab46e72eb51d231947c8ef6e1af..7e2cb1d4aa04e440f62af350cc7b5140c6ac6a3b 100644 --- a/src/lib/parser/shell_expand/ranges.rs +++ b/src/lib/parser/shell_expand/ranges.rs @@ -134,7 +134,7 @@ pub(crate) fn parse_range(input: &str) -> Option<Vec<String>> { } macro_rules! finish_char { - ($inclusive:expr, $end_str:expr, $step:expr) => { + ($inclusive: expr, $end_str: expr, $step: expr) => { if first.len() == 1 && $end_str.len() == 1 { let start = first.as_bytes()[0]; let end = $end_str.as_bytes()[0]; @@ -146,7 +146,7 @@ pub(crate) fn parse_range(input: &str) -> Option<Vec<String>> { } macro_rules! finish { - ($inclusive:expr, $read:expr) => { + ($inclusive: expr, $read: expr) => { let end_str = &input[$read..]; if let Some((start, end)) = strings_to_isizes(first, end_str) { return numeric_range( @@ -159,7 +159,7 @@ pub(crate) fn parse_range(input: &str) -> Option<Vec<String>> { finish_char!($inclusive, end_str, 1); } }; - ($inclusive:expr, $read:expr, $step:expr) => { + ($inclusive: expr, $read: expr, $step: expr) => { let end_str = &input[$read..]; if let Some((start, end)) = strings_to_isizes(first, end_str) { return numeric_range(start, end, $step, $inclusive); diff --git a/src/lib/parser/shell_expand/words/methods/strings.rs b/src/lib/parser/shell_expand/words/methods/strings.rs index 0f37e167cd2f5124b707aa523517990307b990ab..6fbf342d98aa0ed3a9f24dcc82dc86f3eae3df59 100644 --- a/src/lib/parser/shell_expand/words/methods/strings.rs +++ b/src/lib/parser/shell_expand/words/methods/strings.rs @@ -101,7 +101,7 @@ impl<'a> StringMethod<'a> { let pattern = MethodArgs::new(self.pattern, expand); macro_rules! string_eval { - ($variable:ident $method:tt) => {{ + ($variable: ident $method: tt) => {{ let pattern = pattern.join(" "); let is_true = if let Some(value) = expand.variable($variable, false) { value.$method(&pattern) @@ -117,7 +117,7 @@ impl<'a> StringMethod<'a> { } macro_rules! path_eval { - ($method:tt) => {{ + ($method: tt) => {{ if let Some(value) = expand.variable(variable, false) { output.push_str( Path::new(&value) @@ -138,7 +138,7 @@ impl<'a> StringMethod<'a> { } macro_rules! string_case { - ($method:tt) => {{ + ($method: tt) => {{ if let Some(value) = expand.variable(variable, false) { output.push_str(value.$method().as_str()); } else if is_expression(variable) { diff --git a/src/lib/shell/binary/mod.rs b/src/lib/shell/binary/mod.rs index 785bc2b158a750a09a3ddc205db80dfc33481300..c1c37875cd909fe8b6bfa5211c54a8893ac2afe5 100644 --- a/src/lib/shell/binary/mod.rs +++ b/src/lib/shell/binary/mod.rs @@ -192,7 +192,7 @@ fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> { let mut word_start = None; macro_rules! check_boundary { - ($c:expr, $index:expr, $escaped:expr) => {{ + ($c: expr, $index: expr, $escaped: expr) => {{ if let Some(start) = word_start { if $c == ' ' && !$escaped { res.push((start, $index)); diff --git a/src/lib/shell/completer.rs b/src/lib/shell/completer.rs index 5b0ec0811de1da4807ef99b5e5ed128079766e44..8dd1a3f9f3d47669ea7d3f9b9ff22472e65fec8c 100644 --- a/src/lib/shell/completer.rs +++ b/src/lib/shell/completer.rs @@ -1,4 +1,5 @@ use super::{directory_stack::DirectoryStack, variables::Variables}; +use glob::glob; use liner::{Completer, FilenameCompleter}; /// Performs escaping to an inner `FilenameCompleter` to enable a handful of special cases @@ -20,7 +21,7 @@ impl IonFileCompleter { vars: *const Variables, ) -> IonFileCompleter { IonFileCompleter { - inner: FilenameCompleter::new(path), + inner: FilenameCompleter::new(path), dir_stack, vars, } @@ -47,7 +48,7 @@ impl Completer for IonFileCompleter { if let Some(expanded) = unsafe { (*self.vars).tilde_expansion(start, &*self.dir_stack) } { // Now we obtain completions for the `expanded` form of the `start` value. - let completions = self.inner.completions(&expanded); + let completions = filename_completion(&expanded, |x| self.inner.completions(x)); let mut iterator = completions.iter(); // And then we will need to take those completions and remove the expanded form @@ -77,7 +78,7 @@ impl Completer for IonFileCompleter { // search pattern begins, and re-use that index to slice the completions so // that we may re-add the tilde character with the completion that follows. if let Some(completion) = iterator.next() { - if let Some(e_index) = completion.rfind(search) { + if let Some(e_index) = expanded.rfind(search) { completions.push(escape(&[tilde, &completion[e_index..]].concat())); for completion in iterator { let expanded = &completion[e_index..]; @@ -91,12 +92,50 @@ impl Completer for IonFileCompleter { } } - self.inner - .completions(&unescape(start)) + filename_completion(&start, |x| self.inner.completions(x)) + } +} + +fn filename_completion<LC>(start: &str, liner_complete: LC) -> Vec<String> +where + LC: Fn(&str) -> Vec<String>, +{ + let unescaped_start = unescape(start); + + let start_split: Vec<&str> = unescaped_start.split("/").collect(); + + // When 'start' is an absolute path, "/..." gets split to ["", "..."] + // So we ignore the first element and add "/" to the start of the string + let start_for_glob = match unescaped_start.starts_with("/") { + true => ["/", &start_split[1..].join("*/"), "*"].concat(), + false => [&start_split.join("*/"), "*"].concat(), + }; + + let mut inner_glob: Vec<String> = match glob(&start_for_glob) { + Ok(completions) => completions + .filter_map(Result::ok) + .map(|x| x.to_string_lossy().into_owned()) + .collect(), + _ => vec![], + }; + if inner_glob.len() == 0 { + inner_glob.push(escape(&start.to_string())); + } + + let mut completions = vec![]; + + // Use Liner::Completer as well, to preserve the previous behaviour + // around single-directory completions + for path in inner_glob { + let liner_completions: Vec<String> = liner_complete(&path) .iter() .map(|x| escape(x.as_str())) - .collect() + .collect(); + for c in liner_completions { + completions.push(c) + } } + completions } /// Escapes filenames from the completer so that special characters will be properly escaped. @@ -165,3 +204,32 @@ where completions } } + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + #[test] + fn filename_completion() { + let current_dir = env::current_dir().expect("Unable to get current directory"); + + let completer = IonFileCompleter::new( + current_dir.to_str(), + &DirectoryStack::new(), + &Variables::default(), + ); + assert_eq!(completer.completions("testing"), vec!["testing/"]); + assert_eq!( + completer.completions("testing/file"), + vec!["testing/file_with_text"] + ); + + assert_eq!(completer.completions("~"), vec!["~/"]); + + assert_eq!( + completer.completions("tes/fil"), + vec!["testing/file_with_text"] + ); + } +} diff --git a/src/lib/shell/flow_control.rs b/src/lib/shell/flow_control.rs index 3709379b98fa4b176366dad486f83018ddd006de..c97c849b19f9ed0cc479aac3f5b8680d1cdac105 100644 --- a/src/lib/shell/flow_control.rs +++ b/src/lib/shell/flow_control.rs @@ -251,7 +251,7 @@ where I: Iterator<Item = Statement>, { macro_rules! add_to_case { - ($statement:expr) => { + ($statement: expr) => { match cases.last_mut() { // XXX: When does this actually happen? What syntax error is this??? None => { diff --git a/src/lib/shell/job.rs b/src/lib/shell/job.rs index 6357ff92527c08bccf7094a25b99c1e2359787a7..e9f5727331a596e25ead1fca8d70220304c4fa74 100644 --- a/src/lib/shell/job.rs +++ b/src/lib/shell/job.rs @@ -179,7 +179,7 @@ impl TeeItem { } macro_rules! set_field { - ($self:expr, $field:ident, $arg:expr) => { + ($self: expr, $field: ident, $arg: expr) => { match *$self { RefinedJob::External { ref mut $field, .. } | RefinedJob::Builtin { ref mut $field, .. } diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index dc82fb4277f4bc5147bb8c2619c3ad2f75f17f6d..a3e24880265faeade0e446ed23dc022621754068 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -90,7 +90,7 @@ fn is_implicit_cd(argument: &str) -> bool { /// redirection if necessary. fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, JobKind)>> { macro_rules! get_infile { - ($input:expr) => { + ($input: expr) => { match $input { Input::File(ref filename) => match File::open(filename) { Ok(file) => Some(file), @@ -147,7 +147,7 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J }; macro_rules! set_no_tee { - ($outputs:ident, $job:ident) => { + ($outputs: ident, $job: ident) => { // XXX: Possibly add an assertion here for correctness for output in $outputs { match if output.append {