@@ -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 } \xa0${ 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