slice.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /* eslint max-lines: "off" */
  2. "use strict";
  3. var reAnsi = require("./regex-ansi")
  4. , stringifiable = require("es5-ext/object/validate-stringifiable-value")
  5. , length = require("./get-stripped-length")
  6. , sgr = require("./lib/sgr")
  7. , max = Math.max;
  8. var Token = function (token) { this.token = token; };
  9. var tokenize = function (str) {
  10. var match = reAnsi().exec(str);
  11. if (!match) {
  12. return [str];
  13. }
  14. var index = match.index, head, prehead, tail;
  15. if (index === 0) {
  16. head = match[0];
  17. tail = str.slice(head.length);
  18. return [new Token(head)].concat(tokenize(tail));
  19. }
  20. prehead = str.slice(0, index);
  21. head = match[0];
  22. tail = str.slice(index + head.length);
  23. return [prehead, new Token(head)].concat(tokenize(tail));
  24. };
  25. var isChunkInSlice = function (chunk, index, begin, end) {
  26. var endIndex = chunk.length + index;
  27. if (begin > endIndex) return false;
  28. if (end < index) return false;
  29. return true;
  30. };
  31. // eslint-disable-next-line max-lines-per-function
  32. var sliceSeq = function (seq, begin, end) {
  33. var sliced = seq.reduce(
  34. function (state, chunk) {
  35. var index = state.index;
  36. if (chunk instanceof Token) {
  37. var code = sgr.extractCode(chunk.token);
  38. if (index <= begin) {
  39. if (code in sgr.openers) {
  40. sgr.openStyle(state.preOpeners, code);
  41. }
  42. if (code in sgr.closers) {
  43. sgr.closeStyle(state.preOpeners, code);
  44. }
  45. } else if (index < end) {
  46. if (code in sgr.openers) {
  47. sgr.openStyle(state.inOpeners, code);
  48. state.seq.push(chunk);
  49. } else if (code in sgr.closers) {
  50. state.inClosers.push(code);
  51. state.seq.push(chunk);
  52. }
  53. }
  54. } else {
  55. var nextChunk = "";
  56. if (isChunkInSlice(chunk, index, begin, end)) {
  57. var relBegin = Math.max(begin - index, 0)
  58. , relEnd = Math.min(end - index, chunk.length);
  59. nextChunk = chunk.slice(relBegin, relEnd);
  60. }
  61. state.seq.push(nextChunk);
  62. state.index = index + chunk.length;
  63. }
  64. return state;
  65. },
  66. {
  67. index: 0,
  68. seq: [],
  69. // preOpeners -> [ mod ]
  70. // preOpeners must be prepended to the slice if they wasn't closed til the end of it
  71. // preOpeners must be closed if they wasn't closed til the end of the slice
  72. preOpeners: [],
  73. // inOpeners -> [ mod ]
  74. // inOpeners already in the slice and must not be prepended to the slice
  75. // inOpeners must be closed if they wasn't closed til the end of the slice
  76. inOpeners: [], // opener CSI inside slice
  77. // inClosers -> [ code ]
  78. // closer CSIs for determining which pre/in-Openers must be closed
  79. inClosers: []
  80. }
  81. );
  82. sliced.seq = [].concat(
  83. sgr.prepend(sliced.preOpeners), sliced.seq,
  84. sgr.complete([].concat(sliced.preOpeners, sliced.inOpeners), sliced.inClosers)
  85. );
  86. return sliced.seq;
  87. };
  88. module.exports = function (str /*, begin, end*/) {
  89. var seq, begin = Number(arguments[1]), end = Number(arguments[2]), len;
  90. str = stringifiable(str);
  91. len = length(str);
  92. if (isNaN(begin)) {
  93. begin = 0;
  94. }
  95. if (isNaN(end)) {
  96. end = len;
  97. }
  98. if (begin < 0) {
  99. begin = max(len + begin, 0);
  100. }
  101. if (end < 0) {
  102. end = max(len + end, 0);
  103. }
  104. seq = tokenize(str);
  105. seq = sliceSeq(seq, begin, end);
  106. return seq
  107. .map(function (chunk) {
  108. if (chunk instanceof Token) {
  109. return chunk.token;
  110. }
  111. return chunk;
  112. })
  113. .join("");
  114. };