UNPKG

terminal-menu-2

Version:

retro ansi terminal menus for serious 80s technicolor business

205 lines (178 loc) 5.35 kB
var createCharm = require('charm'); var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; var resumer = require('resumer'); module.exports = function (opts) { return new Menu(opts || {}); } function Menu (opts) { var self = this; self.width = opts.width || 50; self.x = opts.x || 1; self.y = opts.y || 1; self.init = { x: self.x, y: self.y }; self.items = []; self.lines = {}; self.selected = 0; self.colors = { fg: opts.fg || 'white', bg: opts.bg || 'blue' }; self.padding = opts.padding || { left: 2, right: 2, top: 1, bottom: 1 }; if (typeof self.padding === 'number') { self.padding = { left: self.padding, right: self.padding, top: self.padding, bottom: self.padding }; } self.x += self.padding.left; self.y += self.padding.top; self.size = { x: self.width + self.padding.left + self.padding.right }; self.charm = opts.charm || createCharm(); self.stream = self.charm.pipe(resumer()); self.charm.display('reset'); self.charm.display('bright'); self._ondata = function (buf) { self._ondataHandler(buf); }; process.nextTick(function () { self._ticked = true; self.charm.cursor(false); self._draw(); }); process.stdin.on('data', self._ondata); process.stdin.setRawMode(true); process.stdin.resume(); }; inherits(Menu, EventEmitter); Menu.prototype.createStream = function () { return this.stream; }; Menu.prototype.add = function (label, cb) { var index = this.items.length; if (cb) { this.on('select', function (x, ix) { if (ix === index) cb(x, ix); }); } this.items.push({ x: this.x, y: this.y, label: label }); this._fillLine(this.y); this.y ++; }; Menu.prototype._fillLine = function (y) { if (!this.lines[y]) { this.charm.position(this.init.x, y); this.charm.write(Array(1 + this.size.x).join(' ')); this.lines[y] = true; } }; Menu.prototype.jump = function (name) { var index = typeof name === 'number' ? name : this.items .map(function (item) { return item.label }) .indexOf(name) ; if (index < 0) return; var prev = this.selected; this.selected = index; if (this._ticked) { this._drawRow(prev); this._drawRow(index); } }; Menu.prototype.close = function () { process.stdin.setRawMode(false); process.stdin.removeListener('data', this._ondata); this.charm.cursor(true); this.charm.display('reset'); this.charm.position(1, this.y + 1); this.charm.end(); process.stdin.pause(); }; Menu.prototype.reset = function () { this.charm.reset(); }; Menu.prototype.write = function (msg) { this.charm.background(this.colors.bg); this.charm.foreground(this.colors.fg); this._fillLine(this.y); var parts = msg.split('\n'); for (var i = 0; i < parts.length; i++) { if (parts[i].length) { this.charm.position(this.x, this.y); this.charm.write(parts[i]); } if (i !== parts.length - 1) { this.x = this.init.x + this.padding.left; this._fillLine(this.y); this.y ++; } } }; Menu.prototype._draw = function () { for (var i = 0; i < this.padding.top; i++) { this._fillLine(this.init.y + i); } for (var i = 0; i < this.items.length; i++) this._drawRow(i); // reset foreground and background colors this.charm.background(this.colors.bg); this.charm.foreground(this.colors.fg); for (var i = 0; i < this.padding.bottom; i++) { this._fillLine(this.y + i); } }; Menu.prototype._drawRow = function (index) { index = (index + this.items.length) % this.items.length; var item = this.items[index]; this.charm.position(item.x, item.y); if (this.selected === index) { this.charm.background(this.colors.fg); this.charm.foreground(this.colors.bg); } else { this.charm.background(this.colors.bg); this.charm.foreground(this.colors.fg); } var wc = (item.label.match(/[^\u0000-\u00ff]/g) || '').length; this.charm.write( item.label + Array(Math.max(0, this.width - item.label.length - wc)).join(' ') ); }; Menu.prototype._ondataHandler = function ondata (buf) { var codes = [].join.call(buf, '.'); if (codes === '27.91.65' || codes === '107') { // up || k this.selected = (this.selected - 1 + this.items.length) % this.items.length ; this._drawRow(this.selected + 1); this._drawRow(this.selected); } else if (codes === '27.91.66' || codes === '106') { // down || j this.selected = (this.selected + 1) % this.items.length; this._drawRow(this.selected - 1); this._drawRow(this.selected); } else if (codes === '3' || codes == '113') { // ^C || q process.stdin.setRawMode(false); this.charm.reset(); process.exit(); } else if (codes === '13') { // enter this.emit('select', this.items[this.selected].label, this.selected); } };