| |
| /** |
| * 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 |
| } |