use prelude::v1::*;
use autocomplete::*;
use cli::*;
use keys::*;
use terminal::*;
use utils::*;
use i18n::*;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PromptEvent {
Ok,
Break
}
enum AutocompleteRequest {
None,
HaveMultipleOptions { lines: Vec<AutocompleteLine> }
}
pub struct PromptBuffer {
line_buffer: Vec<u8>,
change_path_enabled: bool,
current_path: Vec<String>,
path_separator: char,
autocomplete: AutocompleteRequest,
options: PromptBufferOptions,
strings: Box<Strings>
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum NewlineSequence {
Newline,
CarriageReturn,
NewlineOrCarriageReturn
}
pub struct PromptBufferOptions {
pub prompt: Cow<'static, str>,
pub newline: Cow<'static, str>,
pub max_line_length: usize,
pub echo: bool,
pub newline_key_sequence: NewlineSequence
}
impl Default for PromptBufferOptions {
fn default() -> PromptBufferOptions {
PromptBufferOptions {
prompt: "# ".into(),
echo: true,
newline: "\r\n".into(),
max_line_length: 512,
newline_key_sequence: NewlineSequence::NewlineOrCarriageReturn
}
}
}
impl PromptBuffer {
pub fn new(options: PromptBufferOptions) -> PromptBuffer {
PromptBuffer {
line_buffer: Vec::new(),
change_path_enabled: false,
current_path: vec![],
path_separator: '/',
autocomplete: AutocompleteRequest::None,
options: options,
strings: Box::new(English)
}
}
pub fn print_prompt<T: CharacterTerminalWriter>(&self, output: &mut T) {
if !self.options.prompt.len() == 0 { return; }
if self.change_path_enabled {
let sep = self.path_separator.to_string();
let path: String = {
if self.current_path.len() == 0 {
sep
} else {
format!("{}{}{}", &sep, self.current_path.join(&sep), &sep)
}
};
let prompt = self.options.prompt.replace("\\W", &path);
output.print_str(&prompt);
} else {
output.print_str(&self.options.prompt);
}
}
pub fn handle_terminal_key<T, F: FnOnce(&mut CliExecutor) -> ()>(&mut self, terminal: &mut T, call_commands: F) -> Result<PromptEvent, TerminalError>
where T: CharacterTerminalWriter + CharacterTerminalReader + FmtWrite
{
let key = terminal.read()?;
Ok(self.handle_key(key, terminal, call_commands))
}
pub fn handle_key<T, F: FnOnce(&mut CliExecutor) -> ()>(&mut self, key: Key, terminal: &mut T, call_commands: F) -> PromptEvent
where T: CharacterTerminalWriter + FmtWrite
{
let mut handled_autocomplete = false;
let is_line_finished = {
match self.options.newline_key_sequence {
NewlineSequence::Newline => key == Key::Newline,
NewlineSequence::CarriageReturn => key == Key::CarriageReturn,
NewlineSequence::NewlineOrCarriageReturn => {
key == Key::Newline || key == Key::CarriageReturn
}
}
};
if is_line_finished {
terminal.print_line("");
if let Ok(line) = str::from_utf8(self.line_buffer.as_slice()) {
let result = {
let mut matcher = CliLineMatcher::new(&line, LineMatcherMode::Execute);
let mut executor = CliExecutor::new(matcher, &*self.strings, terminal);
call_commands(&mut executor);
executor.close().finish()
};
match result {
LineBufferResult::NoMatchFound => {
if line.trim().len() > 0 {
self.strings.cmd_not_recognized(terminal, line.trim());
terminal.newline();
}
},
_ => ()
}
}
self.line_buffer.clear();
self.print_prompt(terminal);
} else {
match key {
Key::Tab => {
match self.autocomplete {
AutocompleteRequest::None => {
let mut single_match_additional_chars = None;
if let Ok(line) = str::from_utf8(self.line_buffer.as_slice()) {
let result = {
let matcher = CliLineMatcher::new(&line, LineMatcherMode::AutocompleteOnly);
let mut executor = CliExecutor::new(matcher, &*self.strings, terminal);
call_commands(&mut executor);
executor.close().finish()
};
match result {
LineBufferResult::Autocomplete { result } => {
match result {
AutocompleteResult::None => (),
AutocompleteResult::SingleMatch { line } => {
terminal.print_str(line.get_additional_part());
single_match_additional_chars = Some(line.full_new_line);
},
AutocompleteResult::MultipleMatches { lines } => {
self.autocomplete = AutocompleteRequest::HaveMultipleOptions {
lines: lines
};
}
}
},
_ => ()
}
}
if let Some(single_match_additional_chars) = single_match_additional_chars.take() {
self.line_buffer.clear();
for c in single_match_additional_chars.bytes() {
self.line_buffer.push(c);
}
}
handled_autocomplete = true;
},
AutocompleteRequest::HaveMultipleOptions { ref lines } => {
terminal.print_line("");
let suggestions = lines.iter().map(|l| { l.get_display() }).collect::<Vec<&str>>();
format_in_columns(suggestions.as_slice(), 80, 4, &self.options.newline, terminal);
self.print_prompt(terminal);
terminal.print(&self.line_buffer);
handled_autocomplete = false;
}
}
},
Key::Newline | Key::CarriageReturn => {
},
Key::Backspace => {
if let Some(..) = self.line_buffer.pop() {
if self.options.echo {
terminal.print(&[0x08, 0x20, 0x08]);
}
}
},
Key::Break => {
if self.line_buffer.len() == 0 {
return PromptEvent::Break;
}
self.line_buffer.clear();
terminal.print_line("");
self.print_prompt(terminal);
},
Key::Eot => {
return PromptEvent::Break;
},
Key::Arrow(_) => {
},
Key::Character(c) => {
if c != '\r' as u8 {
self.line_buffer.push(c);
if self.options.echo {
terminal.print(&[c]);
}
}
}
}
}
if handled_autocomplete == false {
self.autocomplete = AutocompleteRequest::None;
}
PromptEvent::Ok
}
}