@@ -28,12 +28,17 @@ type State = {
2828  output : Array < string > , 
2929  commandInProgress : boolean , 
3030  input : string , 
31+   history ?: Array < string > , 
32+   historyPosition : number , 
33+   reverseSearchString ?: string , 
34+   reverseSearchPosition : number , 
3135} 
3236
3337export  default  class  ReactConsole  extends  React . Component < Props ,  State >  { 
3438
3539  inputRef : any  =  null ; 
3640  wrapperRef : any  =  null ; 
41+   reverseStringRef : any  =  null ; 
3742
3843  static  defaultProps  =  { 
3944    prompt : '$' , 
@@ -48,15 +53,29 @@ export default class ReactConsole extends React.Component<Props, State> {
4853    input : '' , 
4954    output : [ ] , 
5055    commandInProgress : false , 
56+     history : [ 
57+       "hello1" , 
58+       "hello2" , 
59+       "ahojky" , 
60+       "ahoj" , 
61+       "hello3" , 
62+       "world" , 
63+     ] , 
64+     reverseSearchString : undefined , 
65+     historyPosition : Infinity , 
66+     reverseSearchPosition : Infinity , 
5167  } ; 
5268
5369  componentDidMount ( )  { 
54-     const  { welcomeMessage}  =  this . props 
70+     const  { welcomeMessage}  =  this . props ; 
5571    if  ( welcomeMessage )  { 
5672      this . setState ( { 
5773        output : [ welcomeMessage ] , 
5874      } ) 
5975    } 
76+     this . setState ( { 
77+       historyPosition : this . state . history . length , 
78+     } ) 
6079  } 
6180
6281  clear  =  ( )  =>  { 
@@ -69,26 +88,44 @@ export default class ReactConsole extends React.Component<Props, State> {
6988    } ) 
7089  } ; 
7190
72-   onSubmit  =  async  ( e : any )  =>  { 
91+   /** 
92+    * Get filtered history entries based on reverse search string 
93+    */ 
94+   getReverseHistory  =  ( ) : Array < boolean >  =>  { 
95+     const  { reverseSearchString}  =  this . state ; 
96+     return  this . state . history . map ( entry  =>  ( reverseSearchString  ===  undefined  ||  reverseSearchString  ===  '' )  ?
97+       // @ts -ignore 
98+       false  : entry . includes ( reverseSearchString ) ) 
99+   } ; 
100+ 
101+   // TODO rename 
102+   getLog  =  ( )  =>  { 
73103    const  { prompt}  =  this . props ; 
104+     const  inputString : string  =  this . state . input ; 
105+     return  `${ prompt } ${ inputString }  ; 
106+   } ; 
107+ 
108+   onSubmit  =  async  ( e : any )  =>  { 
74109    e . preventDefault ( ) ; 
75110
76111    const  inputString : string  =  this . state . input 
77112    if  ( inputString  ===  null )  { 
78113      return 
79114    } 
80115
81-     const  log  =  ` ${ prompt } \xa0 ${ inputString } ` ; 
116+     const  log  =  this . getLog ( ) ; 
82117
83118    if  ( inputString  ===  '' )  { 
84119      this . setState ( { 
85120        output : [ ...this . state . output ,  log ] , 
86121        input : '' , 
87122      } ) ; 
88-       this . scrollToBottom ( ) 
123+       this . scrollToBottom ( ) ; 
89124      return 
90125    } 
91126
127+     this . addHistoryEntry ( inputString ) ; 
128+ 
92129    const  [ cmd ,  ...args ]  =  inputString . split ( " " ) ; 
93130
94131    if  ( cmd  ===  'clear' )  { 
@@ -162,11 +199,12 @@ export default class ReactConsole extends React.Component<Props, State> {
162199              className = { promptClass } 
163200            > { prompt }  </ span > 
164201            < input 
165-               disabled = { this . state . commandInProgress } 
202+               disabled = { this . state . commandInProgress   ||   this . isReverseSearchOn ( ) } 
166203              ref = { ref  =>  this . inputRef  =  ref } 
167204              autoFocus = { autoFocus } 
168205              value = { this . state . input } 
169206              onChange = { this . onInputChange } 
207+               onKeyDown = { this . onKeyDown } 
170208              autoComplete = { 'off' } 
171209              spellCheck = { false } 
172210              autoCapitalize = { 'false' } 
@@ -176,21 +214,134 @@ export default class ReactConsole extends React.Component<Props, State> {
176214            /> 
177215          </ div > 
178216        </ form > 
217+         { this . isReverseSearchOn ( )  &&  < form  onSubmit = { this . onReverseSearchSubmit } > bck-i-search: < input 
218+           value = { this . state . reverseSearchString } 
219+           ref = { ref  =>  this . reverseStringRef  =  ref } 
220+           onKeyDown = { this . onReverseKeyDown } 
221+           className = { classnames ( [ styles . input ,  inputClassName ] ) } 
222+           onChange = { this . onReverseStringInputChange } 
223+         /> 
224+         </ form > } 
179225      </ div > 
180226    ) 
181227  } 
182228
229+   onReverseStringInputChange  =  ( e : any )  =>  { 
230+     this . setState ( { 
231+       reverseSearchString : e . target . value , 
232+     } ,  ( )  =>  { 
233+       const  history : Array < boolean >  =  this . getReverseHistory ( ) ; 
234+       const  historyIndex : number  =  history . lastIndexOf ( true ) ; 
235+       this . executeNextReverseSearch ( historyIndex ) 
236+     } ) 
237+   } ; 
238+ 
239+   nextReverseSearch  =  ( )  =>  { 
240+     const  history : Array < boolean >  =  this . getReverseHistory ( ) ; 
241+     const  endOffset  =  Math . max ( 0 ,  this . state . reverseSearchPosition  -  1 ) ;  // so that we don't go from the end again 
242+     const  historyIndex : number  =  history . lastIndexOf ( true ,  endOffset ) ; 
243+     this . executeNextReverseSearch ( historyIndex ) 
244+   } ; 
245+ 
246+   private  executeNextReverseSearch  =  ( historyIndex : number )  =>  { 
247+     this . setState ( { 
248+       reverseSearchPosition : historyIndex , 
249+     } ) ; 
250+     if  ( historyIndex  !==  - 1 )  { 
251+       this . setPreviewPosition ( historyIndex ) 
252+     } 
253+     if  ( this . state . reverseSearchString  ===  '' )  { 
254+       this . setPreviewPosition ( Infinity ) ; 
255+     } 
256+   } ; 
257+ 
258+   onReverseSearch  =  ( )  =>  { 
259+     // we enabled reverse search 
260+     this . setState ( { 
261+       reverseSearchString : '' , 
262+     } ,  ( )  =>  { 
263+       this . reverseStringRef . focus ( ) 
264+     } ) 
265+   } ; 
266+ 
267+   onReverseSearchSubmit  =  ( e : any )  =>  { 
268+     e . preventDefault ( ) ; 
269+     this . disableReverseSearch ( ) ; 
270+   } ; 
271+ 
183272  onInputChange  =  ( e : any )  =>  { 
184273    this . setState ( { 
185274      input : e . target . value , 
186275    } ) 
187276  } ; 
188277
278+   isReverseSearchOn  =  ( ) : boolean  =>  this . state . reverseSearchString  !==  undefined ; 
279+ 
280+   disableReverseSearch  =  ( reset : boolean  =  false )  =>  { 
281+     this . setState ( { 
282+       reverseSearchString : undefined , 
283+     } ) ; 
284+     if  ( reset )  { 
285+       this . setState ( { 
286+         input : '' , 
287+       } ) 
288+     } 
289+     setTimeout ( ( )  =>  { 
290+       this . inputRef . focus ( ) ; 
291+     } ) ; 
292+   } ; 
293+ 
294+   onReverseKeyDown  =  ( e : any )  =>  { 
295+     if  ( e . which  ===  38  ||  e . which  ===  40 )  {  // up or down 
296+       this . disableReverseSearch ( ) 
297+     }  else  if  ( e . which  ===  67  &&  e . ctrlKey )  {  // ctrl + c 
298+       this . disableReverseSearch ( true ) ; 
299+     }  else  if  ( e . which  ===  82  &&  e . ctrlKey )  {  // ctrl + r 
300+       this . nextReverseSearch ( ) ; 
301+     } 
302+   } ; 
303+ 
304+   setPreviewPosition  =  ( historyPosition : number )  =>  { 
305+     this . setState ( { 
306+       historyPosition, 
307+       input : this . state . history [ historyPosition ]  ||  '' , 
308+     } ) ; 
309+   } ; 
310+ 
311+   onKeyDown  =  ( e : any )  =>  { 
312+     if  ( e . which  ===  38 )  {  // key up 
313+       const  historyPosition  =  Math . max ( 0 ,  this . state . historyPosition  -  1 ) ; 
314+       this . setPreviewPosition ( historyPosition ) ; 
315+       e . preventDefault ( ) 
316+     }  else  if  ( e . which  ===  40 )  { 
317+       const  historyPosition  =  Math . min ( this . state . history . length ,  this . state . historyPosition  +  1 ) ; 
318+       this . setPreviewPosition ( historyPosition ) ; 
319+       e . preventDefault ( ) 
320+     }  else  if  ( e . which  ===  82  &&  e . ctrlKey )  {  // ctrl + r 
321+       console . log ( 'reverse search mode' ) ; 
322+       this . onReverseSearch ( ) 
323+     }  else  if  ( e . which  ===  67  &&  e . ctrlKey )  {  // ctrl + c 
324+       this . setState ( { 
325+         output : [ ...this . state . output ,  this . getLog ( ) ] , 
326+         input : '' , 
327+       } ) ; 
328+       this . scrollToBottom ( ) ; 
329+     } 
330+   } ; 
331+ 
189332  focusConsole  =  ( )  =>  { 
190333    if  ( this . inputRef )  { 
191334      if  ( document . getSelection ( ) . isCollapsed )  { 
192335        this . inputRef . focus ( ) 
193336      } 
194337    } 
338+   } ; 
339+ 
340+   private  addHistoryEntry ( inputString : string )  { 
341+     const  history : Array < string >  =  [ ...this . state . history ,  inputString ] ; 
342+     this . setState ( { 
343+       history, 
344+       historyPosition : history . length , 
345+     } ) 
195346  } 
196347} 
0 commit comments