88use React \Stream \Util ;
99use Clue \React \Utf8 \Sequencer as Utf8Sequencer ;
1010use Clue \React \Term \ControlCodeParser ;
11- use Clue \React \Stdio \Readline \Autocomplete ;
12- use Clue \React \Stdio \Readline \NullAutocomplete ;
1311
1412class Readline extends EventEmitter implements ReadableStreamInterface
1513{
@@ -31,15 +29,10 @@ class Readline extends EventEmitter implements ReadableStreamInterface
3129 private $ historyUnsaved = null ;
3230 private $ historyLimit = 500 ;
3331
34- public function __construct (ReadableStreamInterface $ input , WritableStreamInterface $ output, Autocomplete $ autocomplete = null )
32+ public function __construct (ReadableStreamInterface $ input , WritableStreamInterface $ output )
3533 {
36- if ($ autocomplete === null ) {
37- $ autocomplete = new NullAutocomplete ();
38- }
39-
4034 $ this ->input = $ input ;
4135 $ this ->output = $ output ;
42- $ this ->autocomplete = $ autocomplete ;
4336
4437 if (!$ this ->input ->isReadable ()) {
4538 return $ this ->close ();
@@ -398,30 +391,22 @@ public function limitHistory($limit)
398391 * The autocomplete handler will be called whenever the user hits the TAB
399392 * key.
400393 *
401- * If you do not want to use autocomplet support, simply pass a `NullAutocomplete` object.
402- *
403- * @param Autocomplete $autocomplete
394+ * @param callable|null $autocomplete
404395 * @return self
396+ * @throws InvalidArgumentException if the given callable is invalid
405397 */
406398
407- public function setAutocomplete (Autocomplete $ autocomplete )
399+ public function setAutocomplete ($ autocomplete )
408400 {
401+ if ($ autocomplete !== null && !is_callable ($ autocomplete )) {
402+ throw new \InvalidArgumentException ('Invalid autocomplete function given ' );
403+ }
404+
409405 $ this ->autocomplete = $ autocomplete ;
410406
411407 return $ this ;
412408 }
413409
414- /**
415- * Gets the current autocomplete handler in use
416- *
417- * @return Autocomplete
418- * @see self::setAutocomplete()
419- */
420- public function getAutocomplete ()
421- {
422- return $ this ->autocomplete ;
423- }
424-
425410 /**
426411 * redraw the current input prompt
427412 *
@@ -515,7 +500,61 @@ public function onKeyEnd()
515500 /** @internal */
516501 public function onKeyTab ()
517502 {
518- $ this ->autocomplete ->go ($ this );
503+ if ($ this ->autocomplete === null ) {
504+ return ;
505+ }
506+
507+ // current word prefix and offset for start of word in input buffer
508+ // "echo foo|bar world" will return just "foo" with word offset 5
509+ $ word = $ this ->substr ($ this ->linebuffer , 0 , $ this ->linepos );
510+ $ offset = 0 ;
511+
512+ // buffer prefix and postfix for everything that will *not* be matched
513+ // above example will return "echo " and "bar world"
514+ $ prefix = '' ;
515+ $ postfix = (string )$ this ->substr ($ this ->linebuffer , $ this ->linepos );
516+
517+ // skip everything before last space
518+ $ pos = strrpos ($ word , ' ' );
519+ if ($ pos !== false ) {
520+ $ offset = $ pos + 1 ;
521+ $ prefix = (string )substr ($ word , 0 , $ pos + 1 );
522+ $ word = (string )substr ($ word , $ pos + 1 );
523+ }
524+
525+ // invoke automcomplete callback
526+ $ words = call_user_func ($ this ->autocomplete , $ word , $ offset );
527+
528+ // return early if autocomplete does not return anything
529+ if ($ words === null ) {
530+ return ;
531+ }
532+
533+ // remove all from list of possible words that do not start with $word
534+ $ len = strlen ($ word );
535+ foreach ($ words as $ i => $ w ) {
536+ if ($ word !== substr ($ w , 0 , $ len )) {
537+ unset($ words [$ i ]);
538+ }
539+ }
540+
541+ // return if neither of the possible words match
542+ if (!$ words ) {
543+ return ;
544+ }
545+
546+ // TODO: find the BEST match
547+ // TODO: always picks first match for now
548+ $ found = reset ($ words );
549+
550+ // append single space after this argument unless there's a postfix
551+ if ($ postfix === '' ) {
552+ $ found .= ' ' ;
553+ }
554+
555+ // replace word in input with best match and adjust cursor
556+ $ this ->linebuffer = $ prefix . $ found . $ postfix ;
557+ $ this ->moveCursorBy ($ this ->strlen ($ found ) - $ this ->strlen ($ word ));
519558 }
520559
521560 /** @internal */
0 commit comments