@lvce-editor/chat-message-parsing-worker 1.0.0 → 1.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.
|
@@ -1066,8 +1066,961 @@ const handleMessagePort = async port => {
|
|
|
1066
1066
|
});
|
|
1067
1067
|
};
|
|
1068
1068
|
|
|
1069
|
+
const windowsAbsolutePathRegex = /^[a-zA-Z]:[\\/]/;
|
|
1070
|
+
const pathSeparatorRegex$1 = /[\\/]/;
|
|
1071
|
+
const isPathTraversalAttempt = path => {
|
|
1072
|
+
if (!path) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
if (path.startsWith('/') || path.startsWith('\\')) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
if (path.startsWith('file://')) {
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
if (windowsAbsolutePathRegex.test(path)) {
|
|
1082
|
+
return true;
|
|
1083
|
+
}
|
|
1084
|
+
const segments = path.split(pathSeparatorRegex$1);
|
|
1085
|
+
return segments.includes('..');
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const pathSeparatorRegex = /[\\/]/;
|
|
1089
|
+
const normalizeRelativePath = path => {
|
|
1090
|
+
const segments = path.split(pathSeparatorRegex).filter(segment => segment && segment !== '.');
|
|
1091
|
+
if (segments.length === 0) {
|
|
1092
|
+
return '.';
|
|
1093
|
+
}
|
|
1094
|
+
return segments.join('/');
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
const isAlphaNumeric = value => {
|
|
1098
|
+
if (!value) {
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
const code = value.codePointAt(0) ?? 0;
|
|
1102
|
+
if (code >= 48 && code <= 57) {
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
if (code >= 65 && code <= 90) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
return code >= 97 && code <= 122;
|
|
1109
|
+
};
|
|
1110
|
+
const sanitizeLinkUrl = url => {
|
|
1111
|
+
const trimmedUrl = url.trim();
|
|
1112
|
+
const normalized = url.trim().toLowerCase();
|
|
1113
|
+
if (normalized.startsWith('http://') || normalized.startsWith('https://') || normalized.startsWith('file://') || normalized.startsWith('vscode-references://')) {
|
|
1114
|
+
return trimmedUrl;
|
|
1115
|
+
}
|
|
1116
|
+
if (!trimmedUrl || trimmedUrl.startsWith('#') || trimmedUrl.startsWith('?') || trimmedUrl.startsWith('/') || trimmedUrl.startsWith('\\')) {
|
|
1117
|
+
return '#';
|
|
1118
|
+
}
|
|
1119
|
+
if (trimmedUrl.includes('://') || trimmedUrl.includes(':')) {
|
|
1120
|
+
return '#';
|
|
1121
|
+
}
|
|
1122
|
+
if (isPathTraversalAttempt(trimmedUrl)) {
|
|
1123
|
+
return '#';
|
|
1124
|
+
}
|
|
1125
|
+
const normalizedPath = normalizeRelativePath(trimmedUrl);
|
|
1126
|
+
if (normalizedPath === '.') {
|
|
1127
|
+
return '#';
|
|
1128
|
+
}
|
|
1129
|
+
return `file:///workspace/${normalizedPath}`;
|
|
1130
|
+
};
|
|
1131
|
+
const sanitizeImageUrl = url => {
|
|
1132
|
+
const normalized = url.trim().toLowerCase();
|
|
1133
|
+
if (normalized.startsWith('http://') || normalized.startsWith('https://')) {
|
|
1134
|
+
return url;
|
|
1135
|
+
}
|
|
1136
|
+
return '#';
|
|
1137
|
+
};
|
|
1138
|
+
const isOpenBracket = value => {
|
|
1139
|
+
return value === '(' || value === '[' || value === '{';
|
|
1140
|
+
};
|
|
1141
|
+
const isCloseBracket = value => {
|
|
1142
|
+
return value === ')' || value === ']' || value === '}';
|
|
1143
|
+
};
|
|
1144
|
+
const findRawUrlEnd = (value, start) => {
|
|
1145
|
+
let index = start;
|
|
1146
|
+
while (index < value.length) {
|
|
1147
|
+
const current = value[index];
|
|
1148
|
+
if (current === ' ' || current === '\n' || current === '\r' || current === '\t' || current === '"' || current === "'" || current === '`' || current === '<' || current === '>') {
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
index++;
|
|
1152
|
+
}
|
|
1153
|
+
return index;
|
|
1154
|
+
};
|
|
1155
|
+
const trimRawUrlEnd = url => {
|
|
1156
|
+
let end = url.length;
|
|
1157
|
+
while (end > 0) {
|
|
1158
|
+
const current = url[end - 1];
|
|
1159
|
+
if (current === '.' || current === ',' || current === ':' || current === ';' || current === '!' || current === '?') {
|
|
1160
|
+
end--;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (isCloseBracket(current)) {
|
|
1164
|
+
const inner = url.slice(0, end - 1);
|
|
1165
|
+
let openCount = 0;
|
|
1166
|
+
let closeCount = 0;
|
|
1167
|
+
for (let i = 0; i < inner.length; i++) {
|
|
1168
|
+
if (isOpenBracket(inner[i])) {
|
|
1169
|
+
openCount++;
|
|
1170
|
+
} else if (isCloseBracket(inner[i])) {
|
|
1171
|
+
closeCount++;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (closeCount >= openCount) {
|
|
1175
|
+
end--;
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
return url.slice(0, end);
|
|
1182
|
+
};
|
|
1183
|
+
const parseRawLinkToken = (value, start) => {
|
|
1184
|
+
if (!value.startsWith('https://', start) && !value.startsWith('http://', start)) {
|
|
1185
|
+
return undefined;
|
|
1186
|
+
}
|
|
1187
|
+
if (start >= 2 && value[start - 1] === '(' && value[start - 2] === ']') {
|
|
1188
|
+
return undefined;
|
|
1189
|
+
}
|
|
1190
|
+
const end = findRawUrlEnd(value, start);
|
|
1191
|
+
const rawUrl = value.slice(start, end);
|
|
1192
|
+
const href = trimRawUrlEnd(rawUrl);
|
|
1193
|
+
if (!href) {
|
|
1194
|
+
return undefined;
|
|
1195
|
+
}
|
|
1196
|
+
return {
|
|
1197
|
+
length: href.length,
|
|
1198
|
+
node: {
|
|
1199
|
+
href: sanitizeLinkUrl(href),
|
|
1200
|
+
text: href,
|
|
1201
|
+
type: 'link'
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
};
|
|
1205
|
+
const parseLinkToken = (value, start) => {
|
|
1206
|
+
if (value[start] !== '[') {
|
|
1207
|
+
return undefined;
|
|
1208
|
+
}
|
|
1209
|
+
const textEnd = value.indexOf(']', start + 1);
|
|
1210
|
+
if (textEnd === -1) {
|
|
1211
|
+
return undefined;
|
|
1212
|
+
}
|
|
1213
|
+
if (value[textEnd + 1] !== '(') {
|
|
1214
|
+
return undefined;
|
|
1215
|
+
}
|
|
1216
|
+
let depth = 1;
|
|
1217
|
+
let index = textEnd + 2;
|
|
1218
|
+
while (index < value.length) {
|
|
1219
|
+
const current = value[index];
|
|
1220
|
+
if (current === '\n') {
|
|
1221
|
+
return undefined;
|
|
1222
|
+
}
|
|
1223
|
+
if (current === '(') {
|
|
1224
|
+
depth++;
|
|
1225
|
+
} else if (current === ')') {
|
|
1226
|
+
depth--;
|
|
1227
|
+
if (depth === 0) {
|
|
1228
|
+
const text = value.slice(start + 1, textEnd);
|
|
1229
|
+
const href = value.slice(textEnd + 2, index);
|
|
1230
|
+
if (!text || !href) {
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
length: index - start + 1,
|
|
1235
|
+
node: {
|
|
1236
|
+
href: sanitizeLinkUrl(href),
|
|
1237
|
+
text,
|
|
1238
|
+
type: 'link'
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
index++;
|
|
1244
|
+
}
|
|
1245
|
+
return undefined;
|
|
1246
|
+
};
|
|
1247
|
+
const parseImageToken = (value, start) => {
|
|
1248
|
+
if (value[start] !== '!' || value[start + 1] !== '[') {
|
|
1249
|
+
return undefined;
|
|
1250
|
+
}
|
|
1251
|
+
const textEnd = value.indexOf(']', start + 2);
|
|
1252
|
+
if (textEnd === -1) {
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1255
|
+
if (value[textEnd + 1] !== '(') {
|
|
1256
|
+
return undefined;
|
|
1257
|
+
}
|
|
1258
|
+
let depth = 1;
|
|
1259
|
+
let index = textEnd + 2;
|
|
1260
|
+
while (index < value.length) {
|
|
1261
|
+
const current = value[index];
|
|
1262
|
+
if (current === '\n') {
|
|
1263
|
+
return undefined;
|
|
1264
|
+
}
|
|
1265
|
+
if (current === '(') {
|
|
1266
|
+
depth++;
|
|
1267
|
+
} else if (current === ')') {
|
|
1268
|
+
depth--;
|
|
1269
|
+
if (depth === 0) {
|
|
1270
|
+
const alt = value.slice(start + 2, textEnd);
|
|
1271
|
+
const src = value.slice(textEnd + 2, index);
|
|
1272
|
+
if (!src) {
|
|
1273
|
+
return undefined;
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
length: index - start + 1,
|
|
1277
|
+
node: {
|
|
1278
|
+
alt,
|
|
1279
|
+
src: sanitizeImageUrl(src),
|
|
1280
|
+
type: 'image'
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
index++;
|
|
1286
|
+
}
|
|
1287
|
+
return undefined;
|
|
1288
|
+
};
|
|
1289
|
+
const parseBoldToken = (value, start) => {
|
|
1290
|
+
if (value[start] !== '*' || value[start + 1] !== '*') {
|
|
1291
|
+
return undefined;
|
|
1292
|
+
}
|
|
1293
|
+
const end = value.indexOf('**', start + 2);
|
|
1294
|
+
if (end === -1) {
|
|
1295
|
+
return undefined;
|
|
1296
|
+
}
|
|
1297
|
+
const text = value.slice(start + 2, end);
|
|
1298
|
+
if (!text || text.includes('\n')) {
|
|
1299
|
+
return undefined;
|
|
1300
|
+
}
|
|
1301
|
+
return {
|
|
1302
|
+
length: end - start + 2,
|
|
1303
|
+
node: {
|
|
1304
|
+
children: parseInlineNodes(text),
|
|
1305
|
+
type: 'bold'
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
};
|
|
1309
|
+
const parseBoldItalicToken = (value, start) => {
|
|
1310
|
+
if (value[start] !== '*' || value[start + 1] !== '*' || value[start + 2] !== '*') {
|
|
1311
|
+
return undefined;
|
|
1312
|
+
}
|
|
1313
|
+
const end = value.indexOf('***', start + 3);
|
|
1314
|
+
if (end === -1) {
|
|
1315
|
+
return undefined;
|
|
1316
|
+
}
|
|
1317
|
+
const text = value.slice(start + 3, end);
|
|
1318
|
+
if (!text || text.includes('\n')) {
|
|
1319
|
+
return undefined;
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
length: end - start + 3,
|
|
1323
|
+
node: {
|
|
1324
|
+
children: [{
|
|
1325
|
+
children: parseInlineNodes(text),
|
|
1326
|
+
type: 'italic'
|
|
1327
|
+
}],
|
|
1328
|
+
type: 'bold'
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
};
|
|
1332
|
+
const findItalicEnd = (value, start) => {
|
|
1333
|
+
let index = start + 1;
|
|
1334
|
+
while (index < value.length) {
|
|
1335
|
+
if (value[index] !== '*') {
|
|
1336
|
+
index++;
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
if (value[index + 1] !== '*') {
|
|
1340
|
+
return index;
|
|
1341
|
+
}
|
|
1342
|
+
const boldEnd = value.indexOf('**', index + 2);
|
|
1343
|
+
if (boldEnd === -1) {
|
|
1344
|
+
return -1;
|
|
1345
|
+
}
|
|
1346
|
+
index = boldEnd + 2;
|
|
1347
|
+
}
|
|
1348
|
+
return -1;
|
|
1349
|
+
};
|
|
1350
|
+
const parseItalicToken = (value, start) => {
|
|
1351
|
+
if (value[start] !== '*' || value[start + 1] === '*') {
|
|
1352
|
+
return undefined;
|
|
1353
|
+
}
|
|
1354
|
+
const end = findItalicEnd(value, start);
|
|
1355
|
+
if (end === -1) {
|
|
1356
|
+
return undefined;
|
|
1357
|
+
}
|
|
1358
|
+
const text = value.slice(start + 1, end);
|
|
1359
|
+
if (!text) {
|
|
1360
|
+
return undefined;
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
length: end - start + 1,
|
|
1364
|
+
node: {
|
|
1365
|
+
children: parseInlineNodes(text),
|
|
1366
|
+
type: 'italic'
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
};
|
|
1370
|
+
const parseStrikethroughToken = (value, start) => {
|
|
1371
|
+
if (value[start] !== '~' || value[start + 1] !== '~') {
|
|
1372
|
+
return undefined;
|
|
1373
|
+
}
|
|
1374
|
+
const end = value.indexOf('~~', start + 2);
|
|
1375
|
+
if (end === -1) {
|
|
1376
|
+
return undefined;
|
|
1377
|
+
}
|
|
1378
|
+
const text = value.slice(start + 2, end);
|
|
1379
|
+
if (!text || text.includes('\n')) {
|
|
1380
|
+
return undefined;
|
|
1381
|
+
}
|
|
1382
|
+
return {
|
|
1383
|
+
length: end - start + 2,
|
|
1384
|
+
node: {
|
|
1385
|
+
children: parseInlineNodes(text),
|
|
1386
|
+
type: 'strikethrough'
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
const parseInlineCodeToken = (value, start) => {
|
|
1391
|
+
if (value[start] !== '`') {
|
|
1392
|
+
return undefined;
|
|
1393
|
+
}
|
|
1394
|
+
const end = value.indexOf('`', start + 1);
|
|
1395
|
+
if (end === -1) {
|
|
1396
|
+
return undefined;
|
|
1397
|
+
}
|
|
1398
|
+
const codeText = value.slice(start + 1, end);
|
|
1399
|
+
if (!codeText || codeText.includes('\n')) {
|
|
1400
|
+
return undefined;
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
length: end - start + 1,
|
|
1404
|
+
node: {
|
|
1405
|
+
text: codeText,
|
|
1406
|
+
type: 'inline-code'
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
};
|
|
1410
|
+
const parseMathToken = (value, start) => {
|
|
1411
|
+
if (value[start] !== '$') {
|
|
1412
|
+
return undefined;
|
|
1413
|
+
}
|
|
1414
|
+
const delimiterLength = value[start + 1] === '$' ? 2 : 1;
|
|
1415
|
+
const previous = value[start - 1];
|
|
1416
|
+
if (isAlphaNumeric(previous)) {
|
|
1417
|
+
return undefined;
|
|
1418
|
+
}
|
|
1419
|
+
const next = value[start + delimiterLength];
|
|
1420
|
+
if (!next || next === '.') {
|
|
1421
|
+
return undefined;
|
|
1422
|
+
}
|
|
1423
|
+
if (next === '(' && (value[start + delimiterLength + 1] === '"' || value[start + delimiterLength + 1] === "'")) {
|
|
1424
|
+
return undefined;
|
|
1425
|
+
}
|
|
1426
|
+
let index = start + delimiterLength;
|
|
1427
|
+
while (index < value.length) {
|
|
1428
|
+
if (value[index] === '\n') {
|
|
1429
|
+
return undefined;
|
|
1430
|
+
}
|
|
1431
|
+
const isClosed = delimiterLength === 2 ? value.startsWith('$$', index) : value[index] === '$';
|
|
1432
|
+
if (isClosed) {
|
|
1433
|
+
const body = value.slice(start + delimiterLength, index);
|
|
1434
|
+
const following = value[index + delimiterLength];
|
|
1435
|
+
if (!body || isAlphaNumeric(following)) {
|
|
1436
|
+
return undefined;
|
|
1437
|
+
}
|
|
1438
|
+
return {
|
|
1439
|
+
length: index - start + delimiterLength,
|
|
1440
|
+
node: {
|
|
1441
|
+
displayMode: delimiterLength === 2,
|
|
1442
|
+
text: body.trim(),
|
|
1443
|
+
type: 'math-inline'
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
if (value[index] === '\\') {
|
|
1448
|
+
index += 2;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
index++;
|
|
1452
|
+
}
|
|
1453
|
+
return undefined;
|
|
1454
|
+
};
|
|
1455
|
+
const parseInlineToken = (value, start) => {
|
|
1456
|
+
return parseImageToken(value, start) || parseLinkToken(value, start) || parseRawLinkToken(value, start) || parseBoldItalicToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseLinkToken(value, start) || parseBoldItalicToken(value, start) || parseBoldToken(value, start) || parseItalicToken(value, start) || parseStrikethroughToken(value, start) || parseInlineCodeToken(value, start) || parseMathToken(value, start);
|
|
1457
|
+
};
|
|
1458
|
+
const parseInlineNodes = value => {
|
|
1459
|
+
const nodes = [];
|
|
1460
|
+
let textStart = 0;
|
|
1461
|
+
let index = 0;
|
|
1462
|
+
const pushText = end => {
|
|
1463
|
+
if (end <= textStart) {
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
nodes.push({
|
|
1467
|
+
text: value.slice(textStart, end),
|
|
1468
|
+
type: 'text'
|
|
1469
|
+
});
|
|
1470
|
+
};
|
|
1471
|
+
while (index < value.length) {
|
|
1472
|
+
const parsed = parseInlineToken(value, index);
|
|
1473
|
+
if (!parsed) {
|
|
1474
|
+
index++;
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
pushText(index);
|
|
1478
|
+
nodes.push(parsed.node);
|
|
1479
|
+
index += parsed.length;
|
|
1480
|
+
textStart = index;
|
|
1481
|
+
}
|
|
1482
|
+
pushText(value.length);
|
|
1483
|
+
if (nodes.length === 0) {
|
|
1484
|
+
return [{
|
|
1485
|
+
text: value,
|
|
1486
|
+
type: 'text'
|
|
1487
|
+
}];
|
|
1488
|
+
}
|
|
1489
|
+
return nodes;
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
const markdownMathBlockDelimiter = '$$';
|
|
1493
|
+
const escapedNewlineRegex = /\\r\\n|\\n/g;
|
|
1494
|
+
const lineBreakRegex = /\r?\n/;
|
|
1495
|
+
const tableDelimiterRegex = /\|\s*[-:]{3,}/;
|
|
1496
|
+
const inlineTableCellRegex = /\|\s+\|/g;
|
|
1497
|
+
const normalizeEscapedNewlines = value => {
|
|
1498
|
+
if (value.includes('\\n')) {
|
|
1499
|
+
return value.replaceAll(escapedNewlineRegex, '\n');
|
|
1500
|
+
}
|
|
1501
|
+
return value;
|
|
1502
|
+
};
|
|
1503
|
+
const normalizeInlineTables = value => {
|
|
1504
|
+
return value.split(lineBreakRegex).map(line => {
|
|
1505
|
+
if (!line.includes('|')) {
|
|
1506
|
+
return line;
|
|
1507
|
+
}
|
|
1508
|
+
if (!tableDelimiterRegex.test(line)) {
|
|
1509
|
+
return line;
|
|
1510
|
+
}
|
|
1511
|
+
return line.replaceAll(inlineTableCellRegex, '|\n|');
|
|
1512
|
+
}).join('\n');
|
|
1513
|
+
};
|
|
1514
|
+
const parseHeadingLine = line => {
|
|
1515
|
+
const trimmedStart = line.trimStart();
|
|
1516
|
+
let index = 0;
|
|
1517
|
+
while (index < trimmedStart.length && trimmedStart[index] === '#') {
|
|
1518
|
+
index++;
|
|
1519
|
+
}
|
|
1520
|
+
if (index === 0 || index > 6) {
|
|
1521
|
+
return undefined;
|
|
1522
|
+
}
|
|
1523
|
+
if (trimmedStart[index] !== ' ') {
|
|
1524
|
+
return undefined;
|
|
1525
|
+
}
|
|
1526
|
+
const text = trimmedStart.slice(index).trimStart();
|
|
1527
|
+
return {
|
|
1528
|
+
level: index,
|
|
1529
|
+
text
|
|
1530
|
+
};
|
|
1531
|
+
};
|
|
1532
|
+
const parseOrderedListItemLine = line => {
|
|
1533
|
+
let indentation = 0;
|
|
1534
|
+
while (indentation < line.length && line[indentation] === ' ') {
|
|
1535
|
+
indentation++;
|
|
1536
|
+
}
|
|
1537
|
+
let index = indentation;
|
|
1538
|
+
const firstDigit = index;
|
|
1539
|
+
while (index < line.length && line[index] >= '0' && line[index] <= '9') {
|
|
1540
|
+
index++;
|
|
1541
|
+
}
|
|
1542
|
+
if (index === firstDigit || line[index] !== '.') {
|
|
1543
|
+
return undefined;
|
|
1544
|
+
}
|
|
1545
|
+
index++;
|
|
1546
|
+
if (line[index] !== ' ') {
|
|
1547
|
+
return undefined;
|
|
1548
|
+
}
|
|
1549
|
+
while (index < line.length && line[index] === ' ') {
|
|
1550
|
+
index++;
|
|
1551
|
+
}
|
|
1552
|
+
return {
|
|
1553
|
+
indentation,
|
|
1554
|
+
text: line.slice(index)
|
|
1555
|
+
};
|
|
1556
|
+
};
|
|
1557
|
+
const parseUnorderedListItemLine = line => {
|
|
1558
|
+
let indentation = 0;
|
|
1559
|
+
while (indentation < line.length && line[indentation] === ' ') {
|
|
1560
|
+
indentation++;
|
|
1561
|
+
}
|
|
1562
|
+
const marker = line[indentation];
|
|
1563
|
+
if (marker !== '-' && marker !== '*') {
|
|
1564
|
+
return undefined;
|
|
1565
|
+
}
|
|
1566
|
+
let index = indentation + 1;
|
|
1567
|
+
if (line[index] !== ' ') {
|
|
1568
|
+
return undefined;
|
|
1569
|
+
}
|
|
1570
|
+
while (index < line.length && line[index] === ' ') {
|
|
1571
|
+
index++;
|
|
1572
|
+
}
|
|
1573
|
+
return {
|
|
1574
|
+
indentation,
|
|
1575
|
+
text: line.slice(index)
|
|
1576
|
+
};
|
|
1577
|
+
};
|
|
1578
|
+
const parseBlockQuoteLine = line => {
|
|
1579
|
+
const trimmedStart = line.trimStart();
|
|
1580
|
+
if (!trimmedStart.startsWith('>')) {
|
|
1581
|
+
return undefined;
|
|
1582
|
+
}
|
|
1583
|
+
const content = trimmedStart.slice(1);
|
|
1584
|
+
if (!content) {
|
|
1585
|
+
return '';
|
|
1586
|
+
}
|
|
1587
|
+
if (content.startsWith(' ')) {
|
|
1588
|
+
return content.slice(1);
|
|
1589
|
+
}
|
|
1590
|
+
return content;
|
|
1591
|
+
};
|
|
1592
|
+
const isThematicBreakLine = line => {
|
|
1593
|
+
const trimmedStart = line.trimStart();
|
|
1594
|
+
const leadingSpaces = line.length - trimmedStart.length;
|
|
1595
|
+
if (leadingSpaces > 3) {
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
const trimmed = trimmedStart.trimEnd();
|
|
1599
|
+
if (!trimmed) {
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
const marker = trimmed[0];
|
|
1603
|
+
if (marker !== '-' && marker !== '_' && marker !== '*') {
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
let markerCount = 0;
|
|
1607
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
1608
|
+
const char = trimmed[i];
|
|
1609
|
+
if (char === marker) {
|
|
1610
|
+
markerCount++;
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (char !== ' ') {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return markerCount >= 3;
|
|
1618
|
+
};
|
|
1619
|
+
const isTableRow = line => {
|
|
1620
|
+
const trimmed = line.trim();
|
|
1621
|
+
if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) {
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
return trimmed.length > 2;
|
|
1625
|
+
};
|
|
1626
|
+
const getTableCells = line => {
|
|
1627
|
+
const trimmed = line.trim();
|
|
1628
|
+
return trimmed.slice(1, -1).split('|').map(part => part.trim());
|
|
1629
|
+
};
|
|
1630
|
+
const scanBlockTokens = rawMessage => {
|
|
1631
|
+
const normalizedMessage = normalizeInlineTables(normalizeEscapedNewlines(rawMessage));
|
|
1632
|
+
const lines = normalizedMessage.split(lineBreakRegex);
|
|
1633
|
+
const tokens = [];
|
|
1634
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1635
|
+
const line = lines[i];
|
|
1636
|
+
const trimmed = line.trim();
|
|
1637
|
+
if (!trimmed) {
|
|
1638
|
+
tokens.push({
|
|
1639
|
+
type: 'blank-line'
|
|
1640
|
+
});
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
const blockQuoteLine = parseBlockQuoteLine(line);
|
|
1644
|
+
if (blockQuoteLine !== undefined) {
|
|
1645
|
+
tokens.push({
|
|
1646
|
+
text: blockQuoteLine,
|
|
1647
|
+
type: 'blockquote-line'
|
|
1648
|
+
});
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (trimmed.startsWith('```')) {
|
|
1652
|
+
const language = trimmed.slice(3).trim();
|
|
1653
|
+
const codeLines = [];
|
|
1654
|
+
i++;
|
|
1655
|
+
while (i < lines.length && !lines[i].trim().startsWith('```')) {
|
|
1656
|
+
codeLines.push(lines[i]);
|
|
1657
|
+
i++;
|
|
1658
|
+
}
|
|
1659
|
+
if (language) {
|
|
1660
|
+
tokens.push({
|
|
1661
|
+
language,
|
|
1662
|
+
text: codeLines.join('\n'),
|
|
1663
|
+
type: 'code-block'
|
|
1664
|
+
});
|
|
1665
|
+
} else {
|
|
1666
|
+
tokens.push({
|
|
1667
|
+
text: codeLines.join('\n'),
|
|
1668
|
+
type: 'code-block'
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
if (trimmed === markdownMathBlockDelimiter) {
|
|
1674
|
+
const endIndex = lines.findIndex((candidate, index) => {
|
|
1675
|
+
if (index <= i) {
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
return candidate.trim() === markdownMathBlockDelimiter;
|
|
1679
|
+
});
|
|
1680
|
+
if (endIndex !== -1) {
|
|
1681
|
+
tokens.push({
|
|
1682
|
+
text: lines.slice(i + 1, endIndex).join('\n').trim(),
|
|
1683
|
+
type: 'math-block'
|
|
1684
|
+
});
|
|
1685
|
+
i = endIndex;
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
const heading = parseHeadingLine(line);
|
|
1690
|
+
if (heading) {
|
|
1691
|
+
tokens.push({
|
|
1692
|
+
level: heading.level,
|
|
1693
|
+
text: heading.text,
|
|
1694
|
+
type: 'heading-line'
|
|
1695
|
+
});
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
if (isThematicBreakLine(line)) {
|
|
1699
|
+
tokens.push({
|
|
1700
|
+
type: 'thematic-break'
|
|
1701
|
+
});
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
const ordered = parseOrderedListItemLine(line);
|
|
1705
|
+
if (ordered) {
|
|
1706
|
+
tokens.push({
|
|
1707
|
+
indentation: ordered.indentation,
|
|
1708
|
+
text: ordered.text,
|
|
1709
|
+
type: 'ordered-list-item-line'
|
|
1710
|
+
});
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
const unordered = parseUnorderedListItemLine(line);
|
|
1714
|
+
if (unordered) {
|
|
1715
|
+
tokens.push({
|
|
1716
|
+
indentation: unordered.indentation,
|
|
1717
|
+
text: unordered.text,
|
|
1718
|
+
type: 'unordered-list-item-line'
|
|
1719
|
+
});
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
if (isTableRow(line)) {
|
|
1723
|
+
tokens.push({
|
|
1724
|
+
cells: getTableCells(line),
|
|
1725
|
+
line,
|
|
1726
|
+
type: 'table-row-line'
|
|
1727
|
+
});
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
tokens.push({
|
|
1731
|
+
text: line,
|
|
1732
|
+
type: 'paragraph-line'
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
return tokens;
|
|
1736
|
+
};
|
|
1737
|
+
|
|
1738
|
+
const isTableSeparatorCell = value => {
|
|
1739
|
+
if (!value) {
|
|
1740
|
+
return false;
|
|
1741
|
+
}
|
|
1742
|
+
let index = 0;
|
|
1743
|
+
if (value[index] === ':') {
|
|
1744
|
+
index++;
|
|
1745
|
+
}
|
|
1746
|
+
let dashCount = 0;
|
|
1747
|
+
while (index < value.length && value[index] === '-') {
|
|
1748
|
+
dashCount++;
|
|
1749
|
+
index++;
|
|
1750
|
+
}
|
|
1751
|
+
if (dashCount < 3) {
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
if (index < value.length && value[index] === ':') {
|
|
1755
|
+
index++;
|
|
1756
|
+
}
|
|
1757
|
+
return index === value.length;
|
|
1758
|
+
};
|
|
1759
|
+
const isTableSeparatorToken = (token, expectedColumns) => {
|
|
1760
|
+
if (!token || token.type !== 'table-row-line') {
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
if (token.cells.length !== expectedColumns) {
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
return token.cells.every(isTableSeparatorCell);
|
|
1767
|
+
};
|
|
1768
|
+
const toTableCell = value => {
|
|
1769
|
+
return {
|
|
1770
|
+
children: parseInlineNodes(value),
|
|
1771
|
+
type: 'table-cell'
|
|
1772
|
+
};
|
|
1773
|
+
};
|
|
1774
|
+
const toTableRow = token => {
|
|
1775
|
+
return {
|
|
1776
|
+
cells: token.cells.map(toTableCell),
|
|
1777
|
+
type: 'table-row'
|
|
1778
|
+
};
|
|
1779
|
+
};
|
|
1780
|
+
const getEmptyTextNode = () => {
|
|
1781
|
+
return [{
|
|
1782
|
+
children: [{
|
|
1783
|
+
text: '',
|
|
1784
|
+
type: 'text'
|
|
1785
|
+
}],
|
|
1786
|
+
type: 'text'
|
|
1787
|
+
}];
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
1791
|
+
const parseBlockTokens = tokens => {
|
|
1792
|
+
if (tokens.length === 0) {
|
|
1793
|
+
return getEmptyTextNode();
|
|
1794
|
+
}
|
|
1795
|
+
const nodes = [];
|
|
1796
|
+
let paragraphLines = [];
|
|
1797
|
+
let listItems = [];
|
|
1798
|
+
let listType = '';
|
|
1799
|
+
let orderedListPathStack = [];
|
|
1800
|
+
const getListItemAtPath = (items, path) => {
|
|
1801
|
+
let currentItems = items;
|
|
1802
|
+
let currentItem;
|
|
1803
|
+
for (const index of path) {
|
|
1804
|
+
currentItem = currentItems[index];
|
|
1805
|
+
if (!currentItem) {
|
|
1806
|
+
return undefined;
|
|
1807
|
+
}
|
|
1808
|
+
currentItems = currentItem.nestedItems || [];
|
|
1809
|
+
}
|
|
1810
|
+
return currentItem;
|
|
1811
|
+
};
|
|
1812
|
+
const appendNestedItemAtPath = (items, path, item, nestedListType) => {
|
|
1813
|
+
if (path.length === 0) {
|
|
1814
|
+
return [...items, item];
|
|
1815
|
+
}
|
|
1816
|
+
const [index, ...rest] = path;
|
|
1817
|
+
const current = items[index];
|
|
1818
|
+
if (!current) {
|
|
1819
|
+
return [...items];
|
|
1820
|
+
}
|
|
1821
|
+
const nextNestedItems = rest.length > 0 ? appendNestedItemAtPath(current.nestedItems || [], rest, item, nestedListType) : [...(current.nestedItems || []), item];
|
|
1822
|
+
const nextItem = {
|
|
1823
|
+
...current,
|
|
1824
|
+
nestedItems: nextNestedItems,
|
|
1825
|
+
nestedListType
|
|
1826
|
+
};
|
|
1827
|
+
return [...items.slice(0, index), nextItem, ...items.slice(index + 1)];
|
|
1828
|
+
};
|
|
1829
|
+
const flushParagraph = () => {
|
|
1830
|
+
if (paragraphLines.length === 0) {
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
nodes.push({
|
|
1834
|
+
children: parseInlineNodes(paragraphLines.join('\n')),
|
|
1835
|
+
type: 'text'
|
|
1836
|
+
});
|
|
1837
|
+
paragraphLines = [];
|
|
1838
|
+
};
|
|
1839
|
+
const flushList = () => {
|
|
1840
|
+
if (listItems.length === 0) {
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
nodes.push({
|
|
1844
|
+
items: listItems,
|
|
1845
|
+
type: listType || 'ordered-list'
|
|
1846
|
+
});
|
|
1847
|
+
listItems = [];
|
|
1848
|
+
listType = '';
|
|
1849
|
+
orderedListPathStack = [];
|
|
1850
|
+
};
|
|
1851
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1852
|
+
const token = tokens[i];
|
|
1853
|
+
if (token.type === 'blank-line') {
|
|
1854
|
+
flushParagraph();
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
if (token.type === 'code-block') {
|
|
1858
|
+
flushList();
|
|
1859
|
+
flushParagraph();
|
|
1860
|
+
if (token.language) {
|
|
1861
|
+
nodes.push({
|
|
1862
|
+
language: token.language,
|
|
1863
|
+
text: token.text,
|
|
1864
|
+
type: 'code-block'
|
|
1865
|
+
});
|
|
1866
|
+
} else {
|
|
1867
|
+
nodes.push({
|
|
1868
|
+
text: token.text,
|
|
1869
|
+
type: 'code-block'
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
if (token.type === 'math-block') {
|
|
1875
|
+
flushList();
|
|
1876
|
+
flushParagraph();
|
|
1877
|
+
nodes.push({
|
|
1878
|
+
text: token.text,
|
|
1879
|
+
type: 'math-block'
|
|
1880
|
+
});
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
if (token.type === 'thematic-break') {
|
|
1884
|
+
flushList();
|
|
1885
|
+
flushParagraph();
|
|
1886
|
+
nodes.push({
|
|
1887
|
+
type: 'thematic-break'
|
|
1888
|
+
});
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
if (token.type === 'blockquote-line') {
|
|
1892
|
+
flushList();
|
|
1893
|
+
flushParagraph();
|
|
1894
|
+
const lines = [];
|
|
1895
|
+
while (i < tokens.length && tokens[i].type === 'blockquote-line') {
|
|
1896
|
+
const quoteToken = tokens[i];
|
|
1897
|
+
if (quoteToken.type === 'blockquote-line') {
|
|
1898
|
+
lines.push(quoteToken.text);
|
|
1899
|
+
}
|
|
1900
|
+
i++;
|
|
1901
|
+
}
|
|
1902
|
+
i--;
|
|
1903
|
+
nodes.push({
|
|
1904
|
+
children: parseBlockTokens(scanBlockTokens(lines.join('\n'))),
|
|
1905
|
+
type: 'blockquote'
|
|
1906
|
+
});
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
if (token.type === 'table-row-line') {
|
|
1910
|
+
const expectedColumns = token.cells.length;
|
|
1911
|
+
if (isTableSeparatorToken(tokens[i + 1], expectedColumns)) {
|
|
1912
|
+
flushList();
|
|
1913
|
+
flushParagraph();
|
|
1914
|
+
const rows = [];
|
|
1915
|
+
i += 2;
|
|
1916
|
+
while (i < tokens.length && tokens[i].type === 'table-row-line') {
|
|
1917
|
+
const rowToken = tokens[i];
|
|
1918
|
+
if (rowToken.type !== 'table-row-line') {
|
|
1919
|
+
break;
|
|
1920
|
+
}
|
|
1921
|
+
if (rowToken.cells.length === expectedColumns) {
|
|
1922
|
+
rows.push(toTableRow(rowToken));
|
|
1923
|
+
}
|
|
1924
|
+
i++;
|
|
1925
|
+
}
|
|
1926
|
+
i--;
|
|
1927
|
+
nodes.push({
|
|
1928
|
+
headers: token.cells.map(toTableCell),
|
|
1929
|
+
rows,
|
|
1930
|
+
type: 'table'
|
|
1931
|
+
});
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
flushList();
|
|
1935
|
+
paragraphLines.push(token.line);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
if (token.type === 'ordered-list-item-line') {
|
|
1939
|
+
if (listType === 'ordered-list' && listItems.length > 0 && token.indentation > 0 && orderedListPathStack.length > 0) {
|
|
1940
|
+
const parentEntry = orderedListPathStack.toReversed().find(entry => entry.indentation < token.indentation);
|
|
1941
|
+
if (parentEntry) {
|
|
1942
|
+
const parentItem = getListItemAtPath(listItems, parentEntry.path);
|
|
1943
|
+
if (parentItem) {
|
|
1944
|
+
const nextItem = {
|
|
1945
|
+
children: parseInlineNodes(token.text),
|
|
1946
|
+
type: 'list-item'
|
|
1947
|
+
};
|
|
1948
|
+
const nextIndex = parentItem.nestedItems?.length || 0;
|
|
1949
|
+
listItems = appendNestedItemAtPath(listItems, parentEntry.path, nextItem, 'ordered-list');
|
|
1950
|
+
const nextPath = [...parentEntry.path, nextIndex];
|
|
1951
|
+
orderedListPathStack = [...orderedListPathStack.filter(entry => entry.indentation < token.indentation), {
|
|
1952
|
+
indentation: token.indentation,
|
|
1953
|
+
path: nextPath
|
|
1954
|
+
}];
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
if (listType && listType !== 'ordered-list') {
|
|
1960
|
+
flushList();
|
|
1961
|
+
}
|
|
1962
|
+
flushParagraph();
|
|
1963
|
+
listType = 'ordered-list';
|
|
1964
|
+
const nextItem = {
|
|
1965
|
+
children: parseInlineNodes(token.text),
|
|
1966
|
+
type: 'list-item'
|
|
1967
|
+
};
|
|
1968
|
+
listItems.push(nextItem);
|
|
1969
|
+
const nextIndex = listItems.length - 1;
|
|
1970
|
+
orderedListPathStack = [...orderedListPathStack.filter(entry => entry.indentation < token.indentation), {
|
|
1971
|
+
indentation: token.indentation,
|
|
1972
|
+
path: [nextIndex]
|
|
1973
|
+
}];
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1976
|
+
if (token.type === 'unordered-list-item-line') {
|
|
1977
|
+
if (listType === 'ordered-list' && listItems.length > 0 && token.indentation > 0 && orderedListPathStack.length > 0) {
|
|
1978
|
+
const parentEntry = orderedListPathStack.toReversed().find(entry => entry.indentation < token.indentation);
|
|
1979
|
+
if (parentEntry) {
|
|
1980
|
+
const nextItem = {
|
|
1981
|
+
children: parseInlineNodes(token.text),
|
|
1982
|
+
type: 'list-item'
|
|
1983
|
+
};
|
|
1984
|
+
listItems = appendNestedItemAtPath(listItems, parentEntry.path, nextItem, 'unordered-list');
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
if (listType && listType !== 'unordered-list') {
|
|
1989
|
+
flushList();
|
|
1990
|
+
}
|
|
1991
|
+
flushParagraph();
|
|
1992
|
+
listType = 'unordered-list';
|
|
1993
|
+
listItems.push({
|
|
1994
|
+
children: parseInlineNodes(token.text),
|
|
1995
|
+
type: 'list-item'
|
|
1996
|
+
});
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
if (token.type === 'heading-line') {
|
|
2000
|
+
flushList();
|
|
2001
|
+
flushParagraph();
|
|
2002
|
+
nodes.push({
|
|
2003
|
+
children: parseInlineNodes(token.text),
|
|
2004
|
+
level: token.level,
|
|
2005
|
+
type: 'heading'
|
|
2006
|
+
});
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
flushList();
|
|
2010
|
+
paragraphLines.push(token.text);
|
|
2011
|
+
}
|
|
2012
|
+
flushList();
|
|
2013
|
+
flushParagraph();
|
|
2014
|
+
return nodes.length === 0 ? getEmptyTextNode() : nodes;
|
|
2015
|
+
};
|
|
2016
|
+
|
|
2017
|
+
const parseMessageContent = rawMessage => {
|
|
2018
|
+
return parseBlockTokens(scanBlockTokens(rawMessage));
|
|
2019
|
+
};
|
|
2020
|
+
|
|
1069
2021
|
const commandMap = {
|
|
1070
2022
|
...networkCommandMap,
|
|
2023
|
+
'ChatParser.parseMessageContent': parseMessageContent,
|
|
1071
2024
|
'HandleMessagePort.handleMessagePort': handleMessagePort
|
|
1072
2025
|
};
|
|
1073
2026
|
|