opencode-gbk-tools 0.1.13 → 0.1.15
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 +2 -0
- package/dist/opencode-tools/gbk_edit.js +419 -13
- package/dist/opencode-tools/gbk_read.js +123 -114
- package/dist/opencode-tools/gbk_search.js +52 -56
- package/dist/opencode-tools/gbk_write.js +19 -8
- package/dist/opencode-tools/text_edit.js +1 -0
- package/dist/opencode-tools/text_read.js +1 -0
- package/dist/opencode-tools/text_write.js +1 -0
- package/dist/plugin/index.js +522 -160
- package/dist/release-manifest.json +8 -8
- package/package.json +1 -1
|
@@ -16305,6 +16305,11 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16305
16305
|
|
|
16306
16306
|
// src/lib/gbk-file.ts
|
|
16307
16307
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16308
|
+
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16309
|
+
var gbkLineIndexCache = /* @__PURE__ */ new Map();
|
|
16310
|
+
function toSafeNumber(value) {
|
|
16311
|
+
return typeof value === "bigint" ? Number(value) : value;
|
|
16312
|
+
}
|
|
16308
16313
|
function assertEncodingSupported(encoding) {
|
|
16309
16314
|
if (encoding !== "gbk" && encoding !== "gb18030") {
|
|
16310
16315
|
throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|
|
@@ -16392,6 +16397,16 @@ function finalizeNewlineStyle(crlfCount, lfCount) {
|
|
|
16392
16397
|
}
|
|
16393
16398
|
return "none";
|
|
16394
16399
|
}
|
|
16400
|
+
function formatLineWindowContent(text, startLine, expectedLineCount) {
|
|
16401
|
+
const lines = text.length === 0 ? [""] : text.split(/\r?\n/);
|
|
16402
|
+
while (lines.length > expectedLineCount && lines[lines.length - 1] === "") {
|
|
16403
|
+
lines.pop();
|
|
16404
|
+
}
|
|
16405
|
+
while (lines.length < expectedLineCount) {
|
|
16406
|
+
lines.push("");
|
|
16407
|
+
}
|
|
16408
|
+
return lines.slice(0, expectedLineCount).map((line, index) => `${startLine + index}: ${line}`).join("\n");
|
|
16409
|
+
}
|
|
16395
16410
|
async function readBufferAsText(buffer, encoding) {
|
|
16396
16411
|
assertEncodingSupported(encoding);
|
|
16397
16412
|
assertNotBinary(buffer);
|
|
@@ -16433,21 +16448,36 @@ async function readWholeGbkTextFile(input) {
|
|
|
16433
16448
|
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16434
16449
|
}
|
|
16435
16450
|
}
|
|
16436
|
-
async function
|
|
16437
|
-
const
|
|
16438
|
-
|
|
16451
|
+
async function getGbkLineIndex(input) {
|
|
16452
|
+
const cached2 = gbkLineIndexCache.get(input.filePath);
|
|
16453
|
+
if (cached2 && cached2.fileSize === toSafeNumber(input.stat.size) && cached2.mtimeMs === toSafeNumber(input.stat.mtimeMs)) {
|
|
16454
|
+
return cached2;
|
|
16455
|
+
}
|
|
16456
|
+
const lineStartOffsets = [0];
|
|
16457
|
+
let byteOffset = 0;
|
|
16458
|
+
let previousByteWasCR = false;
|
|
16459
|
+
let crlfCount = 0;
|
|
16460
|
+
let lfCount = 0;
|
|
16461
|
+
const stream = createReadStream(input.filePath, { highWaterMark: STREAM_READ_CHUNK_SIZE_BYTES });
|
|
16439
16462
|
try {
|
|
16440
16463
|
for await (const chunk of stream) {
|
|
16441
16464
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
16442
16465
|
assertNotBinary(buffer);
|
|
16443
|
-
|
|
16444
|
-
|
|
16445
|
-
|
|
16466
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
16467
|
+
const byte = buffer[index];
|
|
16468
|
+
if (byte === 10) {
|
|
16469
|
+
if (previousByteWasCR) {
|
|
16470
|
+
crlfCount += 1;
|
|
16471
|
+
} else {
|
|
16472
|
+
lfCount += 1;
|
|
16473
|
+
}
|
|
16474
|
+
lineStartOffsets.push(byteOffset + index + 1);
|
|
16475
|
+
previousByteWasCR = false;
|
|
16476
|
+
continue;
|
|
16477
|
+
}
|
|
16478
|
+
previousByteWasCR = byte === 13;
|
|
16446
16479
|
}
|
|
16447
|
-
|
|
16448
|
-
const trailingText = decoder.end();
|
|
16449
|
-
if (trailingText && trailingText.length > 0) {
|
|
16450
|
-
await visitor(trailingText);
|
|
16480
|
+
byteOffset += buffer.length;
|
|
16451
16481
|
}
|
|
16452
16482
|
} catch (error45) {
|
|
16453
16483
|
if (error45 instanceof Error && "code" in error45) {
|
|
@@ -16457,110 +16487,50 @@ async function visitDecodedTextChunks(input, visitor) {
|
|
|
16457
16487
|
} finally {
|
|
16458
16488
|
stream.destroy();
|
|
16459
16489
|
}
|
|
16490
|
+
const result = {
|
|
16491
|
+
filePath: input.filePath,
|
|
16492
|
+
fileSize: toSafeNumber(input.stat.size),
|
|
16493
|
+
mtimeMs: toSafeNumber(input.stat.mtimeMs),
|
|
16494
|
+
lineStartOffsets,
|
|
16495
|
+
totalLines: lineStartOffsets.length,
|
|
16496
|
+
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16497
|
+
};
|
|
16498
|
+
gbkLineIndexCache.set(input.filePath, result);
|
|
16499
|
+
return result;
|
|
16460
16500
|
}
|
|
16461
|
-
function
|
|
16462
|
-
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
let start = 0;
|
|
16480
|
-
while (true) {
|
|
16481
|
-
const newlineIndex = combined.indexOf("\n", start);
|
|
16482
|
-
if (newlineIndex === -1) {
|
|
16483
|
-
break;
|
|
16484
|
-
}
|
|
16485
|
-
let line = combined.slice(start, newlineIndex);
|
|
16486
|
-
if (line.endsWith("\r")) {
|
|
16487
|
-
line = line.slice(0, -1);
|
|
16488
|
-
crlfCount += 1;
|
|
16489
|
-
} else {
|
|
16490
|
-
lfCount += 1;
|
|
16491
|
-
}
|
|
16492
|
-
emitLine(line);
|
|
16493
|
-
start = newlineIndex + 1;
|
|
16501
|
+
async function readDecodedGbkByteRange(input, start, endExclusive) {
|
|
16502
|
+
if (endExclusive <= start) {
|
|
16503
|
+
return "";
|
|
16504
|
+
}
|
|
16505
|
+
const decoder = import_iconv_lite.default.getDecoder(input.encoding);
|
|
16506
|
+
const stream = createReadStream(input.filePath, {
|
|
16507
|
+
start,
|
|
16508
|
+
end: endExclusive - 1,
|
|
16509
|
+
highWaterMark: STREAM_READ_CHUNK_SIZE_BYTES
|
|
16510
|
+
});
|
|
16511
|
+
const parts = [];
|
|
16512
|
+
try {
|
|
16513
|
+
for await (const chunk of stream) {
|
|
16514
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
16515
|
+
assertNotBinary(buffer);
|
|
16516
|
+
const text = decoder.write(buffer);
|
|
16517
|
+
if (text.length > 0) {
|
|
16518
|
+
parts.push(text);
|
|
16494
16519
|
}
|
|
16495
|
-
pending = combined.slice(start);
|
|
16496
|
-
},
|
|
16497
|
-
finish() {
|
|
16498
|
-
emitLine(pending);
|
|
16499
|
-
const endLine = lines.length === 0 ? totalLines : offset + lines.length - 1;
|
|
16500
|
-
return {
|
|
16501
|
-
startLine: offset,
|
|
16502
|
-
endLine,
|
|
16503
|
-
totalLines,
|
|
16504
|
-
content: lines.join("\n"),
|
|
16505
|
-
tail: false,
|
|
16506
|
-
truncated: endLine < totalLines,
|
|
16507
|
-
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16508
|
-
};
|
|
16509
16520
|
}
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
assertPositiveInteger(limit, "limit");
|
|
16514
|
-
const lines = [];
|
|
16515
|
-
let pending = "";
|
|
16516
|
-
let totalLines = 0;
|
|
16517
|
-
let crlfCount = 0;
|
|
16518
|
-
let lfCount = 0;
|
|
16519
|
-
const emitLine = (line) => {
|
|
16520
|
-
totalLines += 1;
|
|
16521
|
-
lines.push(line);
|
|
16522
|
-
if (lines.length > limit) {
|
|
16523
|
-
lines.shift();
|
|
16521
|
+
const trailingText = decoder.end() ?? "";
|
|
16522
|
+
if (trailingText.length > 0) {
|
|
16523
|
+
parts.push(trailingText);
|
|
16524
16524
|
}
|
|
16525
|
-
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
return;
|
|
16530
|
-
}
|
|
16531
|
-
const combined = pending + text;
|
|
16532
|
-
let start = 0;
|
|
16533
|
-
while (true) {
|
|
16534
|
-
const newlineIndex = combined.indexOf("\n", start);
|
|
16535
|
-
if (newlineIndex === -1) {
|
|
16536
|
-
break;
|
|
16537
|
-
}
|
|
16538
|
-
let line = combined.slice(start, newlineIndex);
|
|
16539
|
-
if (line.endsWith("\r")) {
|
|
16540
|
-
line = line.slice(0, -1);
|
|
16541
|
-
crlfCount += 1;
|
|
16542
|
-
} else {
|
|
16543
|
-
lfCount += 1;
|
|
16544
|
-
}
|
|
16545
|
-
emitLine(line);
|
|
16546
|
-
start = newlineIndex + 1;
|
|
16547
|
-
}
|
|
16548
|
-
pending = combined.slice(start);
|
|
16549
|
-
},
|
|
16550
|
-
finish() {
|
|
16551
|
-
emitLine(pending);
|
|
16552
|
-
const startLine = Math.max(1, totalLines - lines.length + 1);
|
|
16553
|
-
return {
|
|
16554
|
-
startLine,
|
|
16555
|
-
endLine: totalLines,
|
|
16556
|
-
totalLines,
|
|
16557
|
-
content: lines.map((line, index) => `${startLine + index}: ${line}`).join("\n"),
|
|
16558
|
-
truncated: startLine > 1,
|
|
16559
|
-
tail: true,
|
|
16560
|
-
newlineStyle: finalizeNewlineStyle(crlfCount, lfCount)
|
|
16561
|
-
};
|
|
16525
|
+
return parts.join("");
|
|
16526
|
+
} catch (error45) {
|
|
16527
|
+
if (error45 instanceof Error && "code" in error45) {
|
|
16528
|
+
throw error45;
|
|
16562
16529
|
}
|
|
16563
|
-
|
|
16530
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16531
|
+
} finally {
|
|
16532
|
+
stream.destroy();
|
|
16533
|
+
}
|
|
16564
16534
|
}
|
|
16565
16535
|
async function readGbkFile(input) {
|
|
16566
16536
|
const offset = normalizeOptionalPositiveInteger(input.offset, "offset") ?? 1;
|
|
@@ -16587,11 +16557,49 @@ async function readGbkFile(input) {
|
|
|
16587
16557
|
...lineWindow2
|
|
16588
16558
|
};
|
|
16589
16559
|
}
|
|
16590
|
-
const
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16560
|
+
const lineIndex = await getGbkLineIndex(resolved);
|
|
16561
|
+
const totalLines = lineIndex.totalLines;
|
|
16562
|
+
let lineWindow;
|
|
16563
|
+
if (tail) {
|
|
16564
|
+
const startLine = Math.max(1, totalLines - limit + 1);
|
|
16565
|
+
const expectedLineCount = totalLines - startLine + 1;
|
|
16566
|
+
const startOffset = lineIndex.lineStartOffsets[startLine - 1] ?? 0;
|
|
16567
|
+
const text = await readDecodedGbkByteRange(resolved, startOffset, toSafeNumber(resolved.stat.size));
|
|
16568
|
+
lineWindow = {
|
|
16569
|
+
startLine,
|
|
16570
|
+
endLine: totalLines,
|
|
16571
|
+
totalLines,
|
|
16572
|
+
content: formatLineWindowContent(text, startLine, expectedLineCount),
|
|
16573
|
+
truncated: startLine > 1,
|
|
16574
|
+
tail: true,
|
|
16575
|
+
newlineStyle: lineIndex.newlineStyle
|
|
16576
|
+
};
|
|
16577
|
+
} else if (offset > totalLines) {
|
|
16578
|
+
lineWindow = {
|
|
16579
|
+
startLine: offset,
|
|
16580
|
+
endLine: totalLines,
|
|
16581
|
+
totalLines,
|
|
16582
|
+
content: "",
|
|
16583
|
+
tail: false,
|
|
16584
|
+
truncated: false,
|
|
16585
|
+
newlineStyle: lineIndex.newlineStyle
|
|
16586
|
+
};
|
|
16587
|
+
} else {
|
|
16588
|
+
const endLine = Math.min(offset + limit - 1, totalLines);
|
|
16589
|
+
const expectedLineCount = endLine - offset + 1;
|
|
16590
|
+
const startOffset = lineIndex.lineStartOffsets[offset - 1] ?? 0;
|
|
16591
|
+
const endOffset = endLine < totalLines ? lineIndex.lineStartOffsets[endLine] : toSafeNumber(resolved.stat.size);
|
|
16592
|
+
const text = await readDecodedGbkByteRange(resolved, startOffset, endOffset);
|
|
16593
|
+
lineWindow = {
|
|
16594
|
+
startLine: offset,
|
|
16595
|
+
endLine,
|
|
16596
|
+
totalLines,
|
|
16597
|
+
content: formatLineWindowContent(text, offset, expectedLineCount),
|
|
16598
|
+
tail: false,
|
|
16599
|
+
truncated: endLine < totalLines,
|
|
16600
|
+
newlineStyle: lineIndex.newlineStyle
|
|
16601
|
+
};
|
|
16602
|
+
}
|
|
16595
16603
|
return {
|
|
16596
16604
|
filePath: resolved.filePath,
|
|
16597
16605
|
encoding: resolved.encoding,
|
|
@@ -16607,6 +16615,7 @@ var gbk_read_default = tool({
|
|
|
16607
16615
|
description: `Read GBK/GB18030 encoded text files with line numbers.
|
|
16608
16616
|
|
|
16609
16617
|
Returns up to 'limit' lines (default 2000) starting from 'offset'.
|
|
16618
|
+
Large files use a cached line-byte index so reading a small window does not require decoding the whole file each time.
|
|
16610
16619
|
When the file has more lines than the window, 'truncated' is true and 'totalLines' shows the full count.
|
|
16611
16620
|
|
|
16612
16621
|
IMPORTANT \u2014 line number format: each output line is prefixed with "N: " (e.g. "3787: content").
|
|
@@ -16305,6 +16305,7 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16305
16305
|
|
|
16306
16306
|
// src/lib/gbk-file.ts
|
|
16307
16307
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16308
|
+
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16308
16309
|
function assertEncodingSupported(encoding) {
|
|
16309
16310
|
if (encoding !== "gbk" && encoding !== "gb18030") {
|
|
16310
16311
|
throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|
|
@@ -16315,11 +16316,6 @@ function assertNotBinary(buffer) {
|
|
|
16315
16316
|
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
|
|
16316
16317
|
}
|
|
16317
16318
|
}
|
|
16318
|
-
async function readBufferAsText(buffer, encoding) {
|
|
16319
|
-
assertEncodingSupported(encoding);
|
|
16320
|
-
assertNotBinary(buffer);
|
|
16321
|
-
return import_iconv_lite.default.decode(buffer, encoding);
|
|
16322
|
-
}
|
|
16323
16319
|
async function resolveReadableGbkFile(input) {
|
|
16324
16320
|
const encoding = input.encoding ?? "gbk";
|
|
16325
16321
|
assertEncodingSupported(encoding);
|
|
@@ -16339,26 +16335,9 @@ async function resolveReadableGbkFile(input) {
|
|
|
16339
16335
|
stat
|
|
16340
16336
|
};
|
|
16341
16337
|
}
|
|
16342
|
-
async function readWholeGbkTextFile(input) {
|
|
16343
|
-
try {
|
|
16344
|
-
const buffer = await fs2.readFile(input.filePath);
|
|
16345
|
-
const content = await readBufferAsText(buffer, input.encoding);
|
|
16346
|
-
return {
|
|
16347
|
-
filePath: input.filePath,
|
|
16348
|
-
encoding: input.encoding,
|
|
16349
|
-
content,
|
|
16350
|
-
bytesRead: buffer.byteLength
|
|
16351
|
-
};
|
|
16352
|
-
} catch (error45) {
|
|
16353
|
-
if (error45 instanceof Error && "code" in error45) {
|
|
16354
|
-
throw error45;
|
|
16355
|
-
}
|
|
16356
|
-
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${input.filePath}`, error45);
|
|
16357
|
-
}
|
|
16358
|
-
}
|
|
16359
16338
|
async function visitDecodedTextChunks(input, visitor) {
|
|
16360
16339
|
const decoder = import_iconv_lite.default.getDecoder(input.encoding);
|
|
16361
|
-
const stream = createReadStream(input.filePath);
|
|
16340
|
+
const stream = createReadStream(input.filePath, { highWaterMark: STREAM_READ_CHUNK_SIZE_BYTES });
|
|
16362
16341
|
try {
|
|
16363
16342
|
for await (const chunk of stream) {
|
|
16364
16343
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
@@ -16381,47 +16360,64 @@ async function visitDecodedTextChunks(input, visitor) {
|
|
|
16381
16360
|
stream.destroy();
|
|
16382
16361
|
}
|
|
16383
16362
|
}
|
|
16384
|
-
async function loadGbkTextFile(input) {
|
|
16385
|
-
const resolved = await resolveReadableGbkFile(input);
|
|
16386
|
-
if (resolved.stat.size < STREAMING_FILE_SIZE_THRESHOLD_BYTES) {
|
|
16387
|
-
return await readWholeGbkTextFile(resolved);
|
|
16388
|
-
}
|
|
16389
|
-
const chunks = [];
|
|
16390
|
-
await visitDecodedTextChunks(resolved, (text) => {
|
|
16391
|
-
chunks.push(text);
|
|
16392
|
-
});
|
|
16393
|
-
return {
|
|
16394
|
-
filePath: resolved.filePath,
|
|
16395
|
-
encoding: resolved.encoding,
|
|
16396
|
-
content: chunks.join(""),
|
|
16397
|
-
bytesRead: resolved.stat.size
|
|
16398
|
-
};
|
|
16399
|
-
}
|
|
16400
16363
|
async function searchGbkFile(input) {
|
|
16401
16364
|
if (input.pattern.length === 0) {
|
|
16402
16365
|
throw createGbkError("GBK_INVALID_ARGUMENT", "pattern \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16403
16366
|
}
|
|
16404
16367
|
const contextLines = Math.max(0, input.contextLines ?? 3);
|
|
16405
16368
|
const resolved = await resolveReadableGbkFile(input);
|
|
16406
|
-
const loaded = await loadGbkTextFile({
|
|
16407
|
-
filePath: resolved.filePath,
|
|
16408
|
-
encoding: resolved.encoding,
|
|
16409
|
-
allowExternal: input.allowExternal,
|
|
16410
|
-
context: input.context
|
|
16411
|
-
});
|
|
16412
|
-
const lines = loaded.content.split(/\r?\n/);
|
|
16413
|
-
const totalLines = lines.length;
|
|
16414
16369
|
const matches = [];
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16418
|
-
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16370
|
+
const beforeBuffer = [];
|
|
16371
|
+
const activeAfterCollectors = [];
|
|
16372
|
+
let pending = "";
|
|
16373
|
+
let totalLines = 0;
|
|
16374
|
+
const emitLine = (line) => {
|
|
16375
|
+
totalLines += 1;
|
|
16376
|
+
for (let index = activeAfterCollectors.length - 1; index >= 0; index -= 1) {
|
|
16377
|
+
const collector = activeAfterCollectors[index];
|
|
16378
|
+
if (collector.remaining > 0) {
|
|
16379
|
+
collector.match.contextAfter.push(line);
|
|
16380
|
+
collector.remaining -= 1;
|
|
16381
|
+
}
|
|
16382
|
+
if (collector.remaining === 0) {
|
|
16383
|
+
activeAfterCollectors.splice(index, 1);
|
|
16384
|
+
}
|
|
16385
|
+
}
|
|
16386
|
+
if (line.includes(input.pattern)) {
|
|
16387
|
+
const match = {
|
|
16388
|
+
lineNumber: totalLines,
|
|
16389
|
+
line,
|
|
16390
|
+
contextBefore: beforeBuffer.slice(Math.max(0, beforeBuffer.length - contextLines)),
|
|
16391
|
+
contextAfter: []
|
|
16392
|
+
};
|
|
16393
|
+
matches.push(match);
|
|
16394
|
+
if (contextLines > 0) {
|
|
16395
|
+
activeAfterCollectors.push({ match, remaining: contextLines });
|
|
16396
|
+
}
|
|
16423
16397
|
}
|
|
16424
|
-
|
|
16398
|
+
beforeBuffer.push(line);
|
|
16399
|
+
if (beforeBuffer.length > contextLines) {
|
|
16400
|
+
beforeBuffer.shift();
|
|
16401
|
+
}
|
|
16402
|
+
};
|
|
16403
|
+
await visitDecodedTextChunks(resolved, (text) => {
|
|
16404
|
+
const combined = pending + text;
|
|
16405
|
+
let start = 0;
|
|
16406
|
+
while (true) {
|
|
16407
|
+
const newlineIndex = combined.indexOf("\n", start);
|
|
16408
|
+
if (newlineIndex === -1) {
|
|
16409
|
+
break;
|
|
16410
|
+
}
|
|
16411
|
+
let line = combined.slice(start, newlineIndex);
|
|
16412
|
+
if (line.endsWith("\r")) {
|
|
16413
|
+
line = line.slice(0, -1);
|
|
16414
|
+
}
|
|
16415
|
+
emitLine(line);
|
|
16416
|
+
start = newlineIndex + 1;
|
|
16417
|
+
}
|
|
16418
|
+
pending = combined.slice(start);
|
|
16419
|
+
});
|
|
16420
|
+
emitLine(pending);
|
|
16425
16421
|
return {
|
|
16426
16422
|
filePath: resolved.filePath,
|
|
16427
16423
|
encoding: resolved.encoding,
|
|
@@ -16305,11 +16305,24 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16305
16305
|
|
|
16306
16306
|
// src/lib/gbk-file.ts
|
|
16307
16307
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16308
|
+
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16309
|
+
var gbkLineIndexCache = /* @__PURE__ */ new Map();
|
|
16310
|
+
function invalidateGbkLineIndex(filePath) {
|
|
16311
|
+
gbkLineIndexCache.delete(filePath);
|
|
16312
|
+
}
|
|
16308
16313
|
function assertEncodingSupported(encoding) {
|
|
16309
16314
|
if (encoding !== "gbk" && encoding !== "gb18030") {
|
|
16310
16315
|
throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|
|
16311
16316
|
}
|
|
16312
16317
|
}
|
|
16318
|
+
async function appendEncodedText(filePath, encoding, text) {
|
|
16319
|
+
if (text.length === 0) {
|
|
16320
|
+
return 0;
|
|
16321
|
+
}
|
|
16322
|
+
const buffer = import_iconv_lite.default.encode(text, encoding);
|
|
16323
|
+
await fs2.appendFile(filePath, buffer);
|
|
16324
|
+
return buffer.byteLength;
|
|
16325
|
+
}
|
|
16313
16326
|
async function writeGbkFile(input) {
|
|
16314
16327
|
const encoding = input.encoding ?? "gbk";
|
|
16315
16328
|
const createDirectories = input.createDirectories ?? true;
|
|
@@ -16334,24 +16347,21 @@ async function writeGbkFile(input) {
|
|
|
16334
16347
|
throw error45;
|
|
16335
16348
|
}
|
|
16336
16349
|
}
|
|
16337
|
-
let existingContent = "";
|
|
16338
16350
|
let existed = false;
|
|
16339
16351
|
try {
|
|
16340
|
-
|
|
16341
|
-
existingContent = import_iconv_lite.default.decode(existingBuffer, encoding);
|
|
16352
|
+
await fs2.stat(candidatePath);
|
|
16342
16353
|
existed = true;
|
|
16343
16354
|
} catch (error45) {
|
|
16344
16355
|
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
16345
16356
|
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
|
|
16346
16357
|
}
|
|
16347
16358
|
}
|
|
16348
|
-
const
|
|
16349
|
-
|
|
16350
|
-
await fs2.writeFile(candidatePath, buffer);
|
|
16359
|
+
const bytesWritten = await appendEncodedText(candidatePath, encoding, input.content);
|
|
16360
|
+
invalidateGbkLineIndex(candidatePath);
|
|
16351
16361
|
return {
|
|
16352
16362
|
filePath: candidatePath,
|
|
16353
16363
|
encoding,
|
|
16354
|
-
bytesWritten
|
|
16364
|
+
bytesWritten,
|
|
16355
16365
|
created: !existed,
|
|
16356
16366
|
overwritten: false,
|
|
16357
16367
|
appended: true
|
|
@@ -16393,6 +16403,7 @@ async function writeGbkFile(input) {
|
|
|
16393
16403
|
const existed = await fs2.stat(candidatePath).then(() => true).catch(() => false);
|
|
16394
16404
|
const buffer = import_iconv_lite.default.encode(input.content, encoding);
|
|
16395
16405
|
await fs2.writeFile(candidatePath, buffer);
|
|
16406
|
+
invalidateGbkLineIndex(candidatePath);
|
|
16396
16407
|
return {
|
|
16397
16408
|
filePath: candidatePath,
|
|
16398
16409
|
encoding,
|
|
@@ -16413,7 +16424,7 @@ var gbk_write_default = tool({
|
|
|
16413
16424
|
description: `Write GBK encoded text files.
|
|
16414
16425
|
|
|
16415
16426
|
**append=true** (recommended for adding content to existing files):
|
|
16416
|
-
-
|
|
16427
|
+
- Appends encoded content directly to the end of the file without re-reading the whole file.
|
|
16417
16428
|
- Works whether the file exists or not (creates it if missing).
|
|
16418
16429
|
- Use this whenever you want to add lines/content to an existing GBK file.
|
|
16419
16430
|
- Example: gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
|
|
@@ -16311,6 +16311,7 @@ import { createReadStream } from "fs";
|
|
|
16311
16311
|
import fs2 from "fs/promises";
|
|
16312
16312
|
import path2 from "path";
|
|
16313
16313
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16314
|
+
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16314
16315
|
function assertStringArgument(value, name) {
|
|
16315
16316
|
if (typeof value !== "string") {
|
|
16316
16317
|
throw createGbkError("GBK_INVALID_ARGUMENT", `${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
@@ -16311,6 +16311,7 @@ import { createReadStream } from "fs";
|
|
|
16311
16311
|
import fs2 from "fs/promises";
|
|
16312
16312
|
import path2 from "path";
|
|
16313
16313
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16314
|
+
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16314
16315
|
function assertPositiveInteger(value, name) {
|
|
16315
16316
|
if (!Number.isInteger(value) || Number(value) <= 0) {
|
|
16316
16317
|
throw createGbkError("GBK_INVALID_ARGUMENT", `${name} \u5FC5\u987B\u662F\u6B63\u6574\u6570`);
|
|
@@ -16311,6 +16311,7 @@ import { createReadStream } from "fs";
|
|
|
16311
16311
|
import fs2 from "fs/promises";
|
|
16312
16312
|
import path2 from "path";
|
|
16313
16313
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16314
|
+
var STREAM_READ_CHUNK_SIZE_BYTES = 1024 * 1024;
|
|
16314
16315
|
function detectNewlineStyle(text) {
|
|
16315
16316
|
const crlfCount = (text.match(/\r\n/g) ?? []).length;
|
|
16316
16317
|
const lfCount = (text.match(/(^|[^\r])\n/g) ?? []).length;
|