term-md 1.0.0
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/README.md +114 -0
- package/package.json +27 -0
- package/src/index.js +586 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# term-md
|
|
2
|
+
|
|
3
|
+
Render Markdown strings for terminal output.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install term-md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import termMd from 'term-md';
|
|
15
|
+
|
|
16
|
+
const output = termMd('# Title\n\nHello **terminal** `markdown`.');
|
|
17
|
+
console.log(output);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Override styles
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import chalk from 'chalk';
|
|
24
|
+
import termMd from 'term-md';
|
|
25
|
+
|
|
26
|
+
const output = termMd('Read [docs](https://example.com).', {
|
|
27
|
+
heading: chalk.cyan.bold,
|
|
28
|
+
link: chalk.green,
|
|
29
|
+
href: chalk.gray.underline,
|
|
30
|
+
codespan: chalk.yellow,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Reflow text
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import termMd from 'term-md';
|
|
38
|
+
|
|
39
|
+
console.log(
|
|
40
|
+
termMd('A long paragraph that should wrap for narrow terminals.', {
|
|
41
|
+
reflowText: true,
|
|
42
|
+
width: 40,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Custom list wrapper
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import termMd from 'term-md';
|
|
51
|
+
|
|
52
|
+
console.log(
|
|
53
|
+
termMd('- first\n- second', {
|
|
54
|
+
list(body, ordered) {
|
|
55
|
+
return ordered ? `ordered:\n${body}` : `unordered:\n${body}`;
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Options
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import chalk from 'chalk';
|
|
65
|
+
|
|
66
|
+
const options = {
|
|
67
|
+
code: chalk.yellow,
|
|
68
|
+
blockquote: chalk.gray.italic,
|
|
69
|
+
html: chalk.gray,
|
|
70
|
+
heading: chalk.green.bold,
|
|
71
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
72
|
+
hr: chalk.reset,
|
|
73
|
+
listitem: chalk.reset,
|
|
74
|
+
table: chalk.reset,
|
|
75
|
+
paragraph: chalk.reset,
|
|
76
|
+
strong: chalk.bold,
|
|
77
|
+
em: chalk.italic,
|
|
78
|
+
codespan: chalk.yellow,
|
|
79
|
+
del: chalk.dim.gray.strikethrough,
|
|
80
|
+
link: chalk.blue,
|
|
81
|
+
href: chalk.blue.underline,
|
|
82
|
+
|
|
83
|
+
list(body, ordered) {
|
|
84
|
+
return body;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
width: 80,
|
|
88
|
+
reflowText: false,
|
|
89
|
+
showSectionPrefix: true,
|
|
90
|
+
unescape: true,
|
|
91
|
+
emoji: true,
|
|
92
|
+
tableOptions: {
|
|
93
|
+
paddingLeft: 0,
|
|
94
|
+
paddingRight: 0,
|
|
95
|
+
colWidths: [],
|
|
96
|
+
},
|
|
97
|
+
tab: 3,
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Import defaults
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import termMd, { defaultOptions } from 'term-md';
|
|
105
|
+
|
|
106
|
+
const output = termMd('~~old~~ **new**', {
|
|
107
|
+
...defaultOptions,
|
|
108
|
+
del: (value) => value,
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Related
|
|
113
|
+
|
|
114
|
+
- [marked-terminal](https://github.com/mikaelbr/marked-terminal)
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "term-md",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Render Markdown strings for terminal output.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"markdown",
|
|
18
|
+
"terminal",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"chalk": "^5.6.2",
|
|
25
|
+
"marked": "^18.0.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { lexer } from 'marked';
|
|
3
|
+
|
|
4
|
+
const ANSI_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
5
|
+
const EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
6
|
+
const REGIONAL_INDICATOR_PATTERN = /[\u{1F1E6}-\u{1F1FF}]/u;
|
|
7
|
+
const KEYCAP_PATTERN = /[0-9#*]\uFE0F?\u20E3/u;
|
|
8
|
+
const EMOJI_SEQUENCE_PATTERN =
|
|
9
|
+
/[\u{1F1E6}-\u{1F1FF}]{2}|[0-9#*]\uFE0F?\u20E3|\p{Extended_Pictographic}(?:[\uFE0E\uFE0F]|\u200D\p{Extended_Pictographic}(?:[\uFE0E\uFE0F])?)*/gu;
|
|
10
|
+
const DEFAULT_BULLET = '-';
|
|
11
|
+
const NAMED_ENTITIES = new Map([
|
|
12
|
+
['Aacute', '\u00C1'],
|
|
13
|
+
['Acirc', '\u00C2'],
|
|
14
|
+
['Agrave', '\u00C0'],
|
|
15
|
+
['Aring', '\u00C5'],
|
|
16
|
+
['Atilde', '\u00C3'],
|
|
17
|
+
['Auml', '\u00C4'],
|
|
18
|
+
['Ccedil', '\u00C7'],
|
|
19
|
+
['Eacute', '\u00C9'],
|
|
20
|
+
['Ecirc', '\u00CA'],
|
|
21
|
+
['Egrave', '\u00C8'],
|
|
22
|
+
['Euml', '\u00CB'],
|
|
23
|
+
['Iacute', '\u00CD'],
|
|
24
|
+
['Icirc', '\u00CE'],
|
|
25
|
+
['Igrave', '\u00CC'],
|
|
26
|
+
['Iuml', '\u00CF'],
|
|
27
|
+
['Ntilde', '\u00D1'],
|
|
28
|
+
['Oacute', '\u00D3'],
|
|
29
|
+
['Ocirc', '\u00D4'],
|
|
30
|
+
['Ograve', '\u00D2'],
|
|
31
|
+
['Oslash', '\u00D8'],
|
|
32
|
+
['Otilde', '\u00D5'],
|
|
33
|
+
['Ouml', '\u00D6'],
|
|
34
|
+
['Uacute', '\u00DA'],
|
|
35
|
+
['Ucirc', '\u00DB'],
|
|
36
|
+
['Ugrave', '\u00D9'],
|
|
37
|
+
['Uuml', '\u00DC'],
|
|
38
|
+
['Yacute', '\u00DD'],
|
|
39
|
+
['aacute', '\u00E1'],
|
|
40
|
+
['acirc', '\u00E2'],
|
|
41
|
+
['acute', '\u00B4'],
|
|
42
|
+
['aelig', '\u00E6'],
|
|
43
|
+
['agrave', '\u00E0'],
|
|
44
|
+
['amp', '&'],
|
|
45
|
+
['aring', '\u00E5'],
|
|
46
|
+
['apos', "'"],
|
|
47
|
+
['atilde', '\u00E3'],
|
|
48
|
+
['auml', '\u00E4'],
|
|
49
|
+
['brvbar', '\u00A6'],
|
|
50
|
+
['bull', '\u2022'],
|
|
51
|
+
['ccedil', '\u00E7'],
|
|
52
|
+
['cedil', '\u00B8'],
|
|
53
|
+
['cent', '\u00A2'],
|
|
54
|
+
['circ', '\u02C6'],
|
|
55
|
+
['clubs', '\u2663'],
|
|
56
|
+
['copy', '\u00A9'],
|
|
57
|
+
['curren', '\u00A4'],
|
|
58
|
+
['dagger', '\u2020'],
|
|
59
|
+
['Dagger', '\u2021'],
|
|
60
|
+
['deg', '\u00B0'],
|
|
61
|
+
['diams', '\u2666'],
|
|
62
|
+
['divide', '\u00F7'],
|
|
63
|
+
['eacute', '\u00E9'],
|
|
64
|
+
['ecirc', '\u00EA'],
|
|
65
|
+
['egrave', '\u00E8'],
|
|
66
|
+
['emsp', '\u2003'],
|
|
67
|
+
['ensp', '\u2002'],
|
|
68
|
+
['eth', '\u00F0'],
|
|
69
|
+
['euml', '\u00EB'],
|
|
70
|
+
['euro', '\u20AC'],
|
|
71
|
+
['frac12', '\u00BD'],
|
|
72
|
+
['frac14', '\u00BC'],
|
|
73
|
+
['frac34', '\u00BE'],
|
|
74
|
+
['ge', '\u2265'],
|
|
75
|
+
['gt', '>'],
|
|
76
|
+
['harr', '\u2194'],
|
|
77
|
+
['hearts', '\u2665'],
|
|
78
|
+
['hellip', '\u2026'],
|
|
79
|
+
['iacute', '\u00ED'],
|
|
80
|
+
['icirc', '\u00EE'],
|
|
81
|
+
['iexcl', '\u00A1'],
|
|
82
|
+
['igrave', '\u00EC'],
|
|
83
|
+
['iquest', '\u00BF'],
|
|
84
|
+
['iuml', '\u00EF'],
|
|
85
|
+
['laquo', '\u00AB'],
|
|
86
|
+
['larr', '\u2190'],
|
|
87
|
+
['ldquo', '\u201C'],
|
|
88
|
+
['le', '\u2264'],
|
|
89
|
+
['lsaquo', '\u2039'],
|
|
90
|
+
['lsquo', '\u2018'],
|
|
91
|
+
['lt', '<'],
|
|
92
|
+
['mdash', '\u2014'],
|
|
93
|
+
['micro', '\u00B5'],
|
|
94
|
+
['middot', '\u00B7'],
|
|
95
|
+
['minus', '\u2212'],
|
|
96
|
+
['mu', '\u03BC'],
|
|
97
|
+
['nabla', '\u2207'],
|
|
98
|
+
['ndash', '\u2013'],
|
|
99
|
+
['nbsp', '\u00A0'],
|
|
100
|
+
['not', '\u00AC'],
|
|
101
|
+
['ntilde', '\u00F1'],
|
|
102
|
+
['oacute', '\u00F3'],
|
|
103
|
+
['ocirc', '\u00F4'],
|
|
104
|
+
['ograve', '\u00F2'],
|
|
105
|
+
['ordf', '\u00AA'],
|
|
106
|
+
['ordm', '\u00BA'],
|
|
107
|
+
['oslash', '\u00F8'],
|
|
108
|
+
['otilde', '\u00F5'],
|
|
109
|
+
['ouml', '\u00F6'],
|
|
110
|
+
['para', '\u00B6'],
|
|
111
|
+
['permil', '\u2030'],
|
|
112
|
+
['plusmn', '\u00B1'],
|
|
113
|
+
['pound', '\u00A3'],
|
|
114
|
+
['quot', '"'],
|
|
115
|
+
['raquo', '\u00BB'],
|
|
116
|
+
['rarr', '\u2192'],
|
|
117
|
+
['reg', '\u00AE'],
|
|
118
|
+
['rdquo', '\u201D'],
|
|
119
|
+
['rsaquo', '\u203A'],
|
|
120
|
+
['rsquo', '\u2019'],
|
|
121
|
+
['sbquo', '\u201A'],
|
|
122
|
+
['sect', '\u00A7'],
|
|
123
|
+
['shy', '\u00AD'],
|
|
124
|
+
['spades', '\u2660'],
|
|
125
|
+
['sup1', '\u00B9'],
|
|
126
|
+
['sup2', '\u00B2'],
|
|
127
|
+
['sup3', '\u00B3'],
|
|
128
|
+
['szlig', '\u00DF'],
|
|
129
|
+
['thinsp', '\u2009'],
|
|
130
|
+
['thorn', '\u00FE'],
|
|
131
|
+
['tilde', '\u02DC'],
|
|
132
|
+
['times', '\u00D7'],
|
|
133
|
+
['trade', '\u2122'],
|
|
134
|
+
['uacute', '\u00FA'],
|
|
135
|
+
['ucirc', '\u00FB'],
|
|
136
|
+
['ugrave', '\u00F9'],
|
|
137
|
+
['uml', '\u00A8'],
|
|
138
|
+
['uuml', '\u00FC'],
|
|
139
|
+
['yacute', '\u00FD'],
|
|
140
|
+
['yen', '\u00A5'],
|
|
141
|
+
['yuml', '\u00FF'],
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
export const defaultOptions = {
|
|
145
|
+
// Colors
|
|
146
|
+
code: chalk.yellow,
|
|
147
|
+
blockquote: chalk.gray.italic,
|
|
148
|
+
html: chalk.gray,
|
|
149
|
+
heading: chalk.green.bold,
|
|
150
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
151
|
+
hr: chalk.reset,
|
|
152
|
+
listitem: chalk.reset,
|
|
153
|
+
table: chalk.reset,
|
|
154
|
+
paragraph: chalk.reset,
|
|
155
|
+
strong: chalk.bold,
|
|
156
|
+
em: chalk.italic,
|
|
157
|
+
codespan: chalk.yellow,
|
|
158
|
+
del: chalk.dim.gray.strikethrough,
|
|
159
|
+
link: chalk.blue,
|
|
160
|
+
href: chalk.blue.underline,
|
|
161
|
+
|
|
162
|
+
// Formats the bulletpoints and numbers for lists
|
|
163
|
+
list(body) {
|
|
164
|
+
return body;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// Reflow and print-out width
|
|
168
|
+
width: 80,
|
|
169
|
+
reflowText: false,
|
|
170
|
+
|
|
171
|
+
// Should it prefix headers?
|
|
172
|
+
showSectionPrefix: true,
|
|
173
|
+
|
|
174
|
+
// Whether or not to undo marked escaping of entities (" -> " etc)
|
|
175
|
+
unescape: true,
|
|
176
|
+
|
|
177
|
+
// Whether or not to show emojis
|
|
178
|
+
emoji: true,
|
|
179
|
+
|
|
180
|
+
// Options passed to table rendering
|
|
181
|
+
tableOptions: {},
|
|
182
|
+
|
|
183
|
+
// The size of tabs in number of spaces or as tab characters
|
|
184
|
+
tab: 3,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default function termMd(markdown = '', options = {}) {
|
|
188
|
+
const opts = normalizeOptions(options);
|
|
189
|
+
const tokens = lexer(String(markdown));
|
|
190
|
+
const state = { hasRenderedHeading: false };
|
|
191
|
+
|
|
192
|
+
return renderTokens(tokens, opts, state)
|
|
193
|
+
.filter((block) => block.length > 0)
|
|
194
|
+
.join('\n\n');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeOptions(options) {
|
|
198
|
+
return {
|
|
199
|
+
...defaultOptions,
|
|
200
|
+
...options,
|
|
201
|
+
tableOptions: {
|
|
202
|
+
...defaultOptions.tableOptions,
|
|
203
|
+
...options.tableOptions,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function renderTokens(tokens, opts, state) {
|
|
209
|
+
return tokens.map((token) => renderBlock(token, opts, state)).filter((block) => block !== null);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function renderBlock(token, opts, state) {
|
|
213
|
+
switch (token.type) {
|
|
214
|
+
case 'space':
|
|
215
|
+
case 'def':
|
|
216
|
+
return null;
|
|
217
|
+
case 'heading':
|
|
218
|
+
return renderHeading(token, opts, state);
|
|
219
|
+
case 'paragraph':
|
|
220
|
+
return opts.paragraph(renderParagraph(token, opts));
|
|
221
|
+
case 'blockquote':
|
|
222
|
+
return opts.blockquote(renderTokens(token.tokens, opts, state).join('\n\n'));
|
|
223
|
+
case 'list':
|
|
224
|
+
return renderList(token, opts, state);
|
|
225
|
+
case 'code':
|
|
226
|
+
return opts.code(formatText(token.text, opts));
|
|
227
|
+
case 'html':
|
|
228
|
+
return opts.html(formatText(token.raw, opts));
|
|
229
|
+
case 'hr':
|
|
230
|
+
return opts.hr('---');
|
|
231
|
+
case 'table':
|
|
232
|
+
return opts.table(renderTable(token, opts));
|
|
233
|
+
case 'text':
|
|
234
|
+
if (token.tokens) {
|
|
235
|
+
return renderInlineTokens(token.tokens, opts);
|
|
236
|
+
}
|
|
237
|
+
return formatText(token.text, opts);
|
|
238
|
+
default:
|
|
239
|
+
if (token.tokens) {
|
|
240
|
+
return renderTokens(token.tokens, opts, state).join('\n\n');
|
|
241
|
+
}
|
|
242
|
+
return token.raw ? formatText(token.raw, opts) : '';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function renderHeading(token, opts, state) {
|
|
247
|
+
const content = renderInlineTokens(token.tokens ?? [], opts);
|
|
248
|
+
const prefix = opts.showSectionPrefix ? `${'#'.repeat(token.depth)} ` : '';
|
|
249
|
+
const body = `${prefix}${content}`;
|
|
250
|
+
const formatter = state.hasRenderedHeading ? opts.heading : opts.firstHeading;
|
|
251
|
+
|
|
252
|
+
state.hasRenderedHeading = true;
|
|
253
|
+
return formatter(body);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function renderParagraph(token, opts) {
|
|
257
|
+
const body = renderInlineTokens(token.tokens ?? [], opts);
|
|
258
|
+
return opts.reflowText ? reflow(body, opts.width) : body;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function renderList(token, opts, state) {
|
|
262
|
+
const start = Number.isInteger(token.start) ? token.start : 1;
|
|
263
|
+
const body = token.items
|
|
264
|
+
.map((item, index) => {
|
|
265
|
+
const marker = token.ordered ? `${start + index}. ` : `${DEFAULT_BULLET} `;
|
|
266
|
+
const taskMarker = item.task ? `[${item.checked ? 'x' : ' '}] ` : '';
|
|
267
|
+
const content = renderListItem(item, opts, state);
|
|
268
|
+
const lines = `${taskMarker}${content}`.split('\n');
|
|
269
|
+
const firstLine = `${marker}${lines[0] ?? ''}`;
|
|
270
|
+
const continuationPrefix = ' '.repeat(visibleLength(marker));
|
|
271
|
+
const continuationLines = lines.slice(1).map((line) => `${continuationPrefix}${line}`);
|
|
272
|
+
|
|
273
|
+
return opts.listitem([firstLine, ...continuationLines].join('\n'));
|
|
274
|
+
})
|
|
275
|
+
.join('\n');
|
|
276
|
+
|
|
277
|
+
return opts.list(body, token.ordered);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function renderListItem(item, opts, state) {
|
|
281
|
+
return item.tokens
|
|
282
|
+
.map((token) => {
|
|
283
|
+
if (token.type === 'checkbox') {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
if (token.type === 'text' && token.tokens) {
|
|
287
|
+
return renderInlineTokens(token.tokens, opts);
|
|
288
|
+
}
|
|
289
|
+
return renderBlock(token, opts, state);
|
|
290
|
+
})
|
|
291
|
+
.filter((block) => block !== null && block.length > 0)
|
|
292
|
+
.join('\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function renderTable(token, opts) {
|
|
296
|
+
const paddingLeft = readTablePadding(opts.tableOptions.paddingLeft);
|
|
297
|
+
const paddingRight = readTablePadding(opts.tableOptions.paddingRight);
|
|
298
|
+
const header = token.header.map((cell) => renderInlineTokens(cell.tokens ?? [], opts));
|
|
299
|
+
const rows = token.rows.map((row) => row.map((cell) => renderInlineTokens(cell.tokens ?? [], opts)));
|
|
300
|
+
const naturalWidths = header.map((cell, column) =>
|
|
301
|
+
Math.max(visibleLength(cell), ...rows.map((row) => visibleLength(row[column] ?? ''))),
|
|
302
|
+
);
|
|
303
|
+
const widths = naturalWidths.map((width, column) => {
|
|
304
|
+
const configuredWidth = readConfiguredColumnWidth(opts.tableOptions.colWidths?.[column]);
|
|
305
|
+
return Math.max(width + paddingLeft + paddingRight, configuredWidth);
|
|
306
|
+
});
|
|
307
|
+
const aligns = token.align ?? [];
|
|
308
|
+
const separator = widths.map((width) => '-'.repeat(width + 1)).join('|');
|
|
309
|
+
const renderedRows = [
|
|
310
|
+
renderTableRow(header, widths, aligns, paddingLeft, paddingRight),
|
|
311
|
+
separator,
|
|
312
|
+
...rows.map((row) => renderTableRow(row, widths, aligns, paddingLeft, paddingRight)),
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
return renderedRows.join('\n');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function renderTableRow(row, widths, aligns, paddingLeft, paddingRight) {
|
|
319
|
+
return row
|
|
320
|
+
.map((cell, column) => padCell(cell, widths[column], aligns[column], paddingLeft, paddingRight))
|
|
321
|
+
.join(' | ');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function padCell(cell, width, align, paddingLeft, paddingRight) {
|
|
325
|
+
const contentWidth = Math.max(width - paddingLeft - paddingRight, visibleLength(cell));
|
|
326
|
+
const padding = Math.max(contentWidth - visibleLength(cell), 0);
|
|
327
|
+
let content;
|
|
328
|
+
|
|
329
|
+
if (align === 'right') {
|
|
330
|
+
content = `${' '.repeat(padding)}${cell}`;
|
|
331
|
+
} else if (align === 'center') {
|
|
332
|
+
const left = Math.floor(padding / 2);
|
|
333
|
+
const right = padding - left;
|
|
334
|
+
content = `${' '.repeat(left)}${cell}${' '.repeat(right)}`;
|
|
335
|
+
} else {
|
|
336
|
+
content = `${cell}${' '.repeat(padding)}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return `${' '.repeat(paddingLeft)}${content}${' '.repeat(paddingRight)}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function readTablePadding(value) {
|
|
343
|
+
const padding = Number(value ?? 0);
|
|
344
|
+
return Number.isFinite(padding) && padding > 0 ? Math.floor(padding) : 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function readConfiguredColumnWidth(value) {
|
|
348
|
+
const width = Number(value ?? 0);
|
|
349
|
+
return Number.isFinite(width) && width > 0 ? Math.floor(width) : 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function renderInlineTokens(tokens, opts) {
|
|
353
|
+
return tokens.map((token) => renderInlineToken(token, opts)).join('');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function renderInlineToken(token, opts) {
|
|
357
|
+
switch (token.type) {
|
|
358
|
+
case 'text':
|
|
359
|
+
if (token.tokens) {
|
|
360
|
+
return renderInlineTokens(token.tokens, opts);
|
|
361
|
+
}
|
|
362
|
+
return formatText(token.text, opts);
|
|
363
|
+
case 'escape':
|
|
364
|
+
return formatText(token.text, opts);
|
|
365
|
+
case 'strong':
|
|
366
|
+
return opts.strong(renderInlineTokens(token.tokens ?? [], opts));
|
|
367
|
+
case 'em':
|
|
368
|
+
return opts.em(renderInlineTokens(token.tokens ?? [], opts));
|
|
369
|
+
case 'codespan':
|
|
370
|
+
return opts.codespan(formatText(token.text, opts));
|
|
371
|
+
case 'del':
|
|
372
|
+
return opts.del(renderInlineTokens(token.tokens ?? [], opts));
|
|
373
|
+
case 'link':
|
|
374
|
+
return renderLink(token, opts);
|
|
375
|
+
case 'image':
|
|
376
|
+
return renderImage(token, opts);
|
|
377
|
+
case 'br':
|
|
378
|
+
return '\n';
|
|
379
|
+
case 'html':
|
|
380
|
+
return opts.html(formatText(token.raw, opts));
|
|
381
|
+
default:
|
|
382
|
+
if (token.tokens) {
|
|
383
|
+
return renderInlineTokens(token.tokens, opts);
|
|
384
|
+
}
|
|
385
|
+
return token.raw ? formatText(token.raw, opts) : '';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function renderLink(token, opts) {
|
|
390
|
+
const text = opts.link(renderInlineTokens(token.tokens ?? [], opts));
|
|
391
|
+
const href = formatText(token.href ?? '', opts);
|
|
392
|
+
|
|
393
|
+
return href ? `${text} ${opts.href(href)}` : text;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function renderImage(token, opts) {
|
|
397
|
+
const alt = token.text ? opts.link(formatText(token.text, opts)) : '';
|
|
398
|
+
const href = token.href ? opts.href(formatText(token.href, opts)) : '';
|
|
399
|
+
|
|
400
|
+
return [alt, href].filter(Boolean).join(' ');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function formatText(value, opts) {
|
|
404
|
+
let text = expandTabs(String(value), opts.tab);
|
|
405
|
+
|
|
406
|
+
if (opts.unescape) {
|
|
407
|
+
text = unescapeEntities(text);
|
|
408
|
+
}
|
|
409
|
+
if (!opts.emoji) {
|
|
410
|
+
text = stripEmoji(text);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return text;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function expandTabs(value, tab) {
|
|
417
|
+
const replacement = typeof tab === 'number' ? ' '.repeat(Math.max(Math.floor(tab), 0)) : String(tab);
|
|
418
|
+
return value.replaceAll('\t', replacement);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function unescapeEntities(value) {
|
|
422
|
+
return value.replace(/&(#x[\da-f]+|#\d+|[a-z][\da-z]+);/gi, (entity, body) => {
|
|
423
|
+
if (body.startsWith('#x') || body.startsWith('#X')) {
|
|
424
|
+
return decodeCodePoint(entity, Number.parseInt(body.slice(2), 16));
|
|
425
|
+
}
|
|
426
|
+
if (body.startsWith('#')) {
|
|
427
|
+
return decodeCodePoint(entity, Number.parseInt(body.slice(1), 10));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return NAMED_ENTITIES.get(body) ?? NAMED_ENTITIES.get(body.toLowerCase()) ?? entity;
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function decodeCodePoint(entity, codePoint) {
|
|
435
|
+
if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
|
|
436
|
+
return entity;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return String.fromCodePoint(codePoint);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function stripEmoji(value) {
|
|
443
|
+
if (typeof globalThis.Intl?.Segmenter === 'function') {
|
|
444
|
+
const segmenter = new globalThis.Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
445
|
+
return Array.from(segmenter.segment(value), ({ segment }) =>
|
|
446
|
+
isEmojiSegment(segment) ? '' : segment,
|
|
447
|
+
).join('');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return value.replace(EMOJI_SEQUENCE_PATTERN, '').replace(/[\u200D\uFE0E\uFE0F]/g, '');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function isEmojiSegment(segment) {
|
|
454
|
+
return EMOJI_PATTERN.test(segment) || REGIONAL_INDICATOR_PATTERN.test(segment) || KEYCAP_PATTERN.test(segment);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function reflow(value, width) {
|
|
458
|
+
const maxWidth = Math.max(Number(width) || defaultOptions.width, 1);
|
|
459
|
+
const words = value.trim().split(/\s+/);
|
|
460
|
+
const lines = [];
|
|
461
|
+
let line = '';
|
|
462
|
+
|
|
463
|
+
for (const word of words) {
|
|
464
|
+
if (!line) {
|
|
465
|
+
line = word;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (visibleLength(`${line} ${word}`) > maxWidth) {
|
|
470
|
+
lines.push(line);
|
|
471
|
+
line = word;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
line = `${line} ${word}`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (line) {
|
|
479
|
+
lines.push(line);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return lines.join('\n');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function visibleLength(value) {
|
|
486
|
+
return stringWidth(String(value).replace(ANSI_PATTERN, ''));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function stringWidth(value) {
|
|
490
|
+
let width = 0;
|
|
491
|
+
|
|
492
|
+
for (let index = 0; index < value.length; ) {
|
|
493
|
+
const codePoint = value.codePointAt(index);
|
|
494
|
+
const character = String.fromCodePoint(codePoint);
|
|
495
|
+
|
|
496
|
+
index += character.length;
|
|
497
|
+
|
|
498
|
+
if (isZeroWidth(codePoint)) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (EMOJI_PATTERN.test(character)) {
|
|
503
|
+
width += 2;
|
|
504
|
+
index = skipEmojiSequence(value, index);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
width += isFullWidth(codePoint) ? 2 : 1;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return width;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function skipEmojiSequence(value, index) {
|
|
515
|
+
let nextIndex = index;
|
|
516
|
+
|
|
517
|
+
while (nextIndex < value.length) {
|
|
518
|
+
const codePoint = value.codePointAt(nextIndex);
|
|
519
|
+
const character = String.fromCodePoint(codePoint);
|
|
520
|
+
|
|
521
|
+
if (isVariationSelector(codePoint)) {
|
|
522
|
+
nextIndex += character.length;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (character !== '\u200D') {
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
nextIndex += character.length;
|
|
531
|
+
const joinedCodePoint = value.codePointAt(nextIndex);
|
|
532
|
+
if (joinedCodePoint === undefined) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
const joinedCharacter = String.fromCodePoint(joinedCodePoint);
|
|
536
|
+
|
|
537
|
+
if (!EMOJI_PATTERN.test(joinedCharacter)) {
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
nextIndex += joinedCharacter.length;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return nextIndex;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function isZeroWidth(codePoint) {
|
|
548
|
+
return (
|
|
549
|
+
codePoint === 0 ||
|
|
550
|
+
codePoint < 32 ||
|
|
551
|
+
(codePoint >= 0x7f && codePoint < 0xa0) ||
|
|
552
|
+
isCombiningMark(codePoint) ||
|
|
553
|
+
isVariationSelector(codePoint)
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function isCombiningMark(codePoint) {
|
|
558
|
+
return (
|
|
559
|
+
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
560
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
561
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
562
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
563
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f)
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function isVariationSelector(codePoint) {
|
|
568
|
+
return (codePoint >= 0xfe00 && codePoint <= 0xfe0f) || (codePoint >= 0xe0100 && codePoint <= 0xe01ef);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function isFullWidth(codePoint) {
|
|
572
|
+
return (
|
|
573
|
+
codePoint >= 0x1100 &&
|
|
574
|
+
(codePoint <= 0x115f ||
|
|
575
|
+
codePoint === 0x2329 ||
|
|
576
|
+
codePoint === 0x232a ||
|
|
577
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
578
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
579
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
580
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
581
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
582
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
583
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
584
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd))
|
|
585
|
+
);
|
|
586
|
+
}
|