index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import ansiStyles from '#ansi-styles';
  2. import supportsColor from '#supports-color';
  3. import { // eslint-disable-line import/order
  4. stringReplaceAll,
  5. stringEncaseCRLFWithFirstIndex,
  6. } from './utilities.js';
  7. const {stdout: stdoutColor, stderr: stderrColor} = supportsColor;
  8. const GENERATOR = Symbol('GENERATOR');
  9. const STYLER = Symbol('STYLER');
  10. const IS_EMPTY = Symbol('IS_EMPTY');
  11. // `supportsColor.level` → `ansiStyles.color[name]` mapping
  12. const levelMapping = [
  13. 'ansi',
  14. 'ansi',
  15. 'ansi256',
  16. 'ansi16m',
  17. ];
  18. const styles = Object.create(null);
  19. const applyOptions = (object, options = {}) => {
  20. if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
  21. throw new Error('The `level` option should be an integer from 0 to 3');
  22. }
  23. // Detect level if not set manually
  24. const colorLevel = stdoutColor ? stdoutColor.level : 0;
  25. object.level = options.level === undefined ? colorLevel : options.level;
  26. };
  27. export class Chalk {
  28. constructor(options) {
  29. // eslint-disable-next-line no-constructor-return
  30. return chalkFactory(options);
  31. }
  32. }
  33. const chalkFactory = options => {
  34. const chalk = (...strings) => strings.join(' ');
  35. applyOptions(chalk, options);
  36. Object.setPrototypeOf(chalk, createChalk.prototype);
  37. return chalk;
  38. };
  39. function createChalk(options) {
  40. return chalkFactory(options);
  41. }
  42. Object.setPrototypeOf(createChalk.prototype, Function.prototype);
  43. for (const [styleName, style] of Object.entries(ansiStyles)) {
  44. styles[styleName] = {
  45. get() {
  46. const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
  47. Object.defineProperty(this, styleName, {value: builder});
  48. return builder;
  49. },
  50. };
  51. }
  52. styles.visible = {
  53. get() {
  54. const builder = createBuilder(this, this[STYLER], true);
  55. Object.defineProperty(this, 'visible', {value: builder});
  56. return builder;
  57. },
  58. };
  59. const getModelAnsi = (model, level, type, ...arguments_) => {
  60. if (model === 'rgb') {
  61. if (level === 'ansi16m') {
  62. return ansiStyles[type].ansi16m(...arguments_);
  63. }
  64. if (level === 'ansi256') {
  65. return ansiStyles[type].ansi256(ansiStyles.rgbToAnsi256(...arguments_));
  66. }
  67. return ansiStyles[type].ansi(ansiStyles.rgbToAnsi(...arguments_));
  68. }
  69. if (model === 'hex') {
  70. return getModelAnsi('rgb', level, type, ...ansiStyles.hexToRgb(...arguments_));
  71. }
  72. return ansiStyles[type][model](...arguments_);
  73. };
  74. const usedModels = ['rgb', 'hex', 'ansi256'];
  75. for (const model of usedModels) {
  76. styles[model] = {
  77. get() {
  78. const {level} = this;
  79. return function (...arguments_) {
  80. const styler = createStyler(getModelAnsi(model, levelMapping[level], 'color', ...arguments_), ansiStyles.color.close, this[STYLER]);
  81. return createBuilder(this, styler, this[IS_EMPTY]);
  82. };
  83. },
  84. };
  85. const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1);
  86. styles[bgModel] = {
  87. get() {
  88. const {level} = this;
  89. return function (...arguments_) {
  90. const styler = createStyler(getModelAnsi(model, levelMapping[level], 'bgColor', ...arguments_), ansiStyles.bgColor.close, this[STYLER]);
  91. return createBuilder(this, styler, this[IS_EMPTY]);
  92. };
  93. },
  94. };
  95. }
  96. const proto = Object.defineProperties(() => {}, {
  97. ...styles,
  98. level: {
  99. enumerable: true,
  100. get() {
  101. return this[GENERATOR].level;
  102. },
  103. set(level) {
  104. this[GENERATOR].level = level;
  105. },
  106. },
  107. });
  108. const createStyler = (open, close, parent) => {
  109. let openAll;
  110. let closeAll;
  111. if (parent === undefined) {
  112. openAll = open;
  113. closeAll = close;
  114. } else {
  115. openAll = parent.openAll + open;
  116. closeAll = close + parent.closeAll;
  117. }
  118. return {
  119. open,
  120. close,
  121. openAll,
  122. closeAll,
  123. parent,
  124. };
  125. };
  126. const createBuilder = (self, _styler, _isEmpty) => {
  127. // Single argument is hot path, implicit coercion is faster than anything
  128. // eslint-disable-next-line no-implicit-coercion
  129. const builder = (...arguments_) => applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' '));
  130. // We alter the prototype because we must return a function, but there is
  131. // no way to create a function with a different prototype
  132. Object.setPrototypeOf(builder, proto);
  133. builder[GENERATOR] = self;
  134. builder[STYLER] = _styler;
  135. builder[IS_EMPTY] = _isEmpty;
  136. return builder;
  137. };
  138. const applyStyle = (self, string) => {
  139. if (self.level <= 0 || !string) {
  140. return self[IS_EMPTY] ? '' : string;
  141. }
  142. let styler = self[STYLER];
  143. if (styler === undefined) {
  144. return string;
  145. }
  146. const {openAll, closeAll} = styler;
  147. if (string.includes('\u001B')) {
  148. while (styler !== undefined) {
  149. // Replace any instances already present with a re-opening code
  150. // otherwise only the part of the string until said closing code
  151. // will be colored, and the rest will simply be 'plain'.
  152. string = stringReplaceAll(string, styler.close, styler.open);
  153. styler = styler.parent;
  154. }
  155. }
  156. // We can move both next actions out of loop, because remaining actions in loop won't have
  157. // any/visible effect on parts we add here. Close the styling before a linebreak and reopen
  158. // after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92
  159. const lfIndex = string.indexOf('\n');
  160. if (lfIndex !== -1) {
  161. string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
  162. }
  163. return openAll + string + closeAll;
  164. };
  165. Object.defineProperties(createChalk.prototype, styles);
  166. const chalk = createChalk();
  167. export const chalkStderr = createChalk({level: stderrColor ? stderrColor.level : 0});
  168. export {
  169. modifierNames,
  170. foregroundColorNames,
  171. backgroundColorNames,
  172. colorNames,
  173. // TODO: Remove these aliases in the next major version
  174. modifierNames as modifiers,
  175. foregroundColorNames as foregroundColors,
  176. backgroundColorNames as backgroundColors,
  177. colorNames as colors,
  178. } from './vendor/ansi-styles/index.js';
  179. export {
  180. stdoutColor as supportsColor,
  181. stderrColor as supportsColorStderr,
  182. };
  183. export default chalk;