foldFlowLines.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. 'use strict';
  2. const FOLD_FLOW = 'flow';
  3. const FOLD_BLOCK = 'block';
  4. const FOLD_QUOTED = 'quoted';
  5. /**
  6. * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
  7. * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
  8. * terminated with `\n` and started with `indent`.
  9. */
  10. function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
  11. if (!lineWidth || lineWidth < 0)
  12. return text;
  13. if (lineWidth < minContentWidth)
  14. minContentWidth = 0;
  15. const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
  16. if (text.length <= endStep)
  17. return text;
  18. const folds = [];
  19. const escapedFolds = {};
  20. let end = lineWidth - indent.length;
  21. if (typeof indentAtStart === 'number') {
  22. if (indentAtStart > lineWidth - Math.max(2, minContentWidth))
  23. folds.push(0);
  24. else
  25. end = lineWidth - indentAtStart;
  26. }
  27. let split = undefined;
  28. let prev = undefined;
  29. let overflow = false;
  30. let i = -1;
  31. let escStart = -1;
  32. let escEnd = -1;
  33. if (mode === FOLD_BLOCK) {
  34. i = consumeMoreIndentedLines(text, i, indent.length);
  35. if (i !== -1)
  36. end = i + endStep;
  37. }
  38. for (let ch; (ch = text[(i += 1)]);) {
  39. if (mode === FOLD_QUOTED && ch === '\\') {
  40. escStart = i;
  41. switch (text[i + 1]) {
  42. case 'x':
  43. i += 3;
  44. break;
  45. case 'u':
  46. i += 5;
  47. break;
  48. case 'U':
  49. i += 9;
  50. break;
  51. default:
  52. i += 1;
  53. }
  54. escEnd = i;
  55. }
  56. if (ch === '\n') {
  57. if (mode === FOLD_BLOCK)
  58. i = consumeMoreIndentedLines(text, i, indent.length);
  59. end = i + indent.length + endStep;
  60. split = undefined;
  61. }
  62. else {
  63. if (ch === ' ' &&
  64. prev &&
  65. prev !== ' ' &&
  66. prev !== '\n' &&
  67. prev !== '\t') {
  68. // space surrounded by non-space can be replaced with newline + indent
  69. const next = text[i + 1];
  70. if (next && next !== ' ' && next !== '\n' && next !== '\t')
  71. split = i;
  72. }
  73. if (i >= end) {
  74. if (split) {
  75. folds.push(split);
  76. end = split + endStep;
  77. split = undefined;
  78. }
  79. else if (mode === FOLD_QUOTED) {
  80. // white-space collected at end may stretch past lineWidth
  81. while (prev === ' ' || prev === '\t') {
  82. prev = ch;
  83. ch = text[(i += 1)];
  84. overflow = true;
  85. }
  86. // Account for newline escape, but don't break preceding escape
  87. const j = i > escEnd + 1 ? i - 2 : escStart - 1;
  88. // Bail out if lineWidth & minContentWidth are shorter than an escape string
  89. if (escapedFolds[j])
  90. return text;
  91. folds.push(j);
  92. escapedFolds[j] = true;
  93. end = j + endStep;
  94. split = undefined;
  95. }
  96. else {
  97. overflow = true;
  98. }
  99. }
  100. }
  101. prev = ch;
  102. }
  103. if (overflow && onOverflow)
  104. onOverflow();
  105. if (folds.length === 0)
  106. return text;
  107. if (onFold)
  108. onFold();
  109. let res = text.slice(0, folds[0]);
  110. for (let i = 0; i < folds.length; ++i) {
  111. const fold = folds[i];
  112. const end = folds[i + 1] || text.length;
  113. if (fold === 0)
  114. res = `\n${indent}${text.slice(0, end)}`;
  115. else {
  116. if (mode === FOLD_QUOTED && escapedFolds[fold])
  117. res += `${text[fold]}\\`;
  118. res += `\n${indent}${text.slice(fold + 1, end)}`;
  119. }
  120. }
  121. return res;
  122. }
  123. /**
  124. * Presumes `i + 1` is at the start of a line
  125. * @returns index of last newline in more-indented block
  126. */
  127. function consumeMoreIndentedLines(text, i, indent) {
  128. let end = i;
  129. let start = i + 1;
  130. let ch = text[start];
  131. while (ch === ' ' || ch === '\t') {
  132. if (i < start + indent) {
  133. ch = text[++i];
  134. }
  135. else {
  136. do {
  137. ch = text[++i];
  138. } while (ch && ch !== '\n');
  139. end = i;
  140. start = i + 1;
  141. ch = text[start];
  142. }
  143. }
  144. return end;
  145. }
  146. exports.FOLD_BLOCK = FOLD_BLOCK;
  147. exports.FOLD_FLOW = FOLD_FLOW;
  148. exports.FOLD_QUOTED = FOLD_QUOTED;
  149. exports.foldFlowLines = foldFlowLines;