实现自己的readline功能
function readline(callback) {
function onkeypress(s) {
output.write(s)
line += s;
switch (s) {
case '\r':
input.pause();
callback(line)
break;
}
}
const input = process.stdin;
const output = process.stdout;
let line = ''
emitKeypressEvents(input);
input.on('keypress', onkeypress);
input.setRawMode(true);
input.resume();
}
function emitKeypressEvents(stream) {
function onData(s) {
keys.next(s.toString())
}
const keys = emitKeys(stream)
keys.next();
stream.on('data', onData)
}
function* emitKeys(stream) {
while (true) {
let ch = yield;
let s = ch;
stream.emit('keypress', s)
}
}
readline(function (answer) {
console.log('answer:', answer)
})
readline 源码分析
初始化流程
- 通过createInterface方法初始化命令行输入,该方法返回Interface方法,这个方法首先强制将函数转化为构造函数;
- 为构造函数添加事件行为,通过
EventEmitter.call(this)
; - 参数处理,对于用户输入的参数进行处理;
- 获取监听键盘事件的能力,入口为
emitKeypressEvents(input, this);
- 从emitKeypressEvents 中通过generator函数(emitKeys(stream))获取到键盘的输入信息,并对结果做了判断处理后,提交键盘输入事件
stream.emit('keypress', escaped ? undefined : s, key);
- 完成一系列事件监听包括命令行输入事件```if (stream.listenerCount('keypress') > 0) {
键盘事件监听stream.on('data', onData); } else { stream.on('newListener', onNewListener); }```
input.on('keypress', onkeypress);
,在键盘监听事件中,如果有newListener事件,会触发该事件执行该事件的回调来初始化命令行输入事件,代码如下if (event === 'keypress') { stream.on('data', onData); stream.removeListener('newListener', onNewListener); }
- 设置命令行监测逐字输入:
this._setRawMode(true);
执行流程 当用户输入字符时,命令行监听事件先执行回调函数onData,主要是为了触发emitKeys函数,通过代码
stream[ESCAPE_DECODER].next(r[i]);
然后执行emitKeys函数对字符进行判断后触发keypress函数stream.emit('keypress', s, key);
,最后执行keypress函数的回调函数function onkeypress(s, key) { self._ttyWrite(s, key); if (key && key.sequence) { // If the key.sequence is half of a surrogate pair // (>= 0xd800 and <= 0xdfff), refresh the line so // the character is displayed appropriately. const ch = key.sequence.codePointAt(0); if (ch >= 0xd800 && ch <= 0xdfff) self._refreshLine(); } }
判断时候结束,结束后执行相关的后续结束处理
手写命令行交互列表,类似于inqurer
```javascript const EventEmitter = require('events'); // 事件库 const MuteStream = require('mute-stream'); // 包装输入输出流 const readline = require('readline'); // 命令行输入监听 const { fromEvent } = require('rxjs'); const ansiEscapes = require('ansi-escapes'); // 清屏
const option = {
type: 'list', name: 'name', message: 'select name:', choices: [ { name: 'sunshine', value: 'sunshine' }, { name: 'curry', value: 'curry' }, { name: 'james', value: 'james' } ]
}
function prompt(option) {
return new Promise((resolve, reject) => { try { const list = new List(option); list.render() list.on('exit', function (answers) { resolve(answers) }) } catch (e) { reject(e) } })
}
class List extends EventEmitter {
constructor(option) { super(); this.name = option.name; this.message = option.message; this.choices = option.choices; this.input = process.stdin; const ms = new MuteStream(); ms.pipe(process.stdout); this.output = ms; this.selected = 0; // 默认选择list的第一位 this.height = this.choices.length + 1; // 命令行的默认高度,为了清空命令行用的 this.readline = readline.createInterface({ input: this.input, output: this.output }); this.keypress = fromEvent(this.readline.input, 'keypress').forEach(this.onKeypress); this.hasSelected = false } render() { this.output.unmute(); this.clear(); this.output.write(this.getContext()); this.output.mute(); } // 监听键盘输入事件 onKeypress = (res) => { const key = res[1]; if (key.name === 'up') { this.selected--; if (this.selected < 0) { this.selected = this.choices.length - 1; } this.render() } if (key.name === 'down') { this.selected++; if (this.selected === this.choices.length) { this.selected = 0 } this.render() } if (key.name === 'return') { this.hasSelected = true this.emit('exit', this.choices[this.selected]); this.render(); this.close(); } } // 显示在命令行中的文本信息 getContext() { if (!this.hasSelected) { let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m \x1B[0m\x1B[2m(Use arrow keys)\x1B[22m\n'; this.choices.forEach((choice, index) => { if (index === this.selected) { title += '\x1B[36m❯ ' + choice.name + '\x1B[39m ' title += index === this.choices.length - 1 ? '' : '\n' } else { title += ` ${choice.name} `; title += index === this.choices.length - 1 ? '' : '\n' } }) return title } else { const name = this.choices[this.selected].name; let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m \x1B[36m' + name + '\x1B[39m\x1B[0m \n'; return title } } // 清空命令行 clear() { const lines = ansiEscapes.eraseLines(this.height); this.output.write(lines) } // 关闭命令行交互、退出程序 close() { this.output.unmute(); this.readline.output.end(); // 输出流结束 this.readline.pause(); // readline 停止监听 this.readline.close(); // readline 关闭 }
}
prompt(option).then(answer => {
console.log('answers:',answer)
})
```