Skip to content

Commit

Permalink
Refactor readln logic into it's own module
Browse files Browse the repository at this point in the history
This will move the readln logic from `shell::binary` into
`shell::binary::readln`.
  • Loading branch information
mmstick committed Nov 1, 2017
1 parent 2529362 commit 9f8e9e5
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 168 deletions.
174 changes: 6 additions & 168 deletions src/shell/binary/mod.rs
Original file line number Original file line Diff line number Diff line change
@@ -1,26 +1,23 @@
//! Contains the binary logic of Ion. //! Contains the binary logic of Ion.
mod prompt; mod prompt;
mod readln;


use self::prompt::{prompt, prompt_fn}; use self::prompt::{prompt, prompt_fn};
use super::{DirectoryStack, FlowLogic, JobControl, Shell, ShellHistory, Variables}; use self::readln::readln;
use super::completer::*; use super::{FlowLogic, JobControl, Shell, ShellHistory};
use super::flags::*; use super::flags::*;
use super::flow_control::Statement; use super::flow_control::Statement;
use super::library::IonLibrary; use super::library::IonLibrary;
use super::status::*; use super::status::*;
use liner::{BasicCompleter, Buffer, Context, CursorPosition, Event, EventKind}; use liner::{Buffer, Context};
use parser::QuoteTerminator; use parser::QuoteTerminator;
use smallstring::SmallString;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{self, ErrorKind, Write}; use std::io::{self, ErrorKind, Write};
use std::iter::{self, FromIterator}; use std::iter::{self, FromIterator};
use std::mem; use std::path::Path;
use std::path::{Path, PathBuf};
use std::process; use std::process;
use sys;
use types::*;


pub(crate) trait Binary { pub(crate) trait Binary {
/// Launches the shell, parses arguments, and then diverges into one of the `execution` /// Launches the shell, parses arguments, and then diverges into one of the `execution`
Expand Down Expand Up @@ -50,135 +47,7 @@ impl Binary for Shell {


fn prompt_fn(&mut self) -> Option<String> { prompt_fn(self) } fn prompt_fn(&mut self) -> Option<String> { prompt_fn(self) }


fn readln(&mut self) -> Option<String> { fn readln(&mut self) -> Option<String> { readln(self) }
{
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 terminate_script_quotes<I: Iterator<Item = String>>(&mut self, mut lines: I) -> i32 { fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, mut lines: I) -> i32 {
while let Some(command) = lines.next() { while let Some(command) = lines.next() {
Expand Down Expand Up @@ -400,34 +269,3 @@ fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> {
} }
res 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
}
168 changes: 168 additions & 0 deletions src/shell/binary/readln.rs
Original file line number Original file line Diff line number Diff line change
@@ -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
}

0 comments on commit 9f8e9e5

Please sign in to comment.