实现自己的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 源码分析

  • 初始化流程

    1. 通过createInterface方法初始化命令行输入,该方法返回Interface方法,这个方法首先强制将函数转化为构造函数;
    2. 为构造函数添加事件行为,通过 EventEmitter.call(this);
    3. 参数处理,对于用户输入的参数进行处理;
    4. 获取监听键盘事件的能力,入口为emitKeypressEvents(input, this);
    5. 从emitKeypressEvents 中通过generator函数(emitKeys(stream))获取到键盘的输入信息,并对结果做了判断处理后,提交键盘输入事件 stream.emit('keypress', escaped ? undefined : s, key);
    6. 完成一系列事件监听包括命令行输入事件```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);
      }
      
    7. 设置命令行监测逐字输入: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)
    

    })

```

Copyright © imooc-lego (2020 - present) all right reserved,powered by GitbookFile Modify: 2021-06-27 08:04:56

results matching ""

    No results matching ""