Cleanup some stuff related to the repl

This commit is contained in:
coolreader18
2019-10-13 22:42:32 -05:00
committed by Noah
parent 67f005e9f7
commit 1edf7c85aa
3 changed files with 158 additions and 114 deletions

View File

@@ -1,3 +1,4 @@
mod readline;
#[cfg(not(target_os = "wasi"))]
mod rustyline_helper;
@@ -10,11 +11,8 @@ use rustpython_vm::{
scope::Scope,
VirtualMachine,
};
#[cfg(not(target_os = "wasi"))]
use rustyline_helper::ShellHelper;
use std::io;
use std::path::Path;
use readline::{Readline, ReadlineResult};
enum ShellExecResult {
Ok,
@@ -45,110 +43,6 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResul
}
}
enum ReadlineResult {
Line(String),
EOF,
Interrupt,
IO(std::io::Error),
EncodingError,
Other(Box<dyn std::error::Error>),
}
#[allow(unused)]
struct BasicReadline;
#[allow(unused)]
impl BasicReadline {
fn new(_vm: &VirtualMachine, _scope: Scope) -> Self {
BasicReadline
}
fn load_history(&mut self, _path: &Path) -> io::Result<()> {
Ok(())
}
fn save_history(&mut self, _path: &Path) -> io::Result<()> {
Ok(())
}
fn add_history_entry(&mut self, _entry: &str) {}
fn readline(&mut self, prompt: &str) -> ReadlineResult {
use std::io::prelude::*;
print!("{}", prompt);
if let Err(e) = io::stdout().flush() {
return ReadlineResult::IO(e);
}
match io::stdin().lock().lines().next() {
Some(Ok(line)) => ReadlineResult::Line(line),
None => ReadlineResult::EOF,
Some(Err(e)) => match e.kind() {
io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
io::ErrorKind::InvalidData => ReadlineResult::EncodingError,
_ => ReadlineResult::IO(e),
},
}
}
}
#[cfg(target_os = "wasi")]
type Readline = BasicReadline;
#[cfg(not(target_os = "wasi"))]
struct RustylineReadline<'vm> {
repl: rustyline::Editor<ShellHelper<'vm>>,
}
#[cfg(not(target_os = "wasi"))]
impl<'vm> RustylineReadline<'vm> {
fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self {
use rustyline::{CompletionType, Config, Editor};
let mut repl = Editor::with_config(
Config::builder()
.completion_type(CompletionType::List)
.tab_stop(4)
.build(),
);
repl.set_helper(Some(ShellHelper::new(vm, scope)));
RustylineReadline { repl }
}
fn load_history(&mut self, path: &Path) -> rustyline::Result<()> {
self.repl.load_history(path)
}
fn save_history(&mut self, path: &Path) -> rustyline::Result<()> {
if !path.exists() {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
}
self.repl.save_history(path)
}
fn add_history_entry(&mut self, entry: &str) {
self.repl.add_history_entry(entry);
}
fn readline(&mut self, prompt: &str) -> ReadlineResult {
use rustyline::error::ReadlineError;
match self.repl.readline(prompt) {
Ok(line) => ReadlineResult::Line(line),
Err(ReadlineError::Interrupted) => ReadlineResult::Interrupt,
Err(ReadlineError::Eof) => ReadlineResult::EOF,
Err(ReadlineError::Io(e)) => ReadlineResult::IO(e),
#[cfg(unix)]
Err(ReadlineError::Utf8Error) => ReadlineResult::EncodingError,
#[cfg(windows)]
Err(ReadlineError::Decode(_)) => ReadlineResult::EncodingError,
Err(e) => ReadlineResult::Other(e.into()),
}
}
}
#[cfg(not(target_os = "wasi"))]
type Readline<'a> = RustylineReadline<'a>;
pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
println!(
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
@@ -187,7 +81,7 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
ReadlineResult::Line(line) => {
debug!("You entered {:?}", line);
repl.add_history_entry(line.trim_end());
repl.add_history_entry(line.trim_end()).unwrap();
let stop_continuing = line.is_empty();

153
src/shell/readline.rs Normal file
View File

@@ -0,0 +1,153 @@
use std::io;
use std::path::Path;
use rustpython_vm::{scope::Scope, VirtualMachine};
type OtherError = Box<dyn std::error::Error>;
type OtherResult<T> = Result<T, OtherError>;
pub enum ReadlineResult {
Line(String),
EOF,
Interrupt,
IO(std::io::Error),
EncodingError,
Other(OtherError),
}
#[allow(unused)]
mod basic_readline {
use super::*;
pub struct BasicReadline<'vm> {
vm: &'vm VirtualMachine,
}
impl<'vm> BasicReadline<'vm> {
pub fn new(vm: &'vm VirtualMachine, _scope: Scope) -> Self {
BasicReadline { vm }
}
pub fn load_history(&mut self, _path: &Path) -> OtherResult<()> {
Ok(())
}
pub fn save_history(&mut self, _path: &Path) -> OtherResult<()> {
Ok(())
}
pub fn add_history_entry(&mut self, _entry: &str) -> OtherResult<()> {
Ok(())
}
pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
use std::io::prelude::*;
print!("{}", prompt);
if let Err(e) = io::stdout().flush() {
return ReadlineResult::IO(e);
}
match io::stdin().lock().lines().next() {
Some(Ok(line)) => ReadlineResult::Line(line),
None => ReadlineResult::EOF,
Some(Err(e)) => match e.kind() {
io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
io::ErrorKind::InvalidData => ReadlineResult::EncodingError,
_ => ReadlineResult::IO(e),
},
}
}
}
}
#[cfg(not(target_os = "wasi"))]
mod rustyline_readline {
use super::{super::rustyline_helper::ShellHelper, *};
pub struct RustylineReadline<'vm> {
repl: rustyline::Editor<ShellHelper<'vm>>,
}
impl<'vm> RustylineReadline<'vm> {
pub fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self {
use rustyline::{At, Cmd, CompletionType, Config, Editor, KeyPress, Movement, Word};
let mut repl = Editor::with_config(
Config::builder()
.completion_type(CompletionType::List)
.tab_stop(8)
.build(),
);
repl.bind_sequence(
KeyPress::ControlLeft,
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
);
repl.bind_sequence(
KeyPress::ControlRight,
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
);
repl.set_helper(Some(ShellHelper::new(vm, scope)));
RustylineReadline { repl }
}
pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
self.repl.load_history(path)?;
Ok(())
}
pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
if !path.exists() {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
}
self.repl.save_history(path)?;
Ok(())
}
pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
self.repl.add_history_entry(entry);
Ok(())
}
pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
use rustyline::error::ReadlineError;
match self.repl.readline(prompt) {
Ok(line) => ReadlineResult::Line(line),
Err(ReadlineError::Interrupted) => ReadlineResult::Interrupt,
Err(ReadlineError::Eof) => ReadlineResult::EOF,
Err(ReadlineError::Io(e)) => ReadlineResult::IO(e),
#[cfg(unix)]
Err(ReadlineError::Utf8Error) => ReadlineResult::EncodingError,
#[cfg(windows)]
Err(ReadlineError::Decode(_)) => ReadlineResult::EncodingError,
Err(e) => ReadlineResult::Other(e.into()),
}
}
}
}
#[cfg(target_os = "wasi")]
type ReadlineInner<'vm> = basic_readline::BasicReadline<'vm>;
#[cfg(not(target_os = "wasi"))]
type ReadlineInner<'vm> = rustyline_readline::RustylineReadline<'vm>;
pub struct Readline<'vm>(ReadlineInner<'vm>);
impl<'vm> Readline<'vm> {
pub fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self {
Readline(ReadlineInner::new(vm, scope))
}
pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
self.0.load_history(path)
}
pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
self.0.save_history(path)
}
pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
self.0.add_history_entry(entry)
}
pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
self.0.readline(prompt)
}
}

View File

@@ -152,14 +152,11 @@ impl Completer for ShellHelper<'_> {
pos: usize,
_ctx: &Context,
) -> rustyline::Result<(usize, Vec<String>)> {
if pos != line.len() {
return Ok((0, vec![]));
}
Ok(self
.complete_opt(line)
.complete_opt(&line[0..pos])
// as far as I can tell, there's no better way to do both completion
// and indentation (or even just indentation)
.unwrap_or_else(|| (line.len(), vec![" ".to_string()])))
.unwrap_or_else(|| (line.len(), vec!["\t".to_string()])))
}
}