foldFlowLines.js 4.7 KB

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