@wangzhizhi/remi 0.0.1-alpha
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 +9 -0
- package/dist/doctor.js +108 -0
- package/dist/git.js +41 -0
- package/dist/help.js +27 -0
- package/dist/i18n.js +422 -0
- package/dist/index.js +97 -0
- package/dist/initPrompt.js +17 -0
- package/dist/model.js +116 -0
- package/dist/modelSelection.js +34 -0
- package/dist/permissionDisplay.js +46 -0
- package/dist/permissions.js +206 -0
- package/dist/repl.js +346 -0
- package/dist/resume.js +3 -0
- package/dist/setup.js +62 -0
- package/dist/statusline.js +59 -0
- package/dist/style.js +48 -0
- package/dist/syntaxTheme.js +39 -0
- package/dist/tui/RemiApp.js +1756 -0
- package/dist/tui/commands.js +427 -0
- package/dist/tui/index.js +42 -0
- package/dist/tui/renderers/Header.js +28 -0
- package/dist/tui/renderers/MessageList.js +1176 -0
- package/dist/tui/renderers/PromptBox.js +118 -0
- package/dist/tui/renderers/StatusLine.js +124 -0
- package/dist/tui/renderers/WorkingIndicator.js +70 -0
- package/dist/tui/slashCommandHighlight.js +8 -0
- package/dist/tui/theme.js +13 -0
- package/dist/tui/types.js +1 -0
- package/dist/usage.js +66 -0
- package/dist/version.js +5 -0
- package/node_modules/@remi/compact/dist/index.js +389 -0
- package/node_modules/@remi/compact/package.json +8 -0
- package/node_modules/@remi/config/dist/index.js +426 -0
- package/node_modules/@remi/config/package.json +8 -0
- package/node_modules/@remi/core/dist/contextBuilder.js +344 -0
- package/node_modules/@remi/core/dist/directoryOverview.js +359 -0
- package/node_modules/@remi/core/dist/index.js +2843 -0
- package/node_modules/@remi/core/dist/projectInstructions.js +123 -0
- package/node_modules/@remi/core/dist/responseStyles.js +98 -0
- package/node_modules/@remi/core/package.json +8 -0
- package/node_modules/@remi/llm/dist/index.js +804 -0
- package/node_modules/@remi/llm/package.json +8 -0
- package/node_modules/@remi/memory/dist/index.js +312 -0
- package/node_modules/@remi/memory/package.json +8 -0
- package/node_modules/@remi/permissions/dist/index.js +90 -0
- package/node_modules/@remi/permissions/package.json +8 -0
- package/node_modules/@remi/sessions/dist/index.js +370 -0
- package/node_modules/@remi/sessions/package.json +8 -0
- package/node_modules/@remi/skills/dist/index.js +273 -0
- package/node_modules/@remi/skills/package.json +8 -0
- package/node_modules/@remi/terminal-markdown/dist/index.js +1412 -0
- package/node_modules/@remi/terminal-markdown/package.json +8 -0
- package/node_modules/@remi/tools/dist/index.js +3875 -0
- package/node_modules/@remi/tools/package.json +8 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1412 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import hljs from 'highlight.js/lib/common';
|
|
5
|
+
import { marked } from 'marked';
|
|
6
|
+
export const defaultTerminalMarkdownTheme = {
|
|
7
|
+
text: '#d7d9dd',
|
|
8
|
+
muted: '#7d838b',
|
|
9
|
+
accent: '#59f3f3',
|
|
10
|
+
success: '#56d364',
|
|
11
|
+
danger: '#ff6b6b',
|
|
12
|
+
warning: '#f2cc60',
|
|
13
|
+
emphasis: '#f4b8e4',
|
|
14
|
+
strong: '#ffffff',
|
|
15
|
+
strongEmphasis: '#c7a4ff',
|
|
16
|
+
orderedListMarker: '#c7a4ff',
|
|
17
|
+
quoteText: '#7d838b',
|
|
18
|
+
diffAddBackground: '#123d28',
|
|
19
|
+
diffRemoveBackground: '#4a1e1e',
|
|
20
|
+
codeBackground: '#1f2226',
|
|
21
|
+
codeKeyword: '#c792ea',
|
|
22
|
+
codeString: '#a6e3a1',
|
|
23
|
+
codeNumber: '#f9e2af',
|
|
24
|
+
codeComment: '#7d838b',
|
|
25
|
+
codeType: '#89ddff',
|
|
26
|
+
codeTitle: '#82aaff',
|
|
27
|
+
codeFunction: '#7aa2f7',
|
|
28
|
+
codeVariable: '#f2cdcd',
|
|
29
|
+
codeProperty: '#94e2d5',
|
|
30
|
+
codeOperator: '#89ddff',
|
|
31
|
+
codeMeta: '#f38ba8',
|
|
32
|
+
codeSection: '#fab387',
|
|
33
|
+
codeRegexp: '#fab387',
|
|
34
|
+
codePunctuation: '#bac2de',
|
|
35
|
+
};
|
|
36
|
+
export const terminalSyntaxThemeIds = [
|
|
37
|
+
'default',
|
|
38
|
+
'catppuccin-frappe',
|
|
39
|
+
'catppuccin-latte',
|
|
40
|
+
'catppuccin-macchiato',
|
|
41
|
+
'catppuccin-mocha',
|
|
42
|
+
'base16-256',
|
|
43
|
+
'base16-eighties-dark',
|
|
44
|
+
'base16-mocha-dark',
|
|
45
|
+
'base16-ocean-dark',
|
|
46
|
+
'base16-ocean-light',
|
|
47
|
+
];
|
|
48
|
+
const terminalSyntaxThemeDefinitions = {
|
|
49
|
+
default: {
|
|
50
|
+
id: 'default',
|
|
51
|
+
label: 'Default',
|
|
52
|
+
description: 'Current Remi syntax palette.',
|
|
53
|
+
palette: {},
|
|
54
|
+
},
|
|
55
|
+
'catppuccin-frappe': {
|
|
56
|
+
id: 'catppuccin-frappe',
|
|
57
|
+
label: 'Catppuccin Frappe',
|
|
58
|
+
description: 'Soft dark blue-gray palette.',
|
|
59
|
+
palette: {
|
|
60
|
+
text: '#c6d0f5',
|
|
61
|
+
muted: '#838ba7',
|
|
62
|
+
emphasis: '#f4b8e4',
|
|
63
|
+
strong: '#ffffff',
|
|
64
|
+
strongEmphasis: '#ca9ee6',
|
|
65
|
+
orderedListMarker: '#ca9ee6',
|
|
66
|
+
quoteText: '#838ba7',
|
|
67
|
+
diffAddBackground: '#143d2b',
|
|
68
|
+
diffRemoveBackground: '#4b2028',
|
|
69
|
+
codeBackground: '#292c3c',
|
|
70
|
+
codeKeyword: '#ca9ee6',
|
|
71
|
+
codeString: '#a6d189',
|
|
72
|
+
codeNumber: '#ef9f76',
|
|
73
|
+
codeComment: '#838ba7',
|
|
74
|
+
codeType: '#8caaee',
|
|
75
|
+
codeTitle: '#8caaee',
|
|
76
|
+
codeFunction: '#8caaee',
|
|
77
|
+
codeVariable: '#c6d0f5',
|
|
78
|
+
codeProperty: '#81c8be',
|
|
79
|
+
codeOperator: '#99d1db',
|
|
80
|
+
codeMeta: '#e78284',
|
|
81
|
+
codeSection: '#eebebe',
|
|
82
|
+
codeRegexp: '#f2d5cf',
|
|
83
|
+
codePunctuation: '#b5bfe2',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
'catppuccin-latte': {
|
|
87
|
+
id: 'catppuccin-latte',
|
|
88
|
+
label: 'Catppuccin Latte',
|
|
89
|
+
description: 'Light palette with warm comments.',
|
|
90
|
+
palette: {
|
|
91
|
+
text: '#4c4f69',
|
|
92
|
+
muted: '#8c8fa1',
|
|
93
|
+
emphasis: '#ea76cb',
|
|
94
|
+
strong: '#1e1e2e',
|
|
95
|
+
strongEmphasis: '#8839ef',
|
|
96
|
+
orderedListMarker: '#8839ef',
|
|
97
|
+
quoteText: '#8c8fa1',
|
|
98
|
+
diffAddBackground: '#d7f0d2',
|
|
99
|
+
diffRemoveBackground: '#f6d6d6',
|
|
100
|
+
codeBackground: '#eff1f5',
|
|
101
|
+
codeKeyword: '#8839ef',
|
|
102
|
+
codeString: '#40a02b',
|
|
103
|
+
codeNumber: '#fe640b',
|
|
104
|
+
codeComment: '#8c8fa1',
|
|
105
|
+
codeType: '#1e66f5',
|
|
106
|
+
codeTitle: '#1e66f5',
|
|
107
|
+
codeFunction: '#1e66f5',
|
|
108
|
+
codeVariable: '#4c4f69',
|
|
109
|
+
codeProperty: '#179299',
|
|
110
|
+
codeOperator: '#04a5e5',
|
|
111
|
+
codeMeta: '#d20f39',
|
|
112
|
+
codeSection: '#df8e1d',
|
|
113
|
+
codeRegexp: '#dd7878',
|
|
114
|
+
codePunctuation: '#6c6f85',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
'catppuccin-macchiato': {
|
|
118
|
+
id: 'catppuccin-macchiato',
|
|
119
|
+
label: 'Catppuccin Macchiato',
|
|
120
|
+
description: 'Dark lavender-blue palette.',
|
|
121
|
+
palette: {
|
|
122
|
+
text: '#cad3f5',
|
|
123
|
+
muted: '#8087a2',
|
|
124
|
+
emphasis: '#f5bde6',
|
|
125
|
+
strong: '#ffffff',
|
|
126
|
+
strongEmphasis: '#c6a0f6',
|
|
127
|
+
orderedListMarker: '#c6a0f6',
|
|
128
|
+
quoteText: '#8087a2',
|
|
129
|
+
diffAddBackground: '#153e2e',
|
|
130
|
+
diffRemoveBackground: '#4f2028',
|
|
131
|
+
codeBackground: '#24273a',
|
|
132
|
+
codeKeyword: '#c6a0f6',
|
|
133
|
+
codeString: '#a6da95',
|
|
134
|
+
codeNumber: '#f5a97f',
|
|
135
|
+
codeComment: '#8087a2',
|
|
136
|
+
codeType: '#8aadf4',
|
|
137
|
+
codeTitle: '#8aadf4',
|
|
138
|
+
codeFunction: '#8aadf4',
|
|
139
|
+
codeVariable: '#cad3f5',
|
|
140
|
+
codeProperty: '#8bd5ca',
|
|
141
|
+
codeOperator: '#91d7e3',
|
|
142
|
+
codeMeta: '#ed8796',
|
|
143
|
+
codeSection: '#f0c6c6',
|
|
144
|
+
codeRegexp: '#f4dbd6',
|
|
145
|
+
codePunctuation: '#b8c0e0',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
'catppuccin-mocha': {
|
|
149
|
+
id: 'catppuccin-mocha',
|
|
150
|
+
label: 'Catppuccin Mocha',
|
|
151
|
+
description: 'High-contrast dark pastel palette.',
|
|
152
|
+
palette: {
|
|
153
|
+
text: '#cdd6f4',
|
|
154
|
+
muted: '#7f849c',
|
|
155
|
+
emphasis: '#f5c2e7',
|
|
156
|
+
strong: '#ffffff',
|
|
157
|
+
strongEmphasis: '#cba6f7',
|
|
158
|
+
orderedListMarker: '#cba6f7',
|
|
159
|
+
quoteText: '#7f849c',
|
|
160
|
+
diffAddBackground: '#143c2c',
|
|
161
|
+
diffRemoveBackground: '#4a1f2a',
|
|
162
|
+
codeBackground: '#1e1e2e',
|
|
163
|
+
codeKeyword: '#cba6f7',
|
|
164
|
+
codeString: '#a6e3a1',
|
|
165
|
+
codeNumber: '#fab387',
|
|
166
|
+
codeComment: '#7f849c',
|
|
167
|
+
codeType: '#89b4fa',
|
|
168
|
+
codeTitle: '#89b4fa',
|
|
169
|
+
codeFunction: '#89b4fa',
|
|
170
|
+
codeVariable: '#cdd6f4',
|
|
171
|
+
codeProperty: '#94e2d5',
|
|
172
|
+
codeOperator: '#89dceb',
|
|
173
|
+
codeMeta: '#f38ba8',
|
|
174
|
+
codeSection: '#f2cdcd',
|
|
175
|
+
codeRegexp: '#f5e0dc',
|
|
176
|
+
codePunctuation: '#bac2de',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
'base16-256': {
|
|
180
|
+
id: 'base16-256',
|
|
181
|
+
label: 'Base16 256',
|
|
182
|
+
description: 'ANSI-friendly dark palette.',
|
|
183
|
+
palette: {
|
|
184
|
+
text: '#e0e0e0',
|
|
185
|
+
muted: '#808080',
|
|
186
|
+
diffAddBackground: '#1f3a2b',
|
|
187
|
+
diffRemoveBackground: '#3a2020',
|
|
188
|
+
codeBackground: '#101010',
|
|
189
|
+
codeKeyword: '#af87ff',
|
|
190
|
+
codeString: '#afd75f',
|
|
191
|
+
codeNumber: '#ffaf5f',
|
|
192
|
+
codeComment: '#808080',
|
|
193
|
+
codeType: '#87afff',
|
|
194
|
+
codeTitle: '#87d7ff',
|
|
195
|
+
codeFunction: '#87d7ff',
|
|
196
|
+
codeVariable: '#e0e0e0',
|
|
197
|
+
codeProperty: '#87d7d7',
|
|
198
|
+
codeOperator: '#87d7ff',
|
|
199
|
+
codeMeta: '#ff5f87',
|
|
200
|
+
codeSection: '#ffd75f',
|
|
201
|
+
codeRegexp: '#ffaf5f',
|
|
202
|
+
codePunctuation: '#bcbcbc',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
'base16-eighties-dark': {
|
|
206
|
+
id: 'base16-eighties-dark',
|
|
207
|
+
label: 'Base16 Eighties Dark',
|
|
208
|
+
description: 'Retro dark palette with warm strings.',
|
|
209
|
+
palette: {
|
|
210
|
+
text: '#d3d0c8',
|
|
211
|
+
muted: '#747369',
|
|
212
|
+
diffAddBackground: '#253827',
|
|
213
|
+
diffRemoveBackground: '#442526',
|
|
214
|
+
codeBackground: '#2d2d2d',
|
|
215
|
+
codeKeyword: '#cc99cc',
|
|
216
|
+
codeString: '#99cc99',
|
|
217
|
+
codeNumber: '#f99157',
|
|
218
|
+
codeComment: '#747369',
|
|
219
|
+
codeType: '#6699cc',
|
|
220
|
+
codeTitle: '#6699cc',
|
|
221
|
+
codeFunction: '#6699cc',
|
|
222
|
+
codeVariable: '#d3d0c8',
|
|
223
|
+
codeProperty: '#66cccc',
|
|
224
|
+
codeOperator: '#66cccc',
|
|
225
|
+
codeMeta: '#f2777a',
|
|
226
|
+
codeSection: '#ffcc66',
|
|
227
|
+
codeRegexp: '#f99157',
|
|
228
|
+
codePunctuation: '#d3d0c8',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
'base16-mocha-dark': {
|
|
232
|
+
id: 'base16-mocha-dark',
|
|
233
|
+
label: 'Base16 Mocha Dark',
|
|
234
|
+
description: 'Muted coffee-toned dark palette.',
|
|
235
|
+
palette: {
|
|
236
|
+
text: '#d0c8c6',
|
|
237
|
+
muted: '#7e705a',
|
|
238
|
+
diffAddBackground: '#293a26',
|
|
239
|
+
diffRemoveBackground: '#472525',
|
|
240
|
+
codeBackground: '#211f1f',
|
|
241
|
+
codeKeyword: '#a89bb9',
|
|
242
|
+
codeString: '#beb55b',
|
|
243
|
+
codeNumber: '#b98b56',
|
|
244
|
+
codeComment: '#7e705a',
|
|
245
|
+
codeType: '#8ab3b5',
|
|
246
|
+
codeTitle: '#8ab3b5',
|
|
247
|
+
codeFunction: '#8ab3b5',
|
|
248
|
+
codeVariable: '#d0c8c6',
|
|
249
|
+
codeProperty: '#7bbda4',
|
|
250
|
+
codeOperator: '#7bbda4',
|
|
251
|
+
codeMeta: '#cb6077',
|
|
252
|
+
codeSection: '#f4bc87',
|
|
253
|
+
codeRegexp: '#b98b56',
|
|
254
|
+
codePunctuation: '#b8afad',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
'base16-ocean-dark': {
|
|
258
|
+
id: 'base16-ocean-dark',
|
|
259
|
+
label: 'Base16 Ocean Dark',
|
|
260
|
+
description: 'Cool balanced dark palette.',
|
|
261
|
+
palette: {
|
|
262
|
+
text: '#c0c5ce',
|
|
263
|
+
muted: '#65737e',
|
|
264
|
+
diffAddBackground: '#243b31',
|
|
265
|
+
diffRemoveBackground: '#40262b',
|
|
266
|
+
codeBackground: '#2b303b',
|
|
267
|
+
codeKeyword: '#b48ead',
|
|
268
|
+
codeString: '#a3be8c',
|
|
269
|
+
codeNumber: '#d08770',
|
|
270
|
+
codeComment: '#65737e',
|
|
271
|
+
codeType: '#8fa1b3',
|
|
272
|
+
codeTitle: '#8fa1b3',
|
|
273
|
+
codeFunction: '#8fa1b3',
|
|
274
|
+
codeVariable: '#c0c5ce',
|
|
275
|
+
codeProperty: '#96b5b4',
|
|
276
|
+
codeOperator: '#96b5b4',
|
|
277
|
+
codeMeta: '#bf616a',
|
|
278
|
+
codeSection: '#ebcb8b',
|
|
279
|
+
codeRegexp: '#d08770',
|
|
280
|
+
codePunctuation: '#c0c5ce',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
'base16-ocean-light': {
|
|
284
|
+
id: 'base16-ocean-light',
|
|
285
|
+
label: 'Base16 Ocean Light',
|
|
286
|
+
description: 'Light variant of the ocean palette.',
|
|
287
|
+
palette: {
|
|
288
|
+
text: '#4f5b66',
|
|
289
|
+
muted: '#a7adba',
|
|
290
|
+
diffAddBackground: '#dcebd7',
|
|
291
|
+
diffRemoveBackground: '#f1d7da',
|
|
292
|
+
codeBackground: '#eff1f5',
|
|
293
|
+
codeKeyword: '#b48ead',
|
|
294
|
+
codeString: '#90a959',
|
|
295
|
+
codeNumber: '#d08770',
|
|
296
|
+
codeComment: '#a7adba',
|
|
297
|
+
codeType: '#8fa1b3',
|
|
298
|
+
codeTitle: '#8fa1b3',
|
|
299
|
+
codeFunction: '#8fa1b3',
|
|
300
|
+
codeVariable: '#4f5b66',
|
|
301
|
+
codeProperty: '#5fb3b3',
|
|
302
|
+
codeOperator: '#5fb3b3',
|
|
303
|
+
codeMeta: '#bf616a',
|
|
304
|
+
codeSection: '#f7ca88',
|
|
305
|
+
codeRegexp: '#d08770',
|
|
306
|
+
codePunctuation: '#65737e',
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
export function listTerminalSyntaxThemes() {
|
|
311
|
+
return terminalSyntaxThemeIds.map(id => {
|
|
312
|
+
const { label, description } = terminalSyntaxThemeDefinitions[id];
|
|
313
|
+
return { id, label, description };
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
export function isTerminalSyntaxThemeId(value) {
|
|
317
|
+
return typeof value === 'string' && terminalSyntaxThemeIds.includes(value);
|
|
318
|
+
}
|
|
319
|
+
export function resolveTerminalSyntaxThemeId(value) {
|
|
320
|
+
return isTerminalSyntaxThemeId(value) ? value : 'default';
|
|
321
|
+
}
|
|
322
|
+
export function terminalSyntaxThemeLabel(id) {
|
|
323
|
+
const resolved = resolveTerminalSyntaxThemeId(id);
|
|
324
|
+
return terminalSyntaxThemeDefinitions[resolved].label;
|
|
325
|
+
}
|
|
326
|
+
export function applyTerminalSyntaxTheme(theme, id) {
|
|
327
|
+
const resolved = resolveTerminalSyntaxThemeId(id);
|
|
328
|
+
return { ...theme, ...terminalSyntaxThemeDefinitions[resolved].palette };
|
|
329
|
+
}
|
|
330
|
+
const markdownSyntaxPattern = /[`*_#[\]>|~-]|\d+[.)、]\s|```/;
|
|
331
|
+
const maxHighlightJsLineLength = 500;
|
|
332
|
+
const maxHighlightJsBlockLength = 120_000;
|
|
333
|
+
const maxHighlightCacheEntries = 2000;
|
|
334
|
+
const maxHighlightBlockCacheEntries = 500;
|
|
335
|
+
const codeTabSize = 4;
|
|
336
|
+
const codePanelPadding = ' ';
|
|
337
|
+
const omissionMarker = '⋮';
|
|
338
|
+
const highlightLineCache = new Map();
|
|
339
|
+
const highlightBlockCache = new Map();
|
|
340
|
+
export function TerminalMarkdown({ text, theme = defaultTerminalMarkdownTheme, width, paddingX = 0 }) {
|
|
341
|
+
const tokens = useMemo(() => parseMarkdownTokens(text), [text]);
|
|
342
|
+
const contentWidth = paddedContentWidth(width, paddingX);
|
|
343
|
+
return (_jsx(_Fragment, { children: tokens.map((token, index) => (_jsx(MarkdownBlockView, { token: token, theme: theme, width: width, contentWidth: contentWidth, paddingX: paddingX }, `${index}-${token.type}-${token.raw ?? token.text ?? ''}`))) }));
|
|
344
|
+
}
|
|
345
|
+
export function StreamingMarkdown({ text, theme = defaultTerminalMarkdownTheme, width, paddingX = 0 }) {
|
|
346
|
+
const { stable, tail } = useMemo(() => splitStableMarkdownPrefix(text), [text]);
|
|
347
|
+
const stableTokens = useMemo(() => parseMarkdownTokens(stable), [stable]);
|
|
348
|
+
const tailTokens = useMemo(() => parseMarkdownTokens(tail), [tail]);
|
|
349
|
+
const contentWidth = paddedContentWidth(width, paddingX);
|
|
350
|
+
return (_jsxs(_Fragment, { children: [stableTokens.map((token, index) => (_jsx(MarkdownBlockView, { token: token, theme: theme, width: width, contentWidth: contentWidth, paddingX: paddingX }, `stable-${index}-${token.type}-${token.raw ?? token.text ?? ''}`))), tailTokens.map((token, index) => (_jsx(MarkdownBlockView, { token: token, theme: theme, width: width, contentWidth: contentWidth, paddingX: paddingX }, `tail-${index}-${token.type}-${token.raw ?? token.text ?? ''}`)))] }));
|
|
351
|
+
}
|
|
352
|
+
function paddedContentWidth(width, paddingX) {
|
|
353
|
+
return width === undefined ? undefined : Math.max(1, width - paddingX * 2);
|
|
354
|
+
}
|
|
355
|
+
export function TerminalInlineMarkdown({ text, theme = defaultTerminalMarkdownTheme }) {
|
|
356
|
+
return (_jsx(Text, { color: theme.text, children: _jsx(InlineTokens, { tokens: undefined, fallback: text, theme: theme }) }));
|
|
357
|
+
}
|
|
358
|
+
function parseMarkdownTokens(text) {
|
|
359
|
+
if (text.length === 0) {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
if (!markdownSyntaxPattern.test(text.slice(0, 500))) {
|
|
363
|
+
return text.split(/\r?\n/).map(line => ({ type: line.trim().length === 0 ? 'space' : 'paragraph', text: line, raw: line }));
|
|
364
|
+
}
|
|
365
|
+
return marked.lexer(text, { gfm: true, breaks: false });
|
|
366
|
+
}
|
|
367
|
+
function splitStableMarkdownPrefix(text) {
|
|
368
|
+
const boundary = lastStableBlockBoundary(text);
|
|
369
|
+
if (boundary <= 0 || boundary >= text.length) {
|
|
370
|
+
return { stable: '', tail: text };
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
stable: text.slice(0, boundary),
|
|
374
|
+
tail: text.slice(boundary),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function lastStableBlockBoundary(text) {
|
|
378
|
+
if (hasUnclosedFence(text)) {
|
|
379
|
+
return text.lastIndexOf('\n```');
|
|
380
|
+
}
|
|
381
|
+
const paragraphBoundary = text.lastIndexOf('\n\n');
|
|
382
|
+
if (paragraphBoundary >= 0) {
|
|
383
|
+
return paragraphBoundary + 2;
|
|
384
|
+
}
|
|
385
|
+
const lineBoundary = text.lastIndexOf('\n');
|
|
386
|
+
return lineBoundary >= 0 ? lineBoundary + 1 : 0;
|
|
387
|
+
}
|
|
388
|
+
function hasUnclosedFence(text) {
|
|
389
|
+
const matches = text.match(/^```/gm);
|
|
390
|
+
return (matches?.length ?? 0) % 2 === 1;
|
|
391
|
+
}
|
|
392
|
+
function MarkdownBlockView({ token, theme, width, contentWidth, paddingX, }) {
|
|
393
|
+
if (token.type === 'hr') {
|
|
394
|
+
return _jsx(MarkdownTokenView, { token: token, theme: theme, width: width });
|
|
395
|
+
}
|
|
396
|
+
const rendered = _jsx(MarkdownTokenView, { token: token, theme: theme, width: contentWidth });
|
|
397
|
+
return paddingX > 0 ? (_jsx(Box, { flexDirection: "column", paddingX: paddingX, children: rendered })) : rendered;
|
|
398
|
+
}
|
|
399
|
+
function MarkdownTokenView({ token, theme, width }) {
|
|
400
|
+
if (token.type === 'space') {
|
|
401
|
+
return _jsx(Box, { height: 1 });
|
|
402
|
+
}
|
|
403
|
+
if (token.type === 'heading') {
|
|
404
|
+
return (_jsx(Text, { color: theme.accent, bold: true, children: inlineText(token.tokens, token.text) }));
|
|
405
|
+
}
|
|
406
|
+
if (token.type === 'paragraph' || token.type === 'text') {
|
|
407
|
+
const decorated = renderDecoratedPlainLine(token.text ?? token.raw ?? '', theme);
|
|
408
|
+
if (decorated) {
|
|
409
|
+
return decorated;
|
|
410
|
+
}
|
|
411
|
+
return (_jsx(Text, { wrap: "wrap", children: _jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? token.raw ?? '', theme: theme }) }));
|
|
412
|
+
}
|
|
413
|
+
if (token.type === 'list') {
|
|
414
|
+
return _jsx(MarkdownList, { token: token, theme: theme });
|
|
415
|
+
}
|
|
416
|
+
if (token.type === 'code') {
|
|
417
|
+
return _jsx(TerminalCodeBlock, { language: (token.lang ?? '').toLowerCase(), code: token.text ?? '', theme: theme, width: width });
|
|
418
|
+
}
|
|
419
|
+
if (token.type === 'table') {
|
|
420
|
+
return _jsx(MarkdownTable, { header: token.header ?? [], rows: token.rows ?? [], raw: token.raw, theme: theme, width: width });
|
|
421
|
+
}
|
|
422
|
+
if (token.type === 'blockquote') {
|
|
423
|
+
const quoteTheme = quoteThemeFor(theme);
|
|
424
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.muted, children: "\u2502 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: (token.tokens ?? []).map((child, index) => (_jsx(MarkdownTokenView, { token: child, theme: quoteTheme, width: width ? Math.max(0, width - 2) : undefined }, `${index}-${child.type}-${child.raw ?? child.text ?? ''}`))) })] }));
|
|
425
|
+
}
|
|
426
|
+
if (token.type === 'hr') {
|
|
427
|
+
return _jsx(Text, { color: theme.muted, wrap: "truncate-end", children: '─'.repeat(Math.max(1, width ?? 40)) });
|
|
428
|
+
}
|
|
429
|
+
return (_jsx(Text, { wrap: "wrap", children: _jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? token.raw ?? '', theme: theme }) }));
|
|
430
|
+
}
|
|
431
|
+
function MarkdownList({ token, theme }) {
|
|
432
|
+
const start = typeof token.start === 'number' ? token.start : 1;
|
|
433
|
+
return (_jsx(_Fragment, { children: (token.items ?? []).map((item, index) => {
|
|
434
|
+
const marker = token.ordered ? `${start + index}.` : '·';
|
|
435
|
+
const markerColor = token.ordered ? theme.orderedListMarker : theme.accent;
|
|
436
|
+
const content = inlineText(item.tokens, item.text ?? item.raw ?? '').replace(/\n+$/g, '');
|
|
437
|
+
const [firstLine = '', ...continuationLines] = content.split(/\r?\n/);
|
|
438
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: markerColor, children: [marker, ' '] }), continuationLines.length === 0 ? (_jsx(InlineTokens, { tokens: item.tokens, fallback: content, theme: theme })) : (_jsx(InlineTokens, { tokens: undefined, fallback: firstLine, theme: theme }))] }), continuationLines.map((line, lineIndex) => (_jsx(DecoratedPlainLine, { line: line, theme: theme }, `${lineIndex}-${line}`)))] }, `${index}-${content}`));
|
|
439
|
+
}) }));
|
|
440
|
+
}
|
|
441
|
+
function renderDecoratedPlainLine(line, theme) {
|
|
442
|
+
if (!isDecoratedPlainLine(line)) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
return _jsx(DecoratedPlainLine, { line: line, theme: theme });
|
|
446
|
+
}
|
|
447
|
+
function DecoratedPlainLine({ line, theme }) {
|
|
448
|
+
const ordered = /^(\s*)((?:\d+(?:\.\d+)*[.、])|(?:[一二三四五六七八九十]+、))\s*(.+)$/.exec(line);
|
|
449
|
+
if (ordered?.[2] && ordered[3]) {
|
|
450
|
+
return (_jsxs(Text, { wrap: "wrap", children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: theme.orderedListMarker, children: [ordered[2], ' '] }), _jsx(InlineTokens, { tokens: undefined, fallback: ordered[3], theme: theme })] }));
|
|
451
|
+
}
|
|
452
|
+
return (_jsx(Text, { wrap: "wrap", children: _jsx(InlineTokens, { tokens: undefined, fallback: line, theme: theme }) }));
|
|
453
|
+
}
|
|
454
|
+
function isDecoratedPlainLine(line) {
|
|
455
|
+
return /^(\s*)((?:\d+(?:\.\d+)*[.、])|(?:[一二三四五六七八九十]+、))\s*(.+)$/.test(line);
|
|
456
|
+
}
|
|
457
|
+
function InlineTokens({ tokens, fallback, theme, style = defaultInlineStyle, }) {
|
|
458
|
+
const effectiveTokens = tokens?.length ? tokens : splitFallbackInlineTokens(fallback);
|
|
459
|
+
return (_jsx(_Fragment, { children: effectiveTokens.map((token, index) => (_jsx(InlineTokenView, { token: token, theme: theme, style: style }, `${index}-${token.type}-${token.raw ?? token.text ?? ''}`))) }));
|
|
460
|
+
}
|
|
461
|
+
const defaultInlineStyle = {
|
|
462
|
+
bold: false,
|
|
463
|
+
italic: false,
|
|
464
|
+
underline: false,
|
|
465
|
+
};
|
|
466
|
+
function InlineTokenView({ token, theme, style, }) {
|
|
467
|
+
if (token.type === 'codespan') {
|
|
468
|
+
return _jsx(StyledInlineText, { text: token.text ?? '', theme: theme, style: withInlineStyle(defaultInlineStyle, { color: theme.accent }) });
|
|
469
|
+
}
|
|
470
|
+
if (token.type === 'strong_em') {
|
|
471
|
+
return (_jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? '', theme: theme, style: withInlineStyle(style, { bold: true, italic: true, underline: true, color: theme.strongEmphasis }) }));
|
|
472
|
+
}
|
|
473
|
+
if (token.type === 'strong') {
|
|
474
|
+
return _jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? '', theme: theme, style: withInlineStyle(style, { bold: true, color: theme.strong }) });
|
|
475
|
+
}
|
|
476
|
+
if (token.type === 'em') {
|
|
477
|
+
return (_jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? '', theme: theme, style: withInlineStyle(style, { italic: true, underline: true, color: theme.emphasis }) }));
|
|
478
|
+
}
|
|
479
|
+
if (token.type === 'link') {
|
|
480
|
+
const linkStyle = withInlineStyle(style, { color: theme.accent });
|
|
481
|
+
if (token.tokens?.length) {
|
|
482
|
+
return _jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? token.href ?? '', theme: theme, style: linkStyle });
|
|
483
|
+
}
|
|
484
|
+
return _jsx(StyledInlineText, { text: token.text ?? token.href ?? '', theme: theme, style: linkStyle });
|
|
485
|
+
}
|
|
486
|
+
if (token.type === 'br') {
|
|
487
|
+
return _jsx(Text, { children: '\n' });
|
|
488
|
+
}
|
|
489
|
+
if (token.tokens?.length) {
|
|
490
|
+
return _jsx(InlineTokens, { tokens: token.tokens, fallback: token.text ?? '', theme: theme, style: style });
|
|
491
|
+
}
|
|
492
|
+
return _jsx(StyledInlineText, { text: token.text ?? token.raw ?? '', theme: theme, style: style });
|
|
493
|
+
}
|
|
494
|
+
function StyledInlineText({ text, theme, style }) {
|
|
495
|
+
return (_jsx(Text, { color: style.color ?? theme.text, ...(style.bold ? { bold: true } : {}), ...(style.italic ? { italic: true } : {}), ...(style.underline ? { underline: true } : {}), children: text }));
|
|
496
|
+
}
|
|
497
|
+
function withInlineStyle(style, patch) {
|
|
498
|
+
const next = {
|
|
499
|
+
bold: patch.bold ?? style.bold,
|
|
500
|
+
italic: patch.italic ?? style.italic,
|
|
501
|
+
underline: patch.underline ?? style.underline,
|
|
502
|
+
};
|
|
503
|
+
const color = patch.color ?? style.color;
|
|
504
|
+
if (color !== undefined) {
|
|
505
|
+
next.color = color;
|
|
506
|
+
}
|
|
507
|
+
return next;
|
|
508
|
+
}
|
|
509
|
+
function splitFallbackInlineTokens(text) {
|
|
510
|
+
const tokens = [];
|
|
511
|
+
let index = 0;
|
|
512
|
+
let plainStart = 0;
|
|
513
|
+
const flushPlain = (end) => {
|
|
514
|
+
if (end > plainStart) {
|
|
515
|
+
tokens.push({ type: 'text', text: text.slice(plainStart, end) });
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
while (index < text.length) {
|
|
519
|
+
const marker = inlineMarkerAt(text, index);
|
|
520
|
+
if (!marker) {
|
|
521
|
+
index += 1;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const end = text.indexOf(marker.raw, index + marker.raw.length);
|
|
525
|
+
if (end < 0 || end === index + marker.raw.length) {
|
|
526
|
+
index += marker.raw.length;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
flushPlain(index);
|
|
530
|
+
tokens.push({ type: marker.type, text: text.slice(index + marker.raw.length, end) });
|
|
531
|
+
index = end + marker.raw.length;
|
|
532
|
+
plainStart = index;
|
|
533
|
+
}
|
|
534
|
+
flushPlain(text.length);
|
|
535
|
+
return tokens.length > 0 ? tokens : [{ type: 'text', text }];
|
|
536
|
+
}
|
|
537
|
+
function inlineMarkerAt(text, index) {
|
|
538
|
+
if (text.startsWith('`', index)) {
|
|
539
|
+
return { raw: '`', type: 'codespan' };
|
|
540
|
+
}
|
|
541
|
+
if (text.startsWith('***', index)) {
|
|
542
|
+
return { raw: '***', type: 'strong_em' };
|
|
543
|
+
}
|
|
544
|
+
if (text.startsWith('**', index)) {
|
|
545
|
+
return { raw: '**', type: 'strong' };
|
|
546
|
+
}
|
|
547
|
+
if (text.startsWith('*', index)) {
|
|
548
|
+
return { raw: '*', type: 'em' };
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
function inlineText(tokens, fallback) {
|
|
553
|
+
if (!tokens?.length) {
|
|
554
|
+
return fallback ?? '';
|
|
555
|
+
}
|
|
556
|
+
return tokens.map(token => inlineText(token.tokens, token.text ?? token.raw ?? '')).join('');
|
|
557
|
+
}
|
|
558
|
+
export function MarkdownTable({ header, rows, raw, theme = defaultTerminalMarkdownTheme, width, }) {
|
|
559
|
+
const parsedRawTable = parseRawMarkdownTable(raw, header.length);
|
|
560
|
+
const shouldUseRawTable = parsedRawTable?.hadExtraCells === true;
|
|
561
|
+
const normalizedHeader = shouldUseRawTable ? parsedRawTable.header : header.map(cellText);
|
|
562
|
+
const normalizedRows = shouldUseRawTable ? parsedRawTable.rows : rows.map(row => row.map(cellText));
|
|
563
|
+
const columnCount = Math.max(normalizedHeader.length, ...normalizedRows.map(row => row.length), 0);
|
|
564
|
+
const tableRows = [normalizeTableRow(normalizedHeader, columnCount), ...normalizedRows.map(row => normalizeTableRow(row, columnCount))];
|
|
565
|
+
const widths = tableColumnWidths(tableRows, width);
|
|
566
|
+
const renderedLines = [
|
|
567
|
+
tableBorder('top', widths),
|
|
568
|
+
...wrappedTableRow(tableRows[0] ?? [], widths),
|
|
569
|
+
tableBorder('middle', widths),
|
|
570
|
+
...tableRows.slice(1).flatMap(row => wrappedTableRow(row, widths)),
|
|
571
|
+
tableBorder('bottom', widths),
|
|
572
|
+
];
|
|
573
|
+
return (_jsx(Box, { flexDirection: "column", children: renderedLines.map((line, index) => (_jsx(Text, { color: theme.text, children: line }, `${index}-${line}`))) }));
|
|
574
|
+
}
|
|
575
|
+
function cellText(cell) {
|
|
576
|
+
return inlineText(cell.tokens, cell.text ?? '');
|
|
577
|
+
}
|
|
578
|
+
function parseRawMarkdownTable(raw, expectedColumnCount) {
|
|
579
|
+
if (!raw || expectedColumnCount <= 0) {
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
const lines = raw.trim().split(/\r?\n/).filter(line => line.trim().length > 0);
|
|
583
|
+
if (lines.length < 2) {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
const header = mergeMarkdownTableCells(splitMarkdownTableLine(lines[0] ?? ''), expectedColumnCount);
|
|
587
|
+
let hadExtraCells = false;
|
|
588
|
+
const rows = lines.slice(2).map(line => {
|
|
589
|
+
const cells = splitMarkdownTableLine(line);
|
|
590
|
+
if (cells.length > expectedColumnCount) {
|
|
591
|
+
hadExtraCells = true;
|
|
592
|
+
}
|
|
593
|
+
return mergeMarkdownTableCells(cells, expectedColumnCount);
|
|
594
|
+
});
|
|
595
|
+
return { header, rows, hadExtraCells };
|
|
596
|
+
}
|
|
597
|
+
function splitMarkdownTableLine(line) {
|
|
598
|
+
const trimmed = line.trim();
|
|
599
|
+
const withoutLeadingPipe = trimmed.startsWith('|') ? trimmed.slice(1) : trimmed;
|
|
600
|
+
const content = withoutLeadingPipe.endsWith('|') && !isEscapedPipe(withoutLeadingPipe, withoutLeadingPipe.length - 1)
|
|
601
|
+
? withoutLeadingPipe.slice(0, -1)
|
|
602
|
+
: withoutLeadingPipe;
|
|
603
|
+
const cells = [];
|
|
604
|
+
let current = '';
|
|
605
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
606
|
+
const char = content[index];
|
|
607
|
+
if (char === '\\' && content[index + 1] === '|') {
|
|
608
|
+
current += '|';
|
|
609
|
+
index += 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (char === '|') {
|
|
613
|
+
cells.push(current);
|
|
614
|
+
current = '';
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
current += char;
|
|
618
|
+
}
|
|
619
|
+
cells.push(current);
|
|
620
|
+
return cells;
|
|
621
|
+
}
|
|
622
|
+
function mergeMarkdownTableCells(cells, expectedColumnCount) {
|
|
623
|
+
if (cells.length <= expectedColumnCount) {
|
|
624
|
+
return normalizeTableRow(cells.map(cell => markdownInlinePlainText(cell.trim())), expectedColumnCount);
|
|
625
|
+
}
|
|
626
|
+
const leadingCells = cells.slice(0, expectedColumnCount - 1).map(cell => markdownInlinePlainText(cell.trim()));
|
|
627
|
+
const mergedTail = markdownInlinePlainText(cells.slice(expectedColumnCount - 1).join('|').trim());
|
|
628
|
+
return normalizeTableRow([...leadingCells, mergedTail], expectedColumnCount);
|
|
629
|
+
}
|
|
630
|
+
function markdownInlinePlainText(value) {
|
|
631
|
+
if (value.length === 0) {
|
|
632
|
+
return '';
|
|
633
|
+
}
|
|
634
|
+
const tokens = marked.lexer(value, { gfm: true, breaks: false });
|
|
635
|
+
return tokens.map(token => inlineText(token.tokens, token.text ?? token.raw ?? '')).join(' ');
|
|
636
|
+
}
|
|
637
|
+
function isEscapedPipe(value, index) {
|
|
638
|
+
let slashCount = 0;
|
|
639
|
+
for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\'; cursor -= 1) {
|
|
640
|
+
slashCount += 1;
|
|
641
|
+
}
|
|
642
|
+
return slashCount % 2 === 1;
|
|
643
|
+
}
|
|
644
|
+
export function TerminalCodeBlock({ language, code, theme = defaultTerminalMarkdownTheme, width, }) {
|
|
645
|
+
const lines = code.length > 0 ? code.split(/\r?\n/) : [];
|
|
646
|
+
const normalizedLanguage = normalizeCodeLanguage(language, lines);
|
|
647
|
+
if (normalizedLanguage === 'diff') {
|
|
648
|
+
return _jsx(StructuredDiffView, { diff: code, theme: theme, width: width });
|
|
649
|
+
}
|
|
650
|
+
if (normalizedLanguage === 'shell') {
|
|
651
|
+
return _jsx(ShellCommandView, { command: code, theme: theme, width: width });
|
|
652
|
+
}
|
|
653
|
+
const isPlain = normalizedLanguage === 'plain' || normalizedLanguage === 'tree';
|
|
654
|
+
const highlightedLines = useMemo(() => (isPlain ? [] : highlightCodeBlock(code, normalizedLanguage, theme)), [code, normalizedLanguage, theme, isPlain]);
|
|
655
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, index) => (isPlain ? (_jsx(Box, { children: _jsx(CodeLine, { language: normalizedLanguage, line: line, theme: theme, parts: highlightedLines[index], width: width }) }, `${index}-${line}`)) : (_jsx(Box, { children: _jsx(CodeLine, { language: normalizedLanguage, line: line, theme: theme, parts: highlightedLines[index], width: width }) }, `${index}-${line}`)))) }));
|
|
656
|
+
}
|
|
657
|
+
function CodeLine({ language, line, theme, parts, width, }) {
|
|
658
|
+
const sourceParts = language === 'plain' || language === 'tree'
|
|
659
|
+
? [{ text: line, color: theme.text }]
|
|
660
|
+
: parts ?? [{ text: line, color: theme.text }];
|
|
661
|
+
const renderedParts = expandTabsInCodeParts(sourceParts);
|
|
662
|
+
const contentWidth = safeLineWidth(codePanelContentWidth(width));
|
|
663
|
+
const visibleParts = contentWidth === undefined ? renderedParts : truncateCodePartsToDisplayWidth(renderedParts, contentWidth);
|
|
664
|
+
const padding = contentWidth === undefined ? '' : ' '.repeat(Math.max(0, contentWidth - codePartsDisplayWidth(visibleParts)));
|
|
665
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: codePanelPadding }), _jsxs(Text, { wrap: "truncate-end", backgroundColor: theme.codeBackground, children: [visibleParts.map((part, index) => (_jsx(CodePartText, { part: part, backgroundColor: theme.codeBackground, wrap: "truncate-end" }, `${index}-${part.text}`))), padding] })] }));
|
|
666
|
+
}
|
|
667
|
+
export function StructuredDiffView({ diff, theme = defaultTerminalMarkdownTheme, language, width, }) {
|
|
668
|
+
const lines = diff.split(/\r?\n/);
|
|
669
|
+
const structuredLines = parseStructuredDiffPreview(lines);
|
|
670
|
+
if (structuredLines) {
|
|
671
|
+
return _jsx(StructuredDiffPreview, { lines: structuredLines, theme: theme, language: language, width: width });
|
|
672
|
+
}
|
|
673
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, index) => (_jsx(Box, { children: _jsx(DiffLine, { line: line, theme: theme, width: width }) }, `${index}-${line}`))) }));
|
|
674
|
+
}
|
|
675
|
+
export function isStructuredDiffPreviewText(diff) {
|
|
676
|
+
return Boolean(parseStructuredDiffPreview(diff.split(/\r?\n/)));
|
|
677
|
+
}
|
|
678
|
+
function parseStructuredDiffPreview(lines) {
|
|
679
|
+
if (lines.length === 0) {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
const parsed = [];
|
|
683
|
+
let matched = 0;
|
|
684
|
+
for (const line of lines) {
|
|
685
|
+
const omissionText = normalizeOmissionLine(line);
|
|
686
|
+
if (omissionText) {
|
|
687
|
+
parsed.push({ kind: 'ellipsis', text: omissionText });
|
|
688
|
+
matched += 1;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
const bareLineNumberMatch = /^(\s*\d+)\s*$/.exec(line);
|
|
692
|
+
if (bareLineNumberMatch?.[1]) {
|
|
693
|
+
parsed.push({ kind: 'same', lineNumber: Number(bareLineNumberMatch[1].trim()), text: '' });
|
|
694
|
+
matched += 1;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const match = /^(\s*\d+)( [+\-] | {3})(.*)$/.exec(line);
|
|
698
|
+
if (!match?.[1] || match[2] === undefined) {
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
const marker = match[2][1] ?? ' ';
|
|
702
|
+
parsed.push({
|
|
703
|
+
kind: marker === '+' ? 'add' : marker === '-' ? 'remove' : 'same',
|
|
704
|
+
lineNumber: Number(match[1].trim()),
|
|
705
|
+
text: match[3] ?? '',
|
|
706
|
+
});
|
|
707
|
+
matched += 1;
|
|
708
|
+
}
|
|
709
|
+
return matched > 0 ? parsed : undefined;
|
|
710
|
+
}
|
|
711
|
+
function normalizeOmissionLine(line) {
|
|
712
|
+
if (line === omissionMarker || line === '...') {
|
|
713
|
+
return omissionMarker;
|
|
714
|
+
}
|
|
715
|
+
const legacyCountMatch = /^\.\.\. (\d+) diff lines omitted \.\.\.$/.exec(line);
|
|
716
|
+
if (legacyCountMatch?.[1]) {
|
|
717
|
+
return `${omissionMarker} ${legacyCountMatch[1]} diff lines omitted`;
|
|
718
|
+
}
|
|
719
|
+
const countMatch = /^⋮ (\d+) diff lines omitted$/.exec(line);
|
|
720
|
+
if (countMatch) {
|
|
721
|
+
return line;
|
|
722
|
+
}
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
function StructuredDiffPreview({ lines, theme, language, width, }) {
|
|
726
|
+
const lineNumberWidth = structuredDiffLineNumberWidth(lines);
|
|
727
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, index) => (_jsx(StructuredDiffPreviewRow, { line: line, theme: theme, language: language, width: width, lineNumberWidth: lineNumberWidth }, `${index}-${line.kind}-${line.lineNumber ?? ''}-${line.text}`))) }));
|
|
728
|
+
}
|
|
729
|
+
function StructuredDiffPreviewRow({ line, theme, language, width, lineNumberWidth, }) {
|
|
730
|
+
if (line.kind === 'ellipsis') {
|
|
731
|
+
return _jsx(FullWidthText, { text: line.text, color: theme.muted, backgroundColor: theme.codeBackground, width: width });
|
|
732
|
+
}
|
|
733
|
+
const backgroundColor = line.kind === 'add' ? theme.diffAddBackground : line.kind === 'remove' ? theme.diffRemoveBackground : theme.codeBackground;
|
|
734
|
+
const marker = line.kind === 'add' ? '+' : line.kind === 'remove' ? '-' : ' ';
|
|
735
|
+
const markerColor = line.kind === 'add' ? theme.success : line.kind === 'remove' ? theme.danger : theme.muted;
|
|
736
|
+
const lineNumber = String(line.lineNumber ?? '').padEnd(lineNumberWidth);
|
|
737
|
+
const codeParts = diffCodeParts(line.text, language, theme);
|
|
738
|
+
const safeWidth = safeLineWidth(codePanelContentWidth(width));
|
|
739
|
+
const prefixWidth = displayWidth(lineNumber) + 3;
|
|
740
|
+
const visibleCodeParts = safeWidth === undefined ? codeParts : truncateCodePartsToDisplayWidth(codeParts, Math.max(0, safeWidth - prefixWidth));
|
|
741
|
+
const visibleWidth = prefixWidth + codePartsDisplayWidth(visibleCodeParts);
|
|
742
|
+
const padding = safeWidth === undefined ? '' : ' '.repeat(Math.max(0, safeWidth - visibleWidth));
|
|
743
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: codePanelPadding }), _jsxs(Text, { backgroundColor: backgroundColor, wrap: "truncate-end", children: [_jsxs(Text, { color: theme.muted, backgroundColor: backgroundColor, children: [lineNumber, " "] }), _jsx(Text, { color: markerColor, backgroundColor: backgroundColor, children: marker }), _jsx(Text, { color: theme.muted, backgroundColor: backgroundColor, children: " " }), _jsx(DiffCodeParts, { parts: visibleCodeParts, backgroundColor: backgroundColor }), padding] })] }));
|
|
744
|
+
}
|
|
745
|
+
function structuredDiffLineNumberWidth(lines) {
|
|
746
|
+
return lines.reduce((max, line) => {
|
|
747
|
+
if (line.kind === 'ellipsis') {
|
|
748
|
+
return max;
|
|
749
|
+
}
|
|
750
|
+
return Math.max(max, displayWidth(String(line.lineNumber ?? '')));
|
|
751
|
+
}, 1);
|
|
752
|
+
}
|
|
753
|
+
function diffCodeParts(text, language, theme) {
|
|
754
|
+
const expandedText = expandTabs(text);
|
|
755
|
+
const normalizedLanguage = language ? normalizeCodeLanguage(language, [expandedText]) : 'plain';
|
|
756
|
+
return normalizedLanguage === 'plain' || normalizedLanguage === 'tree' ? [{ text: expandedText, color: theme.text }] : highlightCodeLine(expandedText, normalizedLanguage, theme);
|
|
757
|
+
}
|
|
758
|
+
function DiffCodeParts({ parts, backgroundColor }) {
|
|
759
|
+
return (_jsx(_Fragment, { children: parts.map((part, index) => (_jsx(CodePartText, { part: part, backgroundColor: backgroundColor, wrap: "truncate-end" }, `${index}-${part.text}`))) }));
|
|
760
|
+
}
|
|
761
|
+
function DiffLine({ line, theme, width }) {
|
|
762
|
+
if (line.startsWith('+')) {
|
|
763
|
+
return _jsx(FullWidthText, { text: line, color: theme.text, backgroundColor: theme.diffAddBackground, width: width });
|
|
764
|
+
}
|
|
765
|
+
if (line.startsWith('-')) {
|
|
766
|
+
return _jsx(FullWidthText, { text: line, color: theme.text, backgroundColor: theme.diffRemoveBackground, width: width });
|
|
767
|
+
}
|
|
768
|
+
if (line.startsWith('@@')) {
|
|
769
|
+
return (_jsxs(Text, { children: [_jsx(Text, { children: codePanelPadding }), _jsx(Text, { color: theme.accent, children: line })] }));
|
|
770
|
+
}
|
|
771
|
+
if (line.startsWith('diff ') || line.startsWith('index ')) {
|
|
772
|
+
return (_jsxs(Text, { children: [_jsx(Text, { children: codePanelPadding }), _jsx(Text, { color: theme.muted, children: line })] }));
|
|
773
|
+
}
|
|
774
|
+
return _jsx(FullWidthText, { text: line, color: theme.text, backgroundColor: theme.codeBackground, width: width });
|
|
775
|
+
}
|
|
776
|
+
function FullWidthText({ text, color, backgroundColor, width, }) {
|
|
777
|
+
const safeWidth = safeLineWidth(codePanelContentWidth(width));
|
|
778
|
+
const expandedText = expandTabs(text);
|
|
779
|
+
const visibleText = safeWidth === undefined ? expandedText : truncateTextToDisplayWidth(expandedText, safeWidth);
|
|
780
|
+
const padding = safeWidth === undefined ? '' : ' '.repeat(Math.max(0, safeWidth - displayWidth(visibleText)));
|
|
781
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: codePanelPadding }), _jsxs(Text, { wrap: "truncate-end", color: color, backgroundColor: backgroundColor, children: [visibleText, padding] })] }));
|
|
782
|
+
}
|
|
783
|
+
function codePanelContentWidth(width) {
|
|
784
|
+
return width === undefined ? undefined : Math.max(0, width - displayWidth(codePanelPadding));
|
|
785
|
+
}
|
|
786
|
+
function safeLineWidth(width) {
|
|
787
|
+
if (width === undefined) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
// Leave two terminal cells free to avoid hard autowrap after full-width background rows in nested Ink layouts.
|
|
791
|
+
return Math.max(1, width - 2);
|
|
792
|
+
}
|
|
793
|
+
export function ShellCommandView({ command, theme = defaultTerminalMarkdownTheme, width, }) {
|
|
794
|
+
return (_jsx(Box, { flexDirection: "column", children: command.split(/\r?\n/).map((line, index) => (_jsx(ShellCommandLine, { line: line, theme: theme, width: width }, `${index}-${line}`))) }));
|
|
795
|
+
}
|
|
796
|
+
export function ShellInlineCommand({ command, theme = defaultTerminalMarkdownTheme, }) {
|
|
797
|
+
const parts = highlightShellLine(command, theme);
|
|
798
|
+
return (_jsx(_Fragment, { children: parts.map((part, index) => (_jsx(CodePartText, { part: part, wrap: "wrap" }, `${index}-${part.text}`))) }));
|
|
799
|
+
}
|
|
800
|
+
function ShellCommandLine({ line, theme, width }) {
|
|
801
|
+
const parts = highlightShellLine(line, theme);
|
|
802
|
+
const contentWidth = safeLineWidth(codePanelContentWidth(width));
|
|
803
|
+
const visibleParts = contentWidth === undefined ? parts : truncateCodePartsToDisplayWidth(parts, contentWidth);
|
|
804
|
+
const padding = contentWidth === undefined ? '' : ' '.repeat(Math.max(0, contentWidth - codePartsDisplayWidth(visibleParts)));
|
|
805
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: codePanelPadding }), _jsxs(Text, { wrap: "truncate-end", backgroundColor: theme.codeBackground, children: [visibleParts.map((part, partIndex) => (_jsx(CodePartText, { part: part, backgroundColor: theme.codeBackground, wrap: "truncate-end" }, `${partIndex}-${part.text}`))), padding] })] }));
|
|
806
|
+
}
|
|
807
|
+
function expandTabsInCodeParts(parts, tabSize = codeTabSize) {
|
|
808
|
+
const expanded = [];
|
|
809
|
+
let column = 0;
|
|
810
|
+
for (const part of parts) {
|
|
811
|
+
let text = '';
|
|
812
|
+
for (const char of part.text) {
|
|
813
|
+
if (char === '\t') {
|
|
814
|
+
const spaces = tabSize - (column % tabSize);
|
|
815
|
+
text += ' '.repeat(spaces);
|
|
816
|
+
column += spaces;
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
text += char;
|
|
820
|
+
column += displayWidth(char);
|
|
821
|
+
}
|
|
822
|
+
expanded.push({ ...part, text });
|
|
823
|
+
}
|
|
824
|
+
return expanded;
|
|
825
|
+
}
|
|
826
|
+
function expandTabs(value, tabSize = codeTabSize) {
|
|
827
|
+
let expanded = '';
|
|
828
|
+
let column = 0;
|
|
829
|
+
for (const char of value) {
|
|
830
|
+
if (char === '\t') {
|
|
831
|
+
const spaces = tabSize - (column % tabSize);
|
|
832
|
+
expanded += ' '.repeat(spaces);
|
|
833
|
+
column += spaces;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
expanded += char;
|
|
837
|
+
column += displayWidth(char);
|
|
838
|
+
}
|
|
839
|
+
return expanded;
|
|
840
|
+
}
|
|
841
|
+
function codePartsDisplayWidth(parts) {
|
|
842
|
+
return parts.reduce((width, part) => width + displayWidth(part.text), 0);
|
|
843
|
+
}
|
|
844
|
+
function truncateCodePartsToDisplayWidth(parts, maxWidth) {
|
|
845
|
+
if (maxWidth <= 0) {
|
|
846
|
+
return [];
|
|
847
|
+
}
|
|
848
|
+
const truncated = [];
|
|
849
|
+
let usedWidth = 0;
|
|
850
|
+
for (const part of parts) {
|
|
851
|
+
if (usedWidth >= maxWidth) {
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
const text = truncateTextToDisplayWidth(part.text, maxWidth - usedWidth);
|
|
855
|
+
if (text.length > 0) {
|
|
856
|
+
truncated.push({ ...part, text });
|
|
857
|
+
usedWidth += displayWidth(text);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return truncated;
|
|
861
|
+
}
|
|
862
|
+
function truncateTextToDisplayWidth(value, maxWidth) {
|
|
863
|
+
if (maxWidth <= 0) {
|
|
864
|
+
return '';
|
|
865
|
+
}
|
|
866
|
+
let text = '';
|
|
867
|
+
let usedWidth = 0;
|
|
868
|
+
for (const char of value) {
|
|
869
|
+
const charWidth = displayWidth(char);
|
|
870
|
+
if (usedWidth + charWidth > maxWidth) {
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
text += char;
|
|
874
|
+
usedWidth += charWidth;
|
|
875
|
+
}
|
|
876
|
+
return text;
|
|
877
|
+
}
|
|
878
|
+
function CodePartText({ part, backgroundColor, wrap }) {
|
|
879
|
+
return (_jsx(Text, { color: part.color, ...(wrap ? { wrap } : {}), ...(part.bold ? { bold: true } : {}), ...(part.italic ? { italic: true } : {}), ...(part.underline ? { underline: true } : {}), ...(backgroundColor ? { backgroundColor } : {}), children: part.text }));
|
|
880
|
+
}
|
|
881
|
+
function highlightCodeBlock(code, language, theme) {
|
|
882
|
+
const lines = code.length > 0 ? code.split(/\r?\n/) : [];
|
|
883
|
+
if (!hljs.getLanguage(language) || code.length === 0 || code.length > maxHighlightJsBlockLength) {
|
|
884
|
+
return lines.map(line => highlightCodeLine(line, language, theme));
|
|
885
|
+
}
|
|
886
|
+
const cacheKey = highlightBlockCacheKey(code, language, theme);
|
|
887
|
+
const cached = highlightBlockCache.get(cacheKey);
|
|
888
|
+
if (cached) {
|
|
889
|
+
highlightBlockCache.delete(cacheKey);
|
|
890
|
+
highlightBlockCache.set(cacheKey, cached);
|
|
891
|
+
return cached;
|
|
892
|
+
}
|
|
893
|
+
try {
|
|
894
|
+
const html = hljs.highlight(code, { language, ignoreIllegals: true }).value;
|
|
895
|
+
const highlightedLines = splitCodePartsIntoLines(htmlToCodeParts(html, theme), lines.length, theme);
|
|
896
|
+
return cacheHighlightBlock(cacheKey, highlightedLines);
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
return lines.map(line => highlightCodeLine(line, language, theme));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function splitCodePartsIntoLines(parts, lineCount, theme) {
|
|
903
|
+
const lines = [[]];
|
|
904
|
+
for (const part of parts) {
|
|
905
|
+
const chunks = part.text.split(/\r?\n/);
|
|
906
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
907
|
+
if (index > 0) {
|
|
908
|
+
lines.push([]);
|
|
909
|
+
}
|
|
910
|
+
const chunk = chunks[index] ?? '';
|
|
911
|
+
if (chunk.length > 0) {
|
|
912
|
+
lines[lines.length - 1]?.push({ ...part, text: chunk });
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
while (lines.length < lineCount) {
|
|
917
|
+
lines.push([]);
|
|
918
|
+
}
|
|
919
|
+
return lines.slice(0, lineCount).map(line => line.length > 0 ? line : [{ text: '', color: theme.text }]);
|
|
920
|
+
}
|
|
921
|
+
function highlightBlockCacheKey(code, language, theme) {
|
|
922
|
+
return [
|
|
923
|
+
'block',
|
|
924
|
+
language,
|
|
925
|
+
code.length,
|
|
926
|
+
hashContent(code),
|
|
927
|
+
theme.text,
|
|
928
|
+
theme.muted,
|
|
929
|
+
theme.accent,
|
|
930
|
+
theme.success,
|
|
931
|
+
theme.danger,
|
|
932
|
+
theme.warning,
|
|
933
|
+
theme.codeKeyword,
|
|
934
|
+
theme.codeString,
|
|
935
|
+
theme.codeNumber,
|
|
936
|
+
theme.codeComment,
|
|
937
|
+
theme.codeType,
|
|
938
|
+
theme.codeTitle,
|
|
939
|
+
theme.codeFunction,
|
|
940
|
+
theme.codeVariable,
|
|
941
|
+
theme.codeProperty,
|
|
942
|
+
theme.codeOperator,
|
|
943
|
+
theme.codeMeta,
|
|
944
|
+
theme.codeSection,
|
|
945
|
+
theme.codeRegexp,
|
|
946
|
+
theme.codePunctuation,
|
|
947
|
+
].join('\u0000');
|
|
948
|
+
}
|
|
949
|
+
function cacheHighlightBlock(key, parts) {
|
|
950
|
+
highlightBlockCache.set(key, parts);
|
|
951
|
+
if (highlightBlockCache.size > maxHighlightBlockCacheEntries) {
|
|
952
|
+
const firstKey = highlightBlockCache.keys().next().value;
|
|
953
|
+
if (firstKey) {
|
|
954
|
+
highlightBlockCache.delete(firstKey);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return parts;
|
|
958
|
+
}
|
|
959
|
+
function highlightCodeLine(line, language, theme) {
|
|
960
|
+
const cacheKey = highlightCacheKey(line, language, theme);
|
|
961
|
+
const cached = highlightLineCache.get(cacheKey);
|
|
962
|
+
if (cached) {
|
|
963
|
+
return cached;
|
|
964
|
+
}
|
|
965
|
+
const highlightParts = highlightJsLine(line, language, theme);
|
|
966
|
+
if (highlightParts.length > 0) {
|
|
967
|
+
return cacheHighlightParts(cacheKey, highlightParts);
|
|
968
|
+
}
|
|
969
|
+
if (line.trim().length === 0) {
|
|
970
|
+
return [{ text: line, color: theme.text }];
|
|
971
|
+
}
|
|
972
|
+
const commentIndex = codeCommentIndex(line, language);
|
|
973
|
+
if (commentIndex === 0) {
|
|
974
|
+
return cacheHighlightParts(cacheKey, [{ text: line, color: theme.codeComment, italic: true }]);
|
|
975
|
+
}
|
|
976
|
+
if (commentIndex > 0) {
|
|
977
|
+
return cacheHighlightParts(cacheKey, [
|
|
978
|
+
...highlightCodeLineWithoutComment(line.slice(0, commentIndex), language, theme),
|
|
979
|
+
{ text: line.slice(commentIndex), color: theme.codeComment, italic: true },
|
|
980
|
+
]);
|
|
981
|
+
}
|
|
982
|
+
return cacheHighlightParts(cacheKey, highlightCodeLineWithoutComment(line, language, theme));
|
|
983
|
+
}
|
|
984
|
+
function highlightJsLine(line, language, theme) {
|
|
985
|
+
if (!hljs.getLanguage(language) || line.trim().length === 0 || line.length > maxHighlightJsLineLength) {
|
|
986
|
+
return [];
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
const html = hljs.highlight(line, { language, ignoreIllegals: true }).value;
|
|
990
|
+
return htmlToCodeParts(html, theme);
|
|
991
|
+
}
|
|
992
|
+
catch {
|
|
993
|
+
return [];
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function highlightCacheKey(line, language, theme) {
|
|
997
|
+
return [
|
|
998
|
+
language,
|
|
999
|
+
theme.text,
|
|
1000
|
+
theme.muted,
|
|
1001
|
+
theme.accent,
|
|
1002
|
+
theme.success,
|
|
1003
|
+
theme.danger,
|
|
1004
|
+
theme.warning,
|
|
1005
|
+
theme.codeKeyword,
|
|
1006
|
+
theme.codeString,
|
|
1007
|
+
theme.codeNumber,
|
|
1008
|
+
theme.codeComment,
|
|
1009
|
+
theme.codeType,
|
|
1010
|
+
theme.codeTitle,
|
|
1011
|
+
theme.codeFunction,
|
|
1012
|
+
theme.codeVariable,
|
|
1013
|
+
theme.codeProperty,
|
|
1014
|
+
theme.codeOperator,
|
|
1015
|
+
theme.codeMeta,
|
|
1016
|
+
theme.codeSection,
|
|
1017
|
+
theme.codeRegexp,
|
|
1018
|
+
theme.codePunctuation,
|
|
1019
|
+
line,
|
|
1020
|
+
].join('\u0000');
|
|
1021
|
+
}
|
|
1022
|
+
function cacheHighlightParts(key, parts) {
|
|
1023
|
+
highlightLineCache.set(key, parts);
|
|
1024
|
+
if (highlightLineCache.size > maxHighlightCacheEntries) {
|
|
1025
|
+
const firstKey = highlightLineCache.keys().next().value;
|
|
1026
|
+
if (firstKey) {
|
|
1027
|
+
highlightLineCache.delete(firstKey);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return parts;
|
|
1031
|
+
}
|
|
1032
|
+
function quoteThemeFor(theme) {
|
|
1033
|
+
return {
|
|
1034
|
+
...theme,
|
|
1035
|
+
text: theme.quoteText,
|
|
1036
|
+
accent: theme.quoteText,
|
|
1037
|
+
success: theme.quoteText,
|
|
1038
|
+
danger: theme.quoteText,
|
|
1039
|
+
warning: theme.quoteText,
|
|
1040
|
+
emphasis: theme.quoteText,
|
|
1041
|
+
strong: theme.quoteText,
|
|
1042
|
+
strongEmphasis: theme.quoteText,
|
|
1043
|
+
orderedListMarker: theme.quoteText,
|
|
1044
|
+
codeBackground: theme.codeBackground,
|
|
1045
|
+
codeKeyword: theme.quoteText,
|
|
1046
|
+
codeString: theme.quoteText,
|
|
1047
|
+
codeNumber: theme.quoteText,
|
|
1048
|
+
codeComment: theme.quoteText,
|
|
1049
|
+
codeType: theme.quoteText,
|
|
1050
|
+
codeTitle: theme.quoteText,
|
|
1051
|
+
codeFunction: theme.quoteText,
|
|
1052
|
+
codeVariable: theme.quoteText,
|
|
1053
|
+
codeProperty: theme.quoteText,
|
|
1054
|
+
codeOperator: theme.quoteText,
|
|
1055
|
+
codeMeta: theme.quoteText,
|
|
1056
|
+
codeSection: theme.quoteText,
|
|
1057
|
+
codeRegexp: theme.quoteText,
|
|
1058
|
+
codePunctuation: theme.quoteText,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function htmlToCodeParts(html, theme) {
|
|
1062
|
+
const parts = [];
|
|
1063
|
+
const spanPattern = /<span class="hljs-([^"]+)">([\s\S]*?)<\/span>/g;
|
|
1064
|
+
let lastIndex = 0;
|
|
1065
|
+
let match;
|
|
1066
|
+
while ((match = spanPattern.exec(html)) !== null) {
|
|
1067
|
+
if (match.index > lastIndex) {
|
|
1068
|
+
parts.push({ text: decodeHtml(stripHtml(html.slice(lastIndex, match.index))), color: theme.text });
|
|
1069
|
+
}
|
|
1070
|
+
const className = match[1] ?? '';
|
|
1071
|
+
const content = decodeHtml(stripHtml(match[2] ?? ''));
|
|
1072
|
+
parts.push({ text: content, ...styleForHighlightClass(className, theme) });
|
|
1073
|
+
lastIndex = match.index + match[0].length;
|
|
1074
|
+
}
|
|
1075
|
+
if (lastIndex < html.length) {
|
|
1076
|
+
parts.push({ text: decodeHtml(stripHtml(html.slice(lastIndex))), color: theme.text });
|
|
1077
|
+
}
|
|
1078
|
+
return parts.filter(part => part.text.length > 0);
|
|
1079
|
+
}
|
|
1080
|
+
function styleForHighlightClass(className, theme) {
|
|
1081
|
+
if (/(comment|quote)/.test(className)) {
|
|
1082
|
+
return { color: theme.codeComment, italic: true };
|
|
1083
|
+
}
|
|
1084
|
+
if (/(keyword|selector-tag|doctag)/.test(className)) {
|
|
1085
|
+
return { color: theme.codeKeyword };
|
|
1086
|
+
}
|
|
1087
|
+
if (/(built_in|builtin-name)/.test(className)) {
|
|
1088
|
+
return { color: theme.codeType };
|
|
1089
|
+
}
|
|
1090
|
+
if (/(type|class)/.test(className)) {
|
|
1091
|
+
return { color: theme.codeType };
|
|
1092
|
+
}
|
|
1093
|
+
if (/(title|name)/.test(className) && /function_/.test(className)) {
|
|
1094
|
+
return { color: theme.codeFunction };
|
|
1095
|
+
}
|
|
1096
|
+
if (/(title|name|selector-id|selector-class)/.test(className)) {
|
|
1097
|
+
return { color: theme.codeTitle };
|
|
1098
|
+
}
|
|
1099
|
+
if (/(string|symbol|template-variable|template-tag)/.test(className)) {
|
|
1100
|
+
return { color: theme.codeString };
|
|
1101
|
+
}
|
|
1102
|
+
if (/(regexp)/.test(className)) {
|
|
1103
|
+
return { color: theme.codeRegexp };
|
|
1104
|
+
}
|
|
1105
|
+
if (/(number|literal)/.test(className)) {
|
|
1106
|
+
return { color: theme.codeNumber };
|
|
1107
|
+
}
|
|
1108
|
+
if (/(attr|attribute|property|params|variable)/.test(className)) {
|
|
1109
|
+
return { color: theme.codeVariable };
|
|
1110
|
+
}
|
|
1111
|
+
if (/(meta|meta-keyword|meta-string)/.test(className)) {
|
|
1112
|
+
return { color: theme.codeMeta };
|
|
1113
|
+
}
|
|
1114
|
+
if (/(section|bullet|addition)/.test(className)) {
|
|
1115
|
+
return { color: theme.codeSection };
|
|
1116
|
+
}
|
|
1117
|
+
if (/(deletion)/.test(className)) {
|
|
1118
|
+
return { color: theme.danger };
|
|
1119
|
+
}
|
|
1120
|
+
if (/(operator|subst)/.test(className)) {
|
|
1121
|
+
return { color: theme.codeOperator };
|
|
1122
|
+
}
|
|
1123
|
+
if (/(punctuation)/.test(className)) {
|
|
1124
|
+
return { color: theme.codePunctuation };
|
|
1125
|
+
}
|
|
1126
|
+
if (/(emphasis)/.test(className)) {
|
|
1127
|
+
return { color: theme.emphasis, italic: true, underline: true };
|
|
1128
|
+
}
|
|
1129
|
+
if (/(strong)/.test(className)) {
|
|
1130
|
+
return { color: theme.strong };
|
|
1131
|
+
}
|
|
1132
|
+
return { color: theme.text };
|
|
1133
|
+
}
|
|
1134
|
+
function stripHtml(value) {
|
|
1135
|
+
return value.replace(/<[^>]+>/g, '');
|
|
1136
|
+
}
|
|
1137
|
+
function decodeHtml(value) {
|
|
1138
|
+
return value
|
|
1139
|
+
.replace(/"/g, '"')
|
|
1140
|
+
.replace(/'/g, "'")
|
|
1141
|
+
.replace(/'/g, "'")
|
|
1142
|
+
.replace(/</g, '<')
|
|
1143
|
+
.replace(/>/g, '>')
|
|
1144
|
+
.replace(/&/g, '&');
|
|
1145
|
+
}
|
|
1146
|
+
function hashContent(value) {
|
|
1147
|
+
let hash = 2166136261;
|
|
1148
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1149
|
+
hash ^= value.charCodeAt(index);
|
|
1150
|
+
hash = Math.imul(hash, 16777619);
|
|
1151
|
+
}
|
|
1152
|
+
return (hash >>> 0).toString(36);
|
|
1153
|
+
}
|
|
1154
|
+
function highlightShellLine(line, theme) {
|
|
1155
|
+
const commentIndex = line.indexOf('#');
|
|
1156
|
+
if (commentIndex === 0 || line.trimStart().startsWith('#')) {
|
|
1157
|
+
return [{ text: line, color: theme.codeComment, italic: true }];
|
|
1158
|
+
}
|
|
1159
|
+
const prefix = commentIndex > 0 ? line.slice(0, commentIndex) : line;
|
|
1160
|
+
const suffix = commentIndex > 0 ? line.slice(commentIndex) : '';
|
|
1161
|
+
const parts = [];
|
|
1162
|
+
const pattern = /("(?:\\.|[^"])*"|'(?:\\.|[^'])*'|`(?:\\.|[^`])*`|--?[A-Za-z0-9][\w-]*|\b[A-Za-z_][\w.-]*\b|\d+)/g;
|
|
1163
|
+
let lastIndex = 0;
|
|
1164
|
+
let sawCommand = false;
|
|
1165
|
+
let match;
|
|
1166
|
+
while ((match = pattern.exec(prefix)) !== null) {
|
|
1167
|
+
if (match.index > lastIndex) {
|
|
1168
|
+
parts.push({ text: prefix.slice(lastIndex, match.index), color: theme.text });
|
|
1169
|
+
}
|
|
1170
|
+
const raw = match[0];
|
|
1171
|
+
if (/^['"`]/.test(raw)) {
|
|
1172
|
+
parts.push({ text: raw, color: theme.codeString });
|
|
1173
|
+
}
|
|
1174
|
+
else if (/^--?/.test(raw)) {
|
|
1175
|
+
parts.push({ text: raw, color: theme.codeMeta });
|
|
1176
|
+
}
|
|
1177
|
+
else if (/^\d/.test(raw)) {
|
|
1178
|
+
parts.push({ text: raw, color: theme.codeNumber });
|
|
1179
|
+
}
|
|
1180
|
+
else if (!sawCommand && raw.trim().length > 0) {
|
|
1181
|
+
parts.push({ text: raw, color: theme.codeFunction });
|
|
1182
|
+
sawCommand = true;
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
parts.push({ text: raw, color: theme.codeVariable });
|
|
1186
|
+
}
|
|
1187
|
+
lastIndex = match.index + raw.length;
|
|
1188
|
+
}
|
|
1189
|
+
if (lastIndex < prefix.length) {
|
|
1190
|
+
parts.push({ text: prefix.slice(lastIndex), color: theme.text });
|
|
1191
|
+
}
|
|
1192
|
+
if (suffix) {
|
|
1193
|
+
parts.push({ text: suffix, color: theme.codeComment, italic: true });
|
|
1194
|
+
}
|
|
1195
|
+
return parts.length > 0 ? parts : [{ text: line, color: theme.text }];
|
|
1196
|
+
}
|
|
1197
|
+
function highlightCodeLineWithoutComment(line, language, theme) {
|
|
1198
|
+
const keywords = keywordsForLanguage(language);
|
|
1199
|
+
const parts = [];
|
|
1200
|
+
const pattern = /("(?:\\.|[^"])*"|'(?:\\.|[^'])*'|`(?:\\.|[^`])*`|=>|===|!==|==|!=|<=|>=|&&|\|\||[{}()[\].,:;<>+\-*/%=!&|?]|\b[A-Za-z_][\w]*\b|\b\d+(?:\.\d+)?\b)/g;
|
|
1201
|
+
let lastIndex = 0;
|
|
1202
|
+
let match;
|
|
1203
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
1204
|
+
if (match.index > lastIndex) {
|
|
1205
|
+
parts.push({ text: line.slice(lastIndex, match.index), color: theme.text });
|
|
1206
|
+
}
|
|
1207
|
+
const raw = match[0];
|
|
1208
|
+
if (/^['"`]/.test(raw)) {
|
|
1209
|
+
parts.push({ text: raw, color: theme.codeString });
|
|
1210
|
+
}
|
|
1211
|
+
else if (/^\d/.test(raw)) {
|
|
1212
|
+
parts.push({ text: raw, color: theme.codeNumber });
|
|
1213
|
+
}
|
|
1214
|
+
else if (keywords.has(raw)) {
|
|
1215
|
+
parts.push({ text: raw, color: theme.codeKeyword });
|
|
1216
|
+
}
|
|
1217
|
+
else if (/^[{}()[\].,:;<>]$/.test(raw)) {
|
|
1218
|
+
parts.push({ text: raw, color: theme.codePunctuation });
|
|
1219
|
+
}
|
|
1220
|
+
else if (/^(=>|===|!==|==|!=|<=|>=|&&|\|\||[+\-*/%=!&|?])$/.test(raw)) {
|
|
1221
|
+
parts.push({ text: raw, color: theme.codeOperator });
|
|
1222
|
+
}
|
|
1223
|
+
else if (line.slice(match.index + raw.length).trimStart().startsWith('(')) {
|
|
1224
|
+
parts.push({ text: raw, color: theme.codeFunction });
|
|
1225
|
+
}
|
|
1226
|
+
else if (match.index > 0 && line[match.index - 1] === '.') {
|
|
1227
|
+
parts.push({ text: raw, color: theme.codeProperty });
|
|
1228
|
+
}
|
|
1229
|
+
else if (/^[A-Z]/.test(raw)) {
|
|
1230
|
+
parts.push({ text: raw, color: theme.codeType });
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
parts.push({ text: raw, color: theme.codeVariable });
|
|
1234
|
+
}
|
|
1235
|
+
lastIndex = match.index + raw.length;
|
|
1236
|
+
}
|
|
1237
|
+
if (lastIndex < line.length) {
|
|
1238
|
+
parts.push({ text: line.slice(lastIndex), color: theme.text });
|
|
1239
|
+
}
|
|
1240
|
+
return parts.length > 0 ? parts : [{ text: line, color: theme.text }];
|
|
1241
|
+
}
|
|
1242
|
+
function codeCommentIndex(line, language) {
|
|
1243
|
+
if (language === 'python' || language === 'py' || language === 'ruby') {
|
|
1244
|
+
return line.indexOf('#');
|
|
1245
|
+
}
|
|
1246
|
+
return line.indexOf('//');
|
|
1247
|
+
}
|
|
1248
|
+
function keywordsForLanguage(language) {
|
|
1249
|
+
const common = ['return', 'if', 'else', 'for', 'while', 'break', 'continue', 'try', 'catch', 'throw', 'class', 'new'];
|
|
1250
|
+
const byLanguage = {
|
|
1251
|
+
go: ['package', 'import', 'func', 'var', 'const', 'type', 'struct', 'interface', 'map', 'range', 'defer', 'go', 'select', 'case', 'default'],
|
|
1252
|
+
js: ['const', 'let', 'var', 'function', 'async', 'await', 'export', 'import', 'from', 'default', 'type', 'interface'],
|
|
1253
|
+
jsx: ['const', 'let', 'var', 'function', 'async', 'await', 'export', 'import', 'from', 'default', 'type', 'interface'],
|
|
1254
|
+
ts: ['const', 'let', 'var', 'function', 'async', 'await', 'export', 'import', 'from', 'default', 'type', 'interface'],
|
|
1255
|
+
tsx: ['const', 'let', 'var', 'function', 'async', 'await', 'export', 'import', 'from', 'default', 'type', 'interface'],
|
|
1256
|
+
python: ['def', 'import', 'from', 'as', 'with', 'lambda', 'yield', 'None', 'True', 'False', 'self'],
|
|
1257
|
+
py: ['def', 'import', 'from', 'as', 'with', 'lambda', 'yield', 'None', 'True', 'False', 'self'],
|
|
1258
|
+
java: ['public', 'private', 'protected', 'static', 'final', 'void', 'extends', 'implements'],
|
|
1259
|
+
rust: ['fn', 'let', 'mut', 'pub', 'impl', 'trait', 'match', 'use', 'mod', 'crate'],
|
|
1260
|
+
};
|
|
1261
|
+
return new Set([...common, ...(byLanguage[language] ?? [])]);
|
|
1262
|
+
}
|
|
1263
|
+
function normalizeCodeLanguage(language, lines) {
|
|
1264
|
+
if (language === 'diff' || looksLikeDiff(lines)) {
|
|
1265
|
+
return 'diff';
|
|
1266
|
+
}
|
|
1267
|
+
if (language === 'bash' || language === 'sh' || language === 'shell' || language === 'zsh') {
|
|
1268
|
+
return 'shell';
|
|
1269
|
+
}
|
|
1270
|
+
if (language === '' || language === 'text' || language === 'txt' || language === 'plain' || language === 'plaintext') {
|
|
1271
|
+
return looksLikeTree(lines) ? 'tree' : 'plain';
|
|
1272
|
+
}
|
|
1273
|
+
if (language === 'tree') {
|
|
1274
|
+
return 'tree';
|
|
1275
|
+
}
|
|
1276
|
+
return language;
|
|
1277
|
+
}
|
|
1278
|
+
function looksLikeDiff(lines) {
|
|
1279
|
+
return lines.some(line => line.startsWith('@@') || line.startsWith('diff --git')) || lines.filter(line => /^[+-]/.test(line)).length >= 2;
|
|
1280
|
+
}
|
|
1281
|
+
function looksLikeTree(lines) {
|
|
1282
|
+
return lines.some(line => /(^|\s)[├└│]──|^\s*[A-Za-z0-9_.-]+\/$/.test(line));
|
|
1283
|
+
}
|
|
1284
|
+
function normalizeTableRow(row, columnCount) {
|
|
1285
|
+
return Array.from({ length: columnCount }, (_, index) => row[index] ?? '');
|
|
1286
|
+
}
|
|
1287
|
+
function tableColumnWidths(rows, maxTableWidth) {
|
|
1288
|
+
const columnCount = Math.max(0, ...rows.map(row => row.length));
|
|
1289
|
+
const naturalWidths = Array.from({ length: columnCount }, (_, columnIndex) => Math.max(1, ...rows.map(row => displayWidth(row[columnIndex] ?? ''))));
|
|
1290
|
+
if (maxTableWidth === undefined || columnCount === 0 || tableDisplayWidth(naturalWidths) <= maxTableWidth) {
|
|
1291
|
+
return naturalWidths;
|
|
1292
|
+
}
|
|
1293
|
+
const availableCellWidth = Math.max(columnCount, maxTableWidth - tableFrameWidth(columnCount));
|
|
1294
|
+
const widths = [...naturalWidths];
|
|
1295
|
+
let totalCellWidth = widths.reduce((sum, width) => sum + width, 0);
|
|
1296
|
+
while (totalCellWidth > availableCellWidth && widths.some(width => width > 1)) {
|
|
1297
|
+
let widestIndex = 0;
|
|
1298
|
+
for (let index = 1; index < widths.length; index += 1) {
|
|
1299
|
+
if ((widths[index] ?? 0) > (widths[widestIndex] ?? 0)) {
|
|
1300
|
+
widestIndex = index;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
widths[widestIndex] = Math.max(1, (widths[widestIndex] ?? 1) - 1);
|
|
1304
|
+
totalCellWidth -= 1;
|
|
1305
|
+
}
|
|
1306
|
+
return widths;
|
|
1307
|
+
}
|
|
1308
|
+
function tableFrameWidth(columnCount) {
|
|
1309
|
+
return columnCount * 3 + 1;
|
|
1310
|
+
}
|
|
1311
|
+
function tableDisplayWidth(widths) {
|
|
1312
|
+
return widths.reduce((sum, width) => sum + width, 0) + tableFrameWidth(widths.length);
|
|
1313
|
+
}
|
|
1314
|
+
function tableBorder(kind, widths) {
|
|
1315
|
+
const chars = kind === 'top'
|
|
1316
|
+
? ['┌', '┬', '┐']
|
|
1317
|
+
: kind === 'middle'
|
|
1318
|
+
? ['├', '┼', '┤']
|
|
1319
|
+
: ['└', '┴', '┘'];
|
|
1320
|
+
return `${chars[0]}${widths.map(width => '─'.repeat(width + 2)).join(chars[1])}${chars[2]}`;
|
|
1321
|
+
}
|
|
1322
|
+
function tableRow(row, widths) {
|
|
1323
|
+
return `│${row.map((cell, index) => ` ${padDisplay(cell, widths[index] ?? 1)} `).join('│')}│`;
|
|
1324
|
+
}
|
|
1325
|
+
function wrappedTableRow(row, widths) {
|
|
1326
|
+
const wrappedCells = widths.map((width, index) => wrapTableCell(row[index] ?? '', width));
|
|
1327
|
+
const rowHeight = Math.max(1, ...wrappedCells.map(lines => lines.length));
|
|
1328
|
+
return Array.from({ length: rowHeight }, (_, lineIndex) => tableRow(wrappedCells.map(lines => lines[lineIndex] ?? ''), widths));
|
|
1329
|
+
}
|
|
1330
|
+
function wrapTableCell(value, width) {
|
|
1331
|
+
const lines = [];
|
|
1332
|
+
let current = '';
|
|
1333
|
+
let currentWidth = 0;
|
|
1334
|
+
const pushCurrent = () => {
|
|
1335
|
+
lines.push(current.trimEnd());
|
|
1336
|
+
current = '';
|
|
1337
|
+
currentWidth = 0;
|
|
1338
|
+
};
|
|
1339
|
+
for (const char of value) {
|
|
1340
|
+
if (char === '\n') {
|
|
1341
|
+
pushCurrent();
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
const charWidth = displayWidth(char);
|
|
1345
|
+
if (current.length > 0 && currentWidth + charWidth > width) {
|
|
1346
|
+
pushCurrent();
|
|
1347
|
+
}
|
|
1348
|
+
if (current.length === 0 && /\s/u.test(char)) {
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
current += char;
|
|
1352
|
+
currentWidth += charWidth;
|
|
1353
|
+
}
|
|
1354
|
+
if (current.length > 0 || lines.length === 0) {
|
|
1355
|
+
pushCurrent();
|
|
1356
|
+
}
|
|
1357
|
+
return lines;
|
|
1358
|
+
}
|
|
1359
|
+
function padDisplay(value, width) {
|
|
1360
|
+
return `${value}${' '.repeat(Math.max(0, width - displayWidth(value)))}`;
|
|
1361
|
+
}
|
|
1362
|
+
export function displayWidth(value) {
|
|
1363
|
+
let width = 0;
|
|
1364
|
+
for (const char of value) {
|
|
1365
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
1366
|
+
if (codePoint === 0 ||
|
|
1367
|
+
codePoint < 32 ||
|
|
1368
|
+
(codePoint >= 0x7f && codePoint < 0xa0) ||
|
|
1369
|
+
isZeroWidthCodePoint(codePoint)) {
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
width += isFullWidthCodePoint(codePoint) || isEmojiCodePoint(codePoint) ? 2 : 1;
|
|
1373
|
+
}
|
|
1374
|
+
return width;
|
|
1375
|
+
}
|
|
1376
|
+
function isZeroWidthCodePoint(codePoint) {
|
|
1377
|
+
return (codePoint === 0x200d ||
|
|
1378
|
+
(codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
1379
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
1380
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
1381
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
1382
|
+
(codePoint >= 0xfe00 && codePoint <= 0xfe0f));
|
|
1383
|
+
}
|
|
1384
|
+
function isEmojiCodePoint(codePoint) {
|
|
1385
|
+
return ((codePoint >= 0x1f000 && codePoint <= 0x1faff) ||
|
|
1386
|
+
(codePoint >= 0x2600 && codePoint <= 0x27bf));
|
|
1387
|
+
}
|
|
1388
|
+
function isFullWidthCodePoint(codePoint) {
|
|
1389
|
+
return (codePoint >= 0x1100 &&
|
|
1390
|
+
(codePoint <= 0x115f ||
|
|
1391
|
+
codePoint === 0x2329 ||
|
|
1392
|
+
codePoint === 0x232a ||
|
|
1393
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
1394
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
1395
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
1396
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
1397
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
1398
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
1399
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
1400
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
1401
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
1402
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
1403
|
+
}
|
|
1404
|
+
export function flattenText(node) {
|
|
1405
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
1406
|
+
return String(node);
|
|
1407
|
+
}
|
|
1408
|
+
if (Array.isArray(node)) {
|
|
1409
|
+
return node.map(flattenText).join('');
|
|
1410
|
+
}
|
|
1411
|
+
return '';
|
|
1412
|
+
}
|