Skip to content

Commit 5c9e765

Browse files
committed
Reduce number of writes in order to avoid flickering
1 parent 6a3f864 commit 5c9e765

File tree

2 files changed

+68
-17
lines changed

2 files changed

+68
-17
lines changed

src/Stdio.php

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,40 @@ public function handleBuffer()
7070

7171
public function write($data)
7272
{
73-
// clear readline prompt in order to overwrite with data
74-
$this->readline->clear();
73+
if ((string)$data === '') {
74+
return;
75+
}
76+
77+
$out = $data;
78+
79+
$lastNewline = strrpos($data, "\n");
80+
81+
$restoreReadline = false;
7582

76-
// move one line up if the last write did not end with a newline
7783
if ($this->incompleteLine !== '') {
78-
$this->output->write("\033[A");
79-
$this->output->write("\r\033[" . $this->width($this->incompleteLine) . "C");
80-
}
84+
// the last write did not end with a newline => append to existing row
8185

82-
// write actual data
83-
$this->output->write($data);
86+
// move one line up and move cursor to last position before writing data
87+
$out = "\033[A" . "\r\033[" . $this->width($this->incompleteLine) . "C" . $out;
88+
89+
// data contains a newline, so this will overwrite the readline prompt
90+
if ($lastNewline !== false) {
91+
// move cursor to beginning of readline prompt and clear line
92+
// clearing is important because $data may not overwrite the whole line
93+
$out = "\r\033[K" . $out;
94+
95+
// make sure to restore readline after this output
96+
$restoreReadline = true;
97+
}
98+
} else {
99+
// here, we're writing to a new line => overwrite readline prompt
100+
101+
// move cursor to beginning of readline prompt and clear line
102+
$out = "\r\033[K" . $out;
103+
104+
// we always overwrite the readline prompt, so restore it on next line
105+
$restoreReadline = true;
106+
}
84107

85108
// following write will have have to append to this line if it does not end with a newline
86109
$endsWithNewline = substr($data, -1) === "\n";
@@ -90,9 +113,7 @@ public function write($data)
90113
$this->incompleteLine = '';
91114
} else {
92115
// always end data with newline in order to append readline on next line
93-
$this->output->write("\n");
94-
95-
$lastNewline = strrpos($data, "\n");
116+
$out .= "\n";
96117

97118
if ($lastNewline === false) {
98119
// contains no newline at all, everything is incomplete
@@ -103,8 +124,21 @@ public function write($data)
103124
}
104125
}
105126

106-
// restore original readline prompt and line buffer
107-
$this->readline->redraw();
127+
if ($restoreReadline) {
128+
// write output and restore original readline prompt and line buffer
129+
$this->output->write($out);
130+
$this->readline->redraw();
131+
} else {
132+
// restore original cursor position in readline prompt
133+
$pos = $this->width($this->readline->getPrompt()) + $this->readline->getCursorCell();
134+
if ($pos !== 0) {
135+
// we always start at beginning of line, move right by X
136+
$out .= "\033[" . $pos . "C";
137+
}
138+
139+
// write to actual output stream
140+
$this->output->write($out);
141+
}
108142
}
109143

110144
public function writeln($line)

tests/StdioTest.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@ public function testCtorArgsWillBeReturnedByGetters()
3434
$this->assertSame($readline, $stdio->getReadline());
3535
}
3636

37+
public function testWriteEmptyStringWillNotWriteToOutput()
38+
{
39+
$input = $this->getMock('React\Stream\ReadableStreamInterface');
40+
$output = $this->getMock('React\Stream\WritableStreamInterface');
41+
42+
//$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock();
43+
$readline = new Readline($input, $output);
44+
$readline->setPrompt('> ');
45+
$readline->setInput('input');
46+
47+
$stdio = new Stdio($this->loop, $input, $output, $readline);
48+
49+
$output->expects($this->never())->method('write');
50+
51+
$stdio->write('');
52+
}
53+
3754
public function testWriteWillClearReadlineWriteOutputAndRestoreReadline()
3855
{
3956
$input = $this->getMock('React\Stream\ReadableStreamInterface');
@@ -56,7 +73,7 @@ public function testWriteWillClearReadlineWriteOutputAndRestoreReadline()
5673
$this->assertEquals("\r\033[K" . "test\n" . "\r\033[K" . "> input", $buffer);
5774
}
5875

59-
public function testWriteAgainWillClearReadlineMoveToPreviousLineWriteOutputAndRestoreReadline()
76+
public function testWriteAgainWillMoveToPreviousLineWriteOutputAndRestoreReadlinePosition()
6077
{
6178
$input = $this->getMock('React\Stream\ReadableStreamInterface');
6279
$output = $this->getMock('React\Stream\WritableStreamInterface');
@@ -77,10 +94,10 @@ public function testWriteAgainWillClearReadlineMoveToPreviousLineWriteOutputAndR
7794

7895
$stdio->write('world');
7996

80-
$this->assertEquals("\r\033[K" . "\033[A" . "\r\033[5C" . "world\n" . "\r\033[K" . "> input", $buffer);
97+
$this->assertEquals("\033[A" . "\r\033[5C" . "world\n" . "\033[7C", $buffer);
8198
}
8299

83-
public function testWriteAgainWithBackspaceWillClearReadlineMoveToPreviousLineWriteOutputAndRestoreReadline()
100+
public function testWriteAgainWithBackspaceWillMoveToPreviousLineWriteOutputAndRestoreReadlinePosition()
84101
{
85102
$input = $this->getMock('React\Stream\ReadableStreamInterface');
86103
$output = $this->getMock('React\Stream\WritableStreamInterface');
@@ -101,7 +118,7 @@ public function testWriteAgainWithBackspaceWillClearReadlineMoveToPreviousLineWr
101118

102119
$stdio->write("\x08 world!");
103120

104-
$this->assertEquals("\r\033[K" . "\033[A" . "\r\033[6C" . "\x08 world!\n" . "\r\033[K" . "> input", $buffer);
121+
$this->assertEquals("\033[A" . "\r\033[6C" . "\x08 world!\n" . "\033[7C", $buffer);
105122
}
106123

107124
public function testWriteAgainWithNewlinesWillClearReadlineMoveToPreviousLineWriteOutputAndRestoreReadline()

0 commit comments

Comments
 (0)