From 9f8e9e539fb62f7513f33ff110d1a3253bad2c3c Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Wed, 1 Nov 2017 15:08:12 -0400
Subject: [PATCH] Refactor readln logic into it's own module

This will move the readln logic from `shell::binary` into
`shell::binary::readln`.
---
 src/shell/binary/mod.rs    | 174 ++-----------------------------------
 src/shell/binary/readln.rs | 168 +++++++++++++++++++++++++++++++++++
 2 files changed, 174 insertions(+), 168 deletions(-)
 create mode 100644 src/shell/binary/readln.rs

diff --git a/src/shell/binary/mod.rs b/src/shell/binary/mod.rs
index 31fef2ff..f2b6f0e3 100644
--- a/src/shell/binary/mod.rs
+++ b/src/shell/binary/mod.rs
@@ -1,26 +1,23 @@
 //! Contains the binary logic of Ion.
 mod prompt;
+mod readln;
 
 use self::prompt::{prompt, prompt_fn};
-use super::{DirectoryStack, FlowLogic, JobControl, Shell, ShellHistory, Variables};
-use super::completer::*;
+use self::readln::readln;
+use super::{FlowLogic, JobControl, Shell, ShellHistory};
 use super::flags::*;
 use super::flow_control::Statement;
 use super::library::IonLibrary;
 use super::status::*;
-use liner::{BasicCompleter, Buffer, Context, CursorPosition, Event, EventKind};
+use liner::{Buffer, Context};
 use parser::QuoteTerminator;
-use smallstring::SmallString;
 use smallvec::SmallVec;
 use std::env;
 use std::fs::File;
 use std::io::{self, ErrorKind, Write};
 use std::iter::{self, FromIterator};
-use std::mem;
-use std::path::{Path, PathBuf};
+use std::path::Path;
 use std::process;
-use sys;
-use types::*;
 
 pub(crate) trait Binary {
     /// Launches the shell, parses arguments, and then diverges into one of the `execution`
@@ -50,135 +47,7 @@ impl Binary for Shell {
 
     fn prompt_fn(&mut self) -> Option<String> { prompt_fn(self) }
 
-    fn readln(&mut self) -> Option<String> {
-        {
-            let vars_ptr = &self.variables as *const Variables;
-            let dirs_ptr = &self.directory_stack as *const DirectoryStack;
-
-            // Collects the current list of values from history for completion.
-            let history = &self.context.as_ref().unwrap().history.buffers.iter()
-                // Map each underlying `liner::Buffer` into a `String`.
-                .map(|x| x.chars().cloned().collect())
-                // Collect each result into a vector to avoid borrowing issues.
-                .collect::<Vec<SmallString>>();
-
-            loop {
-                let prompt = self.prompt();
-                let funcs = &self.functions;
-                let vars = &self.variables;
-                let builtins = &self.builtins;
-
-                let line = self.context.as_mut().unwrap().read_line(
-                    prompt,
-                    &mut move |Event { editor, kind }| {
-                        if let EventKind::BeforeComplete = kind {
-                            let (words, pos) = editor.get_words_and_cursor_position();
-
-                            let filename = match pos {
-                                CursorPosition::InWord(index) => index > 0,
-                                CursorPosition::InSpace(Some(_), _) => true,
-                                CursorPosition::InSpace(None, _) => false,
-                                CursorPosition::OnWordLeftEdge(index) => index >= 1,
-                                CursorPosition::OnWordRightEdge(index) => {
-                                    match (words.into_iter().nth(index), env::current_dir()) {
-                                        (Some((start, end)), Ok(file)) => {
-                                            let filename =
-                                                editor.current_buffer().range(start, end);
-                                            complete_as_file(file, filename, index)
-                                        }
-                                        _ => false,
-                                    }
-                                }
-                            };
-
-                            if filename {
-                                if let Ok(current_dir) = env::current_dir() {
-                                    if let Some(url) = current_dir.to_str() {
-                                        let completer =
-                                            IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr);
-                                        mem::replace(
-                                            &mut editor.context().completer,
-                                            Some(Box::new(completer)),
-                                        );
-                                    }
-                                }
-                            } else {
-                                // Creates a list of definitions from the shell environment that
-                                // will be used
-                                // in the creation of a custom completer.
-                                let words = builtins.keys().iter()
-                                // Add built-in commands to the completer's definitions.
-                                .map(|&s| Identifier::from(s))
-                                // Add the history list to the completer's definitions.
-                                .chain(history.iter().cloned())
-                                // Add the aliases to the completer's definitions.
-                                .chain(vars.aliases.keys().cloned())
-                                // Add the list of available functions to the completer's definitions.
-                                .chain(funcs.keys().cloned())
-                                // Add the list of available variables to the completer's definitions.
-                                // TODO: We should make it free to do String->SmallString
-                                //       and mostly free to go back (free if allocated)
-                                .chain(vars.get_vars().map(|s| ["$", &s].concat().into()))
-                                .collect();
-
-                                // 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(sys::PATH_SEPARATOR)
-                                        .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);
-
-                                // Replace the shell's current completer with the newly-created
-                                // completer.
-                                mem::replace(
-                                    &mut editor.context().completer,
-                                    Some(Box::new(completer)),
-                                );
-                            }
-                        }
-                    },
-                );
-
-                match line {
-                    Ok(line) => return Some(line),
-                    // Handles Ctrl + C
-                    Err(ref err) if err.kind() == ErrorKind::Interrupted => return None,
-                    // Handles Ctrl + D
-                    Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => break,
-                    Err(err) => {
-                        let stderr = io::stderr();
-                        let mut stderr = stderr.lock();
-                        let _ = writeln!(stderr, "ion: liner: {}", err);
-                        return None;
-                    }
-                }
-            }
-        }
-
-        let previous_status = self.previous_status;
-        self.exit(previous_status);
-    }
+    fn readln(&mut self) -> Option<String> { readln(self) }
 
     fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, mut lines: I) -> i32 {
         while let Some(command) = lines.next() {
@@ -400,34 +269,3 @@ fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> {
     }
     res
 }
-
-/// Infer if the given filename is actually a partial filename
-fn complete_as_file(current_dir: PathBuf, filename: String, index: usize) -> bool {
-    let filename = filename.trim();
-    let mut file = current_dir.clone();
-    file.push(&filename);
-    // If the user explicitly requests a file through this syntax then complete as a file
-    if filename.starts_with(".") {
-        return true;
-    }
-    // If the file starts with a dollar sign, it's a variable, not a file
-    if filename.starts_with("$") {
-        return false;
-    }
-    // Once we are beyond the first string, assume its a file
-    if index > 0 {
-        return true;
-    }
-    // If we are referencing a file that exists then just complete to that file
-    if file.exists() {
-        return true;
-    }
-    // If we have a partial file inside an existing directory, e.g. /foo/b when /foo/bar
-    // exists, then treat it as file as long as `foo` isn't the current directory, otherwise
-    // this would apply to any string `foo`
-    if let Some(parent) = file.parent() {
-        return parent.exists() && parent != current_dir;
-    }
-    // By default assume its not a file
-    false
-}
diff --git a/src/shell/binary/readln.rs b/src/shell/binary/readln.rs
new file mode 100644
index 00000000..fa45e5dc
--- /dev/null
+++ b/src/shell/binary/readln.rs
@@ -0,0 +1,168 @@
+use super::super::{Binary, DirectoryStack, Shell, Variables};
+use super::super::completer::*;
+use liner::{BasicCompleter, CursorPosition, Event, EventKind};
+use smallstring::SmallString;
+use std::env;
+use std::io::{self, ErrorKind, Write};
+use std::mem;
+use std::path::PathBuf;
+use sys;
+use types::*;
+
+pub(crate) fn readln(shell: &mut Shell) -> Option<String> {
+    {
+        let vars_ptr = &shell.variables as *const Variables;
+        let dirs_ptr = &shell.directory_stack as *const DirectoryStack;
+
+        // Collects the current list of values from history for completion.
+        let history = &shell.context.as_ref().unwrap().history.buffers.iter()
+                // Map each underlying `liner::Buffer` into a `String`.
+                .map(|x| x.chars().cloned().collect())
+                // Collect each result into a vector to avoid borrowing issues.
+                .collect::<Vec<SmallString>>();
+
+        loop {
+            let prompt = shell.prompt();
+            let funcs = &shell.functions;
+            let vars = &shell.variables;
+            let builtins = &shell.builtins;
+
+            let line = shell.context.as_mut().unwrap().read_line(
+                prompt,
+                &mut move |Event { editor, kind }| {
+                    if let EventKind::BeforeComplete = kind {
+                        let (words, pos) = editor.get_words_and_cursor_position();
+
+                        let filename = match pos {
+                            CursorPosition::InWord(index) => index > 0,
+                            CursorPosition::InSpace(Some(_), _) => true,
+                            CursorPosition::InSpace(None, _) => false,
+                            CursorPosition::OnWordLeftEdge(index) => index >= 1,
+                            CursorPosition::OnWordRightEdge(index) => {
+                                match (words.into_iter().nth(index), env::current_dir()) {
+                                    (Some((start, end)), Ok(file)) => {
+                                        let filename = editor.current_buffer().range(start, end);
+                                        complete_as_file(file, filename, index)
+                                    }
+                                    _ => false,
+                                }
+                            }
+                        };
+
+                        if filename {
+                            if let Ok(current_dir) = env::current_dir() {
+                                if let Some(url) = current_dir.to_str() {
+                                    let completer =
+                                        IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr);
+                                    mem::replace(
+                                        &mut editor.context().completer,
+                                        Some(Box::new(completer)),
+                                    );
+                                }
+                            }
+                        } else {
+                            // Creates a list of definitions from the shell environment that
+                            // will be used
+                            // in the creation of a custom completer.
+                            let words = builtins.keys().iter()
+                                // Add built-in commands to the completer's definitions.
+                                .map(|&s| Identifier::from(s))
+                                // Add the history list to the completer's definitions.
+                                .chain(history.iter().cloned())
+                                // Add the aliases to the completer's definitions.
+                                .chain(vars.aliases.keys().cloned())
+                                // Add the list of available functions to the completer's definitions.
+                                .chain(funcs.keys().cloned())
+                                // Add the list of available variables to the completer's definitions.
+                                // TODO: We should make it free to do String->SmallString
+                                //       and mostly free to go back (free if allocated)
+                                .chain(vars.get_vars().map(|s| ["$", &s].concat().into()))
+                                .collect();
+
+                            // 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(sys::PATH_SEPARATOR)
+                                    .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);
+
+                            // Replace the shell's current completer with the newly-created
+                            // completer.
+                            mem::replace(
+                                &mut editor.context().completer,
+                                Some(Box::new(completer)),
+                            );
+                        }
+                    }
+                },
+            );
+
+            match line {
+                Ok(line) => return Some(line),
+                // Handles Ctrl + C
+                Err(ref err) if err.kind() == ErrorKind::Interrupted => return None,
+                // Handles Ctrl + D
+                Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => break,
+                Err(err) => {
+                    let stderr = io::stderr();
+                    let mut stderr = stderr.lock();
+                    let _ = writeln!(stderr, "ion: liner: {}", err);
+                    return None;
+                }
+            }
+        }
+    }
+
+    let previous_status = shell.previous_status;
+    shell.exit(previous_status);
+}
+
+/// Infer if the given filename is actually a partial filename
+fn complete_as_file(current_dir: PathBuf, filename: String, index: usize) -> bool {
+    let filename = filename.trim();
+    let mut file = current_dir.clone();
+    file.push(&filename);
+    // If the user explicitly requests a file through this syntax then complete as a file
+    if filename.starts_with(".") {
+        return true;
+    }
+    // If the file starts with a dollar sign, it's a variable, not a file
+    if filename.starts_with("$") {
+        return false;
+    }
+    // Once we are beyond the first string, assume its a file
+    if index > 0 {
+        return true;
+    }
+    // If we are referencing a file that exists then just complete to that file
+    if file.exists() {
+        return true;
+    }
+    // If we have a partial file inside an existing directory, e.g. /foo/b when /foo/bar
+    // exists, then treat it as file as long as `foo` isn't the current directory, otherwise
+    // this would apply to any string `foo`
+    if let Some(parent) = file.parent() {
+        return parent.exists() && parent != current_dir;
+    }
+    // By default assume its not a file
+    false
+}
-- 
GitLab