/** * 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.enabled && this.write(prefix + c + suffix) this[name] = true return this } Cursor.prototype['reset' + name] = function () { if (!this[name]) return 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