1- use std:: { collections:: HashMap , rc:: Rc } ;
1+ use std:: { collections:: HashMap , rc:: Rc , vec } ;
22
33use clap:: Clap ;
44use prettytable:: { cell, row, Cell , Row , Table } ;
5- use rustyline:: { error:: ReadlineError , Editor } ;
5+ use rustyline:: {
6+ completion:: Completer ,
7+ error:: ReadlineError ,
8+ hint:: { Hinter , HistoryHinter } ,
9+ Config , Editor ,
10+ } ;
11+ use rustyline_derive:: { Helper , Highlighter , Validator } ;
612
713use crate :: { command:: * , storage, Result } ;
814
@@ -32,9 +38,77 @@ pub struct Opt {
3238 music_path : String ,
3339}
3440
41+ struct CmdCompleter {
42+ commands : Vec < String > ,
43+ }
44+
45+ impl CmdCompleter {
46+ fn new ( ) -> Self {
47+ CmdCompleter {
48+ commands : Vec :: new ( ) ,
49+ }
50+ }
51+ fn add_command ( & mut self , cmd : String ) {
52+ self . commands . push ( cmd)
53+ }
54+ }
55+
56+ impl Completer for CmdCompleter {
57+ type Candidate = String ;
58+
59+ fn complete (
60+ & self ,
61+ line : & str ,
62+ _pos : usize ,
63+ _ctx : & rustyline:: Context < ' _ > ,
64+ ) -> rustyline:: Result < ( usize , Vec < Self :: Candidate > ) > {
65+ let res = self
66+ . commands
67+ . iter ( )
68+ . filter ( |s| s. starts_with ( line) )
69+ . map ( |s| s. clone ( ) + " " )
70+ . collect :: < Vec < String > > ( ) ;
71+ Ok ( ( 0 , res) )
72+ }
73+ }
74+
75+ #[ derive( Helper , Highlighter , Validator ) ]
76+ struct CmdlineHelper {
77+ cmd_completer : CmdCompleter ,
78+ cmd_hinter : HistoryHinter ,
79+ }
80+
81+ impl CmdlineHelper {
82+ fn add_command ( & mut self , cmd : String ) {
83+ self . cmd_completer . add_command ( cmd) ;
84+ }
85+ }
86+
87+ impl Completer for CmdlineHelper {
88+ type Candidate = String ;
89+
90+ fn complete (
91+ & self ,
92+ line : & str ,
93+ pos : usize ,
94+ ctx : & rustyline:: Context < ' _ > ,
95+ ) -> rustyline:: Result < ( usize , Vec < Self :: Candidate > ) > {
96+ self . cmd_completer . complete ( line, pos, ctx)
97+ }
98+ }
99+
100+ impl Hinter for CmdlineHelper {
101+ type Hint = String ;
102+
103+ fn hint ( & self , line : & str , pos : usize , ctx : & rustyline:: Context < ' _ > ) -> Option < Self :: Hint > {
104+ self . cmd_hinter . hint ( line, pos, ctx)
105+ }
106+ }
107+
35108pub struct Cmdline {
36109 help_table : Table ,
37110 cmds : HashMap < String , Box < dyn Cmd > > ,
111+ rl : Editor < CmdlineHelper > ,
38112}
39113
40114impl Cmdline {
@@ -44,11 +118,30 @@ impl Cmdline {
44118 opt. record_path ,
45119 opt. music_path ,
46120 ) ?) ;
121+
122+ let config = Config :: builder ( )
123+ . history_ignore_dups ( true )
124+ . history_ignore_space ( true )
125+ . completion_type ( rustyline:: CompletionType :: List )
126+ . output_stream ( rustyline:: OutputStreamType :: Stdout )
127+ . build ( ) ;
128+
129+ let mut helper = CmdlineHelper {
130+ cmd_completer : CmdCompleter :: new ( ) ,
131+ cmd_hinter : HistoryHinter { } ,
132+ } ;
47133 let cmds: HashMap < String , Box < dyn Cmd > > = HashMap :: new ( ) ;
48134 let mut help_table = Table :: new ( ) ;
49135 help_table. add_row ( row ! [ "name" , "usage" , "description" ] ) ;
50136 help_table. add_row ( row ! [ "help" , "help" , "show the help information." ] ) ;
51- let mut cmdline = Cmdline { cmds, help_table } ;
137+ helper. add_command ( String :: from ( "help" ) ) ;
138+ let mut rl = Editor :: with_config ( config) ;
139+ rl. set_helper ( Some ( helper) ) ;
140+ let mut cmdline = Cmdline {
141+ cmds,
142+ help_table,
143+ rl,
144+ } ;
52145
53146 // add commands
54147 cmdline. add_command ( Box :: new ( CmdRecord :: new ( Rc :: clone ( & store) ) ) ) ;
@@ -65,20 +158,23 @@ impl Cmdline {
65158 Cell :: new( cmd. usage( ) ) ,
66159 Cell :: new( cmd. description( ) ) ,
67160 ] ) ) ;
161+ if let Some ( helper) = self . rl . helper_mut ( ) {
162+ helper. add_command ( cmd. name ( ) . to_string ( ) ) ;
163+ }
68164 self . cmds . insert ( cmd. name ( ) . to_string ( ) , cmd) ;
69165 }
70166
71167 fn help ( & self ) {
72168 self . help_table . printstd ( ) ;
73169 }
74170
75- pub fn run ( & self ) -> Result < ( ) > {
171+ pub fn run ( & mut self ) -> Result < ( ) > {
76172 // run interactive cmdline
77- let mut rl = Editor :: < ( ) > :: new ( ) ;
78173 loop {
79- let readline = rl. readline ( ">> " ) ;
174+ let readline = self . rl . readline ( ">> " ) ;
80175 match readline {
81176 Ok ( line) => {
177+ self . rl . add_history_entry ( line. as_str ( ) ) ;
82178 let cmds: Vec < String > = line
83179 . trim ( )
84180 . split ( " " )
@@ -87,7 +183,11 @@ impl Cmdline {
87183 . collect ( ) ;
88184 self . interact ( cmds) ;
89185 }
90- Err ( ReadlineError :: Eof ) | Err ( ReadlineError :: Interrupted ) => {
186+ Err ( ReadlineError :: Interrupted ) => {
187+ println ! ( "<Keyboard Interrupted>" ) ;
188+ continue ;
189+ }
190+ Err ( ReadlineError :: Eof ) => {
91191 println ! ( "Bye" ) ;
92192 break ;
93193 }
0 commit comments