1 | 'use strict';
|
---|
2 |
|
---|
3 | const FOLD_FLOW = 'flow';
|
---|
4 | const FOLD_BLOCK = 'block';
|
---|
5 | const 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 | */
|
---|
11 | function 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);
|
---|
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);
|
---|
58 | end = i + 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) {
|
---|
127 | let ch = text[i + 1];
|
---|
128 | while (ch === ' ' || ch === '\t') {
|
---|
129 | do {
|
---|
130 | ch = text[(i += 1)];
|
---|
131 | } while (ch && ch !== '\n');
|
---|
132 | ch = text[i + 1];
|
---|
133 | }
|
---|
134 | return i;
|
---|
135 | }
|
---|
136 |
|
---|
137 | exports.FOLD_BLOCK = FOLD_BLOCK;
|
---|
138 | exports.FOLD_FLOW = FOLD_FLOW;
|
---|
139 | exports.FOLD_QUOTED = FOLD_QUOTED;
|
---|
140 | exports.foldFlowLines = foldFlowLines;
|
---|