@@ -6,12 +6,13 @@ extern crate alloc;
66#[ macro_use]
77extern crate user_lib;
88
9- use alloc:: { string:: String , vec:: Vec } ;
9+ use alloc:: { collections :: btree_set :: BTreeSet , string:: String , vec:: Vec } ;
1010use user_lib:: {
1111 chdir, close, console:: getchar, dup, exec, fork, getcwd, open, pipe, waitpid, OpenFlags ,
1212} ;
1313
1414const BS : u8 = 0x08 ;
15+ const HT : u8 = 0x09 ;
1516const LF : u8 = 0x0a ;
1617const CR : u8 = 0x0d ;
1718const DL : u8 = 0x7f ;
@@ -72,16 +73,107 @@ impl ProcessArguments {
7273 }
7374}
7475
76+ struct Completer {
77+ // candidates
78+ can : BTreeSet < String > ,
79+ }
80+
81+ impl Completer {
82+ pub fn new ( ) -> Self {
83+ Self {
84+ can : BTreeSet :: new ( ) ,
85+ }
86+ }
87+
88+ pub fn load_root ( & mut self ) {
89+ self . can . extend ( root_bin ( ) ) ;
90+ }
91+
92+ pub fn load_ad_hoc ( & mut self , iter : impl IntoIterator < Item = String > ) {
93+ self . can . extend ( iter) ;
94+ }
95+
96+ pub fn hint_for ( & self , s : & str ) -> Vec < & String > {
97+ self . can . iter ( ) . filter ( |v| v. starts_with ( s) ) . collect ( )
98+ }
99+ }
100+
101+ fn root_bin ( ) -> Vec < String > {
102+ use user_lib:: { getdents, Dirent , FileType } ;
103+ let mut v = Vec :: new ( ) ;
104+
105+ let fd = open ( "/\0 " , OpenFlags :: RDONLY ) ;
106+ if fd == -1 {
107+ return v;
108+ }
109+
110+ const BUF_SIZE : usize = 16 ;
111+ let mut entries = alloc:: vec![ Dirent :: default ( ) ; BUF_SIZE ] ;
112+ let mut n = BUF_SIZE ;
113+ loop {
114+ n = match getdents ( fd as usize , & mut entries. as_mut_slice ( ) [ ..n] ) {
115+ -1 | 0 => break ,
116+ v => v as usize ,
117+ } ;
118+ for i in 0 ..n {
119+ let entry = & entries[ i] ;
120+ if entry. ftype == FileType :: REG {
121+ v. push ( String :: from ( entry. name ( ) ) ) ;
122+ }
123+ }
124+ }
125+ v
126+ }
127+
75128#[ no_mangle]
76129fn main ( ) -> i32 {
77130 println ! ( "Rust user shell" ) ;
131+ // completer
132+ let mut comp = Completer :: new ( ) ;
133+ comp. load_root ( ) ;
134+ comp. load_ad_hoc ( [ String :: from ( "cd" ) , String :: from ( "pwd" ) ] ) ;
78135 let mut line: String = String :: new ( ) ;
136+ let mut comp_leftover: Option < u8 > = None ;
79137 loop {
80138 line. clear ( ) ;
81139 print ! ( "{}" , PROMPT ) ;
82140 ' repl: loop {
83- let c = getchar ( ) ;
141+ let c = match comp_leftover. take ( ) {
142+ Some ( v) => v,
143+ _ => getchar ( ) ,
144+ } ;
84145 match c {
146+ HT => {
147+ let par_input = match line. split_ascii_whitespace ( ) . last ( ) {
148+ Some ( v) if !v. is_empty ( ) => String :: from ( v) ,
149+ _ => continue ' repl,
150+ } ;
151+ let hints = comp. hint_for ( & par_input) ;
152+ if hints. is_empty ( ) {
153+ continue ' repl;
154+ }
155+
156+ let line_end = line. len ( ) ;
157+ for hint in hints. iter ( ) . cycle ( ) {
158+ let rest = & hint[ par_input. len ( ) ..] ;
159+ print ! ( "{}" , rest) ;
160+ line. push_str ( rest) ;
161+
162+ let c = getchar ( ) ;
163+ match c {
164+ HT => {
165+ // tab: clear & try next option
166+ clear_console_ch ( rest. len ( ) ) ;
167+ line. drain ( line_end..) ;
168+ }
169+ _ => {
170+ // else: exit complete mode
171+ comp_leftover = Some ( c) ;
172+ continue ' repl;
173+ }
174+ }
175+ }
176+ }
85177 LF | CR => {
86178 println ! ( "" ) ;
87179 let input = line. trim ( ) ;
@@ -215,7 +307,7 @@ fn main() -> i32 {
215307 }
216308 // exec
217309 if exec ( & args[ 0 ] , args_addr. as_slice ( ) ) == -1 {
218- println ! ( "[shell] cannot exec: {} " , args[ 0 ] ) ;
310+ println ! ( "[shell] cannot exec: `{}' " , args[ 0 ] ) ;
219311 return -4 ;
220312 }
221313 unreachable ! ( )
@@ -241,9 +333,7 @@ fn main() -> i32 {
241333 }
242334 BS | DL => {
243335 if !line. is_empty ( ) {
244- print ! ( "{}" , BS as char ) ;
245- print ! ( " " ) ;
246- print ! ( "{}" , BS as char ) ;
336+ clear_console_ch ( 1 ) ;
247337 line. pop ( ) ;
248338 }
249339 }
@@ -256,6 +346,14 @@ fn main() -> i32 {
256346 }
257347}
258348
349+ fn clear_console_ch ( n : usize ) {
350+ for _ in 0 ..n {
351+ print ! ( "{}" , BS as char ) ; // move cursor back
352+ print ! ( " " ) ; // print SP to overwrite
353+ print ! ( "{}" , BS as char ) ; // then move cursor back again
354+ }
355+ }
356+
259357const UNKNOWN_BUILTIN : & ' static str = "unknown builtin command!" ;
260358const WRONG_NUM_ARGS : & ' static str = "wrong number of args!" ;
261359fn exec_builtin ( cmd : & str , args : & [ String ] ) -> Result < ( ) , & ' static str > {
0 commit comments