patram 0.0.2 → 0.2.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/bin/patram.js +25 -147
- package/lib/build-graph-identity.js +270 -0
- package/lib/build-graph.js +156 -77
- package/lib/check-graph.js +23 -7
- package/lib/claim-helpers.js +55 -0
- package/lib/cli-help-metadata.js +552 -0
- package/lib/command-output.js +83 -0
- package/lib/derived-summary.js +278 -0
- package/lib/format-derived-summary-row.js +9 -0
- package/lib/format-node-header.js +19 -0
- package/lib/format-output-item-block.js +22 -0
- package/lib/format-output-metadata.js +62 -0
- package/lib/layout-stored-queries.js +361 -0
- package/lib/list-queries.js +18 -0
- package/lib/list-source-files.js +50 -15
- package/lib/load-patram-config.js +505 -18
- package/lib/load-patram-config.types.ts +40 -0
- package/lib/load-project-graph.js +124 -0
- package/lib/output-view.types.ts +88 -0
- package/lib/parse-claims.js +38 -158
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-cli-arguments-helpers.js +446 -0
- package/lib/parse-cli-arguments.js +266 -0
- package/lib/parse-cli-arguments.types.ts +69 -0
- package/lib/parse-cli-color-options.js +44 -0
- package/lib/parse-cli-query-pagination.js +49 -0
- package/lib/parse-jsdoc-blocks.js +184 -0
- package/lib/parse-jsdoc-claims.js +280 -0
- package/lib/parse-jsdoc-prose.js +111 -0
- package/lib/parse-markdown-claims.js +242 -0
- package/lib/parse-markdown-directives.js +136 -0
- package/lib/parse-where-clause.js +707 -0
- package/lib/parse-where-clause.types.ts +70 -0
- package/lib/patram-cli.js +464 -0
- package/lib/patram-config.js +3 -1
- package/lib/patram-config.types.ts +2 -1
- package/lib/patram.js +6 -0
- package/lib/query-graph.js +368 -0
- package/lib/query-inspection.js +523 -0
- package/lib/render-check-output.js +315 -0
- package/lib/render-cli-help.js +419 -0
- package/lib/render-json-output.js +161 -0
- package/lib/render-output-view.js +222 -0
- package/lib/render-plain-output.js +182 -0
- package/lib/render-rich-output.js +240 -0
- package/lib/render-rich-source.js +1333 -0
- package/lib/resolve-check-target.js +190 -0
- package/lib/resolve-output-mode.js +60 -0
- package/lib/resolve-patram-graph-config.js +88 -0
- package/lib/resolve-where-clause.js +66 -0
- package/lib/show-document.js +311 -0
- package/lib/source-file-defaults.js +28 -0
- package/lib/tagged-fenced-block-error.js +17 -0
- package/lib/tagged-fenced-block-markdown.js +111 -0
- package/lib/tagged-fenced-block-metadata.js +97 -0
- package/lib/tagged-fenced-block-parser.js +292 -0
- package/lib/tagged-fenced-blocks.js +100 -0
- package/lib/tagged-fenced-blocks.types.ts +38 -0
- package/lib/write-paged-output.js +87 -0
- package/package.json +28 -12
- package/bin/patram.test.js +0 -184
- package/lib/build-graph.test.js +0 -141
- package/lib/check-graph.test.js +0 -103
- package/lib/list-source-files.test.js +0 -101
- package/lib/load-patram-config.test.js +0 -211
- package/lib/parse-claims.test.js +0 -113
- package/lib/patram-config.test.js +0 -147
|
@@ -0,0 +1,1333 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
/**
|
|
3
|
+
* @import { ComarkElement, ComarkNode } from 'md4x';
|
|
4
|
+
* @import { BundledLanguage } from 'shiki';
|
|
5
|
+
* @import { CliColorMode } from './parse-cli-arguments.types.ts';
|
|
6
|
+
* @import { OutputResolvedLinkItem, ShowOutputView } from './output-view.types.ts';
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { extname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { FontStyle } from '@shikijs/vscode-textmate';
|
|
12
|
+
import { Ansis } from 'ansis';
|
|
13
|
+
import { renderMermaidASCII } from 'beautiful-mermaid';
|
|
14
|
+
import { parseAST } from 'md4x';
|
|
15
|
+
import {
|
|
16
|
+
bundledLanguages,
|
|
17
|
+
bundledLanguagesAlias,
|
|
18
|
+
codeToTokensBase,
|
|
19
|
+
getSingletonHighlighter,
|
|
20
|
+
} from 'shiki';
|
|
21
|
+
import stringWidth from 'string-width';
|
|
22
|
+
import wrapAnsi from 'wrap-ansi';
|
|
23
|
+
|
|
24
|
+
const MARKDOWN_EXTENSIONS = new Set(['.markdown', '.md']);
|
|
25
|
+
const BLOCK_WIDTH = 80;
|
|
26
|
+
const CODE_SURFACE = 236;
|
|
27
|
+
const SHIKI_THEME = 'github-dark';
|
|
28
|
+
const URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:/du;
|
|
29
|
+
/** @type {Record<string, string>} */
|
|
30
|
+
const SOURCE_LANGUAGE_BY_EXTENSION = {
|
|
31
|
+
'.cjs': 'javascript',
|
|
32
|
+
'.cts': 'typescript',
|
|
33
|
+
'.js': 'javascript',
|
|
34
|
+
'.json': 'json',
|
|
35
|
+
'.jsx': 'jsx',
|
|
36
|
+
'.mjs': 'javascript',
|
|
37
|
+
'.mts': 'typescript',
|
|
38
|
+
'.tsx': 'tsx',
|
|
39
|
+
'.ts': 'typescript',
|
|
40
|
+
'.yaml': 'yaml',
|
|
41
|
+
'.yml': 'yaml',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const SHIKI_LANGUAGE_NAMES = new Set([
|
|
45
|
+
...Object.keys(bundledLanguages),
|
|
46
|
+
...Object.keys(bundledLanguagesAlias),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {ShowOutputView} output_view
|
|
51
|
+
* @param {{ color_mode: CliColorMode, color_enabled: boolean }} render_options
|
|
52
|
+
* @returns {Promise<string>}
|
|
53
|
+
*/
|
|
54
|
+
export async function renderRichSource(output_view, render_options) {
|
|
55
|
+
const ansi = createAnsi(render_options.color_enabled);
|
|
56
|
+
|
|
57
|
+
if (isMarkdownPath(output_view.path)) {
|
|
58
|
+
return renderRichMarkdownSource(output_view, ansi);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return renderRichSourceFile(output_view.path, output_view.source, ansi);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {ShowOutputView} output_view
|
|
66
|
+
* @param {Ansis} ansi
|
|
67
|
+
* @returns {Promise<string>}
|
|
68
|
+
*/
|
|
69
|
+
async function renderRichMarkdownSource(output_view, ansi) {
|
|
70
|
+
const markdown_tree = parseAST(output_view.source);
|
|
71
|
+
/** @type {string[]} */
|
|
72
|
+
const rendered_blocks = [];
|
|
73
|
+
/** @type {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} */
|
|
74
|
+
const render_state = {
|
|
75
|
+
ansi,
|
|
76
|
+
next_reference: 1,
|
|
77
|
+
resolved_links: output_view.items,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const node of markdown_tree.nodes) {
|
|
81
|
+
const rendered_block = await renderBlockNode(node, render_state, 0);
|
|
82
|
+
|
|
83
|
+
if (rendered_block.length > 0) {
|
|
84
|
+
rendered_blocks.push(rendered_block);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return rendered_blocks.join('\n\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} source_path
|
|
93
|
+
* @param {string} source_text
|
|
94
|
+
* @param {Ansis} ansi
|
|
95
|
+
* @returns {Promise<string>}
|
|
96
|
+
*/
|
|
97
|
+
async function renderRichSourceFile(source_path, source_text, ansi) {
|
|
98
|
+
const source_language = detectSourceLanguage(source_path);
|
|
99
|
+
const source_lines = await renderHighlightedLines(
|
|
100
|
+
source_text,
|
|
101
|
+
source_language,
|
|
102
|
+
ansi,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return renderCodeBlock(source_lines, source_language ?? '', null, 0, ansi);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {ComarkNode} node
|
|
110
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
111
|
+
* @param {number} indent_level
|
|
112
|
+
* @returns {Promise<string>}
|
|
113
|
+
*/
|
|
114
|
+
async function renderBlockNode(node, render_state, indent_level) {
|
|
115
|
+
if (typeof node === 'string') {
|
|
116
|
+
return node;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const node_tag = getElementTag(node);
|
|
120
|
+
const node_children = getElementChildren(node);
|
|
121
|
+
|
|
122
|
+
if (isHeadingTag(node_tag)) {
|
|
123
|
+
return renderHeading(node_tag, node_children, render_state.ansi);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (node_tag === 'p') {
|
|
127
|
+
return renderInlineNodes(node_children, render_state);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (node_tag === 'pre') {
|
|
131
|
+
return renderFencedCodeBlock(node, indent_level, render_state.ansi);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (node_tag === 'ul' || node_tag === 'ol') {
|
|
135
|
+
return renderListBlock(node_tag, node_children, render_state, indent_level);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (node_tag === 'blockquote') {
|
|
139
|
+
return renderBlockquote(node_children, render_state, indent_level);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (node_tag === 'hr') {
|
|
143
|
+
return renderDivider(render_state.ansi);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (node_tag === 'table') {
|
|
147
|
+
return renderTable(node_children);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return renderInlineNodes(node_children, render_state);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {ComarkNode[]} nodes
|
|
155
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
156
|
+
* @returns {string}
|
|
157
|
+
*/
|
|
158
|
+
function renderInlineNodes(nodes, render_state) {
|
|
159
|
+
/** @type {string[]} */
|
|
160
|
+
const rendered_chunks = [];
|
|
161
|
+
|
|
162
|
+
for (const node of nodes) {
|
|
163
|
+
if (typeof node === 'string') {
|
|
164
|
+
rendered_chunks.push(node);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const node_tag = getElementTag(node);
|
|
169
|
+
const node_children = getElementChildren(node);
|
|
170
|
+
|
|
171
|
+
if (node_tag === 'strong') {
|
|
172
|
+
rendered_chunks.push(
|
|
173
|
+
render_state.ansi.bold(renderInlineNodes(node_children, render_state)),
|
|
174
|
+
);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (node_tag === 'em') {
|
|
179
|
+
rendered_chunks.push(
|
|
180
|
+
render_state.ansi.italic(
|
|
181
|
+
renderInlineNodes(node_children, render_state),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (node_tag === 'code') {
|
|
188
|
+
rendered_chunks.push(renderInlineCode(node_children, render_state.ansi));
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (node_tag === 'a') {
|
|
193
|
+
rendered_chunks.push(renderLinkNode(node, render_state));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (node_tag === 's') {
|
|
198
|
+
rendered_chunks.push(
|
|
199
|
+
render_state.ansi.strikethrough(
|
|
200
|
+
renderInlineNodes(node_children, render_state),
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (node_tag === 'br') {
|
|
207
|
+
rendered_chunks.push('\n');
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
rendered_chunks.push(renderInlineNodes(node_children, render_state));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return rendered_chunks.join('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @param {'ol' | 'ul'} nodes_type
|
|
219
|
+
* @param {ComarkNode[]} nodes
|
|
220
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
221
|
+
* @param {number} indent_level
|
|
222
|
+
* @returns {Promise<string>}
|
|
223
|
+
*/
|
|
224
|
+
async function renderListBlock(nodes_type, nodes, render_state, indent_level) {
|
|
225
|
+
/** @type {string[]} */
|
|
226
|
+
const rendered_items = [];
|
|
227
|
+
|
|
228
|
+
for (let item_index = 0; item_index < nodes.length; item_index += 1) {
|
|
229
|
+
const node = nodes[item_index];
|
|
230
|
+
|
|
231
|
+
if (typeof node === 'string' || getElementTag(node) !== 'li') {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
rendered_items.push(
|
|
236
|
+
await renderListItem(
|
|
237
|
+
node,
|
|
238
|
+
item_index + 1,
|
|
239
|
+
nodes_type === 'ol',
|
|
240
|
+
render_state,
|
|
241
|
+
indent_level,
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return rendered_items.join('\n');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @param {ComarkElement} node
|
|
251
|
+
* @param {number} item_number
|
|
252
|
+
* @param {boolean} is_ordered
|
|
253
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
254
|
+
* @param {number} indent_level
|
|
255
|
+
* @returns {Promise<string>}
|
|
256
|
+
*/
|
|
257
|
+
async function renderListItem(
|
|
258
|
+
node,
|
|
259
|
+
item_number,
|
|
260
|
+
is_ordered,
|
|
261
|
+
render_state,
|
|
262
|
+
indent_level,
|
|
263
|
+
) {
|
|
264
|
+
const item_prefix = `${' '.repeat(indent_level)}${is_ordered ? `${item_number}.` : '•'} `;
|
|
265
|
+
const followup_prefix = ' '.repeat(indent_level);
|
|
266
|
+
const { block_nodes, lead_text } = collectListItemParts(node, render_state);
|
|
267
|
+
|
|
268
|
+
/** @type {string[]} */
|
|
269
|
+
const rendered_parts =
|
|
270
|
+
lead_text === null
|
|
271
|
+
? [item_prefix.trimEnd()]
|
|
272
|
+
: renderListParagraph(lead_text, item_prefix, followup_prefix);
|
|
273
|
+
|
|
274
|
+
for (const block_node of block_nodes) {
|
|
275
|
+
const block_tag = getElementTag(block_node);
|
|
276
|
+
|
|
277
|
+
if (block_tag === 'p') {
|
|
278
|
+
rendered_parts.push(
|
|
279
|
+
...renderParagraphLines(
|
|
280
|
+
renderInlineNodes(getElementChildren(block_node), render_state),
|
|
281
|
+
followup_prefix,
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const rendered_block = await renderBlockNode(
|
|
288
|
+
block_node,
|
|
289
|
+
render_state,
|
|
290
|
+
indent_level + 1,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
rendered_parts.push(rendered_block);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return rendered_parts.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {ComarkNode[]} nodes
|
|
301
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
302
|
+
* @param {number} indent_level
|
|
303
|
+
* @returns {Promise<string>}
|
|
304
|
+
*/
|
|
305
|
+
async function renderBlockquote(nodes, render_state, indent_level) {
|
|
306
|
+
/** @type {string[]} */
|
|
307
|
+
const rendered_blocks = [];
|
|
308
|
+
|
|
309
|
+
for (const node of nodes) {
|
|
310
|
+
const rendered_block = await renderBlockNode(
|
|
311
|
+
node,
|
|
312
|
+
render_state,
|
|
313
|
+
indent_level,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (rendered_block.length > 0) {
|
|
317
|
+
rendered_blocks.push(rendered_block);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return renderQuoteBlock(
|
|
322
|
+
rendered_blocks.join('\n\n'),
|
|
323
|
+
indent_level,
|
|
324
|
+
render_state.ansi,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @param {ComarkNode[]} nodes
|
|
330
|
+
* @returns {string}
|
|
331
|
+
*/
|
|
332
|
+
function renderTable(nodes) {
|
|
333
|
+
const table_rows = extractTableRows(nodes);
|
|
334
|
+
|
|
335
|
+
if (table_rows.length === 0) {
|
|
336
|
+
return '';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** @type {number[]} */
|
|
340
|
+
const column_widths = [];
|
|
341
|
+
|
|
342
|
+
for (const row of table_rows) {
|
|
343
|
+
for (let column_index = 0; column_index < row.length; column_index += 1) {
|
|
344
|
+
const cell_text = row[column_index];
|
|
345
|
+
const current_width = column_widths[column_index] ?? 0;
|
|
346
|
+
|
|
347
|
+
column_widths[column_index] = Math.max(current_width, cell_text.length);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** @type {string[]} */
|
|
352
|
+
const rendered_lines = [formatTableRow(table_rows[0], column_widths)];
|
|
353
|
+
|
|
354
|
+
rendered_lines.push(formatTableDivider(column_widths));
|
|
355
|
+
|
|
356
|
+
for (let row_index = 1; row_index < table_rows.length; row_index += 1) {
|
|
357
|
+
rendered_lines.push(formatTableRow(table_rows[row_index], column_widths));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return rendered_lines.join('\n');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* @param {ComarkElement} node
|
|
365
|
+
* @param {number} indent_level
|
|
366
|
+
* @param {Ansis} ansi
|
|
367
|
+
* @returns {Promise<string>}
|
|
368
|
+
*/
|
|
369
|
+
async function renderFencedCodeBlock(node, indent_level, ansi) {
|
|
370
|
+
const node_props = getElementProps(node);
|
|
371
|
+
const code_node = getElementChildren(node)[0];
|
|
372
|
+
const raw_language =
|
|
373
|
+
typeof node_props.language === 'string' ? node_props.language : '';
|
|
374
|
+
const source_language = normalizeShikiLanguage(raw_language);
|
|
375
|
+
const file_name =
|
|
376
|
+
typeof node_props.filename === 'string' ? node_props.filename : null;
|
|
377
|
+
const source_text =
|
|
378
|
+
typeof code_node === 'string'
|
|
379
|
+
? code_node
|
|
380
|
+
: extractInlineText(getElementChildren(code_node));
|
|
381
|
+
|
|
382
|
+
if (isMermaidFence(raw_language)) {
|
|
383
|
+
return renderMermaidBlock(
|
|
384
|
+
source_text,
|
|
385
|
+
raw_language,
|
|
386
|
+
file_name,
|
|
387
|
+
indent_level,
|
|
388
|
+
ansi,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const rendered_lines = await renderHighlightedLines(
|
|
393
|
+
source_text,
|
|
394
|
+
source_language,
|
|
395
|
+
ansi,
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
return renderCodeBlock(
|
|
399
|
+
rendered_lines,
|
|
400
|
+
raw_language,
|
|
401
|
+
file_name,
|
|
402
|
+
indent_level,
|
|
403
|
+
ansi,
|
|
404
|
+
true,
|
|
405
|
+
1,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* @param {string} source_text
|
|
411
|
+
* @param {string} language_label
|
|
412
|
+
* @param {string | null} file_name
|
|
413
|
+
* @param {number} indent_level
|
|
414
|
+
* @param {Ansis} ansi
|
|
415
|
+
* @returns {string}
|
|
416
|
+
*/
|
|
417
|
+
function renderMermaidBlock(
|
|
418
|
+
source_text,
|
|
419
|
+
language_label,
|
|
420
|
+
file_name,
|
|
421
|
+
indent_level,
|
|
422
|
+
ansi,
|
|
423
|
+
) {
|
|
424
|
+
const rendered_lines = splitRenderedLines(
|
|
425
|
+
renderMermaidASCII(stripSingleTrailingLineBreak(source_text), {
|
|
426
|
+
boxBorderPadding: 0,
|
|
427
|
+
colorMode: 'none',
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
return renderCodeBlock(
|
|
432
|
+
rendered_lines,
|
|
433
|
+
language_label,
|
|
434
|
+
file_name,
|
|
435
|
+
indent_level,
|
|
436
|
+
ansi,
|
|
437
|
+
true,
|
|
438
|
+
1,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @param {string} tag_name
|
|
444
|
+
* @param {ComarkNode[]} nodes
|
|
445
|
+
* @param {Ansis} ansi
|
|
446
|
+
* @returns {string}
|
|
447
|
+
*/
|
|
448
|
+
function renderHeading(tag_name, nodes, ansi) {
|
|
449
|
+
const heading_text = extractInlineText(nodes);
|
|
450
|
+
|
|
451
|
+
if (heading_text.length === 0) {
|
|
452
|
+
return '';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return styleHeadingText(
|
|
456
|
+
`${'#'.repeat(getHeadingLevel(tag_name))} ${heading_text}`,
|
|
457
|
+
tag_name,
|
|
458
|
+
ansi,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* @param {ComarkElement} node
|
|
464
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
465
|
+
* @returns {string}
|
|
466
|
+
*/
|
|
467
|
+
function renderLinkNode(node, render_state) {
|
|
468
|
+
const node_children = getElementChildren(node);
|
|
469
|
+
const node_props = getElementProps(node);
|
|
470
|
+
const link_text = renderInlineNodes(node_children, render_state);
|
|
471
|
+
const target_value =
|
|
472
|
+
typeof node_props.href === 'string' ? node_props.href : null;
|
|
473
|
+
|
|
474
|
+
if (!isPathLikeMarkdownTarget(target_value)) {
|
|
475
|
+
return render_state.ansi.underline(link_text);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const resolved_link =
|
|
479
|
+
render_state.resolved_links[render_state.next_reference - 1];
|
|
480
|
+
|
|
481
|
+
render_state.next_reference += 1;
|
|
482
|
+
|
|
483
|
+
if (!resolved_link) {
|
|
484
|
+
return render_state.ansi.underline(link_text);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return `${render_state.ansi.underline(link_text)}${render_state.ansi.gray(`[${resolved_link.reference}]`)}`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @param {string} source_text
|
|
492
|
+
* @param {string | null} source_language
|
|
493
|
+
* @param {Ansis} ansi
|
|
494
|
+
* @returns {Promise<string[]>}
|
|
495
|
+
*/
|
|
496
|
+
async function renderHighlightedLines(source_text, source_language, ansi) {
|
|
497
|
+
const normalized_source = stripSingleTrailingLineBreak(source_text);
|
|
498
|
+
|
|
499
|
+
if (!source_language) {
|
|
500
|
+
return splitRenderedLines(normalized_source);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const highlighted_source = await highlightSourceText(
|
|
504
|
+
normalized_source,
|
|
505
|
+
source_language,
|
|
506
|
+
ansi,
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
return splitRenderedLines(highlighted_source);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* @param {string} source_text
|
|
514
|
+
* @param {string} source_language
|
|
515
|
+
* @param {Ansis} ansi
|
|
516
|
+
* @returns {Promise<string>}
|
|
517
|
+
*/
|
|
518
|
+
async function highlightSourceText(source_text, source_language, ansi) {
|
|
519
|
+
const token_lines = await codeToTokensBase(source_text, {
|
|
520
|
+
lang: /** @type {BundledLanguage} */ (source_language),
|
|
521
|
+
theme: SHIKI_THEME,
|
|
522
|
+
});
|
|
523
|
+
const highlighter = await getSingletonHighlighter();
|
|
524
|
+
const theme_registration = highlighter.getTheme(SHIKI_THEME);
|
|
525
|
+
let highlighted_source = '';
|
|
526
|
+
|
|
527
|
+
for (const token_line of token_lines) {
|
|
528
|
+
for (const token of token_line) {
|
|
529
|
+
highlighted_source += styleHighlightedToken(
|
|
530
|
+
token,
|
|
531
|
+
theme_registration,
|
|
532
|
+
ansi,
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
highlighted_source += '\n';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return highlighted_source;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* @param {{ color?: string, content: string, fontStyle?: number }} token
|
|
544
|
+
* @param {{ fg?: string, type: string }} theme_registration
|
|
545
|
+
* @param {Ansis} ansi
|
|
546
|
+
* @returns {string}
|
|
547
|
+
*/
|
|
548
|
+
function styleHighlightedToken(token, theme_registration, ansi) {
|
|
549
|
+
let token_text = token.content;
|
|
550
|
+
const token_color = token.color ?? theme_registration.fg;
|
|
551
|
+
|
|
552
|
+
if (token_color) {
|
|
553
|
+
token_text = ansi.hex(applyHexAlpha(token_color, theme_registration.type))(
|
|
554
|
+
token_text,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (!token.fontStyle) {
|
|
559
|
+
return token_text;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (token.fontStyle & FontStyle.Bold) {
|
|
563
|
+
token_text = ansi.bold(token_text);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (token.fontStyle & FontStyle.Italic) {
|
|
567
|
+
token_text = ansi.italic(token_text);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (token.fontStyle & FontStyle.Underline) {
|
|
571
|
+
token_text = ansi.underline(token_text);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (token.fontStyle & FontStyle.Strikethrough) {
|
|
575
|
+
token_text = ansi.strikethrough(token_text);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return token_text;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* @param {string} language_label
|
|
583
|
+
* @param {string | null} file_name
|
|
584
|
+
* @returns {string}
|
|
585
|
+
*/
|
|
586
|
+
function formatCodeBlockLabel(language_label, file_name) {
|
|
587
|
+
/** @type {string[]} */
|
|
588
|
+
const label_parts = [];
|
|
589
|
+
|
|
590
|
+
if (language_label.length > 0) {
|
|
591
|
+
label_parts.push(language_label);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (file_name) {
|
|
595
|
+
label_parts.push(`[${file_name}]`);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return label_parts.join(' ');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @param {string[]} source_lines
|
|
603
|
+
* @param {string} language_label
|
|
604
|
+
* @param {string | null} file_name
|
|
605
|
+
* @param {number} indent_level
|
|
606
|
+
* @param {Ansis} ansi
|
|
607
|
+
* @param {boolean} add_bottom_spacer
|
|
608
|
+
* @param {number} content_indent
|
|
609
|
+
* @returns {string}
|
|
610
|
+
*/
|
|
611
|
+
function renderCodeBlock(
|
|
612
|
+
source_lines,
|
|
613
|
+
language_label,
|
|
614
|
+
file_name,
|
|
615
|
+
indent_level,
|
|
616
|
+
ansi,
|
|
617
|
+
add_bottom_spacer = false,
|
|
618
|
+
content_indent = 0,
|
|
619
|
+
) {
|
|
620
|
+
const label = formatCodeBlockLabel(language_label, file_name);
|
|
621
|
+
const content_width = measureCodeBlockWidth(label, source_lines);
|
|
622
|
+
/** @type {string[]} */
|
|
623
|
+
const rendered_lines = [];
|
|
624
|
+
|
|
625
|
+
if (label.length > 0) {
|
|
626
|
+
rendered_lines.push(
|
|
627
|
+
renderCodeBlockLabelLine(label, content_width, indent_level, ansi),
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
for (const source_line of source_lines) {
|
|
632
|
+
rendered_lines.push(
|
|
633
|
+
renderCodeBlockContentLine(
|
|
634
|
+
source_line,
|
|
635
|
+
content_width,
|
|
636
|
+
indent_level,
|
|
637
|
+
ansi,
|
|
638
|
+
content_indent,
|
|
639
|
+
),
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (add_bottom_spacer) {
|
|
644
|
+
rendered_lines.push(
|
|
645
|
+
renderCodeBlockContentLine(
|
|
646
|
+
'',
|
|
647
|
+
content_width,
|
|
648
|
+
indent_level,
|
|
649
|
+
ansi,
|
|
650
|
+
content_indent,
|
|
651
|
+
),
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return rendered_lines.join('\n');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* @param {ComarkNode[]} nodes
|
|
660
|
+
* @returns {string[][]}
|
|
661
|
+
*/
|
|
662
|
+
function extractTableRows(nodes) {
|
|
663
|
+
/** @type {string[][]} */
|
|
664
|
+
const table_rows = [];
|
|
665
|
+
|
|
666
|
+
for (const node of nodes) {
|
|
667
|
+
if (typeof node === 'string') {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const node_tag = getElementTag(node);
|
|
672
|
+
|
|
673
|
+
if (node_tag !== 'thead' && node_tag !== 'tbody') {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
for (const row_node of getElementChildren(node)) {
|
|
678
|
+
if (typeof row_node === 'string' || getElementTag(row_node) !== 'tr') {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
table_rows.push(extractTableRowCells(row_node));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return table_rows;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* @param {string[]} row_cells
|
|
691
|
+
* @param {number[]} column_widths
|
|
692
|
+
* @returns {string}
|
|
693
|
+
*/
|
|
694
|
+
function formatTableRow(row_cells, column_widths) {
|
|
695
|
+
/** @type {string[]} */
|
|
696
|
+
const padded_cells = [];
|
|
697
|
+
|
|
698
|
+
for (
|
|
699
|
+
let column_index = 0;
|
|
700
|
+
column_index < column_widths.length;
|
|
701
|
+
column_index += 1
|
|
702
|
+
) {
|
|
703
|
+
const cell_text = row_cells[column_index] ?? '';
|
|
704
|
+
|
|
705
|
+
padded_cells.push(cell_text.padEnd(column_widths[column_index], ' '));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return `| ${padded_cells.join(' | ')} |`;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* @param {number[]} column_widths
|
|
713
|
+
* @returns {string}
|
|
714
|
+
*/
|
|
715
|
+
function formatTableDivider(column_widths) {
|
|
716
|
+
/** @type {string[]} */
|
|
717
|
+
const divider_cells = [];
|
|
718
|
+
|
|
719
|
+
for (const column_width of column_widths) {
|
|
720
|
+
divider_cells.push('-'.repeat(column_width));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return `|-${divider_cells.join('-|-')}-|`;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* @param {ComarkNode[]} nodes
|
|
728
|
+
* @returns {string}
|
|
729
|
+
*/
|
|
730
|
+
function extractInlineText(nodes) {
|
|
731
|
+
/** @type {string[]} */
|
|
732
|
+
const rendered_chunks = [];
|
|
733
|
+
|
|
734
|
+
for (const node of nodes) {
|
|
735
|
+
if (typeof node === 'string') {
|
|
736
|
+
rendered_chunks.push(node);
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
rendered_chunks.push(extractInlineText(getElementChildren(node)));
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return rendered_chunks.join('');
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* @param {ComarkNode[]} nodes
|
|
748
|
+
* @param {Ansis} ansi
|
|
749
|
+
* @returns {string}
|
|
750
|
+
*/
|
|
751
|
+
function renderInlineCode(nodes, ansi) {
|
|
752
|
+
const code_text = extractInlineText(nodes);
|
|
753
|
+
const hidden_tick = ansi.bg(CODE_SURFACE).fg(CODE_SURFACE)('`');
|
|
754
|
+
const visible_code = ansi.bg(CODE_SURFACE).dim(code_text);
|
|
755
|
+
|
|
756
|
+
return `${hidden_tick}${visible_code}${hidden_tick}`;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* @param {ComarkElement} row_node
|
|
761
|
+
* @returns {string[]}
|
|
762
|
+
*/
|
|
763
|
+
function extractTableRowCells(row_node) {
|
|
764
|
+
/** @type {string[]} */
|
|
765
|
+
const row_cells = [];
|
|
766
|
+
|
|
767
|
+
for (const cell_node of getElementChildren(row_node)) {
|
|
768
|
+
if (typeof cell_node === 'string') {
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
row_cells.push(extractInlineText(getElementChildren(cell_node)));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return row_cells;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* @param {ComarkElement} node
|
|
780
|
+
* @param {{ ansi: Ansis, next_reference: number, resolved_links: OutputResolvedLinkItem[] }} render_state
|
|
781
|
+
* @returns {{ block_nodes: ComarkElement[], lead_text: string | null }}
|
|
782
|
+
*/
|
|
783
|
+
function collectListItemParts(node, render_state) {
|
|
784
|
+
const node_children = getElementChildren(node);
|
|
785
|
+
/** @type {ComarkElement[]} */
|
|
786
|
+
const block_nodes = [];
|
|
787
|
+
/** @type {string | null} */
|
|
788
|
+
let lead_text = null;
|
|
789
|
+
|
|
790
|
+
for (
|
|
791
|
+
let child_index = 0;
|
|
792
|
+
child_index < node_children.length;
|
|
793
|
+
child_index += 1
|
|
794
|
+
) {
|
|
795
|
+
const child = node_children[child_index];
|
|
796
|
+
|
|
797
|
+
if (typeof child === 'string') {
|
|
798
|
+
lead_text = appendLeadText(
|
|
799
|
+
lead_text,
|
|
800
|
+
renderInlineNodes([child], render_state),
|
|
801
|
+
);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const child_tag = getElementTag(child);
|
|
806
|
+
|
|
807
|
+
if (child_tag === 'p') {
|
|
808
|
+
const paragraph_text = renderInlineNodes(
|
|
809
|
+
getElementChildren(child),
|
|
810
|
+
render_state,
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
if (lead_text === null) {
|
|
814
|
+
lead_text = paragraph_text;
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
block_nodes.push(child);
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (isBlockTag(child_tag)) {
|
|
823
|
+
block_nodes.push(child);
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
lead_text = appendLeadText(
|
|
828
|
+
lead_text,
|
|
829
|
+
renderInlineNodes([child], render_state),
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return {
|
|
834
|
+
block_nodes,
|
|
835
|
+
lead_text,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* @param {string} text
|
|
841
|
+
* @param {string} item_prefix
|
|
842
|
+
* @param {string} followup_prefix
|
|
843
|
+
* @returns {string[]}
|
|
844
|
+
*/
|
|
845
|
+
function renderListParagraph(text, item_prefix, followup_prefix) {
|
|
846
|
+
const hanging_prefix = `${' '.repeat(stringWidth(item_prefix))}${followup_prefix.slice(item_prefix.length)}`;
|
|
847
|
+
|
|
848
|
+
return renderWrappedPrefixedLines(
|
|
849
|
+
text,
|
|
850
|
+
item_prefix,
|
|
851
|
+
hanging_prefix,
|
|
852
|
+
hanging_prefix,
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* @param {string} text
|
|
858
|
+
* @param {string} prefix
|
|
859
|
+
* @returns {string[]}
|
|
860
|
+
*/
|
|
861
|
+
function renderParagraphLines(text, prefix) {
|
|
862
|
+
return renderWrappedPrefixedLines(text, prefix, prefix, prefix);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* @param {string} value
|
|
867
|
+
* @param {number} indent_level
|
|
868
|
+
* @param {Ansis} ansi
|
|
869
|
+
* @returns {string}
|
|
870
|
+
*/
|
|
871
|
+
function renderQuoteBlock(value, indent_level, ansi) {
|
|
872
|
+
const indent_prefix = ' '.repeat(indent_level);
|
|
873
|
+
const quote_lines = value.split('\n');
|
|
874
|
+
const content_width = measureMaxLineWidth(quote_lines) + 1;
|
|
875
|
+
|
|
876
|
+
return quote_lines
|
|
877
|
+
.map((line) =>
|
|
878
|
+
renderQuoteLine(
|
|
879
|
+
padRenderedLine(line, content_width),
|
|
880
|
+
indent_prefix,
|
|
881
|
+
ansi,
|
|
882
|
+
),
|
|
883
|
+
)
|
|
884
|
+
.join('\n');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* @param {string} line
|
|
889
|
+
* @param {string} indent_prefix
|
|
890
|
+
* @param {Ansis} ansi
|
|
891
|
+
* @returns {string}
|
|
892
|
+
*/
|
|
893
|
+
function renderQuoteLine(line, indent_prefix, ansi) {
|
|
894
|
+
const quote_border = ansi.bgGray.gray('▕');
|
|
895
|
+
const quote_body = ansi.bgGray(` ${line}`);
|
|
896
|
+
|
|
897
|
+
return `${indent_prefix}${quote_border}${quote_body}`;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* @param {string} source_path
|
|
902
|
+
* @returns {string | null}
|
|
903
|
+
*/
|
|
904
|
+
function detectSourceLanguage(source_path) {
|
|
905
|
+
const source_extension = extname(source_path).toLowerCase();
|
|
906
|
+
const mapped_language = SOURCE_LANGUAGE_BY_EXTENSION[source_extension];
|
|
907
|
+
|
|
908
|
+
if (mapped_language) {
|
|
909
|
+
return mapped_language;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (source_extension.length === 0) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return normalizeShikiLanguage(source_extension.slice(1));
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* @param {string} language_name
|
|
921
|
+
* @returns {string | null}
|
|
922
|
+
*/
|
|
923
|
+
function normalizeShikiLanguage(language_name) {
|
|
924
|
+
const normalized_language = language_name.toLowerCase();
|
|
925
|
+
|
|
926
|
+
if (SHIKI_LANGUAGE_NAMES.has(normalized_language)) {
|
|
927
|
+
return normalized_language;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* @param {string} language_name
|
|
935
|
+
* @returns {boolean}
|
|
936
|
+
*/
|
|
937
|
+
function isMermaidFence(language_name) {
|
|
938
|
+
return language_name.toLowerCase() === 'mermaid';
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* @param {string | null} existing_value
|
|
943
|
+
* @param {string} value
|
|
944
|
+
* @returns {string}
|
|
945
|
+
*/
|
|
946
|
+
function appendLeadText(existing_value, value) {
|
|
947
|
+
if (existing_value === null) {
|
|
948
|
+
return value;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return `${existing_value}${value}`;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* @param {string} value
|
|
956
|
+
* @returns {string[]}
|
|
957
|
+
*/
|
|
958
|
+
function splitRenderedLines(value) {
|
|
959
|
+
const normalized_value = value.replace(/\n+$/du, '');
|
|
960
|
+
|
|
961
|
+
if (normalized_value.length === 0) {
|
|
962
|
+
return [''];
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return normalized_value.split('\n');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* @param {string} value
|
|
970
|
+
* @returns {string}
|
|
971
|
+
*/
|
|
972
|
+
function stripSingleTrailingLineBreak(value) {
|
|
973
|
+
return value.replace(/\n$/u, '');
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* @param {string} source_path
|
|
978
|
+
* @returns {boolean}
|
|
979
|
+
*/
|
|
980
|
+
function isMarkdownPath(source_path) {
|
|
981
|
+
return MARKDOWN_EXTENSIONS.has(extname(source_path).toLowerCase());
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* @param {string | null} target_value
|
|
986
|
+
* @returns {boolean}
|
|
987
|
+
*/
|
|
988
|
+
function isPathLikeMarkdownTarget(target_value) {
|
|
989
|
+
if (!target_value || target_value.startsWith('#')) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return !URI_SCHEME_PATTERN.test(target_value);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* @param {string} tag_name
|
|
998
|
+
* @returns {boolean}
|
|
999
|
+
*/
|
|
1000
|
+
function isHeadingTag(tag_name) {
|
|
1001
|
+
return /^h[1-6]$/du.test(tag_name);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* @param {string} tag_name
|
|
1006
|
+
* @returns {number}
|
|
1007
|
+
*/
|
|
1008
|
+
function getHeadingLevel(tag_name) {
|
|
1009
|
+
return Number.parseInt(tag_name.slice(1), 10);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* @param {string} tag_name
|
|
1014
|
+
* @returns {boolean}
|
|
1015
|
+
*/
|
|
1016
|
+
function isBlockTag(tag_name) {
|
|
1017
|
+
return (
|
|
1018
|
+
tag_name === 'blockquote' ||
|
|
1019
|
+
tag_name === 'ol' ||
|
|
1020
|
+
tag_name === 'pre' ||
|
|
1021
|
+
tag_name === 'table' ||
|
|
1022
|
+
tag_name === 'ul'
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* @param {ComarkElement} node
|
|
1028
|
+
* @returns {string}
|
|
1029
|
+
*/
|
|
1030
|
+
function getElementTag(node) {
|
|
1031
|
+
return node[0] ?? '';
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* @param {ComarkElement} node
|
|
1036
|
+
* @returns {Record<string, unknown>}
|
|
1037
|
+
*/
|
|
1038
|
+
function getElementProps(node) {
|
|
1039
|
+
return /** @type {Record<string, unknown>} */ (node[1]);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* @param {ComarkElement} node
|
|
1044
|
+
* @returns {ComarkNode[]}
|
|
1045
|
+
*/
|
|
1046
|
+
function getElementChildren(node) {
|
|
1047
|
+
return /** @type {ComarkNode[]} */ (node.slice(2));
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* @param {string} text
|
|
1052
|
+
* @param {string} tag_name
|
|
1053
|
+
* @param {Ansis} ansi
|
|
1054
|
+
* @returns {string}
|
|
1055
|
+
*/
|
|
1056
|
+
function styleHeadingText(text, tag_name, ansi) {
|
|
1057
|
+
if (tag_name === 'h1') {
|
|
1058
|
+
return ansi.bold.red(text);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return ansi.bold.blueBright(text);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* @param {Ansis} ansi
|
|
1066
|
+
* @returns {string}
|
|
1067
|
+
*/
|
|
1068
|
+
function renderDivider(ansi) {
|
|
1069
|
+
return ansi.gray(` ${'─'.repeat(BLOCK_WIDTH - 2)} `);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* @param {string} label
|
|
1074
|
+
* @param {number} content_width
|
|
1075
|
+
* @param {number} indent_level
|
|
1076
|
+
* @param {Ansis} ansi
|
|
1077
|
+
* @returns {string}
|
|
1078
|
+
*/
|
|
1079
|
+
function renderCodeBlockLabelLine(label, content_width, indent_level, ansi) {
|
|
1080
|
+
return renderCodeBlockLine(
|
|
1081
|
+
padLeftRenderedLine(label, content_width),
|
|
1082
|
+
content_width,
|
|
1083
|
+
indent_level,
|
|
1084
|
+
ansi,
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* @param {string} source_line
|
|
1090
|
+
* @param {number} content_width
|
|
1091
|
+
* @param {number} indent_level
|
|
1092
|
+
* @param {Ansis} ansi
|
|
1093
|
+
* @param {number} content_indent
|
|
1094
|
+
* @returns {string}
|
|
1095
|
+
*/
|
|
1096
|
+
function renderCodeBlockContentLine(
|
|
1097
|
+
source_line,
|
|
1098
|
+
content_width,
|
|
1099
|
+
indent_level,
|
|
1100
|
+
ansi,
|
|
1101
|
+
content_indent = 0,
|
|
1102
|
+
) {
|
|
1103
|
+
return renderCodeBlockLine(
|
|
1104
|
+
source_line,
|
|
1105
|
+
content_width,
|
|
1106
|
+
indent_level,
|
|
1107
|
+
ansi,
|
|
1108
|
+
content_indent,
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* @param {string} line
|
|
1114
|
+
* @param {number} content_width
|
|
1115
|
+
* @param {number} indent_level
|
|
1116
|
+
* @param {Ansis} ansi
|
|
1117
|
+
* @param {number} content_indent
|
|
1118
|
+
* @returns {string}
|
|
1119
|
+
*/
|
|
1120
|
+
function renderCodeBlockLine(
|
|
1121
|
+
line,
|
|
1122
|
+
content_width,
|
|
1123
|
+
indent_level,
|
|
1124
|
+
ansi,
|
|
1125
|
+
content_indent = 0,
|
|
1126
|
+
) {
|
|
1127
|
+
const padded_line = padRenderedLine(
|
|
1128
|
+
`${' '.repeat(content_indent)}${line}`,
|
|
1129
|
+
content_width,
|
|
1130
|
+
);
|
|
1131
|
+
const code_body = ansi.bg(CODE_SURFACE)(` ${padded_line} `);
|
|
1132
|
+
|
|
1133
|
+
return `${' '.repeat(indent_level)}${code_body}`;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* @param {string} label
|
|
1138
|
+
* @param {string[]} source_lines
|
|
1139
|
+
* @returns {number}
|
|
1140
|
+
*/
|
|
1141
|
+
function measureCodeBlockWidth(label, source_lines) {
|
|
1142
|
+
return Math.max(
|
|
1143
|
+
BLOCK_WIDTH - 2,
|
|
1144
|
+
stringWidth(label),
|
|
1145
|
+
measureMaxLineWidth(source_lines),
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* @param {string} text
|
|
1151
|
+
* @param {string} first_prefix
|
|
1152
|
+
* @param {string} next_prefix
|
|
1153
|
+
* @param {string} continuation_prefix
|
|
1154
|
+
* @returns {string[]}
|
|
1155
|
+
*/
|
|
1156
|
+
function renderWrappedPrefixedLines(
|
|
1157
|
+
text,
|
|
1158
|
+
first_prefix,
|
|
1159
|
+
next_prefix,
|
|
1160
|
+
continuation_prefix,
|
|
1161
|
+
) {
|
|
1162
|
+
const source_lines = text.split('\n');
|
|
1163
|
+
/** @type {string[]} */
|
|
1164
|
+
const rendered_lines = [];
|
|
1165
|
+
|
|
1166
|
+
for (
|
|
1167
|
+
let source_line_index = 0;
|
|
1168
|
+
source_line_index < source_lines.length;
|
|
1169
|
+
source_line_index += 1
|
|
1170
|
+
) {
|
|
1171
|
+
const source_line = source_lines[source_line_index];
|
|
1172
|
+
const line_prefix = source_line_index === 0 ? first_prefix : next_prefix;
|
|
1173
|
+
|
|
1174
|
+
rendered_lines.push(
|
|
1175
|
+
...wrapPrefixedLine(source_line, line_prefix, continuation_prefix),
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
return rendered_lines;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* @param {string} line
|
|
1184
|
+
* @param {string} first_prefix
|
|
1185
|
+
* @param {string} continuation_prefix
|
|
1186
|
+
* @returns {string[]}
|
|
1187
|
+
*/
|
|
1188
|
+
function wrapPrefixedLine(line, first_prefix, continuation_prefix) {
|
|
1189
|
+
if (line.length === 0) {
|
|
1190
|
+
return [first_prefix];
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const wrapped_line = wrapAnsi(
|
|
1194
|
+
line,
|
|
1195
|
+
Math.max(BLOCK_WIDTH - stringWidth(first_prefix), 1),
|
|
1196
|
+
{
|
|
1197
|
+
hard: false,
|
|
1198
|
+
trim: false,
|
|
1199
|
+
wordWrap: true,
|
|
1200
|
+
},
|
|
1201
|
+
);
|
|
1202
|
+
const wrapped_segments = splitRenderedLines(wrapped_line);
|
|
1203
|
+
|
|
1204
|
+
return wrapped_segments.map((segment, segment_index) => {
|
|
1205
|
+
const normalized_segment = segment.replace(/\s+$/u, '');
|
|
1206
|
+
|
|
1207
|
+
if (segment_index === 0) {
|
|
1208
|
+
return `${first_prefix}${normalized_segment}`;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return `${continuation_prefix}${normalized_segment}`;
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* @param {string[]} lines
|
|
1217
|
+
* @returns {number}
|
|
1218
|
+
*/
|
|
1219
|
+
function measureMaxLineWidth(lines) {
|
|
1220
|
+
let max_width = 0;
|
|
1221
|
+
|
|
1222
|
+
for (const line of lines) {
|
|
1223
|
+
max_width = Math.max(max_width, stringWidth(line));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
return max_width;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* @param {string} line
|
|
1231
|
+
* @param {number} content_width
|
|
1232
|
+
* @returns {string}
|
|
1233
|
+
*/
|
|
1234
|
+
function padRenderedLine(line, content_width) {
|
|
1235
|
+
return `${line}${' '.repeat(Math.max(content_width - stringWidth(line), 0))}`;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* @param {string} line
|
|
1240
|
+
* @param {number} content_width
|
|
1241
|
+
* @returns {string}
|
|
1242
|
+
*/
|
|
1243
|
+
function padLeftRenderedLine(line, content_width) {
|
|
1244
|
+
return `${' '.repeat(Math.max(content_width - stringWidth(line), 0))}${line}`;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* @param {boolean} color_enabled
|
|
1249
|
+
* @returns {Ansis}
|
|
1250
|
+
*/
|
|
1251
|
+
function createAnsi(color_enabled) {
|
|
1252
|
+
return new Ansis(color_enabled ? 3 : 0);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* @param {string} hex_value
|
|
1257
|
+
* @returns {{ r: number, g: number, b: number, a: number }}
|
|
1258
|
+
*/
|
|
1259
|
+
function hexToRgba(hex_value) {
|
|
1260
|
+
const normalized_hex = normalizeHex(hex_value);
|
|
1261
|
+
|
|
1262
|
+
return {
|
|
1263
|
+
a: Number.parseInt(normalized_hex.slice(6, 8), 16) / 255,
|
|
1264
|
+
b: Number.parseInt(normalized_hex.slice(4, 6), 16),
|
|
1265
|
+
g: Number.parseInt(normalized_hex.slice(2, 4), 16),
|
|
1266
|
+
r: Number.parseInt(normalized_hex.slice(0, 2), 16),
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* @param {number} red_value
|
|
1272
|
+
* @param {number} green_value
|
|
1273
|
+
* @param {number} blue_value
|
|
1274
|
+
* @returns {string}
|
|
1275
|
+
*/
|
|
1276
|
+
function rgbToHex(red_value, green_value, blue_value) {
|
|
1277
|
+
return [red_value, green_value, blue_value]
|
|
1278
|
+
.map((channel_value) => {
|
|
1279
|
+
const hex_value = channel_value.toString(16);
|
|
1280
|
+
|
|
1281
|
+
if (hex_value.length === 1) {
|
|
1282
|
+
return `0${hex_value}`;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
return hex_value;
|
|
1286
|
+
})
|
|
1287
|
+
.join('');
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* @param {string} hex_value
|
|
1292
|
+
* @param {string} theme_type
|
|
1293
|
+
* @returns {string}
|
|
1294
|
+
*/
|
|
1295
|
+
function applyHexAlpha(hex_value, theme_type) {
|
|
1296
|
+
const rgba_value = hexToRgba(hex_value);
|
|
1297
|
+
|
|
1298
|
+
if (theme_type === 'dark') {
|
|
1299
|
+
return rgbToHex(
|
|
1300
|
+
Math.floor(rgba_value.r * rgba_value.a),
|
|
1301
|
+
Math.floor(rgba_value.g * rgba_value.a),
|
|
1302
|
+
Math.floor(rgba_value.b * rgba_value.a),
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
return rgbToHex(
|
|
1307
|
+
Math.floor(rgba_value.r * rgba_value.a + 255 * (1 - rgba_value.a)),
|
|
1308
|
+
Math.floor(rgba_value.g * rgba_value.a + 255 * (1 - rgba_value.a)),
|
|
1309
|
+
Math.floor(rgba_value.b * rgba_value.a + 255 * (1 - rgba_value.a)),
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* @param {string} hex_value
|
|
1315
|
+
* @returns {string}
|
|
1316
|
+
*/
|
|
1317
|
+
function normalizeHex(hex_value) {
|
|
1318
|
+
const sanitized_hex = hex_value.replace(/^#/u, '');
|
|
1319
|
+
|
|
1320
|
+
if (sanitized_hex.length === 3) {
|
|
1321
|
+
return `${sanitized_hex[0]}${sanitized_hex[0]}${sanitized_hex[1]}${sanitized_hex[1]}${sanitized_hex[2]}${sanitized_hex[2]}ff`;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
if (sanitized_hex.length === 4) {
|
|
1325
|
+
return `${sanitized_hex[0]}${sanitized_hex[0]}${sanitized_hex[1]}${sanitized_hex[1]}${sanitized_hex[2]}${sanitized_hex[2]}${sanitized_hex[3]}${sanitized_hex[3]}`;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (sanitized_hex.length === 6) {
|
|
1329
|
+
return `${sanitized_hex}ff`;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
return sanitized_hex.toLowerCase();
|
|
1333
|
+
}
|