use super::{print_err, print_out, resolve_command};
use crate::config::Config;
use crate::shell::Shell;
use crate::shell::proc::ShellState;
use crate::translator::ioprocessor::IOProcessor;
use crate::utils::buffer;
use crate::utils::console::{self, InputEvent};
pub(super) struct RuntimeProps {
pub config: Config,
pub processor: IOProcessor,
input_buffer: Vec<char>,
input_buffer_cursor: usize,
interactive: bool,
last_state: ShellState,
state_changed: bool,
rev_search: bool,
history_index: usize
}
impl RuntimeProps {
pub(super) fn new(interactive: bool, config: Config, processor: IOProcessor) -> RuntimeProps {
RuntimeProps {
config: config,
processor: processor,
input_buffer: Vec::with_capacity(2048),
input_buffer_cursor: 0,
interactive: interactive,
last_state: ShellState::Unknown,
state_changed: true,
rev_search: false,
history_index: 0
}
}
pub(super) fn clear_buffer(&mut self) {
self.input_buffer.clear();
self.input_buffer_cursor = 0;
}
fn reset_history_index(&mut self) {
self.history_index = 0;
}
pub(super) fn get_last_state(&self) -> ShellState {
self.last_state
}
pub(super) fn get_state_changed(&self) -> bool {
self.state_changed
}
pub(super) fn update_state(&mut self, new_state: ShellState) {
self.last_state = new_state;
self.state_changed = true;
}
pub(super) fn report_state_changed_notified(&mut self) {
self.state_changed = false;
}
pub(super) fn backspace(&mut self) {
if self.input_buffer_cursor > 0 {
self.input_buffer_cursor -= 1;
if self.input_buffer.len() > self.input_buffer_cursor {
self.input_buffer.remove(self.input_buffer_cursor);
}
console::backspace();
}
}
pub(super) fn move_left(&mut self) {
if self.input_buffer_cursor != 0 {
self.input_buffer_cursor -= 1;
console::move_cursor_left();
}
}
pub(super) fn move_right(&mut self) {
if self.input_buffer_cursor + 1 <= self.input_buffer.len() {
self.input_buffer_cursor += 1;
console::move_cursor_right();
}
}
pub(super) fn handle_input_event(&mut self, ev: InputEvent, shell: &mut Shell) {
match ev {
InputEvent::ArrowDown => {
if self.interactive && self.last_state == ShellState::Idle {
self.perform_history_backward(shell);
} else {
let _ = shell.write(console::input_event_to_string(ev));
}
},
InputEvent::ArrowUp => {
if self.interactive && self.last_state == ShellState::Idle {
self.perform_history_forward(shell);
} else {
let _ = shell.write(console::input_event_to_string(ev));
}
},
InputEvent::ArrowLeft => {
if self.interactive && self.last_state == ShellState::Idle {
self.move_left();
} else {
let _ = shell.write(console::input_event_to_string(ev));
}
},
InputEvent::ArrowRight => {
if self.interactive && self.last_state == ShellState::Idle {
self.move_right();
} else {
let _ = shell.write(console::input_event_to_string(ev));
}
},
InputEvent::Backspace => {
self.backspace();
},
InputEvent::CarriageReturn => {
if self.interactive && self.last_state == ShellState::Idle {
console::carriage_return();
} else {
let _ = shell.write(console::input_event_to_string(ev));
}
},
InputEvent::Ctrl(sig) => {
if self.last_state == ShellState::Idle && self.interactive {
match sig {
1 => { for _ in 0..self.input_buffer_cursor {
console::move_cursor_left();
}
self.input_buffer_cursor = 0; },
2 => { self.move_left();
},
3 => { self.clear_buffer();
self.reset_history_index();
console::println(String::new());
console::print(format!("{} ", shell.get_promptline(&self.processor)));
},
4 => { self.backspace();
},
5 => { for _ in self.input_buffer_cursor..self.input_buffer.len() {
console::move_cursor_right();
}
self.input_buffer_cursor = self.input_buffer.len();
},
6 => { self.move_right();
},
7 => { },
8 => { self.backspace();
},
11 => { while self.input_buffer_cursor < self.input_buffer.len() {
let _ = self.input_buffer.pop();
}
},
12 => { console::clear();
console::print(format!("{} {}", shell.get_promptline(&self.processor), buffer::chars_to_string(&self.input_buffer)));
},
18 => { },
_ => {} }
} else {
let _ = shell.write(console::input_event_to_string(ev));
}
},
InputEvent::Key(k) => { for ch in k.chars() {
self.input_buffer.insert(self.input_buffer_cursor, ch);
self.input_buffer_cursor += 1;
}
console::print(k);
},
InputEvent::Enter => { if self.interactive { self.perform_interactive_enter(shell);
} else { self.perform_enter(shell);
}
}
}
}
fn perform_interactive_enter(&mut self, shell: &mut Shell) {
self.reset_history_index();
console::println(String::new());
let stdin_input: String = buffer::chars_to_string(&self.input_buffer);
if stdin_input.trim().len() == 0 {
if self.last_state == ShellState::Idle {
console::print(format!("{} ", shell.get_promptline(&self.processor)));
}
self.clear_buffer();
} else {
let input: String = match self.last_state {
ShellState::Idle => {
let mut argv: Vec<String> = Vec::with_capacity(stdin_input.matches(" ").count() + 1);
for arg in stdin_input.split_whitespace() {
argv.push(String::from(arg));
}
resolve_command(&mut argv, &self.config);
let input: String = argv.join(" ") + "\n";
match self.processor.expression_to_latin(&input) {
Ok(ex) => ex,
Err(err) => {
print_err(String::from(format!("Input error: {:?}", err)), self.config.output_config.translate_output, &self.processor);
self.clear_buffer();
return;
}
}
},
ShellState::SubprocessRunning => self.processor.text_to_latin(&buffer::chars_to_string(&self.input_buffer)),
_ => {
self.clear_buffer();
return;
}
};
self.clear_buffer();
self.process_input_interactive(shell, input);
}
}
fn perform_enter(&mut self, shell: &mut Shell) {
let stdin_input: String = buffer::chars_to_string(&self.input_buffer);
if stdin_input.trim().len() > 0 {
let input: String = self.processor.text_to_latin(&stdin_input);
if let Err(err) = shell.write(input) {
print_err(String::from(err.to_string()), self.config.output_config.translate_output, &self.processor);
}
}
self.clear_buffer();
}
fn process_input_interactive(&mut self, shell: &mut Shell, mut input: String) {
if self.last_state == ShellState::Idle {
if input.starts_with("!") {
let history_index: &str = &input.as_str()[1..input.len() - 1];
if let Ok(history_index) = history_index.parse::<usize>() {
if history_index >= shell.history.len() {
print_err(format!("!{}: event not found", history_index), self.config.output_config.translate_output, &self.processor);
console::print(format!("{} ", shell.get_promptline(&self.processor)));
return;
}
let history_index: usize = shell.history.len() - history_index - 1;
match shell.history.at(history_index) {
Some(cmd) => { input = format!("{}\n", cmd);
},
None => { print_err(format!("!{}: event not found", history_index), self.config.output_config.translate_output, &self.processor);
console::print(format!("{} ", shell.get_promptline(&self.processor)));
return;
}
}
} else { print_err(format!("!{}: event not found", history_index), self.config.output_config.translate_output, &self.processor);
console::print(format!("{} ", shell.get_promptline(&self.processor)));
return;
}
}
shell.history.push(input.clone());
if input.starts_with("clear") {
console::clear();
console::print(format!("{} ", shell.get_promptline(&self.processor)));
} else if input.starts_with("history") {
let history_lines: Vec<String> = shell.history.dump();
for (idx, line) in history_lines.iter().enumerate() {
print_out(format!("{} {}", self.indent_history_index(idx), line), self.config.output_config.translate_output, &self.processor);
}
console::print(format!("{} ", shell.get_promptline(&self.processor)));
} else { if let Err(err) = shell.write(input) {
print_err(String::from(err.to_string()), self.config.output_config.translate_output, &self.processor);
}
}
} else { if let Err(err) = shell.write(input) {
print_err(String::from(err.to_string()), self.config.output_config.translate_output, &self.processor);
}
}
}
fn perform_history_backward(&mut self, shell: &mut Shell) {
if self.history_index > 1 {
self.history_index -= 1;
if let Some(cmd) = shell.history.at(self.history_index - 1) {
let prev_len: usize = self.input_buffer.len();
self.clear_buffer();
for ch in cmd.chars() {
self.input_buffer.push(ch);
self.input_buffer_cursor += 1;
}
console::rewrite(cmd, prev_len);
}
} else if self.history_index == 1 {
let prev_len: usize = self.input_buffer.len();
self.history_index = 0;
self.clear_buffer();
console::rewrite(String::from(""), prev_len);
}
}
fn perform_history_forward(&mut self, shell: &mut Shell) {
if self.history_index + 1 <= shell.history.len() {
self.history_index += 1;
if let Some(cmd) = shell.history.at(self.history_index - 1) {
let prev_len: usize = self.input_buffer.len();
self.clear_buffer();
for ch in cmd.chars() {
self.input_buffer.push(ch);
self.input_buffer_cursor += 1;
}
console::rewrite(cmd, prev_len);
}
}
}
fn indent_history_index(&self, index: usize) -> String {
if index < 10 {
format!(" {}", index)
} else if index < 100 {
format!(" {}", index)
} else if index < 1000 {
format!(" {}", index)
} else {
format!("{}", index)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use crate::translator::ioprocessor::IOProcessor;
use crate::translator::lang::Language;
use crate::translator::new_translator;
use std::time::Duration;
use std::thread::sleep;
#[test]
fn test_runtimeprops_new() {
let props: RuntimeProps = new_runtime_props(true);
assert!(props.config.get_alias(&String::from("ll")).is_none());
assert_eq!(props.processor.language, Language::Russian);
assert_eq!(props.input_buffer.capacity(), 2048);
assert_eq!(props.input_buffer_cursor, 0);
assert_eq!(props.interactive, true);
assert_eq!(props.last_state, ShellState::Unknown);
assert_eq!(props.state_changed, true);
assert_eq!(props.rev_search, false);
assert_eq!(props.history_index, 0);
}
#[test]
fn test_runtimeprops_clear_buffer() {
let mut props: RuntimeProps = new_runtime_props(false);
props.input_buffer = vec!['a', 'b', 'c'];
props.input_buffer_cursor = 3;
props.clear_buffer();
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.history_index = 128;
props.reset_history_index();
assert_eq!(props.history_index, 0);
}
#[test]
fn test_runtimeprops_update_state() {
let mut props: RuntimeProps = new_runtime_props(true);
assert_eq!(props.get_last_state(), ShellState::Unknown);
assert_eq!(props.get_state_changed(), true);
props.report_state_changed_notified();
assert_eq!(props.get_state_changed(), false);
props.update_state(ShellState::Idle);
assert_eq!(props.get_last_state(), ShellState::Idle);
assert_eq!(props.get_state_changed(), true);
}
#[test]
fn test_runtimeprops_backspace() {
let mut props: RuntimeProps = new_runtime_props(true);
props.input_buffer = vec!['a', 'b', 'c'];
props.backspace();
assert_eq!(props.input_buffer_cursor, 0);
assert_eq!(props.input_buffer.len(), 3);
props.input_buffer_cursor = 3;
props.backspace();
assert_eq!(props.input_buffer_cursor, 2);
assert_eq!(props.input_buffer, vec!['a', 'b']);
props.input_buffer_cursor = 1;
props.backspace();
assert_eq!(props.input_buffer_cursor, 0);
assert_eq!(props.input_buffer, vec!['b']);
props.input_buffer = vec!['a', 'b', 'c'];
props.input_buffer_cursor = 4;
props.backspace();
assert_eq!(props.input_buffer_cursor, 3);
assert_eq!(props.input_buffer.len(), 3);
}
#[test]
fn test_runtimeprops_move_cursor() {
let mut props: RuntimeProps = new_runtime_props(true);
props.input_buffer = vec!['a', 'b', 'c', 'd', 'e'];
props.input_buffer_cursor = 5;
props.move_left();
assert_eq!(props.input_buffer_cursor, 4);
props.input_buffer_cursor = 0;
props.move_left();
assert_eq!(props.input_buffer_cursor, 0);
props.move_right();
assert_eq!(props.input_buffer_cursor, 1);
props.input_buffer = vec!['a'];
props.move_right();
assert_eq!(props.input_buffer_cursor, 1);
}
#[test]
fn test_runtimeprops_handle_input_event() {
let mut props: RuntimeProps = new_runtime_props(true);
let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap();
sleep(Duration::from_millis(500)); props.update_state(ShellState::Idle);
shell.history.push(String::from("pwd"));
shell.history.push(String::from("ls -l"));
assert_eq!(props.history_index, 0);
props.handle_input_event(InputEvent::ArrowUp, &mut shell);
assert_eq!(props.history_index, 1); assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-', 'l']); assert_eq!(props.input_buffer_cursor, 5);
props.handle_input_event(InputEvent::ArrowUp, &mut shell);
assert_eq!(props.history_index, 2); assert_eq!(props.input_buffer, vec!['p', 'w', 'd']); assert_eq!(props.input_buffer_cursor, 3);
props.handle_input_event(InputEvent::ArrowUp, &mut shell);
assert_eq!(props.history_index, 2); assert_eq!(props.input_buffer, vec!['p', 'w', 'd']); assert_eq!(props.input_buffer_cursor, 3);
props.handle_input_event(InputEvent::ArrowDown, &mut shell);
assert_eq!(props.history_index, 1); assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-', 'l']); assert_eq!(props.input_buffer_cursor, 5);
props.handle_input_event(InputEvent::ArrowDown, &mut shell);
assert_eq!(props.history_index, 0); assert_eq!(props.input_buffer.len(), 0); assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.input_buffer = vec!['l', 's'];
props.input_buffer_cursor = 2;
props.handle_input_event(InputEvent::ArrowDown, &mut shell);
assert_eq!(props.history_index, 0); assert_eq!(props.input_buffer.len(), 2); assert_eq!(props.input_buffer_cursor, 2);
props.input_buffer = vec!['l', 's', ' ', '-', 'l'];
props.input_buffer_cursor = 5;
props.handle_input_event(InputEvent::ArrowLeft, &mut shell);
assert_eq!(props.input_buffer_cursor, 4);
props.handle_input_event(InputEvent::ArrowRight, &mut shell);
assert_eq!(props.input_buffer_cursor, 5);
props.handle_input_event(InputEvent::Backspace, &mut shell);
assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-']);
assert_eq!(props.input_buffer_cursor, 4);
props.handle_input_event(InputEvent::CarriageReturn, &mut shell);
props.last_state = ShellState::Idle;
props.handle_input_event(InputEvent::Ctrl(1), &mut shell);
assert_eq!(props.input_buffer_cursor, 0);
props.input_buffer_cursor = 2;
props.handle_input_event(InputEvent::Ctrl(2), &mut shell);
assert_eq!(props.input_buffer_cursor, 1);
props.history_index = 255;
props.handle_input_event(InputEvent::Ctrl(3), &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
assert_eq!(props.history_index, 0); props.input_buffer = vec!['l', 's', ' ', '-', 'l'];
props.input_buffer_cursor = 5;
props.handle_input_event(InputEvent::Ctrl(4), &mut shell);
assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-']);
assert_eq!(props.input_buffer_cursor, 4);
props.input_buffer_cursor = 1;
props.handle_input_event(InputEvent::Ctrl(5), &mut shell);
assert_eq!(props.input_buffer_cursor, 4);
props.input_buffer_cursor = 1;
props.handle_input_event(InputEvent::Ctrl(6), &mut shell);
assert_eq!(props.input_buffer_cursor, 2);
props.handle_input_event(InputEvent::Ctrl(7), &mut shell);
props.handle_input_event(InputEvent::Ctrl(8), &mut shell);
assert_eq!(props.input_buffer, vec!['l', ' ', '-']);
assert_eq!(props.input_buffer_cursor, 1);
props.handle_input_event(InputEvent::Ctrl(11), &mut shell);
assert_eq!(props.input_buffer, vec!['l']);
assert_eq!(props.input_buffer_cursor, 1);
props.handle_input_event(InputEvent::Ctrl(12), &mut shell);
assert_eq!(props.input_buffer, vec!['l']);
assert_eq!(props.input_buffer_cursor, 1);
props.handle_input_event(InputEvent::Ctrl(18), &mut shell);
props.handle_input_event(InputEvent::Ctrl(255), &mut shell);
assert_eq!(props.input_buffer, vec!['l']);
assert_eq!(props.input_buffer_cursor, 1);
props.clear_buffer();
props.handle_input_event(InputEvent::Key(String::from("l")), &mut shell);
assert_eq!(props.input_buffer, vec!['l']);
assert_eq!(props.input_buffer_cursor, 1);
props.handle_input_event(InputEvent::Key(String::from("л")), &mut shell);
assert_eq!(props.input_buffer, vec!['l', 'л']);
assert_eq!(props.input_buffer_cursor, 2);
props.move_left();
props.handle_input_event(InputEvent::Key(String::from("s")), &mut shell);
assert_eq!(props.input_buffer, vec!['l', 's', 'л']);
assert_eq!(props.input_buffer_cursor, 2);
props.last_state = ShellState::Idle;
props.input_buffer = Vec::new();
props.input_buffer_cursor = 0;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
assert_eq!(props.history_index, 0);
props.history_index = 255;
props.last_state = ShellState::Idle;
props.input_buffer = vec!['l', 's'];
props.input_buffer_cursor = 2;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
assert_eq!(props.history_index, 0); assert_eq!(shell.history.at(0).unwrap(), String::from("ls"));
props.last_state = ShellState::Idle;
props.input_buffer = vec!['c', 'l', 'e', 'a', 'r'];
props.input_buffer_cursor = 5;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.last_state = ShellState::Idle;
props.input_buffer = vec!['h', 'i', 's', 't', 'o', 'r', 'y'];
props.input_buffer_cursor = 7;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.last_state = ShellState::Idle;
props.input_buffer = vec!['!', '4', '0'];
props.input_buffer_cursor = 3;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.input_buffer = vec!['!', '1'];
props.input_buffer_cursor = 2;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.input_buffer = vec!['!', 'f', 'o', 'o'];
props.input_buffer_cursor = 4;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.last_state = ShellState::Terminated;
props.input_buffer = vec!['l', 's'];
props.input_buffer_cursor = 2;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.last_state = ShellState::SubprocessRunning;
props.input_buffer = vec!['h', 'e', 'l', 'l', 'o'];
props.input_buffer_cursor = 5;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.handle_input_event(InputEvent::Ctrl(2), &mut shell);
props.handle_input_event(InputEvent::Ctrl(1), &mut shell);
sleep(Duration::from_millis(500)); let _ = shell.stop();
sleep(Duration::from_millis(500)); }
#[test]
fn test_runtimeprops_handle_input_event_not_interactive() {
let mut props: RuntimeProps = new_runtime_props(false);
let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap();
sleep(Duration::from_millis(500)); props.input_buffer = vec!['l', 's'];
props.input_buffer_cursor = 2;
props.last_state = ShellState::SubprocessRunning;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
props.handle_input_event(InputEvent::ArrowDown, &mut shell);
props.handle_input_event(InputEvent::ArrowLeft, &mut shell);
props.handle_input_event(InputEvent::ArrowRight, &mut shell);
props.handle_input_event(InputEvent::ArrowUp, &mut shell);
props.handle_input_event(InputEvent::Ctrl(3), &mut shell);
sleep(Duration::from_millis(500)); let _ = shell.stop();
sleep(Duration::from_millis(500)); assert_eq!(shell.get_state(), ShellState::Terminated);
props.last_state = ShellState::SubprocessRunning;
props.handle_input_event(InputEvent::Ctrl(2), &mut shell);
props.input_buffer = vec!['l', 's'];
props.input_buffer_cursor = 2;
props.last_state = ShellState::SubprocessRunning;
props.handle_input_event(InputEvent::Enter, &mut shell);
assert_eq!(props.input_buffer.len(), 0);
assert_eq!(props.input_buffer_cursor, 0);
sleep(Duration::from_millis(500)); }
#[test]
fn test_runtimeprops_indent_history_index() {
let props: RuntimeProps = new_runtime_props(true);
assert_eq!(props.indent_history_index(0), String::from(" 0"));
assert_eq!(props.indent_history_index(10), String::from(" 10"));
assert_eq!(props.indent_history_index(100), String::from(" 100"));
assert_eq!(props.indent_history_index(1000), String::from("1000"));
}
fn new_runtime_props(interactive: bool) -> RuntimeProps {
RuntimeProps::new(interactive, Config::default(), IOProcessor::new(Language::Russian, new_translator(Language::Russian)))
}
}