406 lines
7.8 KiB
JavaScript
406 lines
7.8 KiB
JavaScript
|
|
/**
|
|
* References:
|
|
*
|
|
* - http://en.wikipedia.org/wiki/ANSI_escape_code
|
|
* - http://www.termsys.demon.co.uk/vtansi.htm
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var emitNewlineEvents = require('./newlines')
|
|
, prefix = '\x1b[' // For all escape codes
|
|
, suffix = 'm' // Only for color codes
|
|
|
|
/**
|
|
* The ANSI escape sequences.
|
|
*/
|
|
|
|
var codes = {
|
|
up: 'A'
|
|
, down: 'B'
|
|
, forward: 'C'
|
|
, back: 'D'
|
|
, nextLine: 'E'
|
|
, previousLine: 'F'
|
|
, horizontalAbsolute: 'G'
|
|
, eraseData: 'J'
|
|
, eraseLine: 'K'
|
|
, scrollUp: 'S'
|
|
, scrollDown: 'T'
|
|
, savePosition: 's'
|
|
, restorePosition: 'u'
|
|
, queryPosition: '6n'
|
|
, hide: '?25l'
|
|
, show: '?25h'
|
|
}
|
|
|
|
/**
|
|
* Rendering ANSI codes.
|
|
*/
|
|
|
|
var styles = {
|
|
bold: 1
|
|
, italic: 3
|
|
, underline: 4
|
|
, inverse: 7
|
|
}
|
|
|
|
/**
|
|
* The negating ANSI code for the rendering modes.
|
|
*/
|
|
|
|
var reset = {
|
|
bold: 22
|
|
, italic: 23
|
|
, underline: 24
|
|
, inverse: 27
|
|
}
|
|
|
|
/**
|
|
* The standard, styleable ANSI colors.
|
|
*/
|
|
|
|
var colors = {
|
|
white: 37
|
|
, black: 30
|
|
, blue: 34
|
|
, cyan: 36
|
|
, green: 32
|
|
, magenta: 35
|
|
, red: 31
|
|
, yellow: 33
|
|
, grey: 90
|
|
, brightBlack: 90
|
|
, brightRed: 91
|
|
, brightGreen: 92
|
|
, brightYellow: 93
|
|
, brightBlue: 94
|
|
, brightMagenta: 95
|
|
, brightCyan: 96
|
|
, brightWhite: 97
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a Cursor instance based off the given `writable stream` instance.
|
|
*/
|
|
|
|
function ansi (stream, options) {
|
|
if (stream._ansicursor) {
|
|
return stream._ansicursor
|
|
} else {
|
|
return stream._ansicursor = new Cursor(stream, options)
|
|
}
|
|
}
|
|
module.exports = exports = ansi
|
|
|
|
/**
|
|
* The `Cursor` class.
|
|
*/
|
|
|
|
function Cursor (stream, options) {
|
|
if (!(this instanceof Cursor)) {
|
|
return new Cursor(stream, options)
|
|
}
|
|
if (typeof stream != 'object' || typeof stream.write != 'function') {
|
|
throw new Error('a valid Stream instance must be passed in')
|
|
}
|
|
|
|
// the stream to use
|
|
this.stream = stream
|
|
|
|
// when 'enabled' is false then all the functions are no-ops except for write()
|
|
this.enabled = options && options.enabled
|
|
if (typeof this.enabled === 'undefined') {
|
|
this.enabled = stream.isTTY
|
|
}
|
|
this.enabled = !!this.enabled
|
|
|
|
// then `buffering` is true, then `write()` calls are buffered in
|
|
// memory until `flush()` is invoked
|
|
this.buffering = !!(options && options.buffering)
|
|
this._buffer = []
|
|
|
|
// controls the foreground and background colors
|
|
this.fg = this.foreground = new Colorer(this, 0)
|
|
this.bg = this.background = new Colorer(this, 10)
|
|
|
|
// defaults
|
|
this.Bold = false
|
|
this.Italic = false
|
|
this.Underline = false
|
|
this.Inverse = false
|
|
|
|
// keep track of the number of "newlines" that get encountered
|
|
this.newlines = 0
|
|
emitNewlineEvents(stream)
|
|
stream.on('newline', function () {
|
|
this.newlines++
|
|
}.bind(this))
|
|
}
|
|
exports.Cursor = Cursor
|
|
|
|
/**
|
|
* Helper function that calls `write()` on the underlying Stream.
|
|
* Returns `this` instead of the write() return value to keep
|
|
* the chaining going.
|
|
*/
|
|
|
|
Cursor.prototype.write = function (data) {
|
|
if (this.buffering) {
|
|
this._buffer.push(arguments)
|
|
} else {
|
|
this.stream.write.apply(this.stream, arguments)
|
|
}
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Buffer `write()` calls into memory.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Cursor.prototype.buffer = function () {
|
|
this.buffering = true
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Write out the in-memory buffer.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Cursor.prototype.flush = function () {
|
|
this.buffering = false
|
|
var str = this._buffer.map(function (args) {
|
|
if (args.length != 1) throw new Error('unexpected args length! ' + args.length);
|
|
return args[0];
|
|
}).join('');
|
|
this._buffer.splice(0); // empty
|
|
this.write(str);
|
|
return this
|
|
}
|
|
|
|
|
|
/**
|
|
* The `Colorer` class manages both the background and foreground colors.
|
|
*/
|
|
|
|
function Colorer (cursor, base) {
|
|
this.current = null
|
|
this.cursor = cursor
|
|
this.base = base
|
|
}
|
|
exports.Colorer = Colorer
|
|
|
|
/**
|
|
* Write an ANSI color code, ensuring that the same code doesn't get rewritten.
|
|
*/
|
|
|
|
Colorer.prototype._setColorCode = function setColorCode (code) {
|
|
var c = String(code)
|
|
if (this.current === c) return
|
|
this.cursor.enabled && this.cursor.write(prefix + c + suffix)
|
|
this.current = c
|
|
return this
|
|
}
|
|
|
|
|
|
/**
|
|
* Set up the positional ANSI codes.
|
|
*/
|
|
|
|
Object.keys(codes).forEach(function (name) {
|
|
var code = String(codes[name])
|
|
Cursor.prototype[name] = function () {
|
|
var c = code
|
|
if (arguments.length > 0) {
|
|
c = toArray(arguments).map(Math.round).join(';') + code
|
|
}
|
|
this.enabled && this.write(prefix + c)
|
|
return this
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Set up the functions for the rendering ANSI codes.
|
|
*/
|
|
|
|
Object.keys(styles).forEach(function (style) {
|
|
var name = style[0].toUpperCase() + style.substring(1)
|
|
, c = styles[style]
|
|
, r = reset[style]
|
|
|
|
Cursor.prototype[style] = function () {
|
|
if (this[name]) return this
|
|
this.enabled && this.write(prefix + c + suffix)
|
|
this[name] = true
|
|
return this
|
|
}
|
|
|
|
Cursor.prototype['reset' + name] = function () {
|
|
if (!this[name]) return this
|
|
this.enabled && this.write(prefix + r + suffix)
|
|
this[name] = false
|
|
return this
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Setup the functions for the standard colors.
|
|
*/
|
|
|
|
Object.keys(colors).forEach(function (color) {
|
|
var code = colors[color]
|
|
|
|
Colorer.prototype[color] = function () {
|
|
this._setColorCode(this.base + code)
|
|
return this.cursor
|
|
}
|
|
|
|
Cursor.prototype[color] = function () {
|
|
return this.foreground[color]()
|
|
}
|
|
})
|
|
|
|
/**
|
|
* Makes a beep sound!
|
|
*/
|
|
|
|
Cursor.prototype.beep = function () {
|
|
this.enabled && this.write('\x07')
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Moves cursor to specific position
|
|
*/
|
|
|
|
Cursor.prototype.goto = function (x, y) {
|
|
x = x | 0
|
|
y = y | 0
|
|
this.enabled && this.write(prefix + y + ';' + x + 'H')
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Resets the color.
|
|
*/
|
|
|
|
Colorer.prototype.reset = function () {
|
|
this._setColorCode(this.base + 39)
|
|
return this.cursor
|
|
}
|
|
|
|
/**
|
|
* Resets all ANSI formatting on the stream.
|
|
*/
|
|
|
|
Cursor.prototype.reset = function () {
|
|
this.enabled && this.write(prefix + '0' + suffix)
|
|
this.Bold = false
|
|
this.Italic = false
|
|
this.Underline = false
|
|
this.Inverse = false
|
|
this.foreground.current = null
|
|
this.background.current = null
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Sets the foreground color with the given RGB values.
|
|
* The closest match out of the 216 colors is picked.
|
|
*/
|
|
|
|
Colorer.prototype.rgb = function (r, g, b) {
|
|
var base = this.base + 38
|
|
, code = rgb(r, g, b)
|
|
this._setColorCode(base + ';5;' + code)
|
|
return this.cursor
|
|
}
|
|
|
|
/**
|
|
* Same as `cursor.fg.rgb(r, g, b)`.
|
|
*/
|
|
|
|
Cursor.prototype.rgb = function (r, g, b) {
|
|
return this.foreground.rgb(r, g, b)
|
|
}
|
|
|
|
/**
|
|
* Accepts CSS color codes for use with ANSI escape codes.
|
|
* For example: `#FF000` would be bright red.
|
|
*/
|
|
|
|
Colorer.prototype.hex = function (color) {
|
|
return this.rgb.apply(this, hex(color))
|
|
}
|
|
|
|
/**
|
|
* Same as `cursor.fg.hex(color)`.
|
|
*/
|
|
|
|
Cursor.prototype.hex = function (color) {
|
|
return this.foreground.hex(color)
|
|
}
|
|
|
|
|
|
// UTIL FUNCTIONS //
|
|
|
|
/**
|
|
* Translates a 255 RGB value to a 0-5 ANSI RGV value,
|
|
* then returns the single ANSI color code to use.
|
|
*/
|
|
|
|
function rgb (r, g, b) {
|
|
var red = r / 255 * 5
|
|
, green = g / 255 * 5
|
|
, blue = b / 255 * 5
|
|
return rgb5(red, green, blue)
|
|
}
|
|
|
|
/**
|
|
* Turns rgb 0-5 values into a single ANSI color code to use.
|
|
*/
|
|
|
|
function rgb5 (r, g, b) {
|
|
var red = Math.round(r)
|
|
, green = Math.round(g)
|
|
, blue = Math.round(b)
|
|
return 16 + (red*36) + (green*6) + blue
|
|
}
|
|
|
|
/**
|
|
* Accepts a hex CSS color code string (# is optional) and
|
|
* translates it into an Array of 3 RGB 0-255 values, which
|
|
* can then be used with rgb().
|
|
*/
|
|
|
|
function hex (color) {
|
|
var c = color[0] === '#' ? color.substring(1) : color
|
|
, r = c.substring(0, 2)
|
|
, g = c.substring(2, 4)
|
|
, b = c.substring(4, 6)
|
|
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]
|
|
}
|
|
|
|
/**
|
|
* Turns an array-like object into a real array.
|
|
*/
|
|
|
|
function toArray (a) {
|
|
var i = 0
|
|
, l = a.length
|
|
, rtn = []
|
|
for (; i<l; i++) {
|
|
rtn.push(a[i])
|
|
}
|
|
return rtn
|
|
}
|