Skip to content

Commit 89da20c

Browse files
committed
Find longest common prefix when multiple words match
1 parent f767d71 commit 89da20c

File tree

2 files changed

+75
-9
lines changed

2 files changed

+75
-9
lines changed

src/Readline.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,9 @@ public function onKeyTab()
530530
return;
531531
}
532532

533-
// remove all from list of possible words that do not start with $word
533+
// remove all from list of possible words that do not start with $word or are duplicates
534534
$len = strlen($word);
535+
$words = array_unique($words);
535536
foreach ($words as $i => $w) {
536537
if ($word !== substr($w, 0, $len)) {
537538
unset($words[$i]);
@@ -543,12 +544,32 @@ public function onKeyTab()
543544
return;
544545
}
545546

546-
// TODO: find the BEST match
547-
// TODO: always picks first match for now
547+
// search longest common prefix among all possible matches
548548
$found = reset($words);
549+
array_shift($words);
550+
$others = count($words);
551+
while ($found !== $word) {
552+
// count all words that start with $found
553+
$matches = count(array_filter($words, function ($word) use ($found) {
554+
return strpos($word, $found) === 0;
555+
}));
556+
557+
// ALL words match $found => common substring found
558+
if ($others === $matches) {
559+
break;
560+
}
561+
562+
// remove last letter from $found and try again
563+
$found = (string)substr($found, 0, -1);
564+
}
565+
566+
// current prefix has multiple possible completions => abort
567+
if ($found === $word && $others) {
568+
return;
569+
}
549570

550-
// append single space after this argument unless there's a postfix
551-
if ($postfix === '') {
571+
// append single space after match unless there's a postfix or there are multiple completions
572+
if ($postfix === '' && $others === 0) {
552573
$found .= ' ';
553574
}
554575

tests/ReadlineTest.php

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -573,15 +573,15 @@ public function testAutocompleteWillBeCalledWithLastWordPrefix()
573573
$this->readline->onKeyTab();
574574
}
575575

576-
public function testAutocompletePicksFirstWhenEmpty()
576+
public function testAutocompleteAddsSpaceAfterComplete()
577577
{
578-
$this->markTestIncomplete();
578+
$this->readline->setAutocomplete(function () { return array('exit'); });
579579

580-
$this->readline->setAutocomplete(function () { return array('first', 'second'); });
580+
$this->readline->setInput('exit');
581581

582582
$this->readline->onKeyTab();
583583

584-
$this->assertEquals('first ', $this->readline->getInput());
584+
$this->assertEquals('exit ', $this->readline->getInput());
585585
}
586586

587587
public function testAutocompletePicksFirstComplete()
@@ -606,6 +606,51 @@ public function testAutocompleteIgnoresNonMatching()
606606
$this->assertEquals('e', $this->readline->getInput());
607607
}
608608

609+
public function testAutocompletePicksNoneWhenEmptyAndMultipleMatch()
610+
{
611+
$this->readline->setAutocomplete(function () { return array('first', 'second'); });
612+
613+
$this->readline->onKeyTab();
614+
615+
$this->assertEquals('', $this->readline->getInput());
616+
}
617+
618+
public function testAutocompletePicksOnlyEntryWhenEmpty()
619+
{
620+
$this->readline->setAutocomplete(function () { return array('first'); });
621+
622+
$this->readline->onKeyTab();
623+
624+
$this->assertEquals('first ', $this->readline->getInput());
625+
}
626+
627+
public function testAutocompleteUsesCommonPrefixWhenMultipleMatch()
628+
{
629+
$this->readline->setAutocomplete(function () { return array('first', 'firm'); });
630+
631+
$this->readline->onKeyTab();
632+
633+
$this->assertEquals('fir', $this->readline->getInput());
634+
}
635+
636+
public function testAutocompleteUsesExactMatchWhenDuplicateMatch()
637+
{
638+
$this->readline->setAutocomplete(function () { return array('first', 'first'); });
639+
640+
$this->readline->onKeyTab();
641+
642+
$this->assertEquals('first ', $this->readline->getInput());
643+
}
644+
645+
public function testAutocompleteUsesCommonPrefixWhenMultipleMatchAndEnd()
646+
{
647+
$this->readline->setAutocomplete(function () { return array('counter', 'count'); });
648+
649+
$this->readline->onKeyTab();
650+
651+
$this->assertEquals('count', $this->readline->getInput());
652+
}
653+
609654
public function testEmitEmptyInputOnEnter()
610655
{
611656
$this->readline->on('data', $this->expectCallableOnceWith(''));

0 commit comments

Comments
 (0)