diff --git a/src/shell/completer.rs b/src/shell/completer.rs index 5429e14eafb52c9d6a2c81f2564141f708859102..b8436a3c48147e9e814b4cc8c2b78fda2adb917d 100644 --- a/src/shell/completer.rs +++ b/src/shell/completer.rs @@ -29,20 +29,48 @@ impl IonFileCompleter { } impl Completer for IonFileCompleter { + /// When the tab key is pressed, **Liner** will use this method to perform completions of + /// filenames. As our `IonFileCompleter` is a wrapper around **Liner**'s `FilenameCompleter`, + /// the purpose of our custom `Completer` is to expand possible `~` characters in the `start` + /// value that we receive from the prompt, grab completions from the inner `FilenameCompleter`, + /// and then escape the resulting filenames, as well as remove the expanded form of the `~` + /// character and re-add the `~` character in it's place. fn completions(&self, start: &str) -> Vec<String> { + // Only if the first character is a tilde character will we perform expansions if start.starts_with('~') { + // Dereferencing the raw pointers here should be entirely safe, theoretically, + // because no changes will occur to either of the underlying references in the + // duration between creation of the completers and execution of their completions. 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 mut iterator = completions.iter(); + + // And then we will need to take those completions and remove the expanded form + // of the tilde pattern and replace it with that pattern yet again. + let mut completions = Vec::new(); + + // We can do that by obtaining the index position where the tilde character ends. + // We don't search with `~` because we also want to handle other tilde variants. let t_index = start.find('/').unwrap_or(1); + // `tilde` is the tilde pattern, and `search` is the pattern that follows. let (tilde, search) = start.split_at(t_index as usize); - let iterator = self.inner.completions(&expanded); - let mut iterator = iterator.iter(); - let mut completions = Vec::new(); - if search.len() <= 1 { + if search.len() < 2 { + // If the length of the search pattern is less than 2, the search pattern is + // empty, and thus the completions actually contain files and directories in + // the home directory. + + // The tilde pattern will actually be our `start` command in itself, + // and the completed form will be all of the characters beyond the length of + // the expanded form of the tilde pattern. for completion in iterator { completions.push([start, &completion[expanded.len()..]].concat()); } } else { + // To save processing time, we should get obtain the index position where our + // 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.find(search) { completions.push(escape(&[tilde, &completion[e_index..]].concat())); @@ -62,6 +90,10 @@ impl Completer for IonFileCompleter { } } +/// Escapes filenames from the completer so that special characters will be properly escaped. +/// +/// NOTE: Perhaps we should submit a PR to Liner to add a &'static [u8] field to +/// `FilenameCompleter` so that we don't have to perform the escaping ourselves? fn escape(input: &str) -> String { let mut output = Vec::with_capacity(input.len()); for character in input.bytes() {