UNPKG

4.82 kBJavaScriptView Raw
1'use strict';
2
3const FOLD_FLOW = 'flow';
4const FOLD_BLOCK = 'block';
5const FOLD_QUOTED = 'quoted';
6/**
7 * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
8 * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
9 * terminated with `\n` and started with `indent`.
10 */
11function foldFlowLines(text, indent, mode = 'flow', { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
12 if (!lineWidth || lineWidth < 0)
13 return text;
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 */
126function 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
146exports.FOLD_BLOCK = FOLD_BLOCK;
147exports.FOLD_FLOW = FOLD_FLOW;
148exports.FOLD_QUOTED = FOLD_QUOTED;
149exports.foldFlowLines = foldFlowLines;