@@ -9,10 +9,10 @@ use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}
99use rustpython_parser:: error:: ParseErrorType ;
1010use rustpython_vm:: {
1111 import, match_class,
12- obj:: { objint:: PyInt , objtuple:: PyTuple , objtype} ,
12+ obj:: { objint:: PyInt , objstr :: PyStringRef , objtuple:: PyTuple , objtype} ,
1313 print_exception,
14- pyobject:: { ItemProtocol , PyObjectRef , PyResult } ,
15- scope:: Scope ,
14+ pyobject:: { ItemProtocol , PyIterable , PyObjectRef , PyResult , TryFromObject } ,
15+ scope:: { NameProtocol , Scope } ,
1616 util, PySettings , VirtualMachine ,
1717} ;
1818use std:: convert:: TryInto ;
@@ -487,18 +487,144 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResul
487487 }
488488}
489489
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 ( iter, prefix) = if let Some ( ( last, parents) ) = rest. split_last ( ) {
536+ // last: the last word, could be empty if it ends with a dot
537+ // parents: the words before the dot
538+
539+ let mut current = self . scope . load_global ( self . vm , first) ?;
540+
541+ for attr in parents {
542+ current = self . vm . get_attribute ( current. clone ( ) , attr. as_str ( ) ) . ok ( ) ?;
543+ }
544+
545+ (
546+ self . vm . call_method ( & current, "__dir__" , vec ! [ ] ) . ok ( ) ?,
547+ last. as_str ( ) ,
548+ )
549+ } else {
550+ (
551+ self . vm
552+ . call_method ( self . scope . globals . as_object ( ) , "keys" , vec ! [ ] )
553+ . ok ( ) ?,
554+ first. as_str ( ) ,
555+ )
556+ } ;
557+ let iter = PyIterable :: < PyStringRef > :: try_from_object ( self . vm , iter) . ok ( ) ?;
558+ let completions = iter
559+ . iter ( self . vm )
560+ . ok ( ) ?
561+ . filter ( |res| {
562+ res. as_ref ( )
563+ . ok ( )
564+ . map_or ( true , |s| s. as_str ( ) . starts_with ( prefix) )
565+ } )
566+ . collect :: < Result < Vec < _ > , _ > > ( )
567+ . ok ( ) ?;
568+ let no_underscore = completions
569+ . iter ( )
570+ . cloned ( )
571+ . filter ( |s| !prefix. starts_with ( "_" ) && !s. as_str ( ) . starts_with ( "_" ) )
572+ . collect :: < Vec < _ > > ( ) ;
573+ let completions = if no_underscore. is_empty ( ) {
574+ completions
575+ } else {
576+ no_underscore
577+ } ;
578+ Some ( (
579+ startpos,
580+ completions
581+ . into_iter ( )
582+ . map ( |s| s. as_str ( ) . to_owned ( ) )
583+ . collect ( ) ,
584+ ) )
585+ }
586+ }
587+
588+ impl rustyline:: completion:: Completer for ShellHelper < ' _ > {
589+ type Candidate = String ;
590+
591+ fn complete (
592+ & self ,
593+ line : & str ,
594+ pos : usize ,
595+ _ctx : & rustyline:: Context ,
596+ ) -> rustyline:: Result < ( usize , Vec < String > ) > {
597+ if pos != line. len ( ) {
598+ return Ok ( ( 0 , vec ! [ ] ) ) ;
599+ }
600+ Ok ( self . complete_opt ( line) . unwrap_or ( ( 0 , vec ! [ ] ) ) )
601+ }
602+ }
603+
604+ impl rustyline:: hint:: Hinter for ShellHelper < ' _ > { }
605+ impl rustyline:: highlight:: Highlighter for ShellHelper < ' _ > { }
606+ impl rustyline:: Helper for ShellHelper < ' _ > { }
607+
490608#[ cfg( not( target_os = "wasi" ) ) ]
491609fn run_shell ( vm : & VirtualMachine , scope : Scope ) -> PyResult < ( ) > {
492- use rustyline:: { error:: ReadlineError , Editor } ;
610+ use rustyline:: { error:: ReadlineError , CompletionType , Config , Editor } ;
493611
494612 println ! (
495613 "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596} " ,
496614 crate_version!( )
497615 ) ;
498616
499617 // Read a single line:
500- let mut input = String :: new ( ) ;
501- let mut repl = Editor :: < ( ) > :: new ( ) ;
618+ let mut repl = Editor :: with_config (
619+ Config :: builder ( )
620+ . completion_type ( CompletionType :: List )
621+ . build ( ) ,
622+ ) ;
623+ repl. set_helper ( Some ( ShellHelper {
624+ vm,
625+ scope : scope. clone ( ) ,
626+ } ) ) ;
627+ let mut full_input = String :: new ( ) ;
502628
503629 // Retrieve a `history_path_str` dependent on the OS
504630 let repl_history_path = match dirs:: config_dir ( ) {
@@ -539,12 +665,12 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
539665
540666 let stop_continuing = line. is_empty ( ) ;
541667
542- if input . is_empty ( ) {
543- input = line;
668+ if full_input . is_empty ( ) {
669+ full_input = line;
544670 } else {
545- input . push_str ( & line) ;
671+ full_input . push_str ( & line) ;
546672 }
547- input . push_str ( "\n " ) ;
673+ full_input . push_str ( "\n " ) ;
548674
549675 if continuing {
550676 if stop_continuing {
@@ -554,24 +680,24 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
554680 }
555681 }
556682
557- match shell_exec ( vm, & input , scope. clone ( ) ) {
683+ match shell_exec ( vm, & full_input , scope. clone ( ) ) {
558684 ShellExecResult :: Ok => {
559- input . clear ( ) ;
685+ full_input . clear ( ) ;
560686 Ok ( ( ) )
561687 }
562688 ShellExecResult :: Continue => {
563689 continuing = true ;
564690 Ok ( ( ) )
565691 }
566692 ShellExecResult :: PyErr ( err) => {
567- input . clear ( ) ;
693+ full_input . clear ( ) ;
568694 Err ( err)
569695 }
570696 }
571697 }
572698 Err ( ReadlineError :: Interrupted ) => {
573699 continuing = false ;
574- input . clear ( ) ;
700+ full_input . clear ( ) ;
575701 let keyboard_interrupt = vm
576702 . new_empty_exception ( vm. ctx . exceptions . keyboard_interrupt . clone ( ) )
577703 . unwrap ( ) ;
0 commit comments