Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 66 additions & 18 deletions src/Readline.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Readline extends EventEmitter implements ReadableStreamInterface
private $linepos = 0;
private $echo = true;
private $move = true;
private $bell = true;
private $encoding = 'utf-8';

private $input;
Expand Down Expand Up @@ -472,10 +473,7 @@ public function limitHistory($limit)
* @param callable|null $autocomplete
* @return self
* @throws \InvalidArgumentException if the given callable is invalid
<<<<<<< HEAD
=======
* @deprecated use Stdio::setAutocomplete() instead
>>>>>>> Deprecate Readline and move all methods to Stdio
*/
public function setAutocomplete($autocomplete)
{
Expand All @@ -488,6 +486,26 @@ public function setAutocomplete($autocomplete)
return $this;
}

/**
* Whether or not to emit a audible/visible BELL signal when using a disabled function
*
* By default, this class will emit a BELL signal when using a disable function,
* such as using the <kbd>left</kbd> or <kbd>backspace</kbd> keys when
* already at the beginning of the line.
*
* Whether or not the BELL is audible/visible depends on the termin and its
* settings, i.e. some terminals may "beep" or flash the screen or emit a
* short vibration.
*
* @param bool $bell
* @return void
* @internal use Stdio::setBell() instead
*/
public function setBell($bell)
{
$this->bell = (bool)$bell;
}

/**
* redraw the current input prompt
*
Expand Down Expand Up @@ -537,36 +555,49 @@ public function getDrawString()
public function onKeyBackspace()
{
// left delete only if not at the beginning
$this->deleteChar($this->linepos - 1);
if ($this->linepos === 0) {
$this->bell();
} else {
$this->deleteChar($this->linepos - 1);
}
}

/** @internal */
public function onKeyDelete()
{
// right delete only if not at the end
$this->deleteChar($this->linepos);
if ($this->isEol()) {
$this->bell();
} else {
$this->deleteChar($this->linepos);
}
}

/** @internal */
public function onKeyHome()
{
if ($this->move) {
if ($this->move && $this->linepos !== 0) {
$this->moveCursorTo(0);
} else {
$this->bell();
}
}

/** @internal */
public function onKeyEnd()
{
if ($this->move) {
if ($this->move && !$this->isEol()) {
$this->moveCursorTo($this->strlen($this->linebuffer));
} else {
$this->bell();
}
}

/** @internal */
public function onKeyTab()
{
if ($this->autocomplete === null) {
$this->bell();
return;
}

Expand Down Expand Up @@ -616,6 +647,7 @@ public function onKeyTab()

// return if neither of the possible words match
if (!$words) {
$this->bell();
return;
}

Expand Down Expand Up @@ -684,16 +716,20 @@ public function onKeyEnter()
/** @internal */
public function onKeyLeft()
{
if ($this->move) {
if ($this->move && $this->linepos !== 0) {
$this->moveCursorBy(-1);
} else {
$this->bell();
}
}

/** @internal */
public function onKeyRight()
{
if ($this->move) {
if ($this->move && !$this->isEol()) {
$this->moveCursorBy(1);
} else {
$this->bell();
}
}

Expand All @@ -702,6 +738,7 @@ public function onKeyUp()
{
// ignore if already at top or history is empty
if ($this->historyPosition === 0 || !$this->historyLines) {
$this->bell();
return;
}

Expand All @@ -722,6 +759,7 @@ public function onKeyDown()
{
// ignore if not currently cycling through history
if ($this->historyPosition === null) {
$this->bell();
return;
}

Expand Down Expand Up @@ -781,18 +819,10 @@ public function onFallback($chars, EventEmitterInterface $base = null)
* Removing a character left to the current cursor will also move the cursor
* to the left.
*
* indices out of range (exceeding current input buffer) are simply ignored
*
* @param int $n
* @internal
*/
public function deleteChar($n)
private function deleteChar($n)
{
$len = $this->strlen($this->linebuffer);
if ($n < 0 || $n >= $len) {
return;
}

// read everything up until before current position
$pre = $this->substr($this->linebuffer, 0, $n);
$post = $this->substr($this->linebuffer, $n + 1);
Expand Down Expand Up @@ -911,6 +941,24 @@ private function strsplit($str)
return preg_split('//u', $str, null, PREG_SPLIT_NO_EMPTY);
}

/**
* @return bool
*/
private function isEol()
{
return $this->linepos === $this->strlen($this->linebuffer);
}

/**
* @return void
*/
private function bell()
{
if ($this->bell) {
$this->output->write("\x07"); // BEL a.k.a. \a
}
}

/** @internal */
public function handleEnd()
{
Expand Down
19 changes: 19 additions & 0 deletions src/Stdio.php
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,25 @@ public function setAutocomplete($autocomplete)
$this->readline->setAutocomplete($autocomplete);
}

/**
* whether or not to emit a audible/visible BELL signal when using a disabled function
*
* By default, this class will emit a BELL signal when using a disable function,
* such as using the <kbd>left</kbd> or <kbd>backspace</kbd> keys when
* already at the beginning of the line.
*
* Whether or not the BELL is audible/visible depends on the termin and its
* settings, i.e. some terminals may "beep" or flash the screen or emit a
* short vibration.
*
* @param bool $bell
* @return void
*/
public function setBell($bell)
{
$this->readline->setBell($bell);
}

private function width($str)
{
return $this->readline->strwidth($str) - 2 * substr_count($str, "\x08");
Expand Down
63 changes: 55 additions & 8 deletions tests/ReadlineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,27 @@ public function testKeysHomeMovesToFront()
return $this->readline;
}

public function testKeysHomeEmitsBellWhenAlreadyAtBeginningOfLine()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyHome();
}

public function testKeysHomeDoesNotEmitBellWhenAlreadyAtBeginningOfLineButBellIsDisabled()
{
$this->output->expects($this->never())->method('write');
$this->readline->setBell(false);
$this->readline->onKeyHome();
}

public function testKeysHomeEmitsBellWhenAlreadyAtBeginningOfLineAndBellIsEnabledAgain()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->setBell(false);
$this->readline->setBell(true);
$this->readline->onKeyHome();
}

/**
* @depends testKeysHomeMovesToFront
* @param Readline $readline
Expand All @@ -324,6 +345,12 @@ public function testKeysEndMovesToEnd(Readline $readline)
return $readline;
}

public function testKeysEndEmitsBellWhenAlreadyAtEndOfLine()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyEnd();
}

/**
* @depends testKeysEndMovesToEnd
* @param Readline $readline
Expand All @@ -337,6 +364,12 @@ public function testKeysLeftMovesToLeft(Readline $readline)
return $readline;
}

public function testKeysLeftEmitsBellWhenAlreadyAtBeginningOfLine()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyLeft();
}

/**
* @depends testKeysLeftMovesToLeft
* @param Readline $readline
Expand All @@ -348,6 +381,12 @@ public function testKeysRightMovesToRight(Readline $readline)
$this->assertEquals(4, $readline->getCursorPosition());
}

public function testKeysRightEmitsBellWhenAlreadyAtEndOfLine()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyRight();
}

public function testKeysSimpleChars()
{
$this->input->emit('data', array('hi!'));
Expand All @@ -372,6 +411,12 @@ public function testKeysBackspaceDeletesLastCharacter(Readline $readline)
$this->assertEquals(2, $readline->getCursorCell());
}

public function testKeysBackspaceEmitsBellWhenAlreadyAtBeginningOfLine()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyBackspace();
}

public function testKeysMultiByteInput()
{
$this->input->emit('data', array('hä'));
Expand Down Expand Up @@ -430,10 +475,11 @@ public function testKeysDeleteMiddle()
$this->assertEquals(2, $this->readline->getCursorCell());
}

public function testKeysDeleteEndDoesNothing()
public function testKeysDeleteEmitsBellWhenAlreadyAtEndOfLine()
{
$this->readline->setInput('test');

$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyDelete();

$this->assertEquals('test', $this->readline->getInput());
Expand Down Expand Up @@ -571,11 +617,9 @@ public function testAutocompleteThrowsIfNotCallable()
$this->assertSame($this->readline, $this->readline->setAutocomplete(123));
}

/**
* @doesNotPerformAssertions
*/
public function testAutocompleteKeyDoesNothingIfUnused()
public function testAutocompleteKeyEmitsBellWhenAutocompleteIsNotSet()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyTab();
}

Expand Down Expand Up @@ -784,12 +828,13 @@ public function testAutocompletePicksFirstComplete()
$this->assertEquals('exit ', $this->readline->getInput());
}

public function testAutocompleteIgnoresNonMatching()
public function testAutocompleteIgnoresNonMatchingAndEmitsBell()
{
$this->readline->setAutocomplete(function () { return array('quit'); });

$this->readline->setInput('e');

$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyTab();

$this->assertEquals('e', $this->readline->getInput());
Expand Down Expand Up @@ -1193,8 +1238,9 @@ public function testHistoryAddEndsUpInList()
$this->assertEquals(array('a', 'b', 'c'), $this->readline->listHistory());
}

public function testHistoryUpEmptyDoesNotChangeInput()
public function testHistoryUpEmptyDoesNotChangeInputAndEmitsBell()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyUp();

$this->assertEquals('', $this->readline->getInput());
Expand Down Expand Up @@ -1236,8 +1282,9 @@ public function testHistoryUpAndThenEnterRestoresCycleToBottom()
$this->assertEquals('b', $this->readline->getInput());
}

public function testHistoryDownNotCyclingDoesNotChangeInput()
public function testHistoryDownNotCyclingDoesNotChangeInputAndEmitsBell()
{
$this->output->expects($this->once())->method('write')->with("\x07");
$this->readline->onKeyDown();

$this->assertEquals('', $this->readline->getInput());
Expand Down
13 changes: 13 additions & 0 deletions tests/StdioTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -560,4 +560,17 @@ public function testHistoryWillBeForwardedToReadline()
$stdio->clearHistory();
$this->assertEquals(array(), $stdio->listHistory());
}

public function testSetBellWillBeForwardedToReadline()
{
$input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
$output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock();

$stdio = new Stdio($this->loop, $input, $output, $readline);

$readline->expects($this->once())->method('setBell')->with(false);

$stdio->setBell(false);
}
}