diff --git a/examples/let.ion b/examples/let.ion index 43faa47a82afdc352c0aeb52219b5ca97bd4ad67..9ee875cc098351591445a58a801a1254992dcffb 100644 --- a/examples/let.ion +++ b/examples/let.ion @@ -10,3 +10,8 @@ let a b c = 1 2 3 echo $a echo $b echo $c +let a b c = ["one two" "three four" "five six"] +let a b c = [$c $a $b] +echo $a +echo $b +echo $c diff --git a/examples/let.out b/examples/let.out index 5f0f24c642d49dc437df4f4f603e4fe7265c9362..55895815de777fa5dab4457f94eb72dd021a5857 100644 --- a/examples/let.out +++ b/examples/let.out @@ -6,3 +6,6 @@ 1 2 3 +five six +one two +three four diff --git a/src/shell/completer.rs b/src/shell/completer.rs index 2c032e40025100d491f83611e9a1c8c1343059fa..93c45a3d457d5e4a67df13c4453f4b6513aba254 100644 --- a/src/shell/completer.rs +++ b/src/shell/completer.rs @@ -1,17 +1,63 @@ use liner::{Completer, FilenameCompleter}; +use super::directory_stack::DirectoryStack; +use super::variables::Variables; +/// Performs escaping to an inner `FilenameCompleter` to enable a handful of special cases +/// needed by the shell, such as expanding '~' to a home directory, or adding a backslash +/// when a special character is contained within an expanded filename. pub struct IonFileCompleter { - inner: FilenameCompleter + /// The completer that this completer is handling. + inner: FilenameCompleter, + /// A pointer to the directory stack in the shell. + dir_stack: *const DirectoryStack, + /// A pointer to the variables map in the shell. + vars: *const Variables, } impl IonFileCompleter { - pub fn new(path: Option<&str>) -> IonFileCompleter { - IonFileCompleter { inner: FilenameCompleter::new(path) } + pub fn new ( + path: Option<&str>, + dir_stack: *const DirectoryStack, + vars: *const Variables + ) -> IonFileCompleter { + IonFileCompleter { + inner: FilenameCompleter::new(path), + dir_stack: dir_stack, + vars: vars + } } } impl Completer for IonFileCompleter { fn completions(&self, start: &str) -> Vec<String> { + if start.starts_with('~') { + if let Some(expanded) = unsafe{ (*self.vars).tilde_expansion(start, &*self.dir_stack) } { + let t_index = start.find('/').unwrap_or(1); + 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 { + for completion in iterator { + completions.push([start, &completion[expanded.len()..]].concat()); + } + } else { + if let Some(completion) = iterator.next() { + if let Some(e_index) = completion.find(search) { + completions.push(escape(&[tilde, &completion[e_index..]].concat())); + for completion in iterator { + let expanded = &completion[e_index..]; + completions.push(escape(&[tilde, expanded].concat())); + } + } + } + } + + return completions + } + } + self.inner.completions(start).iter().map(|x| escape(x.as_str())).collect() } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index ed40ebb1dac76818cc657dffeabc33433f04cabc..c444f9dc3eb76605d50bbc80ba4092b7e33eec5b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -73,6 +73,8 @@ impl<'a> Shell<'a> { } } fn readln(&mut self) -> Option<String> { + let vars_ptr = &self.variables as *const Variables; + let dirs_ptr = &self.directory_stack as *const DirectoryStack; let funcs = &self.functions; let vars = &self.variables; let builtins = self.builtins; @@ -106,26 +108,11 @@ impl<'a> Shell<'a> { if filename { if let Ok(current_dir) = env::current_dir() { if let Some(url) = current_dir.to_str() { - let completer = IonFileCompleter::new(Some(url)); + let completer = IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr); mem::replace(&mut editor.context().completer, Some(Box::new(completer))); } } } else { - // Creates completers containing definitions from all directories listed - // in the environment's **$PATH** variable. - let file_completers = match env::var("PATH") { - Ok(val) => { - if cfg!(unix) { - // UNIX systems separate paths with the `:` character. - val.split(':').map(|x| IonFileCompleter::new(Some(x))).collect::<Vec<_>>() - } else { - // Redox and Windows use the `;` character to separate paths - val.split(';').map(|x| IonFileCompleter::new(Some(x))).collect::<Vec<_>>() - } - }, - Err(_) => vec![IonFileCompleter::new(Some("/bin/"))], - }; - // Creates a list of definitions from the shell environment that will be used // in the creation of a custom completer. let words = builtins.iter() @@ -145,6 +132,24 @@ impl<'a> Shell<'a> { // Initialize a new completer from the definitions collected. let custom_completer = BasicCompleter::new(words); + + // Creates completers containing definitions from all directories listed + // in the environment's **$PATH** variable. + let mut file_completers = if let Ok(val) = env::var("PATH") { + val.split(if cfg!(unix) { ':' } else { ';' }) + .map(|s| IonFileCompleter::new(Some(s), dirs_ptr, vars_ptr)) + .collect() + } else { + vec![IonFileCompleter::new(Some("/bin/"), dirs_ptr, vars_ptr)] + }; + + // Also add files/directories in the current directory to the completion list. + if let Ok(current_dir) = env::current_dir() { + if let Some(url) = current_dir.to_str() { + file_completers.push(IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr)); + } + } + // Merge the collected definitions with the file path definitions. let completer = MultiCompleter::new(file_completers, custom_completer); @@ -156,7 +161,12 @@ impl<'a> Shell<'a> { match line { Ok(line) => return Some(line), + // Handles Ctrl + C Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, + // Handles Ctrl + D + Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => { + process::exit(self.previous_status) + }, Err(err) => { let stderr = io::stderr(); let mut stderr = stderr.lock();