Skip to content

Commit 04a5aec

Browse files
committed
Move ShellHelper into another module
1 parent de7002a commit 04a5aec

File tree

3 files changed

+70
-213
lines changed

3 files changed

+70
-213
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.rs

Lines changed: 6 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}
99
use rustpython_parser::error::ParseErrorType;
1010
use rustpython_vm::{
1111
import, match_class,
12-
obj::{objint::PyInt, objstr::PyStringRef, objtuple::PyTuple, objtype},
12+
obj::{objint::PyInt, objtuple::PyTuple, objtype},
1313
print_exception,
14-
pyobject::{ItemProtocol, PyIterable, PyObjectRef, PyResult, TryFromObject},
15-
scope::{NameProtocol, Scope},
14+
pyobject::{ItemProtocol, PyObjectRef, PyResult},
15+
scope::Scope,
1616
util, PySettings, VirtualMachine,
1717
};
1818
use std::convert::TryInto;
@@ -22,6 +22,8 @@ use std::path::PathBuf;
2222
use std::process;
2323
use std::str::FromStr;
2424

25+
mod shell_helper;
26+
2527
fn main() {
2628
#[cfg(feature = "flame-it")]
2729
let main_guard = flame::start_guard("RustPython main");
@@ -487,154 +489,6 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResul
487489
}
488490
}
489491

490-
struct ShellHelper<'a> {
491-
vm: &'a VirtualMachine,
492-
scope: Scope,
493-
}
494-
495-
impl ShellHelper<'_> {
496-
fn complete_opt(&self, line: &str) -> Option<(usize, Vec<String>)> {
497-
let mut words = vec![String::new()];
498-
fn revlastword(words: &mut Vec<String>) {
499-
let word = words.last_mut().unwrap();
500-
let revword = word.chars().rev().collect();
501-
*word = revword;
502-
}
503-
let mut startpos = 0;
504-
for (i, c) in line.chars().rev().enumerate() {
505-
match c {
506-
'.' => {
507-
// check for a double dot
508-
if i != 0 && words.last().map_or(false, |s| s.is_empty()) {
509-
return None;
510-
}
511-
revlastword(&mut words);
512-
if words.len() == 1 {
513-
startpos = line.len() - i;
514-
}
515-
words.push(String::new());
516-
}
517-
c if c.is_alphanumeric() || c == '_' => words.last_mut().unwrap().push(c),
518-
_ => {
519-
if words.len() == 1 {
520-
if words.last().unwrap().is_empty() {
521-
return None;
522-
}
523-
startpos = line.len() - i;
524-
}
525-
break;
526-
}
527-
}
528-
}
529-
revlastword(&mut words);
530-
words.reverse();
531-
532-
// the very first word and then all the ones after the dot
533-
let (first, rest) = words.split_first().unwrap();
534-
535-
let str_iter = |obj| {
536-
PyIterable::<PyStringRef>::try_from_object(self.vm, obj)
537-
.ok()?
538-
.iter(self.vm)
539-
.ok()
540-
};
541-
542-
type StrIter<'a> = Box<dyn Iterator<Item = PyResult<PyStringRef>> + 'a>;
543-
544-
let (iter, prefix) = if let Some((last, parents)) = rest.split_last() {
545-
// we need to get an attribute based off of the dir() of an object
546-
547-
// last: the last word, could be empty if it ends with a dot
548-
// parents: the words before the dot
549-
550-
let mut current = self.scope.load_global(self.vm, first)?;
551-
552-
for attr in parents {
553-
current = self.vm.get_attribute(current.clone(), attr.as_str()).ok()?;
554-
}
555-
556-
(
557-
Box::new(str_iter(
558-
self.vm.call_method(&current, "__dir__", vec![]).ok()?,
559-
)?) as StrIter,
560-
last.as_str(),
561-
)
562-
} else {
563-
// we need to get a variable based off of globals/builtins
564-
565-
let globals = str_iter(
566-
self.vm
567-
.call_method(self.scope.globals.as_object(), "keys", vec![])
568-
.ok()?,
569-
)?;
570-
let iter = if first.as_str().is_empty() {
571-
// only show globals that don't start with a '_'
572-
Box::new(globals.filter(|r| {
573-
r.as_ref()
574-
.ok()
575-
.map_or(true, |s| !s.as_str().starts_with('_'))
576-
})) as StrIter
577-
} else {
578-
// show globals and builtins
579-
Box::new(
580-
globals.chain(str_iter(
581-
self.vm
582-
.call_method(&self.vm.builtins, "__dir__", vec![])
583-
.ok()?,
584-
)?),
585-
) as StrIter
586-
};
587-
(iter, first.as_str())
588-
};
589-
let completions = iter
590-
.filter(|res| {
591-
res.as_ref()
592-
.ok()
593-
.map_or(true, |s| s.as_str().starts_with(prefix))
594-
})
595-
.collect::<Result<Vec<_>, _>>()
596-
.ok()?;
597-
let no_underscore = completions
598-
.iter()
599-
.cloned()
600-
.filter(|s| !prefix.starts_with('_') && !s.as_str().starts_with('_'))
601-
.collect::<Vec<_>>();
602-
let mut completions = if no_underscore.is_empty() {
603-
completions
604-
} else {
605-
no_underscore
606-
};
607-
completions.sort_by(|a, b| std::cmp::Ord::cmp(a.as_str(), b.as_str()));
608-
Some((
609-
startpos,
610-
completions
611-
.into_iter()
612-
.map(|s| s.as_str().to_owned())
613-
.collect(),
614-
))
615-
}
616-
}
617-
618-
impl rustyline::completion::Completer for ShellHelper<'_> {
619-
type Candidate = String;
620-
621-
fn complete(
622-
&self,
623-
line: &str,
624-
pos: usize,
625-
_ctx: &rustyline::Context,
626-
) -> rustyline::Result<(usize, Vec<String>)> {
627-
if pos != line.len() {
628-
return Ok((0, vec![]));
629-
}
630-
Ok(self.complete_opt(line).unwrap_or((0, vec![])))
631-
}
632-
}
633-
634-
impl rustyline::hint::Hinter for ShellHelper<'_> {}
635-
impl rustyline::highlight::Highlighter for ShellHelper<'_> {}
636-
impl rustyline::Helper for ShellHelper<'_> {}
637-
638492
#[cfg(not(target_os = "wasi"))]
639493
fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
640494
use rustyline::{error::ReadlineError, CompletionType, Config, Editor};
@@ -650,10 +504,7 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
650504
.completion_type(CompletionType::List)
651505
.build(),
652506
);
653-
repl.set_helper(Some(ShellHelper {
654-
vm,
655-
scope: scope.clone(),
656-
}));
507+
repl.set_helper(Some(shell_helper::ShellHelper::new(vm, scope.clone())));
657508
let mut full_input = String::new();
658509

659510
// Retrieve a `history_path_str` dependent on the OS

src/shell_helper.rs

Lines changed: 57 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use rustpython_vm::scope::{NameProtocol, Scope};
44
use rustpython_vm::VirtualMachine;
55
use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, Context, Helper};
66

7-
pub struct ShellHelper<'a> {
8-
vm: &'a VirtualMachine,
7+
pub struct ShellHelper<'vm> {
8+
vm: &'vm VirtualMachine,
99
scope: Scope,
1010
}
1111

@@ -14,7 +14,7 @@ fn reverse_string(s: &mut String) {
1414
*s = rev;
1515
}
1616

17-
fn extract_words(line: &str) -> Option<(usize, Vec<String>)> {
17+
fn split_idents_on_dot(line: &str) -> Option<(usize, Vec<String>)> {
1818
let mut words = vec![String::new()];
1919
let mut startpos = 0;
2020
for (i, c) in line.chars().rev().enumerate() {
@@ -42,34 +42,36 @@ fn extract_words(line: &str) -> Option<(usize, Vec<String>)> {
4242
}
4343
}
4444
}
45+
if words == &[String::new()] {
46+
return None;
47+
}
4548
reverse_string(words.last_mut().unwrap());
4649
words.reverse();
50+
4751
Some((startpos, words))
4852
}
4953

50-
impl<'a> ShellHelper<'a> {
51-
pub fn new(vm: &'a VirtualMachine, scope: Scope) -> Self {
54+
impl<'vm> ShellHelper<'vm> {
55+
pub fn new(vm: &'vm VirtualMachine, scope: Scope) -> Self {
5256
ShellHelper { vm, scope }
5357
}
5458

55-
// fn get_words
56-
57-
fn complete_opt(&self, line: &str) -> Option<(usize, Vec<String>)> {
58-
let (startpos, words) = extract_words(line)?;
59-
59+
fn get_available_completions<'w>(
60+
&self,
61+
words: &'w [String],
62+
) -> Option<(
63+
&'w str,
64+
Box<dyn Iterator<Item = PyResult<PyStringRef>> + 'vm>,
65+
)> {
6066
// the very first word and then all the ones after the dot
6167
let (first, rest) = words.split_first().unwrap();
6268

63-
let str_iter = |obj| {
64-
PyIterable::<PyStringRef>::try_from_object(self.vm, obj)
65-
.ok()?
66-
.iter(self.vm)
67-
.ok()
69+
let str_iter_method = |obj, name| {
70+
let iter = self.vm.call_method(obj, name, vec![])?;
71+
PyIterable::<PyStringRef>::try_from_object(self.vm, iter)?.iter(self.vm)
6872
};
6973

70-
type StrIter<'a> = Box<dyn Iterator<Item = PyResult<PyStringRef>> + 'a>;
71-
72-
let (iter, prefix) = if let Some((last, parents)) = rest.split_last() {
74+
if let Some((last, parents)) = rest.split_last() {
7375
// we need to get an attribute based off of the dir() of an object
7476

7577
// last: the last word, could be empty if it ends with a dot
@@ -81,58 +83,55 @@ impl<'a> ShellHelper<'a> {
8183
current = self.vm.get_attribute(current.clone(), attr.as_str()).ok()?;
8284
}
8385

84-
(
85-
Box::new(str_iter(
86-
self.vm.call_method(&current, "__dir__", vec![]).ok()?,
87-
)?) as StrIter,
88-
last.as_str(),
89-
)
86+
let current_iter = str_iter_method(&current, "__dir__").ok()?;
87+
88+
Some((&last, Box::new(current_iter) as _))
9089
} else {
9190
// we need to get a variable based off of globals/builtins
9291

93-
let globals = str_iter(
94-
self.vm
95-
.call_method(self.scope.globals.as_object(), "keys", vec![])
96-
.ok()?,
97-
)?;
98-
let iter = if first.as_str().is_empty() {
99-
// only show globals that don't start with a '_'
100-
Box::new(globals.filter(|r| {
101-
r.as_ref()
102-
.ok()
103-
.map_or(true, |s| !s.as_str().starts_with('_'))
104-
})) as StrIter
105-
} else {
106-
// show globals and builtins
107-
Box::new(
108-
globals.chain(str_iter(
109-
self.vm
110-
.call_method(&self.vm.builtins, "__dir__", vec![])
111-
.ok()?,
112-
)?),
113-
) as StrIter
114-
};
115-
(iter, first.as_str())
116-
};
117-
let completions = iter
92+
let globals = str_iter_method(self.scope.globals.as_object(), "keys").ok()?;
93+
let builtins = str_iter_method(&self.vm.builtins, "__dir__").ok()?;
94+
Some((&first, Box::new(Iterator::chain(globals, builtins)) as _))
95+
}
96+
}
97+
98+
fn complete_opt(&self, line: &str) -> Option<(usize, Vec<String>)> {
99+
let (startpos, words) = split_idents_on_dot(line)?;
100+
101+
let (word_start, iter) = self.get_available_completions(&words)?;
102+
103+
let all_completions = iter
118104
.filter(|res| {
119105
res.as_ref()
120106
.ok()
121-
.map_or(true, |s| s.as_str().starts_with(prefix))
107+
.map_or(true, |s| s.as_str().starts_with(word_start))
122108
})
123109
.collect::<Result<Vec<_>, _>>()
124110
.ok()?;
125-
let no_underscore = completions
126-
.iter()
127-
.cloned()
128-
.filter(|s| !prefix.starts_with('_') && !s.as_str().starts_with('_'))
129-
.collect::<Vec<_>>();
130-
let mut completions = if no_underscore.is_empty() {
131-
completions
111+
let mut completions = if word_start.starts_with('_') {
112+
// if they're already looking for something starting with a '_', just give
113+
// them all the completions
114+
all_completions
132115
} else {
133-
no_underscore
116+
// only the completions that don't start with a '_'
117+
let no_underscore = all_completions
118+
.iter()
119+
.cloned()
120+
.filter(|s| !s.as_str().starts_with('_'))
121+
.collect::<Vec<_>>();
122+
123+
// if there are only completions that start with a '_', give them all of the
124+
// completions, otherwise only the ones that don't start with '_'
125+
if no_underscore.is_empty() {
126+
all_completions
127+
} else {
128+
no_underscore
129+
}
134130
};
131+
132+
// sort the completions alphabetically
135133
completions.sort_by(|a, b| std::cmp::Ord::cmp(a.as_str(), b.as_str()));
134+
136135
Some((
137136
startpos,
138137
completions

0 commit comments

Comments
 (0)