@@ -17,14 +17,18 @@ class Readline extends EventEmitter implements ReadableStreamInterface
1717 private $ echo = true ;
1818 private $ autocomplete = null ;
1919 private $ move = true ;
20- private $ history = null ;
2120 private $ encoding = 'utf-8 ' ;
2221
2322 private $ input ;
2423 private $ output ;
2524 private $ sequencer ;
2625 private $ closed = false ;
2726
27+ private $ historyLines = array ();
28+ private $ historyPosition = null ;
29+ private $ historyUnsaved = null ;
30+ private $ historyLimit = 500 ;
31+
2832 public function __construct (ReadableStreamInterface $ input , WritableStreamInterface $ output )
2933 {
3034 $ this ->input = $ input ;
@@ -311,17 +315,73 @@ public function getInput()
311315 }
312316
313317 /**
314- * set history handler to use (or none)
318+ * Adds a new line to the (bottom position of the) history list
315319 *
316- * The history handler will be called whenever the user hits the UP or DOWN
317- * arrow keys.
320+ * @param string $line
321+ * @return self
322+ * @uses self::limitHistory() to make sure list does not exceed limits
323+ */
324+ public function addHistory ($ line )
325+ {
326+ $ this ->historyLines []= $ line ;
327+
328+ return $ this ->limitHistory ($ this ->historyLimit );
329+ }
330+
331+ /**
332+ * Clears the complete history list
318333 *
319- * @param HistoryInterface|null $history
320334 * @return self
321335 */
322- public function setHistory (HistoryInterface $ history = null )
336+ public function clearHistory ()
337+ {
338+ $ this ->historyLines = array ();
339+ $ this ->historyPosition = null ;
340+
341+ if ($ this ->historyUnsaved !== null ) {
342+ $ this ->setInput ($ this ->historyUnsaved );
343+ $ this ->historyUnsaved = null ;
344+ }
345+
346+ return $ this ;
347+ }
348+
349+ /**
350+ * Returns an array with all lines in the history
351+ *
352+ * @return string[]
353+ */
354+ public function listHistory ()
323355 {
324- $ this ->history = $ history ;
356+ return $ this ->historyLines ;
357+ }
358+
359+ /**
360+ * Limits the history to a maximum of N entries and truncates the current history list accordingly
361+ *
362+ * @param int|null $limit
363+ * @return self
364+ */
365+ public function limitHistory ($ limit )
366+ {
367+ $ this ->historyLimit = $ limit === null ? null : (int )$ limit ;
368+
369+ // limit send and currently exceeded
370+ if ($ this ->historyLimit !== null && isset ($ this ->historyLines [$ this ->historyLimit ])) {
371+ // adjust position in history according to new position after applying limit
372+ if ($ this ->historyPosition !== null ) {
373+ $ this ->historyPosition -= count ($ this ->historyLines ) - $ this ->historyLimit ;
374+
375+ // current position will drop off from list => restore original
376+ if ($ this ->historyPosition < 0 ) {
377+ $ this ->setInput ($ this ->historyUnsaved );
378+ $ this ->historyPosition = null ;
379+ $ this ->historyUnsaved = null ;
380+ }
381+ }
382+
383+ $ this ->historyLines = array_slice ($ this ->historyLines , -$ this ->historyLimit , $ this ->historyLimit );
384+ }
325385
326386 return $ this ;
327387 }
@@ -468,16 +528,40 @@ public function onKeyRight()
468528 /** @internal */
469529 public function onKeyUp ()
470530 {
471- if ($ this ->history !== null ) {
472- $ this ->history ->up ();
531+ // ignore if already at top or history is empty
532+ if ($ this ->historyPosition === 0 || !$ this ->historyLines ) {
533+ return ;
534+ }
535+
536+ if ($ this ->historyPosition === null ) {
537+ // first time up => move to last entry
538+ $ this ->historyPosition = count ($ this ->historyLines ) - 1 ;
539+ $ this ->historyUnsaved = $ this ->getInput ();
540+ } else {
541+ // somewhere in the list => move by one
542+ $ this ->historyPosition --;
473543 }
544+
545+ $ this ->setInput ($ this ->historyLines [$ this ->historyPosition ]);
474546 }
475547
476548 /** @internal */
477549 public function onKeyDown ()
478550 {
479- if ($ this ->history !== null ) {
480- $ this ->history ->down ();
551+ // ignore if not currently cycling through history
552+ if ($ this ->historyPosition === null ) {
553+ return ;
554+ }
555+
556+ if (isset ($ this ->historyLines [$ this ->historyPosition + 1 ])) {
557+ // this is still a valid position => advance by one and apply
558+ $ this ->historyPosition ++;
559+ $ this ->setInput ($ this ->historyLines [$ this ->historyPosition ]);
560+ } else {
561+ // moved beyond bottom => restore original unsaved input
562+ $ this ->setInput ($ this ->historyUnsaved );
563+ $ this ->historyPosition = null ;
564+ $ this ->historyUnsaved = null ;
481565 }
482566 }
483567
@@ -537,6 +621,10 @@ public function deleteChar($n)
537621 */
538622 protected function processLine ()
539623 {
624+ // reset history cycle position
625+ $ this ->historyPosition = null ;
626+ $ this ->historyUnsaved = null ;
627+
540628 // store and reset/clear/redraw current input
541629 $ line = $ this ->linebuffer ;
542630 if ($ line !== '' ) {
@@ -548,9 +636,6 @@ protected function processLine()
548636 }
549637
550638 // process stored input buffer
551- if ($ this ->history !== null ) {
552- $ this ->history ->addLine ($ line );
553- }
554639 $ this ->emit ('data ' , array ($ line ));
555640 }
556641
0 commit comments