mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Move ShellHelper into another module
This commit is contained in:
3 files changed
+70
-213
No files matched your search
Generated
+7
@@ -374,6 +374,11 @@ dependencies = [
|
||||
"termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exitcode"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.5"
|
||||
@@ -1166,6 +1171,7 @@ dependencies = [
|
||||
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"exitcode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flamer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2055,6 +2061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b"
|
||||
"checksum ena 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f56c93cc076508c549d9bb747f79aa9b4eb098be7b8cad8830c3137ef52d1e00"
|
||||
"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
|
||||
"checksum exitcode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
|
||||
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
|
||||
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
+6
-155
@@ -9,10 +9,10 @@ use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}
|
||||
use rustpython_parser::error::ParseErrorType;
|
||||
use rustpython_vm::{
|
||||
import, match_class,
|
||||
obj::{objint::PyInt, objstr::PyStringRef, objtuple::PyTuple, objtype},
|
||||
obj::{objint::PyInt, objtuple::PyTuple, objtype},
|
||||
print_exception,
|
||||
pyobject::{ItemProtocol, PyIterable, PyObjectRef, PyResult, TryFromObject},
|
||||
scope::{NameProtocol, Scope},
|
||||
pyobject::{ItemProtocol, PyObjectRef, PyResult},
|
||||
scope::Scope,
|
||||
util, PySettings, VirtualMachine,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
@@ -22,6 +22,8 @@ use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod shell_helper;
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "flame-it")]
|
||||
let main_guard = flame::start_guard("RustPython main");
|
||||
@@ -487,154 +489,6 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResul
|
||||
}
|
||||
}
|
||||
|
||||
struct ShellHelper<'a> {
|
||||
vm: &'a VirtualMachine,
|
||||
scope: Scope,
|
||||
}
|
||||
|
||||
impl ShellHelper<'_> {
|
||||
fn complete_opt(&self, line: &str) -> Option<(usize, Vec<String>)> {
|
||||
let mut words = vec![String::new()];
|
||||
fn revlastword(words: &mut Vec<String>) {
|
||||
let word = words.last_mut().unwrap();
|
||||
let revword = word.chars().rev().collect();
|
||||
*word = revword;
|
||||
}
|
||||
let mut startpos = 0;
|
||||
for (i, c) in line.chars().rev().enumerate() {
|
||||
match c {
|
||||
'.' => {
|
||||
// check for a double dot
|
||||
if i != 0 && words.last().map_or(false, |s| s.is_empty()) {
|
||||
return None;
|
||||
}
|
||||
revlastword(&mut words);
|
||||
if words.len() == 1 {
|
||||
startpos = line.len() - i;
|
||||
}
|
||||
words.push(String::new());
|
||||
}
|
||||
c if c.is_alphanumeric() || c == '_' => words.last_mut().unwrap().push(c),
|
||||
_ => {
|
||||
if words.len() == 1 {
|
||||
if words.last().unwrap().is_empty() {
|
||||
return None;
|
||||
}
|
||||
startpos = line.len() - i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
revlastword(&mut words);
|
||||
words.reverse();
|
||||
|
||||
// the very first word and then all the ones after the dot
|
||||
let (first, rest) = words.split_first().unwrap();
|
||||
|
||||
let str_iter = |obj| {
|
||||
PyIterable::<PyStringRef>::try_from_object(self.vm, obj)
|
||||
.ok()?
|
||||
.iter(self.vm)
|
||||
.ok()
|
||||
};
|
||||
|
||||
type StrIter<'a> = Box<dyn Iterator<Item = PyResult<PyStringRef>> + 'a>;
|
||||
|
||||
let (iter, prefix) = if let Some((last, parents)) = rest.split_last() {
|
||||
// we need to get an attribute based off of the dir() of an object
|
||||
|
||||
// last: the last word, could be empty if it ends with a dot
|
||||
// parents: the words before the dot
|
||||
|
||||
let mut current = self.scope.load_global(self.vm, first)?;
|
||||
|
||||
for attr in parents {
|
||||
current = self.vm.get_attribute(current.clone(), attr.as_str()).ok()?;
|
||||
}
|
||||
|
||||
(
|
||||
Box::new(str_iter(
|
||||
self.vm.call_method(¤t, "__dir__", vec![]).ok()?,
|
||||
)?) as StrIter,
|
||||
last.as_str(),
|
||||
)
|
||||
} else {
|
||||
// we need to get a variable based off of globals/builtins
|
||||
|
||||
let globals = str_iter(
|
||||
self.vm
|
||||
.call_method(self.scope.globals.as_object(), "keys", vec![])
|
||||
.ok()?,
|
||||
)?;
|
||||
let iter = if first.as_str().is_empty() {
|
||||
// only show globals that don't start with a '_'
|
||||
Box::new(globals.filter(|r| {
|
||||
r.as_ref()
|
||||
.ok()
|
||||
.map_or(true, |s| !s.as_str().starts_with('_'))
|
||||
})) as StrIter
|
||||
} else {
|
||||
// show globals and builtins
|
||||
Box::new(
|
||||
globals.chain(str_iter(
|
||||
self.vm
|
||||
.call_method(&self.vm.builtins, "__dir__", vec![])
|
||||
.ok()?,
|
||||
)?),
|
||||
) as StrIter
|
||||
};
|
||||
(iter, first.as_str())
|
||||
};
|
||||
let completions = iter
|
||||
.filter(|res| {
|
||||
res.as_ref()
|
||||
.ok()
|
||||
.map_or(true, |s| s.as_str().starts_with(prefix))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.ok()?;
|
||||
let no_underscore = completions
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|s| !prefix.starts_with('_') && !s.as_str().starts_with('_'))
|
||||
.collect::<Vec<_>>();
|
||||
let mut completions = if no_underscore.is_empty() {
|
||||
completions
|
||||
} else {
|
||||
no_underscore
|
||||
};
|
||||
completions.sort_by(|a, b| std::cmp::Ord::cmp(a.as_str(), b.as_str()));
|
||||
Some((
|
||||
startpos,
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|s| s.as_str().to_owned())
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Completer for ShellHelper<'_> {
|
||||
type Candidate = String;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context,
|
||||
) -> rustyline::Result<(usize, Vec<String>)> {
|
||||
if pos != line.len() {
|
||||
return Ok((0, vec![]));
|
||||
}
|
||||
Ok(self.complete_opt(line).unwrap_or((0, vec![])))
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::hint::Hinter for ShellHelper<'_> {}
|
||||
impl rustyline::highlight::Highlighter for ShellHelper<'_> {}
|
||||
impl rustyline::Helper for ShellHelper<'_> {}
|
||||
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
|
||||
use rustyline::{error::ReadlineError, CompletionType, Config, Editor};
|
||||
@@ -650,10 +504,7 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
|
||||
.completion_type(CompletionType::List)
|
||||
.build(),
|
||||
);
|
||||
repl.set_helper(Some(ShellHelper {
|
||||
vm,
|
||||
scope: scope.clone(),
|
||||
}));
|
||||
repl.set_helper(Some(shell_helper::ShellHelper::new(vm, scope.clone())));
|
||||
let mut full_input = String::new();
|
||||
|
||||
// Retrieve a `history_path_str` dependent on the OS
|
||||
|
||||
+57
-58
@@ -4,8 +4,8 @@ use rustpython_vm::scope::{NameProtocol, Scope};
|
||||
use rustpython_vm::VirtualMachine;
|
||||
use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, Context, Helper};
|
||||
|
||||
pub struct ShellHelper<'a> {
|
||||
vm: &'a VirtualMachine,
|
||||
pub struct ShellHelper<'vm> {
|
||||
vm: &'vm VirtualMachine,
|
||||
scope: Scope,
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ fn reverse_string(s: &mut String) {
|
||||
*s = rev;
|
||||
}
|
||||
|
||||
fn extract_words(line: &str) -> Option<(usize, Vec<String>)> {
|
||||
fn split_idents_on_dot(line: &str) -> Option<(usize, Vec<String>)> {
|
||||
let mut words = vec![String::new()];
|
||||
let mut startpos = 0;
|
||||
for (i, c) in line.chars().rev().enumerate() {
|
||||
@@ -42,34 +42,36 @@ fn extract_words(line: &str) -> Option<(usize, Vec<String>)> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if words == &[String::new()] {
|
||||
return None;
|
||||
}
|
||||
reverse_string(words.last_mut().unwrap());
|
||||
words.reverse();
|
||||
|
||||
Some((startpos, words))
|
||||
}
|
||||
|
||||
impl<'a> ShellHelper<'a> {
|
||||
pub fn new(vm: &'a VirtualMachine, scope: Scope) -> Self {
|
||||
impl<'vm> ShellHelper<'vm> {
|
||||
pub fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self {
|
||||
ShellHelper { vm, scope }
|
||||
}
|
||||
|
||||
// fn get_words
|
||||
|
||||
fn complete_opt(&self, line: &str) -> Option<(usize, Vec<String>)> {
|
||||
let (startpos, words) = extract_words(line)?;
|
||||
|
||||
fn get_available_completions<'w>(
|
||||
&self,
|
||||
words: &'w [String],
|
||||
) -> Option<(
|
||||
&'w str,
|
||||
Box<dyn Iterator<Item = PyResult<PyStringRef>> + 'vm>,
|
||||
)> {
|
||||
// the very first word and then all the ones after the dot
|
||||
let (first, rest) = words.split_first().unwrap();
|
||||
|
||||
let str_iter = |obj| {
|
||||
PyIterable::<PyStringRef>::try_from_object(self.vm, obj)
|
||||
.ok()?
|
||||
.iter(self.vm)
|
||||
.ok()
|
||||
let str_iter_method = |obj, name| {
|
||||
let iter = self.vm.call_method(obj, name, vec![])?;
|
||||
PyIterable::<PyStringRef>::try_from_object(self.vm, iter)?.iter(self.vm)
|
||||
};
|
||||
|
||||
type StrIter<'a> = Box<dyn Iterator<Item = PyResult<PyStringRef>> + 'a>;
|
||||
|
||||
let (iter, prefix) = if let Some((last, parents)) = rest.split_last() {
|
||||
if let Some((last, parents)) = rest.split_last() {
|
||||
// we need to get an attribute based off of the dir() of an object
|
||||
|
||||
// last: the last word, could be empty if it ends with a dot
|
||||
@@ -81,58 +83,55 @@ impl<'a> ShellHelper<'a> {
|
||||
current = self.vm.get_attribute(current.clone(), attr.as_str()).ok()?;
|
||||
}
|
||||
|
||||
(
|
||||
Box::new(str_iter(
|
||||
self.vm.call_method(¤t, "__dir__", vec![]).ok()?,
|
||||
)?) as StrIter,
|
||||
last.as_str(),
|
||||
)
|
||||
let current_iter = str_iter_method(¤t, "__dir__").ok()?;
|
||||
|
||||
Some((&last, Box::new(current_iter) as _))
|
||||
} else {
|
||||
// we need to get a variable based off of globals/builtins
|
||||
|
||||
let globals = str_iter(
|
||||
self.vm
|
||||
.call_method(self.scope.globals.as_object(), "keys", vec![])
|
||||
.ok()?,
|
||||
)?;
|
||||
let iter = if first.as_str().is_empty() {
|
||||
// only show globals that don't start with a '_'
|
||||
Box::new(globals.filter(|r| {
|
||||
r.as_ref()
|
||||
.ok()
|
||||
.map_or(true, |s| !s.as_str().starts_with('_'))
|
||||
})) as StrIter
|
||||
} else {
|
||||
// show globals and builtins
|
||||
Box::new(
|
||||
globals.chain(str_iter(
|
||||
self.vm
|
||||
.call_method(&self.vm.builtins, "__dir__", vec![])
|
||||
.ok()?,
|
||||
)?),
|
||||
) as StrIter
|
||||
};
|
||||
(iter, first.as_str())
|
||||
};
|
||||
let completions = iter
|
||||
let globals = str_iter_method(self.scope.globals.as_object(), "keys").ok()?;
|
||||
let builtins = str_iter_method(&self.vm.builtins, "__dir__").ok()?;
|
||||
Some((&first, Box::new(Iterator::chain(globals, builtins)) as _))
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_opt(&self, line: &str) -> Option<(usize, Vec<String>)> {
|
||||
let (startpos, words) = split_idents_on_dot(line)?;
|
||||
|
||||
let (word_start, iter) = self.get_available_completions(&words)?;
|
||||
|
||||
let all_completions = iter
|
||||
.filter(|res| {
|
||||
res.as_ref()
|
||||
.ok()
|
||||
.map_or(true, |s| s.as_str().starts_with(prefix))
|
||||
.map_or(true, |s| s.as_str().starts_with(word_start))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.ok()?;
|
||||
let no_underscore = completions
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|s| !prefix.starts_with('_') && !s.as_str().starts_with('_'))
|
||||
.collect::<Vec<_>>();
|
||||
let mut completions = if no_underscore.is_empty() {
|
||||
completions
|
||||
let mut completions = if word_start.starts_with('_') {
|
||||
// if they're already looking for something starting with a '_', just give
|
||||
// them all the completions
|
||||
all_completions
|
||||
} else {
|
||||
no_underscore
|
||||
// only the completions that don't start with a '_'
|
||||
let no_underscore = all_completions
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|s| !s.as_str().starts_with('_'))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// if there are only completions that start with a '_', give them all of the
|
||||
// completions, otherwise only the ones that don't start with '_'
|
||||
if no_underscore.is_empty() {
|
||||
all_completions
|
||||
} else {
|
||||
no_underscore
|
||||
}
|
||||
};
|
||||
|
||||
// sort the completions alphabetically
|
||||
completions.sort_by(|a, b| std::cmp::Ord::cmp(a.as_str(), b.as_str()));
|
||||
|
||||
Some((
|
||||
startpos,
|
||||
completions
|
||||
|
||||
Reference in new issue
Block a user