@cjser/wrap-ansi 10.0.0-cjser.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjser/index.cjs +380 -0
- package/index.d.ts +41 -0
- package/index.js +468 -0
- package/license +9 -0
- package/package.json +101 -0
- package/readme.md +82 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// packages/@cjser/wrap-ansi/index.js
|
|
30
|
+
var index_exports = {};
|
|
31
|
+
__export(index_exports, {
|
|
32
|
+
default: () => wrapAnsi
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
var import_string_width = __toESM(require("@cjser/string-width"), 1);
|
|
36
|
+
var import_strip_ansi = __toESM(require("@cjser/strip-ansi"), 1);
|
|
37
|
+
var import_ansi_styles = __toESM(require("@cjser/ansi-styles"), 1);
|
|
38
|
+
var ANSI_ESCAPE = "\x1B";
|
|
39
|
+
var ANSI_ESCAPE_CSI = "\x9B";
|
|
40
|
+
var ESCAPES = /* @__PURE__ */ new Set([
|
|
41
|
+
ANSI_ESCAPE,
|
|
42
|
+
ANSI_ESCAPE_CSI
|
|
43
|
+
]);
|
|
44
|
+
var ANSI_ESCAPE_BELL = "\x07";
|
|
45
|
+
var ANSI_CSI = "[";
|
|
46
|
+
var ANSI_OSC = "]";
|
|
47
|
+
var ANSI_SGR_TERMINATOR = "m";
|
|
48
|
+
var ANSI_SGR_RESET = 0;
|
|
49
|
+
var ANSI_SGR_RESET_FOREGROUND = 39;
|
|
50
|
+
var ANSI_SGR_RESET_BACKGROUND = 49;
|
|
51
|
+
var ANSI_SGR_RESET_UNDERLINE_COLOR = 59;
|
|
52
|
+
var ANSI_SGR_FOREGROUND_EXTENDED = 38;
|
|
53
|
+
var ANSI_SGR_BACKGROUND_EXTENDED = 48;
|
|
54
|
+
var ANSI_SGR_UNDERLINE_COLOR_EXTENDED = 58;
|
|
55
|
+
var ANSI_SGR_COLOR_MODE_256 = 5;
|
|
56
|
+
var ANSI_SGR_COLOR_MODE_RGB = 2;
|
|
57
|
+
var ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
|
|
58
|
+
var ANSI_ESCAPE_REGEX = new RegExp(`^\\u001B(?:\\${ANSI_CSI}(?<sgr>[0-9;]*)${ANSI_SGR_TERMINATOR}|${ANSI_ESCAPE_LINK}(?<uri>[^\\u0007\\u001B]*)(?:\\u0007|\\u001B\\\\))`);
|
|
59
|
+
var ANSI_ESCAPE_CSI_REGEX = new RegExp(`^\\u009B(?<sgr>[0-9;]*)${ANSI_SGR_TERMINATOR}`);
|
|
60
|
+
var ANSI_SGR_MODIFIER_CLOSE_CODES = new Set(import_ansi_styles.default.codes.values());
|
|
61
|
+
ANSI_SGR_MODIFIER_CLOSE_CODES.delete(ANSI_SGR_RESET);
|
|
62
|
+
var segmenter = new Intl.Segmenter();
|
|
63
|
+
var getGraphemes = (string) => Array.from(segmenter.segment(string), ({ segment }) => segment);
|
|
64
|
+
var TAB_SIZE = 8;
|
|
65
|
+
var wrapAnsiCode = (code) => `${ANSI_ESCAPE}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
|
|
66
|
+
var wrapAnsiHyperlink = (url) => `${ANSI_ESCAPE}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
|
|
67
|
+
var getSgrTokens = (sgrParameters) => {
|
|
68
|
+
const codes = sgrParameters.split(";").map((sgrParameter) => sgrParameter === "" ? ANSI_SGR_RESET : Number.parseInt(sgrParameter, 10));
|
|
69
|
+
const sgrTokens = [];
|
|
70
|
+
for (let index = 0; index < codes.length; index++) {
|
|
71
|
+
const code = codes[index];
|
|
72
|
+
if (!Number.isFinite(code)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (code === ANSI_SGR_FOREGROUND_EXTENDED || code === ANSI_SGR_BACKGROUND_EXTENDED || code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED) {
|
|
76
|
+
if (index + 1 >= codes.length) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const mode = codes[index + 1];
|
|
80
|
+
if (mode === ANSI_SGR_COLOR_MODE_256 && Number.isFinite(codes[index + 2])) {
|
|
81
|
+
sgrTokens.push([code, mode, codes[index + 2]]);
|
|
82
|
+
index += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const red = codes[index + 2];
|
|
86
|
+
const green = codes[index + 3];
|
|
87
|
+
const blue = codes[index + 4];
|
|
88
|
+
if (mode === ANSI_SGR_COLOR_MODE_RGB && Number.isFinite(red) && Number.isFinite(green) && Number.isFinite(blue)) {
|
|
89
|
+
sgrTokens.push([code, mode, red, green, blue]);
|
|
90
|
+
index += 4;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
sgrTokens.push([code]);
|
|
96
|
+
}
|
|
97
|
+
return sgrTokens;
|
|
98
|
+
};
|
|
99
|
+
var removeActiveStyle = (activeStyles, family) => {
|
|
100
|
+
const activeStyleIndex = activeStyles.findIndex((activeStyle) => activeStyle.family === family);
|
|
101
|
+
if (activeStyleIndex !== -1) {
|
|
102
|
+
activeStyles.splice(activeStyleIndex, 1);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var upsertActiveStyle = (activeStyles, nextActiveStyle) => {
|
|
106
|
+
removeActiveStyle(activeStyles, nextActiveStyle.family);
|
|
107
|
+
activeStyles.push(nextActiveStyle);
|
|
108
|
+
};
|
|
109
|
+
var removeModifierStylesByClose = (activeStyles, closeCode) => {
|
|
110
|
+
for (let index = activeStyles.length - 1; index >= 0; index--) {
|
|
111
|
+
const activeStyle = activeStyles[index];
|
|
112
|
+
if (activeStyle.family.startsWith("modifier-") && activeStyle.close === closeCode) {
|
|
113
|
+
activeStyles.splice(index, 1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var getColorStyle = (code, sgrToken) => {
|
|
118
|
+
if (code >= 30 && code <= 37 || code >= 90 && code <= 97 || code === ANSI_SGR_FOREGROUND_EXTENDED && sgrToken.length > 1) {
|
|
119
|
+
return {
|
|
120
|
+
family: "foreground",
|
|
121
|
+
open: sgrToken.join(";"),
|
|
122
|
+
close: ANSI_SGR_RESET_FOREGROUND
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (code >= 40 && code <= 47 || code >= 100 && code <= 107 || code === ANSI_SGR_BACKGROUND_EXTENDED && sgrToken.length > 1) {
|
|
126
|
+
return {
|
|
127
|
+
family: "background",
|
|
128
|
+
open: sgrToken.join(";"),
|
|
129
|
+
close: ANSI_SGR_RESET_BACKGROUND
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED && sgrToken.length > 1) {
|
|
133
|
+
return {
|
|
134
|
+
family: "underlineColor",
|
|
135
|
+
open: sgrToken.join(";"),
|
|
136
|
+
close: ANSI_SGR_RESET_UNDERLINE_COLOR
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var applySgrResetCode = (code, activeStyles) => {
|
|
141
|
+
if (code === ANSI_SGR_RESET) {
|
|
142
|
+
activeStyles.length = 0;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (code === ANSI_SGR_RESET_FOREGROUND) {
|
|
146
|
+
removeActiveStyle(activeStyles, "foreground");
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (code === ANSI_SGR_RESET_BACKGROUND) {
|
|
150
|
+
removeActiveStyle(activeStyles, "background");
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (code === ANSI_SGR_RESET_UNDERLINE_COLOR) {
|
|
154
|
+
removeActiveStyle(activeStyles, "underlineColor");
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (ANSI_SGR_MODIFIER_CLOSE_CODES.has(code)) {
|
|
158
|
+
removeModifierStylesByClose(activeStyles, code);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
var applySgrToken = (sgrToken, activeStyles) => {
|
|
164
|
+
const [code] = sgrToken;
|
|
165
|
+
if (applySgrResetCode(code, activeStyles)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const colorStyle = getColorStyle(code, sgrToken);
|
|
169
|
+
if (colorStyle) {
|
|
170
|
+
upsertActiveStyle(activeStyles, colorStyle);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const close = import_ansi_styles.default.codes.get(code);
|
|
174
|
+
if (close !== void 0 && close !== ANSI_SGR_RESET) {
|
|
175
|
+
upsertActiveStyle(activeStyles, {
|
|
176
|
+
family: `modifier-${code}`,
|
|
177
|
+
open: sgrToken.join(";"),
|
|
178
|
+
close
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
var applySgrParameters = (sgrParameters, activeStyles) => {
|
|
183
|
+
for (const sgrToken of getSgrTokens(sgrParameters)) {
|
|
184
|
+
applySgrToken(sgrToken, activeStyles);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var applySgrResets = (sgrParameters, activeStyles) => {
|
|
188
|
+
for (const sgrToken of getSgrTokens(sgrParameters)) {
|
|
189
|
+
const [code] = sgrToken;
|
|
190
|
+
applySgrResetCode(code, activeStyles);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var applyLeadingSgrResets = (string, activeStyles) => {
|
|
194
|
+
let remainder = string;
|
|
195
|
+
while (remainder.length > 0) {
|
|
196
|
+
if (remainder.startsWith(ANSI_ESCAPE) && remainder[1] !== "\\") {
|
|
197
|
+
const match = ANSI_ESCAPE_REGEX.exec(remainder);
|
|
198
|
+
if (!match) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (match.groups.sgr !== void 0) {
|
|
202
|
+
applySgrResets(match.groups.sgr, activeStyles);
|
|
203
|
+
}
|
|
204
|
+
remainder = remainder.slice(match[0].length);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (remainder.startsWith(ANSI_ESCAPE_CSI)) {
|
|
208
|
+
const match = ANSI_ESCAPE_CSI_REGEX.exec(remainder);
|
|
209
|
+
if (!match || match.groups.sgr === void 0) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
applySgrResets(match.groups.sgr, activeStyles);
|
|
213
|
+
remainder = remainder.slice(match[0].length);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
var getClosingSgrSequence = (activeStyles) => [...activeStyles].reverse().map((activeStyle) => wrapAnsiCode(activeStyle.close)).join("");
|
|
220
|
+
var getOpeningSgrSequence = (activeStyles) => activeStyles.map((activeStyle) => wrapAnsiCode(activeStyle.open)).join("");
|
|
221
|
+
var wordLengths = (string) => string.split(" ").map((word) => (0, import_string_width.default)(word));
|
|
222
|
+
var wrapWord = (rows, word, columns) => {
|
|
223
|
+
const characters = getGraphemes(word);
|
|
224
|
+
let isInsideEscape = false;
|
|
225
|
+
let isInsideLinkEscape = false;
|
|
226
|
+
let visible = (0, import_string_width.default)((0, import_strip_ansi.default)(rows.at(-1)));
|
|
227
|
+
for (const [index, character] of characters.entries()) {
|
|
228
|
+
const characterLength = (0, import_string_width.default)(character);
|
|
229
|
+
if (visible + characterLength <= columns) {
|
|
230
|
+
rows[rows.length - 1] += character;
|
|
231
|
+
} else {
|
|
232
|
+
rows.push(character);
|
|
233
|
+
visible = 0;
|
|
234
|
+
}
|
|
235
|
+
if (ESCAPES.has(character) && !(isInsideLinkEscape && character === ANSI_ESCAPE && characters[index + 1] === "\\")) {
|
|
236
|
+
isInsideEscape = true;
|
|
237
|
+
const ansiEscapeLinkCandidate = characters.slice(index + 1, index + 1 + ANSI_ESCAPE_LINK.length).join("");
|
|
238
|
+
isInsideLinkEscape = ansiEscapeLinkCandidate === ANSI_ESCAPE_LINK;
|
|
239
|
+
}
|
|
240
|
+
if (isInsideEscape) {
|
|
241
|
+
if (isInsideLinkEscape) {
|
|
242
|
+
if (character === ANSI_ESCAPE_BELL || character === "\\" && index > 0 && characters[index - 1] === ANSI_ESCAPE) {
|
|
243
|
+
isInsideEscape = false;
|
|
244
|
+
isInsideLinkEscape = false;
|
|
245
|
+
}
|
|
246
|
+
} else if (character === ANSI_SGR_TERMINATOR) {
|
|
247
|
+
isInsideEscape = false;
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
visible += characterLength;
|
|
252
|
+
if (visible === columns && index < characters.length - 1) {
|
|
253
|
+
rows.push("");
|
|
254
|
+
visible = 0;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!visible && rows.at(-1).length > 0 && rows.length > 1) {
|
|
258
|
+
rows[rows.length - 2] += rows.pop();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
var stringVisibleTrimSpacesRight = (string) => {
|
|
262
|
+
const words = string.split(" ");
|
|
263
|
+
let last = words.length;
|
|
264
|
+
while (last > 0) {
|
|
265
|
+
if ((0, import_string_width.default)(words[last - 1]) > 0) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
last--;
|
|
269
|
+
}
|
|
270
|
+
if (last === words.length) {
|
|
271
|
+
return string;
|
|
272
|
+
}
|
|
273
|
+
return words.slice(0, last).join(" ") + words.slice(last).join("");
|
|
274
|
+
};
|
|
275
|
+
var expandTabs = (line) => {
|
|
276
|
+
if (!line.includes(" ")) {
|
|
277
|
+
return line;
|
|
278
|
+
}
|
|
279
|
+
const segments = line.split(" ");
|
|
280
|
+
let visible = 0;
|
|
281
|
+
let expandedLine = "";
|
|
282
|
+
for (const [index, segment] of segments.entries()) {
|
|
283
|
+
expandedLine += segment;
|
|
284
|
+
visible += (0, import_string_width.default)(segment);
|
|
285
|
+
if (index < segments.length - 1) {
|
|
286
|
+
const spaces = TAB_SIZE - visible % TAB_SIZE;
|
|
287
|
+
expandedLine += " ".repeat(spaces);
|
|
288
|
+
visible += spaces;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return expandedLine;
|
|
292
|
+
};
|
|
293
|
+
var exec = (string, columns, options = {}) => {
|
|
294
|
+
if (options.trim !== false && string.trim() === "") {
|
|
295
|
+
return "";
|
|
296
|
+
}
|
|
297
|
+
let returnValue = "";
|
|
298
|
+
let escapeUrl;
|
|
299
|
+
const activeStyles = [];
|
|
300
|
+
const lengths = wordLengths(string);
|
|
301
|
+
let rows = [""];
|
|
302
|
+
for (const [index, word] of string.split(" ").entries()) {
|
|
303
|
+
if (options.trim !== false) {
|
|
304
|
+
rows[rows.length - 1] = rows.at(-1).trimStart();
|
|
305
|
+
}
|
|
306
|
+
let rowLength = (0, import_string_width.default)(rows.at(-1));
|
|
307
|
+
if (index !== 0) {
|
|
308
|
+
if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
|
|
309
|
+
rows.push("");
|
|
310
|
+
rowLength = 0;
|
|
311
|
+
}
|
|
312
|
+
if (rowLength > 0 || options.trim === false) {
|
|
313
|
+
rows[rows.length - 1] += " ";
|
|
314
|
+
rowLength++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (options.hard && options.wordWrap !== false && lengths[index] > columns) {
|
|
318
|
+
const remainingColumns = columns - rowLength;
|
|
319
|
+
const breaksStartingThisLine = 1 + Math.floor((lengths[index] - remainingColumns - 1) / columns);
|
|
320
|
+
const breaksStartingNextLine = Math.floor((lengths[index] - 1) / columns);
|
|
321
|
+
if (breaksStartingNextLine < breaksStartingThisLine) {
|
|
322
|
+
rows.push("");
|
|
323
|
+
}
|
|
324
|
+
wrapWord(rows, word, columns);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (rowLength + lengths[index] > columns && rowLength > 0 && lengths[index] > 0) {
|
|
328
|
+
if (options.wordWrap === false && rowLength < columns) {
|
|
329
|
+
wrapWord(rows, word, columns);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
rows.push("");
|
|
333
|
+
}
|
|
334
|
+
if (rowLength + lengths[index] > columns && options.wordWrap === false) {
|
|
335
|
+
wrapWord(rows, word, columns);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
rows[rows.length - 1] += word;
|
|
339
|
+
}
|
|
340
|
+
if (options.trim !== false) {
|
|
341
|
+
rows = rows.map((row) => stringVisibleTrimSpacesRight(row));
|
|
342
|
+
}
|
|
343
|
+
const preString = rows.join("\n");
|
|
344
|
+
const pre = getGraphemes(preString);
|
|
345
|
+
let preStringIndex = 0;
|
|
346
|
+
for (const [index, character] of pre.entries()) {
|
|
347
|
+
returnValue += character;
|
|
348
|
+
if (character === ANSI_ESCAPE && pre[index + 1] !== "\\") {
|
|
349
|
+
const { groups } = ANSI_ESCAPE_REGEX.exec(preString.slice(preStringIndex)) || { groups: {} };
|
|
350
|
+
if (groups.sgr !== void 0) {
|
|
351
|
+
applySgrParameters(groups.sgr, activeStyles);
|
|
352
|
+
} else if (groups.uri !== void 0) {
|
|
353
|
+
escapeUrl = groups.uri.length === 0 ? void 0 : groups.uri;
|
|
354
|
+
}
|
|
355
|
+
} else if (character === ANSI_ESCAPE_CSI) {
|
|
356
|
+
const { groups } = ANSI_ESCAPE_CSI_REGEX.exec(preString.slice(preStringIndex)) || { groups: {} };
|
|
357
|
+
if (groups.sgr !== void 0) {
|
|
358
|
+
applySgrParameters(groups.sgr, activeStyles);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (pre[index + 1] === "\n") {
|
|
362
|
+
if (escapeUrl) {
|
|
363
|
+
returnValue += wrapAnsiHyperlink("");
|
|
364
|
+
}
|
|
365
|
+
returnValue += getClosingSgrSequence(activeStyles);
|
|
366
|
+
} else if (character === "\n") {
|
|
367
|
+
const openingStyles = [...activeStyles];
|
|
368
|
+
applyLeadingSgrResets(preString.slice(preStringIndex + 1), openingStyles);
|
|
369
|
+
returnValue += getOpeningSgrSequence(openingStyles);
|
|
370
|
+
if (escapeUrl) {
|
|
371
|
+
returnValue += wrapAnsiHyperlink(escapeUrl);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
preStringIndex += character.length;
|
|
375
|
+
}
|
|
376
|
+
return returnValue;
|
|
377
|
+
};
|
|
378
|
+
function wrapAnsi(string, columns, options) {
|
|
379
|
+
return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec(expandTabs(line), columns, options)).join("\n");
|
|
380
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type Options = {
|
|
2
|
+
/**
|
|
3
|
+
By default the wrap is soft, meaning long words may extend past the column width. Setting this to `true` will make it hard wrap at the column width.
|
|
4
|
+
|
|
5
|
+
@default false
|
|
6
|
+
*/
|
|
7
|
+
readonly hard?: boolean;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
By default, an attempt is made to split words at spaces, ensuring that they don't extend past the configured columns. If wordWrap is `false`, each column will instead be completely filled splitting words as necessary.
|
|
11
|
+
|
|
12
|
+
@default true
|
|
13
|
+
*/
|
|
14
|
+
readonly wordWrap?: boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
Whitespace on all lines is removed by default. Set this option to `false` if you don't want to trim.
|
|
18
|
+
|
|
19
|
+
@default true
|
|
20
|
+
*/
|
|
21
|
+
readonly trim?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Wrap words to the specified column width.
|
|
26
|
+
|
|
27
|
+
@param string - A string with ANSI escape codes, like one styled by [`chalk`](https://github.com/chalk/chalk). Newline characters will be normalized to `\n`. Tab characters are expanded to spaces using 8-column tab stops before wrapping.
|
|
28
|
+
@param columns - The number of columns to wrap the text to.
|
|
29
|
+
|
|
30
|
+
@example
|
|
31
|
+
```
|
|
32
|
+
import chalk from 'chalk';
|
|
33
|
+
import wrapAnsi from '@cjser/wrap-ansi';
|
|
34
|
+
|
|
35
|
+
const input = 'The quick brown ' + chalk.red('fox jumped over ') +
|
|
36
|
+
'the lazy ' + chalk.green('dog and then ran away with the unicorn.');
|
|
37
|
+
|
|
38
|
+
console.log(wrapAnsi(input, 20));
|
|
39
|
+
```
|
|
40
|
+
*/
|
|
41
|
+
export default function wrapAnsi(string: string, columns: number, options?: Options): string;
|
package/index.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import stringWidth from '@cjser/string-width';
|
|
2
|
+
import stripAnsi from '@cjser/strip-ansi';
|
|
3
|
+
import ansiStyles from '@cjser/ansi-styles';
|
|
4
|
+
|
|
5
|
+
const ANSI_ESCAPE = '\u001B';
|
|
6
|
+
const ANSI_ESCAPE_CSI = '\u009B';
|
|
7
|
+
const ESCAPES = new Set([
|
|
8
|
+
ANSI_ESCAPE,
|
|
9
|
+
ANSI_ESCAPE_CSI,
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const ANSI_ESCAPE_BELL = '\u0007';
|
|
13
|
+
const ANSI_CSI = '[';
|
|
14
|
+
const ANSI_OSC = ']';
|
|
15
|
+
const ANSI_SGR_TERMINATOR = 'm';
|
|
16
|
+
const ANSI_SGR_RESET = 0;
|
|
17
|
+
const ANSI_SGR_RESET_FOREGROUND = 39;
|
|
18
|
+
const ANSI_SGR_RESET_BACKGROUND = 49;
|
|
19
|
+
const ANSI_SGR_RESET_UNDERLINE_COLOR = 59;
|
|
20
|
+
const ANSI_SGR_FOREGROUND_EXTENDED = 38;
|
|
21
|
+
const ANSI_SGR_BACKGROUND_EXTENDED = 48;
|
|
22
|
+
const ANSI_SGR_UNDERLINE_COLOR_EXTENDED = 58;
|
|
23
|
+
const ANSI_SGR_COLOR_MODE_256 = 5;
|
|
24
|
+
const ANSI_SGR_COLOR_MODE_RGB = 2;
|
|
25
|
+
const ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
|
|
26
|
+
const ANSI_ESCAPE_REGEX = new RegExp(`^\\u001B(?:\\${ANSI_CSI}(?<sgr>[0-9;]*)${ANSI_SGR_TERMINATOR}|${ANSI_ESCAPE_LINK}(?<uri>[^\\u0007\\u001B]*)(?:\\u0007|\\u001B\\\\))`);
|
|
27
|
+
const ANSI_ESCAPE_CSI_REGEX = new RegExp(`^\\u009B(?<sgr>[0-9;]*)${ANSI_SGR_TERMINATOR}`);
|
|
28
|
+
const ANSI_SGR_MODIFIER_CLOSE_CODES = new Set(ansiStyles.codes.values());
|
|
29
|
+
ANSI_SGR_MODIFIER_CLOSE_CODES.delete(ANSI_SGR_RESET);
|
|
30
|
+
|
|
31
|
+
const segmenter = new Intl.Segmenter();
|
|
32
|
+
const getGraphemes = string => Array.from(segmenter.segment(string), ({segment}) => segment);
|
|
33
|
+
const TAB_SIZE = 8;
|
|
34
|
+
|
|
35
|
+
const wrapAnsiCode = code => `${ANSI_ESCAPE}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
|
|
36
|
+
const wrapAnsiHyperlink = url => `${ANSI_ESCAPE}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
|
|
37
|
+
|
|
38
|
+
const getSgrTokens = sgrParameters => {
|
|
39
|
+
const codes = sgrParameters.split(';').map(sgrParameter => sgrParameter === '' ? ANSI_SGR_RESET : Number.parseInt(sgrParameter, 10));
|
|
40
|
+
const sgrTokens = [];
|
|
41
|
+
|
|
42
|
+
for (let index = 0; index < codes.length; index++) {
|
|
43
|
+
const code = codes[index];
|
|
44
|
+
|
|
45
|
+
if (!Number.isFinite(code)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
(
|
|
51
|
+
code === ANSI_SGR_FOREGROUND_EXTENDED
|
|
52
|
+
|| code === ANSI_SGR_BACKGROUND_EXTENDED
|
|
53
|
+
|| code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
if (index + 1 >= codes.length) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const mode = codes[index + 1];
|
|
61
|
+
|
|
62
|
+
if (mode === ANSI_SGR_COLOR_MODE_256 && Number.isFinite(codes[index + 2])) {
|
|
63
|
+
sgrTokens.push([code, mode, codes[index + 2]]);
|
|
64
|
+
index += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const red = codes[index + 2];
|
|
69
|
+
const green = codes[index + 3];
|
|
70
|
+
const blue = codes[index + 4];
|
|
71
|
+
if (
|
|
72
|
+
mode === ANSI_SGR_COLOR_MODE_RGB
|
|
73
|
+
&& Number.isFinite(red)
|
|
74
|
+
&& Number.isFinite(green)
|
|
75
|
+
&& Number.isFinite(blue)
|
|
76
|
+
) {
|
|
77
|
+
sgrTokens.push([code, mode, red, green, blue]);
|
|
78
|
+
index += 4;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
sgrTokens.push([code]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return sgrTokens;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const removeActiveStyle = (activeStyles, family) => {
|
|
92
|
+
const activeStyleIndex = activeStyles.findIndex(activeStyle => activeStyle.family === family);
|
|
93
|
+
|
|
94
|
+
if (activeStyleIndex !== -1) {
|
|
95
|
+
activeStyles.splice(activeStyleIndex, 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const upsertActiveStyle = (activeStyles, nextActiveStyle) => {
|
|
100
|
+
removeActiveStyle(activeStyles, nextActiveStyle.family);
|
|
101
|
+
activeStyles.push(nextActiveStyle);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const removeModifierStylesByClose = (activeStyles, closeCode) => {
|
|
105
|
+
for (let index = activeStyles.length - 1; index >= 0; index--) {
|
|
106
|
+
const activeStyle = activeStyles[index];
|
|
107
|
+
if (activeStyle.family.startsWith('modifier-') && activeStyle.close === closeCode) {
|
|
108
|
+
activeStyles.splice(index, 1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getColorStyle = (code, sgrToken) => {
|
|
114
|
+
if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97) || (code === ANSI_SGR_FOREGROUND_EXTENDED && sgrToken.length > 1)) {
|
|
115
|
+
return {
|
|
116
|
+
family: 'foreground',
|
|
117
|
+
open: sgrToken.join(';'),
|
|
118
|
+
close: ANSI_SGR_RESET_FOREGROUND,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107) || (code === ANSI_SGR_BACKGROUND_EXTENDED && sgrToken.length > 1)) {
|
|
123
|
+
return {
|
|
124
|
+
family: 'background',
|
|
125
|
+
open: sgrToken.join(';'),
|
|
126
|
+
close: ANSI_SGR_RESET_BACKGROUND,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (code === ANSI_SGR_UNDERLINE_COLOR_EXTENDED && sgrToken.length > 1) {
|
|
131
|
+
return {
|
|
132
|
+
family: 'underlineColor',
|
|
133
|
+
open: sgrToken.join(';'),
|
|
134
|
+
close: ANSI_SGR_RESET_UNDERLINE_COLOR,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const applySgrResetCode = (code, activeStyles) => {
|
|
140
|
+
if (code === ANSI_SGR_RESET) {
|
|
141
|
+
activeStyles.length = 0;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (code === ANSI_SGR_RESET_FOREGROUND) {
|
|
146
|
+
removeActiveStyle(activeStyles, 'foreground');
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (code === ANSI_SGR_RESET_BACKGROUND) {
|
|
151
|
+
removeActiveStyle(activeStyles, 'background');
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (code === ANSI_SGR_RESET_UNDERLINE_COLOR) {
|
|
156
|
+
removeActiveStyle(activeStyles, 'underlineColor');
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (ANSI_SGR_MODIFIER_CLOSE_CODES.has(code)) {
|
|
161
|
+
removeModifierStylesByClose(activeStyles, code);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return false;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const applySgrToken = (sgrToken, activeStyles) => {
|
|
169
|
+
const [code] = sgrToken;
|
|
170
|
+
|
|
171
|
+
if (applySgrResetCode(code, activeStyles)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const colorStyle = getColorStyle(code, sgrToken);
|
|
176
|
+
if (colorStyle) {
|
|
177
|
+
upsertActiveStyle(activeStyles, colorStyle);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const close = ansiStyles.codes.get(code);
|
|
182
|
+
if (close !== undefined && close !== ANSI_SGR_RESET) {
|
|
183
|
+
upsertActiveStyle(activeStyles, {
|
|
184
|
+
family: `modifier-${code}`,
|
|
185
|
+
open: sgrToken.join(';'),
|
|
186
|
+
close,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const applySgrParameters = (sgrParameters, activeStyles) => {
|
|
192
|
+
for (const sgrToken of getSgrTokens(sgrParameters)) {
|
|
193
|
+
applySgrToken(sgrToken, activeStyles);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const applySgrResets = (sgrParameters, activeStyles) => {
|
|
198
|
+
for (const sgrToken of getSgrTokens(sgrParameters)) {
|
|
199
|
+
const [code] = sgrToken;
|
|
200
|
+
applySgrResetCode(code, activeStyles);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const applyLeadingSgrResets = (string, activeStyles) => {
|
|
205
|
+
let remainder = string;
|
|
206
|
+
|
|
207
|
+
while (remainder.length > 0) {
|
|
208
|
+
if (remainder.startsWith(ANSI_ESCAPE) && remainder[1] !== '\\') {
|
|
209
|
+
const match = ANSI_ESCAPE_REGEX.exec(remainder);
|
|
210
|
+
if (!match) {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (match.groups.sgr !== undefined) {
|
|
215
|
+
applySgrResets(match.groups.sgr, activeStyles);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
remainder = remainder.slice(match[0].length);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (remainder.startsWith(ANSI_ESCAPE_CSI)) {
|
|
223
|
+
const match = ANSI_ESCAPE_CSI_REGEX.exec(remainder);
|
|
224
|
+
if (!match || match.groups.sgr === undefined) {
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
applySgrResets(match.groups.sgr, activeStyles);
|
|
229
|
+
remainder = remainder.slice(match[0].length);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const getClosingSgrSequence = activeStyles => [...activeStyles].reverse().map(activeStyle => wrapAnsiCode(activeStyle.close)).join('');
|
|
238
|
+
const getOpeningSgrSequence = activeStyles => activeStyles.map(activeStyle => wrapAnsiCode(activeStyle.open)).join('');
|
|
239
|
+
|
|
240
|
+
// Calculate the length of words split on ' ', ignoring
|
|
241
|
+
// the extra characters added by ANSI escape codes
|
|
242
|
+
const wordLengths = string => string.split(' ').map(word => stringWidth(word));
|
|
243
|
+
|
|
244
|
+
// Wrap a long word across multiple rows
|
|
245
|
+
// ANSI escape codes do not count towards length
|
|
246
|
+
const wrapWord = (rows, word, columns) => {
|
|
247
|
+
const characters = getGraphemes(word);
|
|
248
|
+
|
|
249
|
+
let isInsideEscape = false;
|
|
250
|
+
let isInsideLinkEscape = false;
|
|
251
|
+
let visible = stringWidth(stripAnsi(rows.at(-1)));
|
|
252
|
+
|
|
253
|
+
for (const [index, character] of characters.entries()) {
|
|
254
|
+
const characterLength = stringWidth(character);
|
|
255
|
+
|
|
256
|
+
if (visible + characterLength <= columns) {
|
|
257
|
+
rows[rows.length - 1] += character;
|
|
258
|
+
} else {
|
|
259
|
+
rows.push(character);
|
|
260
|
+
visible = 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (ESCAPES.has(character) && !(isInsideLinkEscape && character === ANSI_ESCAPE && characters[index + 1] === '\\')) {
|
|
264
|
+
isInsideEscape = true;
|
|
265
|
+
|
|
266
|
+
const ansiEscapeLinkCandidate = characters.slice(index + 1, index + 1 + ANSI_ESCAPE_LINK.length).join('');
|
|
267
|
+
isInsideLinkEscape = ansiEscapeLinkCandidate === ANSI_ESCAPE_LINK;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isInsideEscape) {
|
|
271
|
+
if (isInsideLinkEscape) {
|
|
272
|
+
if (
|
|
273
|
+
character === ANSI_ESCAPE_BELL
|
|
274
|
+
|| (character === '\\' && index > 0 && characters[index - 1] === ANSI_ESCAPE) // ST terminator (ESC \)
|
|
275
|
+
) {
|
|
276
|
+
isInsideEscape = false;
|
|
277
|
+
isInsideLinkEscape = false;
|
|
278
|
+
}
|
|
279
|
+
} else if (character === ANSI_SGR_TERMINATOR) {
|
|
280
|
+
isInsideEscape = false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
visible += characterLength;
|
|
287
|
+
|
|
288
|
+
if (visible === columns && index < characters.length - 1) {
|
|
289
|
+
rows.push('');
|
|
290
|
+
visible = 0;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// It's possible that the last row we copy over is only
|
|
295
|
+
// ANSI escape characters, handle this edge-case
|
|
296
|
+
if (!visible && rows.at(-1).length > 0 && rows.length > 1) {
|
|
297
|
+
rows[rows.length - 2] += rows.pop();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// Trims spaces from a string ignoring invisible sequences
|
|
302
|
+
const stringVisibleTrimSpacesRight = string => {
|
|
303
|
+
const words = string.split(' ');
|
|
304
|
+
let last = words.length;
|
|
305
|
+
|
|
306
|
+
while (last > 0) {
|
|
307
|
+
if (stringWidth(words[last - 1]) > 0) {
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
last--;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (last === words.length) {
|
|
315
|
+
return string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return words.slice(0, last).join(' ') + words.slice(last).join('');
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const expandTabs = line => {
|
|
322
|
+
if (!line.includes('\t')) {
|
|
323
|
+
return line;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const segments = line.split('\t');
|
|
327
|
+
let visible = 0;
|
|
328
|
+
let expandedLine = '';
|
|
329
|
+
|
|
330
|
+
for (const [index, segment] of segments.entries()) {
|
|
331
|
+
expandedLine += segment;
|
|
332
|
+
visible += stringWidth(segment);
|
|
333
|
+
|
|
334
|
+
if (index < segments.length - 1) {
|
|
335
|
+
const spaces = TAB_SIZE - (visible % TAB_SIZE);
|
|
336
|
+
expandedLine += ' '.repeat(spaces);
|
|
337
|
+
visible += spaces;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return expandedLine;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// The wrap-ansi module can be invoked in either 'hard' or 'soft' wrap mode.
|
|
345
|
+
//
|
|
346
|
+
// 'hard' will never allow a string to take up more than columns characters.
|
|
347
|
+
//
|
|
348
|
+
// 'soft' allows long words to expand past the column length.
|
|
349
|
+
const exec = (string, columns, options = {}) => {
|
|
350
|
+
if (options.trim !== false && string.trim() === '') {
|
|
351
|
+
return '';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let returnValue = '';
|
|
355
|
+
let escapeUrl;
|
|
356
|
+
const activeStyles = [];
|
|
357
|
+
|
|
358
|
+
const lengths = wordLengths(string);
|
|
359
|
+
let rows = [''];
|
|
360
|
+
|
|
361
|
+
for (const [index, word] of string.split(' ').entries()) {
|
|
362
|
+
if (options.trim !== false) {
|
|
363
|
+
rows[rows.length - 1] = rows.at(-1).trimStart();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let rowLength = stringWidth(rows.at(-1));
|
|
367
|
+
|
|
368
|
+
if (index !== 0) {
|
|
369
|
+
if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
|
|
370
|
+
// If we start with a new word but the current row length equals the length of the columns, add a new row
|
|
371
|
+
rows.push('');
|
|
372
|
+
rowLength = 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (rowLength > 0 || options.trim === false) {
|
|
376
|
+
rows[rows.length - 1] += ' ';
|
|
377
|
+
rowLength++;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// In 'hard' wrap mode, the length of a line is never allowed to extend past 'columns'
|
|
382
|
+
if (options.hard && options.wordWrap !== false && lengths[index] > columns) {
|
|
383
|
+
const remainingColumns = columns - rowLength;
|
|
384
|
+
const breaksStartingThisLine = 1 + Math.floor((lengths[index] - remainingColumns - 1) / columns);
|
|
385
|
+
const breaksStartingNextLine = Math.floor((lengths[index] - 1) / columns);
|
|
386
|
+
if (breaksStartingNextLine < breaksStartingThisLine) {
|
|
387
|
+
rows.push('');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
wrapWord(rows, word, columns);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (rowLength + lengths[index] > columns && rowLength > 0 && lengths[index] > 0) {
|
|
395
|
+
if (options.wordWrap === false && rowLength < columns) {
|
|
396
|
+
wrapWord(rows, word, columns);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
rows.push('');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (rowLength + lengths[index] > columns && options.wordWrap === false) {
|
|
404
|
+
wrapWord(rows, word, columns);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
rows[rows.length - 1] += word;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (options.trim !== false) {
|
|
412
|
+
rows = rows.map(row => stringVisibleTrimSpacesRight(row));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const preString = rows.join('\n');
|
|
416
|
+
const pre = getGraphemes(preString);
|
|
417
|
+
|
|
418
|
+
// We need to keep a separate index as `String#slice()` works on Unicode code units, while `pre` is an array of grapheme clusters.
|
|
419
|
+
let preStringIndex = 0;
|
|
420
|
+
|
|
421
|
+
for (const [index, character] of pre.entries()) {
|
|
422
|
+
returnValue += character;
|
|
423
|
+
|
|
424
|
+
if (character === ANSI_ESCAPE && pre[index + 1] !== '\\') {
|
|
425
|
+
const {groups} = ANSI_ESCAPE_REGEX.exec(preString.slice(preStringIndex)) || {groups: {}};
|
|
426
|
+
if (groups.sgr !== undefined) {
|
|
427
|
+
applySgrParameters(groups.sgr, activeStyles);
|
|
428
|
+
} else if (groups.uri !== undefined) {
|
|
429
|
+
escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;
|
|
430
|
+
}
|
|
431
|
+
} else if (character === ANSI_ESCAPE_CSI) {
|
|
432
|
+
const {groups} = ANSI_ESCAPE_CSI_REGEX.exec(preString.slice(preStringIndex)) || {groups: {}};
|
|
433
|
+
if (groups.sgr !== undefined) {
|
|
434
|
+
applySgrParameters(groups.sgr, activeStyles);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (pre[index + 1] === '\n') {
|
|
439
|
+
if (escapeUrl) {
|
|
440
|
+
returnValue += wrapAnsiHyperlink('');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
returnValue += getClosingSgrSequence(activeStyles);
|
|
444
|
+
} else if (character === '\n') {
|
|
445
|
+
const openingStyles = [...activeStyles];
|
|
446
|
+
applyLeadingSgrResets(preString.slice(preStringIndex + 1), openingStyles);
|
|
447
|
+
returnValue += getOpeningSgrSequence(openingStyles);
|
|
448
|
+
|
|
449
|
+
if (escapeUrl) {
|
|
450
|
+
returnValue += wrapAnsiHyperlink(escapeUrl);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
preStringIndex += character.length;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return returnValue;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// For each newline, invoke the method separately
|
|
461
|
+
export default function wrapAnsi(string, columns, options) {
|
|
462
|
+
return String(string)
|
|
463
|
+
.normalize()
|
|
464
|
+
.replaceAll('\r\n', '\n')
|
|
465
|
+
.split('\n')
|
|
466
|
+
.map(line => exec(expandTabs(line), columns, options))
|
|
467
|
+
.join('\n');
|
|
468
|
+
}
|
package/license
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cjser/wrap-ansi",
|
|
3
|
+
"version": "10.0.0-cjser.2",
|
|
4
|
+
"description": "Wordwrap a string with ANSI escape codes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://code.moenext.com/3rdeye/cjser.git"
|
|
9
|
+
},
|
|
10
|
+
"funding": "https://github.com/chalk/wrap-ansi?sponsor=1",
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Sindre Sorhus",
|
|
13
|
+
"email": "sindresorhus@gmail.com",
|
|
14
|
+
"url": "https://sindresorhus.com"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
"types": "./index.d.ts",
|
|
19
|
+
"require": "./dist-cjser/index.cjs",
|
|
20
|
+
"default": "./index.js"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "xo && nyc ava && tsd"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"index.d.ts",
|
|
31
|
+
"dist-cjser"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [
|
|
34
|
+
"wrap",
|
|
35
|
+
"break",
|
|
36
|
+
"wordwrap",
|
|
37
|
+
"wordbreak",
|
|
38
|
+
"linewrap",
|
|
39
|
+
"ansi",
|
|
40
|
+
"styles",
|
|
41
|
+
"color",
|
|
42
|
+
"colour",
|
|
43
|
+
"colors",
|
|
44
|
+
"terminal",
|
|
45
|
+
"console",
|
|
46
|
+
"cli",
|
|
47
|
+
"string",
|
|
48
|
+
"tty",
|
|
49
|
+
"escape",
|
|
50
|
+
"formatting",
|
|
51
|
+
"rgb",
|
|
52
|
+
"256",
|
|
53
|
+
"shell",
|
|
54
|
+
"xterm",
|
|
55
|
+
"log",
|
|
56
|
+
"logging",
|
|
57
|
+
"command-line",
|
|
58
|
+
"text"
|
|
59
|
+
],
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@cjser/ansi-styles": "6.2.3-cjser.2",
|
|
62
|
+
"@cjser/string-width": "8.2.1-cjser.2",
|
|
63
|
+
"@cjser/strip-ansi": "7.2.0-cjser.2"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"ava": "^6.4.1",
|
|
67
|
+
"chalk": "^5.6.2",
|
|
68
|
+
"coveralls": "^3.1.1",
|
|
69
|
+
"has-ansi": "^6.0.2",
|
|
70
|
+
"nyc": "^17.1.0",
|
|
71
|
+
"tsd": "^0.33.0",
|
|
72
|
+
"xo": "^1.2.3"
|
|
73
|
+
},
|
|
74
|
+
"types": "./index.d.ts",
|
|
75
|
+
"main": "./dist-cjser/index.cjs",
|
|
76
|
+
"cjser": {
|
|
77
|
+
"sourceVersion": "10.0.0",
|
|
78
|
+
"cjserVersion": 2,
|
|
79
|
+
"original": {
|
|
80
|
+
"name": "wrap-ansi",
|
|
81
|
+
"version": "10.0.0",
|
|
82
|
+
"exports": {
|
|
83
|
+
"types": "./index.d.ts",
|
|
84
|
+
"default": "./index.js"
|
|
85
|
+
},
|
|
86
|
+
"repository": "chalk/wrap-ansi",
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"ansi-styles": "^6.2.3",
|
|
89
|
+
"string-width": "^8.2.0",
|
|
90
|
+
"strip-ansi": "^7.1.2"
|
|
91
|
+
},
|
|
92
|
+
"files": [
|
|
93
|
+
"index.js",
|
|
94
|
+
"index.d.ts"
|
|
95
|
+
],
|
|
96
|
+
"scripts": {
|
|
97
|
+
"test": "xo && nyc ava && tsd"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# wrap-ansi
|
|
2
|
+
|
|
3
|
+
> Wordwrap a string with [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors_and_Styles)
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install wrap-ansi
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import wrapAnsi from 'wrap-ansi';
|
|
16
|
+
|
|
17
|
+
const input = 'The quick brown ' + chalk.red('fox jumped over ') +
|
|
18
|
+
'the lazy ' + chalk.green('dog and then ran away with the unicorn.');
|
|
19
|
+
|
|
20
|
+
console.log(wrapAnsi(input, 20));
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
<img width="331" src="screenshot.png">
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
### wrapAnsi(string, columns, options?)
|
|
28
|
+
|
|
29
|
+
Wrap words to the specified column width.
|
|
30
|
+
|
|
31
|
+
#### string
|
|
32
|
+
|
|
33
|
+
Type: `string`
|
|
34
|
+
|
|
35
|
+
A string with ANSI escape codes, like one styled by [`chalk`](https://github.com/chalk/chalk).
|
|
36
|
+
|
|
37
|
+
Newline characters will be normalized to `\n`.
|
|
38
|
+
|
|
39
|
+
Tab characters are expanded to spaces using 8-column tab stops before wrapping.
|
|
40
|
+
|
|
41
|
+
#### columns
|
|
42
|
+
|
|
43
|
+
Type: `number`
|
|
44
|
+
|
|
45
|
+
The number of columns to wrap the text to.
|
|
46
|
+
|
|
47
|
+
#### options
|
|
48
|
+
|
|
49
|
+
Type: `object`
|
|
50
|
+
|
|
51
|
+
##### hard
|
|
52
|
+
|
|
53
|
+
Type: `boolean`\
|
|
54
|
+
Default: `false`
|
|
55
|
+
|
|
56
|
+
By default the wrap is soft, meaning long words may extend past the column width. Setting this to `true` will make it hard wrap at the column width.
|
|
57
|
+
|
|
58
|
+
##### wordWrap
|
|
59
|
+
|
|
60
|
+
Type: `boolean`\
|
|
61
|
+
Default: `true`
|
|
62
|
+
|
|
63
|
+
By default, an attempt is made to split words at spaces, ensuring that they don't extend past the configured columns. If wordWrap is `false`, each column will instead be completely filled splitting words as necessary.
|
|
64
|
+
|
|
65
|
+
##### trim
|
|
66
|
+
|
|
67
|
+
Type: `boolean`\
|
|
68
|
+
Default: `true`
|
|
69
|
+
|
|
70
|
+
Whitespace on all lines is removed by default. Set this option to `false` if you don't want to trim.
|
|
71
|
+
|
|
72
|
+
## Related
|
|
73
|
+
|
|
74
|
+
- [slice-ansi](https://github.com/chalk/slice-ansi) - Slice a string with ANSI escape codes
|
|
75
|
+
- [cli-truncate](https://github.com/sindresorhus/cli-truncate) - Truncate a string to a specific width in the terminal
|
|
76
|
+
- [chalk](https://github.com/chalk/chalk) - Terminal string styling done right
|
|
77
|
+
- [jsesc](https://github.com/mathiasbynens/jsesc) - Generate ASCII-only output from Unicode strings. Useful for creating test fixtures.
|
|
78
|
+
|
|
79
|
+
## cjser
|
|
80
|
+
|
|
81
|
+
This package is a CommonJS-compatible build generated by cjser for projects that still need `require()` support. The source version matches the original npm package version, with a cjser prerelease suffix for this generated build.
|
|
82
|
+
Original repository: https://github.com/chalk/wrap-ansi
|