Skip to content

Commit b2c9227

Browse files
committed
Emit C0 control codes for all single byte control codes
1 parent aa483da commit b2c9227

File tree

5 files changed

+58
-10
lines changed

5 files changed

+58
-10
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ Each generic C1 code gets emitted as an `c1` event with its raw 2-byte sequence:
7979
$stream->on('c1', function ($sequence) { … });
8080
```
8181

82+
All other [C0 control codes](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C0_.28ASCII_and_derivatives.29),
83+
also known as [ASCII control codes](https://en.wikipedia.org/wiki/ASCII#ASCII_control_code_chart),
84+
are supported by just emitting their single-byte value.
85+
Each generic C0 code gets emitted as an `c0` event with its raw single-byte value:
86+
87+
```php
88+
$stream->on('c0', function ($code) {
89+
if ($code === "\n") {
90+
echo 'ENTER pressed';
91+
}
92+
});
93+
```
94+
8295
## Install
8396

8497
The recommended way to install this library is [through Composer](https://getcomposer.org).

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "clue/term-react",
33
"description": "Streaming terminal emulator, built on top of React PHP",
4-
"keywords": ["terminal", "control codes", "xterm", "csi", "osc", "apc", "dps", "pm", "c1", "streaming", "ReactPHP"],
4+
"keywords": ["terminal", "control codes", "xterm", "csi", "osc", "apc", "dps", "pm", "C1", "C0", "streaming", "ReactPHP"],
55
"homepage": "https://github.com/clue/php-term-react",
66
"license": "MIT",
77
"authors": [

examples/stdin-codes.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
$parser->on('csi', $decoder);
2727
$parser->on('osc', $decoder);
28+
$parser->on('c1', $decoder);
29+
$parser->on('c0', $decoder);
2830

2931
$parser->on('data', function ($bytes) {
3032
echo 'Data: ' . $bytes . PHP_EOL;

src/ControlCodeParser.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,27 +96,44 @@ public function handleData($data)
9696
$this->buffer .= $data;
9797

9898
while ($this->buffer !== '') {
99-
// search ESC (\x1B = \033)
100-
$esc = strpos($this->buffer, "\x1B");
99+
// search for first control character (C0 and DEL)
100+
$c0 = false;
101+
for ($i = 0; isset($this->buffer[$i]); ++$i) {
102+
$code = ord($this->buffer[$i]);
103+
if ($code < 0x20 || $code === 0x7F) {
104+
$c0 = $i;
105+
break;
106+
}
107+
}
101108

102-
// no ESC found, emit whole buffer as data
103-
if ($esc === false) {
109+
// no C0 found, emit whole buffer as data
110+
if ($c0 === false) {
104111
$data = $this->buffer;
105112
$this->buffer = '';
106113

107114
$this->emit('data', array($data));
108115
return;
109116
}
110117

111-
// ESC found somewhere inbetween, emit everything before ESC as data
112-
if ($esc !== 0) {
113-
$data = substr($this->buffer, 0, $esc);
114-
$this->buffer = substr($this->buffer, $esc);
118+
// C0 found somewhere inbetween, emit everything before C0 as data
119+
if ($c0 !== 0) {
120+
$data = substr($this->buffer, 0, $c0);
121+
$this->buffer = substr($this->buffer, $c0);
115122

116123
$this->emit('data', array($data));
117124
}
118125

119-
// ESC is now at start of buffer
126+
// C0 is now at start of buffer
127+
// check if this is a normal C0 code or an ESC (\x1B = \033)
128+
// normal C0 will be emitted, ESC will be parsed further
129+
if ($this->buffer[0] !== "\x1B") {
130+
$data = $this->buffer[0];
131+
$this->buffer = (string)substr($this->buffer, 1);
132+
133+
$this->emit('c0', array($data));
134+
break;
135+
}
136+
120137
// check following byte to determine type
121138
if (!isset($this->buffer[1])) {
122139
// type currently unknown, wait for next data chunk

tests/ControlCodeParserTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ public function testEmitsDataInMultipleChunks()
3636
$this->assertEquals('helloworld', $buffer);
3737
}
3838

39+
public function testEmitsC0AsOneChunk()
40+
{
41+
$this->parser->on('data', $this->expectCallableNever());
42+
$this->parser->on('c0', $this->expectCallableOnce("\n"));
43+
44+
$this->input->emit('data', array("\n"));
45+
}
46+
3947
public function testEmitsCsiAsOneChunk()
4048
{
4149
$this->parser->on('data', $this->expectCallableNever());
@@ -71,6 +79,14 @@ public function testEmitsChunkedEndCsiAsOneChunk()
7179
$this->input->emit('data', array("A"));
7280
}
7381

82+
public function testEmitsDataAndC0()
83+
{
84+
$this->parser->on('data', $this->expectCallableOnceWith("hello world"));
85+
$this->parser->on('c0', $this->expectCallableOnceWith("\n"));
86+
87+
$this->input->emit('data', array("hello world\n"));
88+
}
89+
7490
public function testEmitsCsiAndData()
7591
{
7692
$this->parser->on('data', $this->expectCallableOnceWith("hello"));

0 commit comments

Comments
 (0)