类似inquirer列表类型交互实现

一、源码设计思维导图

二、源码

const EventEmitter = require('events');
const readline = require('readline');
const MuteStream = require('mute-stream');
const {fromEvent} = require('rxjs');
var cliCursor = require('cli-cursor');

const ansiEscapes = require('ansi-escapes');
const options = {
  type: 'list',
  name: 'userName',
  message: '请选择你的名字',
  choices: [{
    name: '张三',
    value: '张三'
  }, {
    name: '李四',
    value: '李四'
  }, {
    name: '王五',
    value: '王五'
  }]
};

class List extends EventEmitter {
  constructor(list) {
    super();
    this.type = options.type;
    this.name = options.name;
    this.message = options.message;
    this.choices = options.choices;

    this.input = process.stdin;
    // 对标准输出流进行一次包装
    const ms = new MuteStream();
    ms.pipe(process.stdout);
    this.output = ms;
    // Interface实例
    this.rl = readline.createInterface({
      input: this.input,
      output: this.output
    });
    // 当前选中位置 用于光标位移
    this.selected = 0;
    // 列表高度
    this.height = 0;
    // 监听按键按下事件
    this.keypress = fromEvent(this.rl.input, 'keypress').forEach(this.onKeyPress);
    // 是否已经选中完毕
    this.haveSelected = false;
  }

  onKeyPress = (keymap) => {
    const key = keymap[1];
    if (key.name === 'down') { // 下箭头
      this.selected++;
      if (this.selected === this.choices.length) {
        this.selected = 0;
      }
      this.render();
    } else if (key.name === 'up') { // 上箭头
      this.selected--;
      if (this.selected === -1) {
        this.selected = this.choices.length - 1;
      }
      this.render();
    } else if (key.name === 'return') {
      this.haveSelected = true;
      this.render();
      this.close();
      // 派发给程序开发者的
      this.emit('exit', this.choices[this.selected]);
    }
  }

  close() {
    // this.input.pause();
    this.output.unmute();
    this.rl.output.end();
    //this.rl.close();内部会调 this.rl.pause(); 会调 this.input.pause();
    // this.rl.pause();
    this.rl.close();
    cliCursor.show();
  }

  // 渲染列表
  render() {
    cliCursor.hide();
    // 让用户可以输入
    this.output.unmute();
    this.clean();
    // 输出列表
    this.output.write(this.getContent());
    // 用户不能继续输入
    this.output.mute();
  }

  // 获取输出内容
  getContent() {
    if (!this.haveSelected) { // 用户还没选择结束
      // 加粗无效
      let content = '\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) => {
        // 是否为当前选中项,高亮显示,因为除了最后一个选项外,每一项后面需要加入\n换行
        if (index === this.selected) {
          if (index === this.choices.length - 1) { // 最后一行 不要加\n
            content += '\x1B[36m> ' + choice.name + '\x1B[39m\n';
          } else { // 不是最后一行,添加\n
            content += '\x1B[36m> ' + choice.name + '\x1B[39m\n';
          }
        } else { // 没有选中 除了最后一个选项外,每一项后面需要加入\n换行
          if (index === this.choices.length - 1) { // 最后一行 不要加\n
            content += '  ' + choice.name + '\n';
          } else { // 不是最后一行,添加\n
            content += '  ' + choice.name + '\n';
          }
        }
      });
      // 空行 询问标题占两行
      this.height = this.choices.length + 2;
      return content;
    } else {
      // 输出给使用者看的
      const name = this.choices[this.selected].name;
      return '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m \x1B[36m' + name + '\x1B[39m\x1B[0m \n';
    }
  }

  // 清屏
  clean() {
    const emptyLines = ansiEscapes.eraseLines(this.height);
    this.output.write(emptyLines);
  }
}

function prompt(options) {
  return new Promise((resolve, reject) => {
    try {
      const list = new List(options);
      list.render();
      // 添加退出监听
      list.on('exit', function (result) {
        resolve(result);
      });
    } catch (e) {
      reject(e);
    }
  });
}

prompt(options).then((result) => {
    console.log(result);
  }
);

思考?

​ inquirer的list在用户选择时没有光标,而自实现的有光标。如何去掉?

答案:

​ 使用cli-cursor包提供的hide和show方法完成光标的隐藏与显示(从阅读inquirer源码获得)

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

results matching ""

    No results matching ""