forked from Rust-related/RustPython
Cleanup some stuff related to the repl
This commit is contained in:
112
src/shell.rs
112
src/shell.rs
@@ -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
153
src/shell/readline.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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()])))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user