foldFlowLines.js 4.4 KB

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