1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
const EventEmitter = require('events')
const waitForData = require('./waitForData')
const ansi = require('./ansi')
module.exports = class CommandLineInterfacer extends EventEmitter {
constructor(inStream = process.stdin, outStream = process.stdout, proc = process) {
super()
this.inStream = inStream
this.outStream = outStream
this.process = proc
inStream.on('data', buffer => {
this.emit('inputData', buffer)
})
inStream.setRawMode(true)
proc.on('SIGWINCH', async buffer => {
this.emit('resize', await this.getScreenSize())
})
}
async getScreenSize() {
const waitUntil = cond => waitForData(this.inStream, cond)
// Get old cursor position..
this.outStream.write(ansi.requestCursorPosition())
const { options: oldCoords } = this.parseANSICommand(
await waitUntil(buf => ansi.isANSICommand(buf, 82))
)
// Move far to the bottom right of the screen, then get cursor position..
// (We could use moveCursor here, but the 0-index offset isn't really
// relevant.)
this.outStream.write(ansi.moveCursorRaw(9999, 9999))
this.outStream.write(ansi.requestCursorPosition())
const { options: sizeCoords } = this.parseANSICommand(
await waitUntil(buf => ansi.isANSICommand(buf, 82))
)
// Restore to old cursor position.. (Using moveCursorRaw is actaully
// necessary here, since we'll be passing the coordinates returned from
// another ANSI command.)
this.outStream.write(ansi.moveCursorRaw(oldCoords[0], oldCoords[1]))
// And return dimensions.
const [ sizeLine, sizeCol ] = sizeCoords
return {
lines: sizeLine, cols: sizeCol,
width: sizeCol, height: sizeLine
}
}
parseANSICommand(buffer) {
// Typically ANSI commands are written ESC[1;2;3;4C
// ..where ESC is the ANSI escape code, equal to hexadecimal 1B and
// decimal 33
// ..where [ and ; are the literal strings "[" and ";"
// ..where 1, 2, 3, and 4 are decimal integer arguments written in ASCII
// that may last more than one byte (e.g. "15")
// ..where C is some number representing the code of the command
if (buffer[0] !== 0x1b || buffer[1] !== 0x5b) {
throw new Error('Not an ANSI command')
}
const options = []
let curOption = ''
let commandCode = null
for (const val of buffer.slice(2)) {
if (48 <= val && val <= 57) { // 0124356789
curOption = curOption.concat(val - 48)
} else {
options.push(parseInt(curOption))
curOption = ''
if (val !== 59) { // ;
commandCode = val
break
}
}
}
return {code: commandCode, options: options}
}
write(data) {
this.outStream.write(data)
}
}
|