aboutsummaryrefslogtreecommitdiff
path: root/js/ansi/ansi.js
blob: 52fc8ec8bea14b3c538b8e38df2a10c78842c9ef (plain)
  1. /**
  2.  * References:
  3. *
  4. * - http://en.wikipedia.org/wiki/ANSI_escape_code
  5. * - http://www.termsys.demon.co.uk/vtansi.htm
  6. *
  7. */
  8. /**
  9. * Module dependencies.
  10. */
  11. var emitNewlineEvents = require('./newlines')
  12. , prefix = '\x1b[' // For all escape codes
  13. , suffix = 'm' // Only for color codes
  14. /**
  15. * The ANSI escape sequences.
  16. */
  17. var codes = {
  18. up: 'A'
  19. , down: 'B'
  20. , forward: 'C'
  21. , back: 'D'
  22. , nextLine: 'E'
  23. , previousLine: 'F'
  24. , horizontalAbsolute: 'G'
  25. , eraseData: 'J'
  26. , eraseLine: 'K'
  27. , scrollUp: 'S'
  28. , scrollDown: 'T'
  29. , savePosition: 's'
  30. , restorePosition: 'u'
  31. , queryPosition: '6n'
  32. , hide: '?25l'
  33. , show: '?25h'
  34. }
  35. /**
  36. * Rendering ANSI codes.
  37. */
  38. var styles = {
  39. bold: 1
  40. , italic: 3
  41. , underline: 4
  42. , inverse: 7
  43. }
  44. /**
  45. * The negating ANSI code for the rendering modes.
  46. */
  47. var reset = {
  48. bold: 22
  49. , italic: 23
  50. , underline: 24
  51. , inverse: 27
  52. }
  53. /**
  54. * The standard, styleable ANSI colors.
  55. */
  56. var colors = {
  57. white: 37
  58. , black: 30
  59. , blue: 34
  60. , cyan: 36
  61. , green: 32
  62. , magenta: 35
  63. , red: 31
  64. , yellow: 33
  65. , grey: 90
  66. , brightBlack: 90
  67. , brightRed: 91
  68. , brightGreen: 92
  69. , brightYellow: 93
  70. , brightBlue: 94
  71. , brightMagenta: 95
  72. , brightCyan: 96
  73. , brightWhite: 97
  74. }
  75. /**
  76. * Creates a Cursor instance based off the given `writable stream` instance.
  77. */
  78. function ansi (stream, options) {
  79. if (stream._ansicursor) {
  80. return stream._ansicursor
  81. } else {
  82. return stream._ansicursor = new Cursor(stream, options)
  83. }
  84. }
  85. module.exports = exports = ansi
  86. /**
  87. * The `Cursor` class.
  88. */
  89. function Cursor (stream, options) {
  90. if (!(this instanceof Cursor)) {
  91. return new Cursor(stream, options)
  92. }
  93. if (typeof stream != 'object' || typeof stream.write != 'function') {
  94. throw new Error('a valid Stream instance must be passed in')
  95. }
  96. // the stream to use
  97. this.stream = stream
  98. // when 'enabled' is false then all the functions are no-ops except for write()
  99. this.enabled = options && options.enabled
  100. if (typeof this.enabled === 'undefined') {
  101. this.enabled = stream.isTTY
  102. }
  103. this.enabled = !!this.enabled
  104. // then `buffering` is true, then `write()` calls are buffered in
  105. // memory until `flush()` is invoked
  106. this.buffering = !!(options && options.buffering)
  107. this._buffer = []
  108. // controls the foreground and background colors
  109. this.fg = this.foreground = new Colorer(this, 0)
  110. this.bg = this.background = new Colorer(this, 10)
  111. // defaults
  112. this.Bold = false
  113. this.Italic = false
  114. this.Underline = false
  115. this.Inverse = false
  116. // keep track of the number of "newlines" that get encountered
  117. this.newlines = 0
  118. emitNewlineEvents(stream)
  119. stream.on('newline', function () {
  120. this.newlines++
  121. }.bind(this))
  122. }
  123. exports.Cursor = Cursor
  124. /**
  125. * Helper function that calls `write()` on the underlying Stream.
  126. * Returns `this` instead of the write() return value to keep
  127. * the chaining going.
  128. */
  129. Cursor.prototype.write = function (data) {
  130. if (this.buffering) {
  131. this._buffer.push(arguments)
  132. } else {
  133. this.stream.write.apply(this.stream, arguments)
  134. }
  135. return this
  136. }
  137. /**
  138. * Buffer `write()` calls into memory.
  139. *
  140. * @api public
  141. */
  142. Cursor.prototype.buffer = function () {
  143. this.buffering = true
  144. return this
  145. }
  146. /**
  147. * Write out the in-memory buffer.
  148. *
  149. * @api public
  150. */
  151. Cursor.prototype.flush = function () {
  152. this.buffering = false
  153. var str = this._buffer.map(function (args) {
  154. if (args.length != 1) throw new Error('unexpected args length! ' + args.length);
  155. return args[0];
  156. }).join('');
  157. this._buffer.splice(0); // empty
  158. this.write(str);
  159. return this
  160. }
  161. /**
  162. * The `Colorer` class manages both the background and foreground colors.
  163. */
  164. function Colorer (cursor, base) {
  165. this.current = null
  166. this.cursor = cursor
  167. this.base = base
  168. }
  169. exports.Colorer = Colorer
  170. /**
  171. * Write an ANSI color code, ensuring that the same code doesn't get rewritten.
  172. */
  173. Colorer.prototype._setColorCode = function setColorCode (code) {
  174. var c = String(code)
  175. if (this.current === c) return
  176. this.cursor.enabled && this.cursor.write(prefix + c + suffix)
  177. this.current = c
  178. return this
  179. }
  180. /**
  181. * Set up the positional ANSI codes.
  182. */
  183. Object.keys(codes).forEach(function (name) {
  184. var code = String(codes[name])
  185. Cursor.prototype[name] = function () {
  186. var c = code
  187. if (arguments.length > 0) {
  188. c = toArray(arguments).map(Math.round).join(';') + code
  189. }
  190. this.enabled && this.write(prefix + c)
  191. return this
  192. }
  193. })
  194. /**
  195. * Set up the functions for the rendering ANSI codes.
  196. */
  197. Object.keys(styles).forEach(function (style) {
  198. var name = style[0].toUpperCase() + style.substring(1)
  199. , c = styles[style]
  200. , r = reset[style]
  201. Cursor.prototype[style] = function () {
  202. if (this[name]) return
  203. this.enabled && this.write(prefix + c + suffix)
  204. this[name] = true
  205. return this
  206. }
  207. Cursor.prototype['reset' + name] = function () {
  208. if (!this[name]) return
  209. this.enabled && this.write(prefix + r + suffix)
  210. this[name] = false
  211. return this
  212. }
  213. })
  214. /**
  215. * Setup the functions for the standard colors.
  216. */
  217. Object.keys(colors).forEach(function (color) {
  218. var code = colors[color]
  219. Colorer.prototype[color] = function () {
  220. this._setColorCode(this.base + code)
  221. return this.cursor
  222. }
  223. Cursor.prototype[color] = function () {
  224. return this.foreground[color]()
  225. }
  226. })
  227. /**
  228. * Makes a beep sound!
  229. */
  230. Cursor.prototype.beep = function () {
  231. this.enabled && this.write('\x07')
  232. return this
  233. }
  234. /**
  235. * Moves cursor to specific position
  236. */
  237. Cursor.prototype.goto = function (x, y) {
  238. x = x | 0
  239. y = y | 0
  240. this.enabled && this.write(prefix + y + ';' + x + 'H')
  241. return this
  242. }
  243. /**
  244. * Resets the color.
  245. */
  246. Colorer.prototype.reset = function () {
  247. this._setColorCode(this.base + 39)
  248. return this.cursor
  249. }
  250. /**
  251. * Resets all ANSI formatting on the stream.
  252. */
  253. Cursor.prototype.reset = function () {
  254. this.enabled && this.write(prefix + '0' + suffix)
  255. this.Bold = false
  256. this.Italic = false
  257. this.Underline = false
  258. this.Inverse = false
  259. this.foreground.current = null
  260. this.background.current = null
  261. return this
  262. }
  263. /**
  264. * Sets the foreground color with the given RGB values.
  265. * The closest match out of the 216 colors is picked.
  266. */
  267. Colorer.prototype.rgb = function (r, g, b) {
  268. var base = this.base + 38
  269. , code = rgb(r, g, b)
  270. this._setColorCode(base + ';5;' + code)
  271. return this.cursor
  272. }
  273. /**
  274. * Same as `cursor.fg.rgb(r, g, b)`.
  275. */
  276. Cursor.prototype.rgb = function (r, g, b) {
  277. return this.foreground.rgb(r, g, b)
  278. }
  279. /**
  280. * Accepts CSS color codes for use with ANSI escape codes.
  281. * For example: `#FF000` would be bright red.
  282. */
  283. Colorer.prototype.hex = function (color) {
  284. return this.rgb.apply(this, hex(color))
  285. }
  286. /**
  287. * Same as `cursor.fg.hex(color)`.
  288. */
  289. Cursor.prototype.hex = function (color) {
  290. return this.foreground.hex(color)
  291. }
  292. // UTIL FUNCTIONS //
  293. /**
  294. * Translates a 255 RGB value to a 0-5 ANSI RGV value,
  295. * then returns the single ANSI color code to use.
  296. */
  297. function rgb (r, g, b) {
  298. var red = r / 255 * 5
  299. , green = g / 255 * 5
  300. , blue = b / 255 * 5
  301. return rgb5(red, green, blue)
  302. }
  303. /**
  304. * Turns rgb 0-5 values into a single ANSI color code to use.
  305. */
  306. function rgb5 (r, g, b) {
  307. var red = Math.round(r)
  308. , green = Math.round(g)
  309. , blue = Math.round(b)
  310. return 16 + (red*36) + (green*6) + blue
  311. }
  312. /**
  313. * Accepts a hex CSS color code string (# is optional) and
  314. * translates it into an Array of 3 RGB 0-255 values, which
  315. * can then be used with rgb().
  316. */
  317. function hex (color) {
  318. var c = color[0] === '#' ? color.substring(1) : color
  319. , r = c.substring(0, 2)
  320. , g = c.substring(2, 4)
  321. , b = c.substring(4, 6)
  322. return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]
  323. }
  324. /**
  325. * Turns an array-like object into a real array.
  326. */
  327. function toArray (a) {
  328. var i = 0
  329. , l = a.length
  330. , rtn = []
  331. for (; i<l; i++) {
  332. rtn.push(a[i])
  333. }
  334. return rtn
  335. }