foldFlowLines.js 4.5 KB

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