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 {