opencode-gbk-tools 0.1.9 → 0.1.11
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 +68 -5
- package/dist/cli/index.js +3 -3
- package/dist/opencode-tools/gbk_edit.js +165 -10
- package/dist/opencode-tools/gbk_read.js +13 -0
- package/dist/opencode-tools/gbk_search.js +9 -0
- package/dist/opencode-tools/gbk_write.js +12 -0
- package/dist/opencode-tools/text_edit.js +17016 -0
- package/dist/opencode-tools/text_read.js +16652 -0
- package/dist/opencode-tools/text_write.js +16670 -0
- package/dist/plugin/index.js +1094 -37
- package/dist/plugins/opencode-gbk-tools.js +30 -0
- package/dist/release-manifest.json +29 -5
- package/package.json +2 -2
package/dist/plugin/index.js
CHANGED
|
@@ -182,7 +182,7 @@ var require_internal = __commonJS({
|
|
|
182
182
|
// Codec.
|
|
183
183
|
_internal: InternalCodec
|
|
184
184
|
};
|
|
185
|
-
function InternalCodec(codecOptions,
|
|
185
|
+
function InternalCodec(codecOptions, iconv3) {
|
|
186
186
|
this.enc = codecOptions.encodingName;
|
|
187
187
|
this.bomAware = codecOptions.bomAware;
|
|
188
188
|
if (this.enc === "base64") {
|
|
@@ -194,7 +194,7 @@ var require_internal = __commonJS({
|
|
|
194
194
|
this.encoder = InternalEncoderCesu8;
|
|
195
195
|
if (Buffer2.from("eda0bdedb2a9", "hex").toString() !== "\u{1F4A9}") {
|
|
196
196
|
this.decoder = InternalDecoderCesu8;
|
|
197
|
-
this.defaultCharUnicode =
|
|
197
|
+
this.defaultCharUnicode = iconv3.defaultCharUnicode;
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
}
|
|
@@ -351,8 +351,8 @@ var require_utf32 = __commonJS({
|
|
|
351
351
|
"use strict";
|
|
352
352
|
var Buffer2 = require_safer().Buffer;
|
|
353
353
|
exports._utf32 = Utf32Codec;
|
|
354
|
-
function Utf32Codec(codecOptions,
|
|
355
|
-
this.iconv =
|
|
354
|
+
function Utf32Codec(codecOptions, iconv3) {
|
|
355
|
+
this.iconv = iconv3;
|
|
356
356
|
this.bomAware = true;
|
|
357
357
|
this.isLE = codecOptions.isLE;
|
|
358
358
|
}
|
|
@@ -476,8 +476,8 @@ var require_utf32 = __commonJS({
|
|
|
476
476
|
};
|
|
477
477
|
exports.utf32 = Utf32AutoCodec;
|
|
478
478
|
exports.ucs4 = "utf32";
|
|
479
|
-
function Utf32AutoCodec(options,
|
|
480
|
-
this.iconv =
|
|
479
|
+
function Utf32AutoCodec(options, iconv3) {
|
|
480
|
+
this.iconv = iconv3;
|
|
481
481
|
}
|
|
482
482
|
Utf32AutoCodec.prototype.encoder = Utf32AutoEncoder;
|
|
483
483
|
Utf32AutoCodec.prototype.decoder = Utf32AutoDecoder;
|
|
@@ -627,8 +627,8 @@ var require_utf16 = __commonJS({
|
|
|
627
627
|
this.overflowByte = -1;
|
|
628
628
|
};
|
|
629
629
|
exports.utf16 = Utf16Codec;
|
|
630
|
-
function Utf16Codec(codecOptions,
|
|
631
|
-
this.iconv =
|
|
630
|
+
function Utf16Codec(codecOptions, iconv3) {
|
|
631
|
+
this.iconv = iconv3;
|
|
632
632
|
}
|
|
633
633
|
Utf16Codec.prototype.encoder = Utf16Encoder;
|
|
634
634
|
Utf16Codec.prototype.decoder = Utf16Decoder;
|
|
@@ -726,8 +726,8 @@ var require_utf7 = __commonJS({
|
|
|
726
726
|
var Buffer2 = require_safer().Buffer;
|
|
727
727
|
exports.utf7 = Utf7Codec;
|
|
728
728
|
exports.unicode11utf7 = "utf7";
|
|
729
|
-
function Utf7Codec(codecOptions,
|
|
730
|
-
this.iconv =
|
|
729
|
+
function Utf7Codec(codecOptions, iconv3) {
|
|
730
|
+
this.iconv = iconv3;
|
|
731
731
|
}
|
|
732
732
|
Utf7Codec.prototype.encoder = Utf7Encoder;
|
|
733
733
|
Utf7Codec.prototype.decoder = Utf7Decoder;
|
|
@@ -809,8 +809,8 @@ var require_utf7 = __commonJS({
|
|
|
809
809
|
return res;
|
|
810
810
|
};
|
|
811
811
|
exports.utf7imap = Utf7IMAPCodec;
|
|
812
|
-
function Utf7IMAPCodec(codecOptions,
|
|
813
|
-
this.iconv =
|
|
812
|
+
function Utf7IMAPCodec(codecOptions, iconv3) {
|
|
813
|
+
this.iconv = iconv3;
|
|
814
814
|
}
|
|
815
815
|
Utf7IMAPCodec.prototype.encoder = Utf7IMAPEncoder;
|
|
816
816
|
Utf7IMAPCodec.prototype.decoder = Utf7IMAPDecoder;
|
|
@@ -943,7 +943,7 @@ var require_sbcs_codec = __commonJS({
|
|
|
943
943
|
"use strict";
|
|
944
944
|
var Buffer2 = require_safer().Buffer;
|
|
945
945
|
exports._sbcs = SBCSCodec;
|
|
946
|
-
function SBCSCodec(codecOptions,
|
|
946
|
+
function SBCSCodec(codecOptions, iconv3) {
|
|
947
947
|
if (!codecOptions) {
|
|
948
948
|
throw new Error("SBCS codec is called without the data.");
|
|
949
949
|
}
|
|
@@ -958,7 +958,7 @@ var require_sbcs_codec = __commonJS({
|
|
|
958
958
|
codecOptions.chars = asciiString + codecOptions.chars;
|
|
959
959
|
}
|
|
960
960
|
this.decodeBuf = Buffer2.from(codecOptions.chars, "ucs2");
|
|
961
|
-
var encodeBuf = Buffer2.alloc(65536,
|
|
961
|
+
var encodeBuf = Buffer2.alloc(65536, iconv3.defaultCharSingleByte.charCodeAt(0));
|
|
962
962
|
for (var i = 0; i < codecOptions.chars.length; i++) {
|
|
963
963
|
encodeBuf[codecOptions.chars.charCodeAt(i)] = i;
|
|
964
964
|
}
|
|
@@ -1623,7 +1623,7 @@ var require_dbcs_codec = __commonJS({
|
|
|
1623
1623
|
UNASSIGNED_NODE[i] = UNASSIGNED;
|
|
1624
1624
|
}
|
|
1625
1625
|
var i;
|
|
1626
|
-
function DBCSCodec(codecOptions,
|
|
1626
|
+
function DBCSCodec(codecOptions, iconv3) {
|
|
1627
1627
|
this.encodingName = codecOptions.encodingName;
|
|
1628
1628
|
if (!codecOptions) {
|
|
1629
1629
|
throw new Error("DBCS codec is called without the data.");
|
|
@@ -1672,7 +1672,7 @@ var require_dbcs_codec = __commonJS({
|
|
|
1672
1672
|
}
|
|
1673
1673
|
}
|
|
1674
1674
|
}
|
|
1675
|
-
this.defaultCharUnicode =
|
|
1675
|
+
this.defaultCharUnicode = iconv3.defaultCharUnicode;
|
|
1676
1676
|
this.encodeTable = [];
|
|
1677
1677
|
this.encodeTableSeq = [];
|
|
1678
1678
|
var skipEncodeChars = {};
|
|
@@ -1696,7 +1696,7 @@ var require_dbcs_codec = __commonJS({
|
|
|
1696
1696
|
}
|
|
1697
1697
|
}
|
|
1698
1698
|
}
|
|
1699
|
-
this.defCharSB = this.encodeTable[0][
|
|
1699
|
+
this.defCharSB = this.encodeTable[0][iconv3.defaultCharSingleByte.charCodeAt(0)];
|
|
1700
1700
|
if (this.defCharSB === UNASSIGNED) this.defCharSB = this.encodeTable[0]["?"];
|
|
1701
1701
|
if (this.defCharSB === UNASSIGNED) this.defCharSB = "?".charCodeAt(0);
|
|
1702
1702
|
}
|
|
@@ -4547,10 +4547,10 @@ function mergeDefs(...defs) {
|
|
|
4547
4547
|
function cloneDef(schema) {
|
|
4548
4548
|
return mergeDefs(schema._zod.def);
|
|
4549
4549
|
}
|
|
4550
|
-
function getElementAtPath(obj,
|
|
4551
|
-
if (!
|
|
4550
|
+
function getElementAtPath(obj, path4) {
|
|
4551
|
+
if (!path4)
|
|
4552
4552
|
return obj;
|
|
4553
|
-
return
|
|
4553
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
4554
4554
|
}
|
|
4555
4555
|
function promiseAllObject(promisesObj) {
|
|
4556
4556
|
const keys = Object.keys(promisesObj);
|
|
@@ -4911,11 +4911,11 @@ function aborted(x, startIndex = 0) {
|
|
|
4911
4911
|
}
|
|
4912
4912
|
return false;
|
|
4913
4913
|
}
|
|
4914
|
-
function prefixIssues(
|
|
4914
|
+
function prefixIssues(path4, issues) {
|
|
4915
4915
|
return issues.map((iss) => {
|
|
4916
4916
|
var _a;
|
|
4917
4917
|
(_a = iss).path ?? (_a.path = []);
|
|
4918
|
-
iss.path.unshift(
|
|
4918
|
+
iss.path.unshift(path4);
|
|
4919
4919
|
return iss;
|
|
4920
4920
|
});
|
|
4921
4921
|
}
|
|
@@ -5083,7 +5083,7 @@ function treeifyError(error45, _mapper) {
|
|
|
5083
5083
|
return issue2.message;
|
|
5084
5084
|
};
|
|
5085
5085
|
const result = { errors: [] };
|
|
5086
|
-
const processError = (error46,
|
|
5086
|
+
const processError = (error46, path4 = []) => {
|
|
5087
5087
|
var _a, _b;
|
|
5088
5088
|
for (const issue2 of error46.issues) {
|
|
5089
5089
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -5093,7 +5093,7 @@ function treeifyError(error45, _mapper) {
|
|
|
5093
5093
|
} else if (issue2.code === "invalid_element") {
|
|
5094
5094
|
processError({ issues: issue2.issues }, issue2.path);
|
|
5095
5095
|
} else {
|
|
5096
|
-
const fullpath = [...
|
|
5096
|
+
const fullpath = [...path4, ...issue2.path];
|
|
5097
5097
|
if (fullpath.length === 0) {
|
|
5098
5098
|
result.errors.push(mapper(issue2));
|
|
5099
5099
|
continue;
|
|
@@ -5125,8 +5125,8 @@ function treeifyError(error45, _mapper) {
|
|
|
5125
5125
|
}
|
|
5126
5126
|
function toDotPath(_path) {
|
|
5127
5127
|
const segs = [];
|
|
5128
|
-
const
|
|
5129
|
-
for (const seg of
|
|
5128
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
5129
|
+
for (const seg of path4) {
|
|
5130
5130
|
if (typeof seg === "number")
|
|
5131
5131
|
segs.push(`[${seg}]`);
|
|
5132
5132
|
else if (typeof seg === "symbol")
|
|
@@ -16259,6 +16259,7 @@ var GbkToolError = class extends Error {
|
|
|
16259
16259
|
function createGbkError(code, message, cause) {
|
|
16260
16260
|
return new GbkToolError(code, message, cause);
|
|
16261
16261
|
}
|
|
16262
|
+
var createTextError = createGbkError;
|
|
16262
16263
|
|
|
16263
16264
|
// src/lib/path-sandbox.ts
|
|
16264
16265
|
import fs from "fs/promises";
|
|
@@ -16305,6 +16306,82 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16305
16306
|
|
|
16306
16307
|
// src/lib/gbk-file.ts
|
|
16307
16308
|
var STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
16309
|
+
function assertStringArgument(value, name) {
|
|
16310
|
+
if (typeof value !== "string") {
|
|
16311
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", `${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
16312
|
+
}
|
|
16313
|
+
}
|
|
16314
|
+
function assertReplaceArguments(input) {
|
|
16315
|
+
assertStringArgument(input.oldString, "oldString");
|
|
16316
|
+
assertStringArgument(input.newString, "newString");
|
|
16317
|
+
}
|
|
16318
|
+
function assertInsertArguments(input) {
|
|
16319
|
+
assertStringArgument(input.anchor, "anchor");
|
|
16320
|
+
assertStringArgument(input.content, "content");
|
|
16321
|
+
if (input.content.length === 0) {
|
|
16322
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "content \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16323
|
+
}
|
|
16324
|
+
if (input.replaceAll !== void 0) {
|
|
16325
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 replaceAll");
|
|
16326
|
+
}
|
|
16327
|
+
if (input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0) {
|
|
16328
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 startLine/endLine/startAnchor/endAnchor");
|
|
16329
|
+
}
|
|
16330
|
+
}
|
|
16331
|
+
function findOccurrenceIndex(text, token, occurrence) {
|
|
16332
|
+
assertStringArgument(text, "text");
|
|
16333
|
+
assertStringArgument(token, "anchor");
|
|
16334
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
16335
|
+
if (token.length === 0) {
|
|
16336
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16337
|
+
}
|
|
16338
|
+
let count = 0;
|
|
16339
|
+
let searchFrom = 0;
|
|
16340
|
+
while (true) {
|
|
16341
|
+
const index = text.indexOf(token, searchFrom);
|
|
16342
|
+
if (index === -1) {
|
|
16343
|
+
break;
|
|
16344
|
+
}
|
|
16345
|
+
count += 1;
|
|
16346
|
+
if (count === occurrence) {
|
|
16347
|
+
return { index, total: countOccurrences(text, token) };
|
|
16348
|
+
}
|
|
16349
|
+
searchFrom = index + token.length;
|
|
16350
|
+
}
|
|
16351
|
+
if (count === 0) {
|
|
16352
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
16353
|
+
}
|
|
16354
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${count} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
16355
|
+
}
|
|
16356
|
+
function insertByAnchor(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
16357
|
+
const alignedContent = newlineStyle === "crlf" ? content.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : newlineStyle === "lf" ? content.replace(/\r\n/g, "\n") : content;
|
|
16358
|
+
const located = findOccurrenceIndex(text, anchor, occurrence);
|
|
16359
|
+
const insertionPoint = mode === "insertAfter" ? located.index + anchor.length : located.index;
|
|
16360
|
+
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
16361
|
+
if (alreadyExists) {
|
|
16362
|
+
if (ifExists === "error") {
|
|
16363
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
16364
|
+
}
|
|
16365
|
+
if (ifExists === "skip") {
|
|
16366
|
+
return {
|
|
16367
|
+
outputText: text,
|
|
16368
|
+
inserted: false,
|
|
16369
|
+
skipped: true,
|
|
16370
|
+
anchorMatches: located.total,
|
|
16371
|
+
occurrence,
|
|
16372
|
+
anchor
|
|
16373
|
+
};
|
|
16374
|
+
}
|
|
16375
|
+
}
|
|
16376
|
+
return {
|
|
16377
|
+
outputText: `${text.slice(0, insertionPoint)}${alignedContent}${text.slice(insertionPoint)}`,
|
|
16378
|
+
inserted: true,
|
|
16379
|
+
skipped: false,
|
|
16380
|
+
anchorMatches: located.total,
|
|
16381
|
+
occurrence,
|
|
16382
|
+
anchor
|
|
16383
|
+
};
|
|
16384
|
+
}
|
|
16308
16385
|
function assertEncodingSupported(encoding) {
|
|
16309
16386
|
if (encoding !== "gbk" && encoding !== "gb18030") {
|
|
16310
16387
|
throw createGbkError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|
|
@@ -16474,9 +16551,11 @@ function resolveEditScope(text, input) {
|
|
|
16474
16551
|
};
|
|
16475
16552
|
}
|
|
16476
16553
|
function normalizeNewlines(text) {
|
|
16554
|
+
assertStringArgument(text, "text");
|
|
16477
16555
|
return text.replace(/\r\n/g, "\n");
|
|
16478
16556
|
}
|
|
16479
16557
|
function trimRightSpaces(text) {
|
|
16558
|
+
assertStringArgument(text, "text");
|
|
16480
16559
|
return text.replace(/[ \t]+$/g, "");
|
|
16481
16560
|
}
|
|
16482
16561
|
function splitNormalizedLines(text) {
|
|
@@ -16572,6 +16651,8 @@ function tryLooseBlockReplace(content, oldString, newString) {
|
|
|
16572
16651
|
return null;
|
|
16573
16652
|
}
|
|
16574
16653
|
function countOccurrences(text, target) {
|
|
16654
|
+
assertStringArgument(text, "text");
|
|
16655
|
+
assertStringArgument(target, "oldString");
|
|
16575
16656
|
if (target.length === 0) {
|
|
16576
16657
|
throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16577
16658
|
}
|
|
@@ -16887,11 +16968,56 @@ async function readGbkFile(input) {
|
|
|
16887
16968
|
};
|
|
16888
16969
|
}
|
|
16889
16970
|
async function replaceGbkFileText(input) {
|
|
16971
|
+
const mode = input.mode ?? "replace";
|
|
16890
16972
|
const normalizedInput = {
|
|
16891
16973
|
...input,
|
|
16892
16974
|
startLine: normalizeOptionalPositiveInteger(input.startLine, "startLine"),
|
|
16893
16975
|
endLine: normalizeOptionalPositiveInteger(input.endLine, "endLine")
|
|
16894
16976
|
};
|
|
16977
|
+
if (mode === "insertAfter" || mode === "insertBefore") {
|
|
16978
|
+
assertInsertArguments(input);
|
|
16979
|
+
const resolved2 = await resolveReadableGbkFile(normalizedInput);
|
|
16980
|
+
const current2 = await readWholeGbkTextFile(resolved2);
|
|
16981
|
+
const occurrence = normalizeOptionalPositiveInteger(input.occurrence, "occurrence") ?? 1;
|
|
16982
|
+
const insertResult = insertByAnchor(
|
|
16983
|
+
current2.content,
|
|
16984
|
+
mode,
|
|
16985
|
+
input.anchor,
|
|
16986
|
+
input.content,
|
|
16987
|
+
occurrence,
|
|
16988
|
+
input.ifExists ?? "skip",
|
|
16989
|
+
detectNewlineStyle(current2.content)
|
|
16990
|
+
);
|
|
16991
|
+
if (insertResult.skipped) {
|
|
16992
|
+
return {
|
|
16993
|
+
mode,
|
|
16994
|
+
filePath: current2.filePath,
|
|
16995
|
+
encoding: current2.encoding,
|
|
16996
|
+
anchor: insertResult.anchor,
|
|
16997
|
+
occurrence: insertResult.occurrence,
|
|
16998
|
+
anchorMatches: insertResult.anchorMatches,
|
|
16999
|
+
inserted: false,
|
|
17000
|
+
skipped: true,
|
|
17001
|
+
bytesRead: current2.bytesRead,
|
|
17002
|
+
bytesWritten: 0
|
|
17003
|
+
};
|
|
17004
|
+
}
|
|
17005
|
+
const buffer2 = import_iconv_lite.default.encode(insertResult.outputText, current2.encoding);
|
|
17006
|
+
await fs2.writeFile(current2.filePath, buffer2);
|
|
17007
|
+
return {
|
|
17008
|
+
mode,
|
|
17009
|
+
filePath: current2.filePath,
|
|
17010
|
+
encoding: current2.encoding,
|
|
17011
|
+
anchor: insertResult.anchor,
|
|
17012
|
+
occurrence: insertResult.occurrence,
|
|
17013
|
+
anchorMatches: insertResult.anchorMatches,
|
|
17014
|
+
inserted: true,
|
|
17015
|
+
skipped: false,
|
|
17016
|
+
bytesRead: current2.bytesRead,
|
|
17017
|
+
bytesWritten: buffer2.byteLength
|
|
17018
|
+
};
|
|
17019
|
+
}
|
|
17020
|
+
assertReplaceArguments(input);
|
|
16895
17021
|
if (input.oldString.length === 0) {
|
|
16896
17022
|
throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16897
17023
|
}
|
|
@@ -16908,14 +17034,15 @@ async function replaceGbkFileText(input) {
|
|
|
16908
17034
|
}
|
|
16909
17035
|
const current = await readWholeGbkTextFile(resolved);
|
|
16910
17036
|
const scope = resolveEditScope(current.content, normalizedInput);
|
|
16911
|
-
const occurrencesBefore = countOccurrences(scope.selectedText,
|
|
17037
|
+
const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
|
|
16912
17038
|
if (!replaceAll && occurrencesBefore === 0) {
|
|
16913
|
-
const loose = tryLooseBlockReplace(scope.selectedText,
|
|
17039
|
+
const loose = tryLooseBlockReplace(scope.selectedText, input.oldString, input.newString);
|
|
16914
17040
|
if (loose !== null) {
|
|
16915
17041
|
const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
|
|
16916
17042
|
const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
|
|
16917
17043
|
await fs2.writeFile(current.filePath, buffer2);
|
|
16918
17044
|
return {
|
|
17045
|
+
mode: "replace",
|
|
16919
17046
|
filePath: current.filePath,
|
|
16920
17047
|
encoding: current.encoding,
|
|
16921
17048
|
replacements: 1,
|
|
@@ -16927,20 +17054,21 @@ async function replaceGbkFileText(input) {
|
|
|
16927
17054
|
}
|
|
16928
17055
|
if (replaceAll) {
|
|
16929
17056
|
if (occurrencesBefore === 0) {
|
|
16930
|
-
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText,
|
|
17057
|
+
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
|
|
16931
17058
|
}
|
|
16932
17059
|
} else if (occurrencesBefore === 0) {
|
|
16933
|
-
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText,
|
|
17060
|
+
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
|
|
16934
17061
|
} else if (occurrencesBefore > 1) {
|
|
16935
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${
|
|
17062
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
16936
17063
|
}
|
|
16937
17064
|
const fileNewlineStyle = detectNewlineStyle(current.content);
|
|
16938
|
-
const alignedNewString = fileNewlineStyle === "crlf" ?
|
|
16939
|
-
const replaced = replaceAll ? scope.selectedText.split(
|
|
17065
|
+
const alignedNewString = fileNewlineStyle === "crlf" ? input.newString.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : fileNewlineStyle === "lf" ? input.newString.replace(/\r\n/g, "\n") : input.newString;
|
|
17066
|
+
const replaced = replaceAll ? scope.selectedText.split(input.oldString).join(alignedNewString) : scope.selectedText.replace(input.oldString, alignedNewString);
|
|
16940
17067
|
const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
|
|
16941
17068
|
const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
|
|
16942
17069
|
await fs2.writeFile(current.filePath, buffer);
|
|
16943
17070
|
return {
|
|
17071
|
+
mode: "replace",
|
|
16944
17072
|
filePath: current.filePath,
|
|
16945
17073
|
encoding: current.encoding,
|
|
16946
17074
|
replacements: replaceAll ? occurrencesBefore : 1,
|
|
@@ -17104,11 +17232,20 @@ Recommended workflow for large files (when gbk_read returned truncated=true):
|
|
|
17104
17232
|
3. gbk_edit(oldString=<content without prefixes>, newString=<new content>)
|
|
17105
17233
|
|
|
17106
17234
|
For large files, use 'startLine'/'endLine' or 'startAnchor'/'endAnchor' to narrow the search scope
|
|
17107
|
-
and avoid false matches. Scoped edits also improve performance on very large files
|
|
17235
|
+
and avoid false matches. Scoped edits also improve performance on very large files.
|
|
17236
|
+
|
|
17237
|
+
Insert mode:
|
|
17238
|
+
- Use mode=insertAfter or mode=insertBefore with anchor/content
|
|
17239
|
+
- This is recommended when the intent is "insert after label" instead of exact replacement.`,
|
|
17108
17240
|
args: {
|
|
17109
17241
|
filePath: tool.schema.string().describe("Target file path"),
|
|
17110
|
-
|
|
17111
|
-
|
|
17242
|
+
mode: tool.schema.enum(["replace", "insertAfter", "insertBefore"]).optional().describe("Edit mode, default replace"),
|
|
17243
|
+
oldString: tool.schema.string().optional().describe("Exact text to replace in replace mode"),
|
|
17244
|
+
newString: tool.schema.string().optional().describe("Replacement text in replace mode"),
|
|
17245
|
+
anchor: tool.schema.string().optional().describe("Anchor text used by insertAfter/insertBefore"),
|
|
17246
|
+
content: tool.schema.string().optional().describe("Inserted content used by insertAfter/insertBefore"),
|
|
17247
|
+
occurrence: tool.schema.number().int().positive().optional().describe("1-based anchor occurrence for insert mode"),
|
|
17248
|
+
ifExists: tool.schema.enum(["allow", "skip", "error"]).optional().describe("What to do when inserted content already exists at target position"),
|
|
17112
17249
|
replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences (default: false, requires unique match)"),
|
|
17113
17250
|
startLine: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Restrict edit scope to 1-based start line (inclusive)"),
|
|
17114
17251
|
endLine: tool.schema.union([tool.schema.number().int().positive(), tool.schema.literal(-1)]).optional().describe("Restrict edit scope to 1-based end line (inclusive)"),
|
|
@@ -17119,6 +17256,25 @@ and avoid false matches. Scoped edits also improve performance on very large fil
|
|
|
17119
17256
|
},
|
|
17120
17257
|
async execute(args, context) {
|
|
17121
17258
|
const result = await replaceGbkFileText({ ...args, context });
|
|
17259
|
+
const isReplace = !("mode" in result) || result.mode === "replace";
|
|
17260
|
+
const title = isReplace ? `GBK \u7F16\u8F91 ${result.encoding.toUpperCase()} x${result.replacements}` : result.inserted ? `GBK \u63D2\u5165 ${result.encoding.toUpperCase()} #${result.occurrence}` : `GBK \u8DF3\u8FC7 ${result.encoding.toUpperCase()} #${result.occurrence}`;
|
|
17261
|
+
context.metadata({
|
|
17262
|
+
title,
|
|
17263
|
+
metadata: {
|
|
17264
|
+
filePath: result.filePath,
|
|
17265
|
+
encoding: result.encoding,
|
|
17266
|
+
mode: isReplace ? "replace" : result.mode,
|
|
17267
|
+
replacements: isReplace ? result.replacements : void 0,
|
|
17268
|
+
occurrencesBefore: isReplace ? result.occurrencesBefore : void 0,
|
|
17269
|
+
anchor: isReplace ? void 0 : result.anchor,
|
|
17270
|
+
occurrence: isReplace ? void 0 : result.occurrence,
|
|
17271
|
+
anchorMatches: isReplace ? void 0 : result.anchorMatches,
|
|
17272
|
+
inserted: isReplace ? void 0 : result.inserted,
|
|
17273
|
+
skipped: isReplace ? void 0 : result.skipped,
|
|
17274
|
+
bytesRead: result.bytesRead,
|
|
17275
|
+
bytesWritten: result.bytesWritten
|
|
17276
|
+
}
|
|
17277
|
+
});
|
|
17122
17278
|
return JSON.stringify(result, null, 2);
|
|
17123
17279
|
}
|
|
17124
17280
|
});
|
|
@@ -17153,6 +17309,19 @@ Workflow when truncated=true:
|
|
|
17153
17309
|
},
|
|
17154
17310
|
async execute(args, context) {
|
|
17155
17311
|
const result = await readGbkFile({ ...args, context });
|
|
17312
|
+
const lineRange = `${result.startLine}-${result.endLine}`;
|
|
17313
|
+
context.metadata({
|
|
17314
|
+
title: `GBK \u8BFB\u53D6 ${result.encoding.toUpperCase()} ${lineRange}`,
|
|
17315
|
+
metadata: {
|
|
17316
|
+
filePath: result.filePath,
|
|
17317
|
+
encoding: result.encoding,
|
|
17318
|
+
lineRange,
|
|
17319
|
+
totalLines: result.totalLines,
|
|
17320
|
+
newlineStyle: result.newlineStyle,
|
|
17321
|
+
truncated: result.truncated,
|
|
17322
|
+
tail: result.tail
|
|
17323
|
+
}
|
|
17324
|
+
});
|
|
17156
17325
|
return JSON.stringify(result, null, 2);
|
|
17157
17326
|
}
|
|
17158
17327
|
});
|
|
@@ -17180,6 +17349,15 @@ Workflow for large files:
|
|
|
17180
17349
|
},
|
|
17181
17350
|
async execute(args, context) {
|
|
17182
17351
|
const result = await searchGbkFile({ ...args, context });
|
|
17352
|
+
context.metadata({
|
|
17353
|
+
title: `GBK \u641C\u7D22 ${result.encoding.toUpperCase()} ${result.matchCount} \u547D\u4E2D`,
|
|
17354
|
+
metadata: {
|
|
17355
|
+
filePath: result.filePath,
|
|
17356
|
+
encoding: result.encoding,
|
|
17357
|
+
totalLines: result.totalLines,
|
|
17358
|
+
matchCount: result.matchCount
|
|
17359
|
+
}
|
|
17360
|
+
});
|
|
17183
17361
|
return JSON.stringify(result, null, 2);
|
|
17184
17362
|
}
|
|
17185
17363
|
});
|
|
@@ -17208,6 +17386,879 @@ var gbk_write_default = tool({
|
|
|
17208
17386
|
},
|
|
17209
17387
|
async execute(args, context) {
|
|
17210
17388
|
const result = await writeGbkFile({ ...args, context });
|
|
17389
|
+
const action = result.appended ? "\u8FFD\u52A0" : result.overwritten ? "\u8986\u76D6" : "\u5199\u5165";
|
|
17390
|
+
context.metadata({
|
|
17391
|
+
title: `GBK ${action} ${result.encoding.toUpperCase()}`,
|
|
17392
|
+
metadata: {
|
|
17393
|
+
filePath: result.filePath,
|
|
17394
|
+
encoding: result.encoding,
|
|
17395
|
+
created: result.created,
|
|
17396
|
+
overwritten: result.overwritten,
|
|
17397
|
+
appended: result.appended ?? false,
|
|
17398
|
+
bytesWritten: result.bytesWritten
|
|
17399
|
+
}
|
|
17400
|
+
});
|
|
17401
|
+
return JSON.stringify(result, null, 2);
|
|
17402
|
+
}
|
|
17403
|
+
});
|
|
17404
|
+
|
|
17405
|
+
// src/plugin/text-guidance.ts
|
|
17406
|
+
var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
|
|
17407
|
+
var TEXT_TOOL_SYSTEM_PROMPT = [
|
|
17408
|
+
TEXT_TOOL_SYSTEM_MARKER,
|
|
17409
|
+
"\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
|
|
17410
|
+
"- \u5904\u7406\u6587\u672C\u6587\u4EF6\u65F6\uFF0C\u4F18\u5148\u4F7F\u7528 text_read\u3001text_write\u3001text_edit\u3002",
|
|
17411
|
+
"- text_* \u9ED8\u8BA4\u4F1A\u81EA\u52A8\u8BC6\u522B\u73B0\u6709\u6587\u4EF6\u7F16\u7801\uFF0C\u5E76\u5728\u4FEE\u6539\u65F6\u5C3D\u91CF\u4FDD\u6301\u539F\u7F16\u7801\u3001BOM \u548C\u6362\u884C\u98CE\u683C\u3002",
|
|
17412
|
+
"- \u5982\u679C\u610F\u56FE\u662F\u2018\u5728\u67D0\u6807\u7B7E\u524D\u540E\u63D2\u5165\u5185\u5BB9\u2019\uFF0C\u4F18\u5148\u4F7F\u7528 mode=insertAfter \u6216 mode=insertBefore\uFF0C\u5E76\u4F20 anchor/content\u3002",
|
|
17413
|
+
"- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
|
|
17414
|
+
"- \u82E5\u68C0\u6D4B\u7F6E\u4FE1\u5EA6\u4E0D\u8DB3\u6216\u51FA\u73B0 TEXT_UNKNOWN_ENCODING\uFF0C\u8BF7\u663E\u5F0F\u6307\u5B9A encoding \u540E\u91CD\u8BD5\u3002",
|
|
17415
|
+
"- \u5904\u7406\u660E\u786E\u7684 GBK/GB18030 \u6587\u4EF6\u65F6\uFF0C\u4E5F\u53EF\u7EE7\u7EED\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\u3002"
|
|
17416
|
+
].join("\n");
|
|
17417
|
+
function appendTextToolSystemPrompt(system) {
|
|
17418
|
+
if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
|
|
17419
|
+
return;
|
|
17420
|
+
}
|
|
17421
|
+
system.push(TEXT_TOOL_SYSTEM_PROMPT);
|
|
17422
|
+
}
|
|
17423
|
+
|
|
17424
|
+
// src/lib/text-file.ts
|
|
17425
|
+
var import_iconv_lite2 = __toESM(require_lib(), 1);
|
|
17426
|
+
import fs3 from "fs/promises";
|
|
17427
|
+
import path3 from "path";
|
|
17428
|
+
var TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
17429
|
+
var UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
17430
|
+
function assertStringArgument2(value, name) {
|
|
17431
|
+
if (typeof value !== "string") {
|
|
17432
|
+
throw createTextError("GBK_INVALID_ARGUMENT", `${name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
17433
|
+
}
|
|
17434
|
+
}
|
|
17435
|
+
function assertReplaceArguments2(input) {
|
|
17436
|
+
assertStringArgument2(input.oldString, "oldString");
|
|
17437
|
+
assertStringArgument2(input.newString, "newString");
|
|
17438
|
+
}
|
|
17439
|
+
function resolveEditMode(mode) {
|
|
17440
|
+
return mode ?? "replace";
|
|
17441
|
+
}
|
|
17442
|
+
function resolveInsertIfExists(value) {
|
|
17443
|
+
return value ?? "skip";
|
|
17444
|
+
}
|
|
17445
|
+
function resolveExplicitTextEncoding(value, fallback) {
|
|
17446
|
+
const requested = value ?? "auto";
|
|
17447
|
+
return requested === "auto" ? fallback : requested;
|
|
17448
|
+
}
|
|
17449
|
+
function isSupportedEncoding(value) {
|
|
17450
|
+
return value === "utf8" || value === "utf8-bom" || value === "utf16le" || value === "utf16be" || value === "gbk" || value === "gb18030";
|
|
17451
|
+
}
|
|
17452
|
+
function assertTextEncodingSupported(encoding) {
|
|
17453
|
+
if (encoding !== "auto" && !isSupportedEncoding(encoding)) {
|
|
17454
|
+
throw createTextError("GBK_INVALID_ENCODING", `\u4E0D\u652F\u6301\u7684\u7F16\u7801: ${encoding}`);
|
|
17455
|
+
}
|
|
17456
|
+
}
|
|
17457
|
+
function decodeUtf16be(buffer) {
|
|
17458
|
+
return import_iconv_lite2.default.decode(buffer, "utf16be");
|
|
17459
|
+
}
|
|
17460
|
+
function encodeText(content, encoding, hasBom = encoding === "utf8-bom") {
|
|
17461
|
+
if (encoding === "utf8") {
|
|
17462
|
+
return Buffer.from(content, "utf8");
|
|
17463
|
+
}
|
|
17464
|
+
if (encoding === "utf8-bom") {
|
|
17465
|
+
return Buffer.concat([Buffer.from([239, 187, 191]), Buffer.from(content, "utf8")]);
|
|
17466
|
+
}
|
|
17467
|
+
if (encoding === "utf16le") {
|
|
17468
|
+
const body = Buffer.from(content, "utf16le");
|
|
17469
|
+
return hasBom ? Buffer.concat([Buffer.from([255, 254]), body]) : body;
|
|
17470
|
+
}
|
|
17471
|
+
if (encoding === "utf16be") {
|
|
17472
|
+
const body = import_iconv_lite2.default.encode(content, "utf16be");
|
|
17473
|
+
return hasBom ? Buffer.concat([Buffer.from([254, 255]), body]) : body;
|
|
17474
|
+
}
|
|
17475
|
+
return import_iconv_lite2.default.encode(content, encoding);
|
|
17476
|
+
}
|
|
17477
|
+
function decodeText(buffer, encoding) {
|
|
17478
|
+
if (encoding === "utf8") {
|
|
17479
|
+
return buffer.toString("utf8");
|
|
17480
|
+
}
|
|
17481
|
+
if (encoding === "utf8-bom") {
|
|
17482
|
+
return buffer.subarray(3).toString("utf8");
|
|
17483
|
+
}
|
|
17484
|
+
if (encoding === "utf16le") {
|
|
17485
|
+
const withoutBom = buffer.length >= 2 && buffer[0] === 255 && buffer[1] === 254 ? buffer.subarray(2) : buffer;
|
|
17486
|
+
return withoutBom.toString("utf16le");
|
|
17487
|
+
}
|
|
17488
|
+
if (encoding === "utf16be") {
|
|
17489
|
+
const withoutBom = buffer.length >= 2 && buffer[0] === 254 && buffer[1] === 255 ? buffer.subarray(2) : buffer;
|
|
17490
|
+
return decodeUtf16be(withoutBom);
|
|
17491
|
+
}
|
|
17492
|
+
return import_iconv_lite2.default.decode(buffer, encoding);
|
|
17493
|
+
}
|
|
17494
|
+
function detectBom(buffer) {
|
|
17495
|
+
if (buffer.length >= 3 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191) {
|
|
17496
|
+
return {
|
|
17497
|
+
requestedEncoding: "auto",
|
|
17498
|
+
detectedEncoding: "utf8-bom",
|
|
17499
|
+
confidence: "high",
|
|
17500
|
+
hasBom: true
|
|
17501
|
+
};
|
|
17502
|
+
}
|
|
17503
|
+
if (buffer.length >= 2 && buffer[0] === 255 && buffer[1] === 254) {
|
|
17504
|
+
return {
|
|
17505
|
+
requestedEncoding: "auto",
|
|
17506
|
+
detectedEncoding: "utf16le",
|
|
17507
|
+
confidence: "high",
|
|
17508
|
+
hasBom: true
|
|
17509
|
+
};
|
|
17510
|
+
}
|
|
17511
|
+
if (buffer.length >= 2 && buffer[0] === 254 && buffer[1] === 255) {
|
|
17512
|
+
return {
|
|
17513
|
+
requestedEncoding: "auto",
|
|
17514
|
+
detectedEncoding: "utf16be",
|
|
17515
|
+
confidence: "high",
|
|
17516
|
+
hasBom: true
|
|
17517
|
+
};
|
|
17518
|
+
}
|
|
17519
|
+
return null;
|
|
17520
|
+
}
|
|
17521
|
+
function isValidUtf8(buffer) {
|
|
17522
|
+
try {
|
|
17523
|
+
UTF8_DECODER.decode(buffer);
|
|
17524
|
+
return true;
|
|
17525
|
+
} catch {
|
|
17526
|
+
return false;
|
|
17527
|
+
}
|
|
17528
|
+
}
|
|
17529
|
+
function hasUtf16Pattern(buffer) {
|
|
17530
|
+
const sample = buffer.subarray(0, Math.min(buffer.length, 512));
|
|
17531
|
+
let evenZeros = 0;
|
|
17532
|
+
let oddZeros = 0;
|
|
17533
|
+
for (let index = 0; index < sample.length; index += 1) {
|
|
17534
|
+
if (sample[index] === 0) {
|
|
17535
|
+
if (index % 2 === 0) {
|
|
17536
|
+
evenZeros += 1;
|
|
17537
|
+
} else {
|
|
17538
|
+
oddZeros += 1;
|
|
17539
|
+
}
|
|
17540
|
+
}
|
|
17541
|
+
}
|
|
17542
|
+
return {
|
|
17543
|
+
utf16be: evenZeros >= 2 && evenZeros >= oddZeros * 2,
|
|
17544
|
+
utf16le: oddZeros >= 2 && oddZeros >= evenZeros * 2
|
|
17545
|
+
};
|
|
17546
|
+
}
|
|
17547
|
+
function looksLossyRoundTrip(buffer, encoding) {
|
|
17548
|
+
const decoded = decodeText(buffer, encoding);
|
|
17549
|
+
const reencoded = encodeText(decoded, encoding, false);
|
|
17550
|
+
return !buffer.equals(reencoded);
|
|
17551
|
+
}
|
|
17552
|
+
function assertLikelyTextBuffer(buffer) {
|
|
17553
|
+
if (!buffer.includes(0)) {
|
|
17554
|
+
return;
|
|
17555
|
+
}
|
|
17556
|
+
const bom = detectBom(buffer);
|
|
17557
|
+
if (bom?.detectedEncoding === "utf16le" || bom?.detectedEncoding === "utf16be") {
|
|
17558
|
+
return;
|
|
17559
|
+
}
|
|
17560
|
+
const utf16Pattern = hasUtf16Pattern(buffer);
|
|
17561
|
+
if (utf16Pattern.utf16be || utf16Pattern.utf16le) {
|
|
17562
|
+
return;
|
|
17563
|
+
}
|
|
17564
|
+
throw createTextError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309\u6587\u672C\u5904\u7406");
|
|
17565
|
+
}
|
|
17566
|
+
function getNearestContext2(content, oldString) {
|
|
17567
|
+
assertStringArgument2(content, "content");
|
|
17568
|
+
assertStringArgument2(oldString, "oldString");
|
|
17569
|
+
const lines = content.split(/\r?\n/);
|
|
17570
|
+
const oldLines = oldString.replace(/\r\n/g, "\n").split("\n").filter(Boolean);
|
|
17571
|
+
const firstToken = oldLines[0] || oldString.trim();
|
|
17572
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
17573
|
+
if (lines[index].includes(firstToken)) {
|
|
17574
|
+
return lines.slice(index, index + 4).join("\n");
|
|
17575
|
+
}
|
|
17576
|
+
}
|
|
17577
|
+
return lines.slice(0, 4).join("\n");
|
|
17578
|
+
}
|
|
17579
|
+
function buildNoMatchMessage2(content, oldString) {
|
|
17580
|
+
assertStringArgument2(content, "content");
|
|
17581
|
+
assertStringArgument2(oldString, "oldString");
|
|
17582
|
+
return [
|
|
17583
|
+
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
17584
|
+
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\u3002",
|
|
17585
|
+
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
17586
|
+
getNearestContext2(content, oldString)
|
|
17587
|
+
].join("\n");
|
|
17588
|
+
}
|
|
17589
|
+
function findOccurrenceIndex2(text, token, occurrence) {
|
|
17590
|
+
assertStringArgument2(text, "text");
|
|
17591
|
+
assertStringArgument2(token, "anchor");
|
|
17592
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
17593
|
+
if (token.length === 0) {
|
|
17594
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17595
|
+
}
|
|
17596
|
+
let count = 0;
|
|
17597
|
+
let searchFrom = 0;
|
|
17598
|
+
while (true) {
|
|
17599
|
+
const index = text.indexOf(token, searchFrom);
|
|
17600
|
+
if (index === -1) {
|
|
17601
|
+
break;
|
|
17602
|
+
}
|
|
17603
|
+
count += 1;
|
|
17604
|
+
if (count === occurrence) {
|
|
17605
|
+
return { index, total: countOccurrences(text, token) };
|
|
17606
|
+
}
|
|
17607
|
+
searchFrom = index + token.length;
|
|
17608
|
+
}
|
|
17609
|
+
if (count === 0) {
|
|
17610
|
+
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
17611
|
+
}
|
|
17612
|
+
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${count} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
17613
|
+
}
|
|
17614
|
+
function assertInsertArguments2(input) {
|
|
17615
|
+
assertStringArgument2(input.anchor, "anchor");
|
|
17616
|
+
assertStringArgument2(input.content, "content");
|
|
17617
|
+
if (input.content.length === 0) {
|
|
17618
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "content \u4E0D\u80FD\u4E3A\u7A7A");
|
|
17619
|
+
}
|
|
17620
|
+
if (input.replaceAll !== void 0) {
|
|
17621
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 replaceAll");
|
|
17622
|
+
}
|
|
17623
|
+
if (input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0) {
|
|
17624
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 startLine/endLine/startAnchor/endAnchor");
|
|
17625
|
+
}
|
|
17626
|
+
}
|
|
17627
|
+
function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
17628
|
+
const beforeLines = normalizeNewlines2(beforeText).split("\n");
|
|
17629
|
+
const afterLines = normalizeNewlines2(afterText).split("\n");
|
|
17630
|
+
const maxLines = Math.max(beforeLines.length, afterLines.length);
|
|
17631
|
+
const lines = [
|
|
17632
|
+
`--- ${path3.basename(filePath)} (${encoding})`,
|
|
17633
|
+
`+++ ${path3.basename(filePath)} (${encoding})`
|
|
17634
|
+
];
|
|
17635
|
+
for (let index = 0; index < maxLines; index += 1) {
|
|
17636
|
+
const before = beforeLines[index];
|
|
17637
|
+
const after = afterLines[index];
|
|
17638
|
+
if (before !== void 0 && after !== void 0 && before === after) {
|
|
17639
|
+
lines.push(` ${before}`);
|
|
17640
|
+
continue;
|
|
17641
|
+
}
|
|
17642
|
+
if (before !== void 0) {
|
|
17643
|
+
lines.push(`-${before}`);
|
|
17644
|
+
}
|
|
17645
|
+
if (after !== void 0) {
|
|
17646
|
+
lines.push(`+${after}`);
|
|
17647
|
+
}
|
|
17648
|
+
}
|
|
17649
|
+
return lines.join("\n");
|
|
17650
|
+
}
|
|
17651
|
+
function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
17652
|
+
const alignedContent = alignTextToNewlineStyle(content, newlineStyle);
|
|
17653
|
+
const located = findOccurrenceIndex2(text, anchor, occurrence);
|
|
17654
|
+
const insertionPoint = mode === "insertAfter" ? located.index + anchor.length : located.index;
|
|
17655
|
+
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
17656
|
+
if (alreadyExists) {
|
|
17657
|
+
if (ifExists === "error") {
|
|
17658
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17659
|
+
}
|
|
17660
|
+
if (ifExists === "skip") {
|
|
17661
|
+
return {
|
|
17662
|
+
outputText: text,
|
|
17663
|
+
inserted: false,
|
|
17664
|
+
skipped: true,
|
|
17665
|
+
anchorMatches: located.total,
|
|
17666
|
+
occurrence,
|
|
17667
|
+
anchor,
|
|
17668
|
+
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + anchor.length + 80)),
|
|
17669
|
+
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + anchor.length + 80))
|
|
17670
|
+
};
|
|
17671
|
+
}
|
|
17672
|
+
}
|
|
17673
|
+
const outputText = `${text.slice(0, insertionPoint)}${alignedContent}${text.slice(insertionPoint)}`;
|
|
17674
|
+
const previewStart = Math.max(0, insertionPoint - 80);
|
|
17675
|
+
const previewBeforeEnd = Math.min(text.length, insertionPoint + 80);
|
|
17676
|
+
const previewAfterEnd = Math.min(outputText.length, insertionPoint + alignedContent.length + 80);
|
|
17677
|
+
return {
|
|
17678
|
+
outputText,
|
|
17679
|
+
inserted: true,
|
|
17680
|
+
skipped: false,
|
|
17681
|
+
anchorMatches: located.total,
|
|
17682
|
+
occurrence,
|
|
17683
|
+
anchor,
|
|
17684
|
+
previewBefore: text.slice(previewStart, previewBeforeEnd),
|
|
17685
|
+
previewAfter: outputText.slice(previewStart, previewAfterEnd)
|
|
17686
|
+
};
|
|
17687
|
+
}
|
|
17688
|
+
function detectTextEncodingFromBuffer(buffer, requestedEncoding = "auto") {
|
|
17689
|
+
if (requestedEncoding !== "auto") {
|
|
17690
|
+
assertTextEncodingSupported(requestedEncoding);
|
|
17691
|
+
return {
|
|
17692
|
+
requestedEncoding,
|
|
17693
|
+
detectedEncoding: requestedEncoding,
|
|
17694
|
+
confidence: "high",
|
|
17695
|
+
hasBom: requestedEncoding === "utf8-bom" || requestedEncoding === "utf16le" || requestedEncoding === "utf16be"
|
|
17696
|
+
};
|
|
17697
|
+
}
|
|
17698
|
+
const bom = detectBom(buffer);
|
|
17699
|
+
if (bom) {
|
|
17700
|
+
return bom;
|
|
17701
|
+
}
|
|
17702
|
+
const utf16Pattern = hasUtf16Pattern(buffer);
|
|
17703
|
+
if (utf16Pattern.utf16be) {
|
|
17704
|
+
return {
|
|
17705
|
+
requestedEncoding,
|
|
17706
|
+
detectedEncoding: "utf16be",
|
|
17707
|
+
confidence: "medium",
|
|
17708
|
+
hasBom: false
|
|
17709
|
+
};
|
|
17710
|
+
}
|
|
17711
|
+
if (utf16Pattern.utf16le) {
|
|
17712
|
+
return {
|
|
17713
|
+
requestedEncoding,
|
|
17714
|
+
detectedEncoding: "utf16le",
|
|
17715
|
+
confidence: "medium",
|
|
17716
|
+
hasBom: false
|
|
17717
|
+
};
|
|
17718
|
+
}
|
|
17719
|
+
if (isValidUtf8(buffer)) {
|
|
17720
|
+
return {
|
|
17721
|
+
requestedEncoding,
|
|
17722
|
+
detectedEncoding: "utf8",
|
|
17723
|
+
confidence: "high",
|
|
17724
|
+
hasBom: false
|
|
17725
|
+
};
|
|
17726
|
+
}
|
|
17727
|
+
const gbkLossy = looksLossyRoundTrip(buffer, "gbk");
|
|
17728
|
+
const gb18030Lossy = looksLossyRoundTrip(buffer, "gb18030");
|
|
17729
|
+
if (gb18030Lossy && gbkLossy) {
|
|
17730
|
+
throw createTextError("TEXT_UNKNOWN_ENCODING", "\u65E0\u6CD5\u53EF\u9760\u8BC6\u522B\u6587\u4EF6\u7F16\u7801\uFF0C\u8BF7\u663E\u5F0F\u6307\u5B9A encoding");
|
|
17731
|
+
}
|
|
17732
|
+
return {
|
|
17733
|
+
requestedEncoding,
|
|
17734
|
+
detectedEncoding: gbkLossy ? "gb18030" : "gbk",
|
|
17735
|
+
confidence: gbkLossy || gb18030Lossy ? "medium" : "low",
|
|
17736
|
+
hasBom: false
|
|
17737
|
+
};
|
|
17738
|
+
}
|
|
17739
|
+
async function resolveReadableTextFile(input) {
|
|
17740
|
+
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
17741
|
+
let stat;
|
|
17742
|
+
try {
|
|
17743
|
+
stat = await fs3.stat(candidatePath);
|
|
17744
|
+
} catch (error45) {
|
|
17745
|
+
throw createTextError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
17746
|
+
}
|
|
17747
|
+
if (stat.isDirectory()) {
|
|
17748
|
+
throw createTextError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
17749
|
+
}
|
|
17750
|
+
return { filePath: candidatePath, stat };
|
|
17751
|
+
}
|
|
17752
|
+
async function readWholeTextFile(input) {
|
|
17753
|
+
const resolved = await resolveReadableTextFile(input);
|
|
17754
|
+
try {
|
|
17755
|
+
const buffer = await fs3.readFile(resolved.filePath);
|
|
17756
|
+
assertLikelyTextBuffer(buffer);
|
|
17757
|
+
const detected = detectTextEncodingFromBuffer(buffer, input.encoding ?? "auto");
|
|
17758
|
+
const content = decodeText(buffer, detected.detectedEncoding);
|
|
17759
|
+
return {
|
|
17760
|
+
filePath: resolved.filePath,
|
|
17761
|
+
bytesRead: buffer.byteLength,
|
|
17762
|
+
fileSize: buffer.byteLength,
|
|
17763
|
+
content,
|
|
17764
|
+
encoding: detected.detectedEncoding,
|
|
17765
|
+
requestedEncoding: detected.requestedEncoding,
|
|
17766
|
+
detectedEncoding: detected.detectedEncoding,
|
|
17767
|
+
confidence: detected.confidence,
|
|
17768
|
+
hasBom: detected.hasBom,
|
|
17769
|
+
newlineStyle: detectNewlineStyle(content)
|
|
17770
|
+
};
|
|
17771
|
+
} catch (error45) {
|
|
17772
|
+
if (error45 instanceof Error && "code" in error45) {
|
|
17773
|
+
throw error45;
|
|
17774
|
+
}
|
|
17775
|
+
throw createTextError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${resolved.filePath}`, error45);
|
|
17776
|
+
}
|
|
17777
|
+
}
|
|
17778
|
+
function collectTailLines2(text, limit) {
|
|
17779
|
+
assertPositiveInteger(limit, "limit");
|
|
17780
|
+
const lines = text.split(/\r?\n/);
|
|
17781
|
+
const totalLines = lines.length;
|
|
17782
|
+
const startLine = Math.max(1, totalLines - limit + 1);
|
|
17783
|
+
const selected = lines.slice(startLine - 1);
|
|
17784
|
+
return {
|
|
17785
|
+
startLine,
|
|
17786
|
+
endLine: totalLines,
|
|
17787
|
+
totalLines,
|
|
17788
|
+
content: selected.map((line, index) => `${startLine + index}: ${line}`).join("\n"),
|
|
17789
|
+
truncated: startLine > 1,
|
|
17790
|
+
tail: true
|
|
17791
|
+
};
|
|
17792
|
+
}
|
|
17793
|
+
function normalizeNewlines2(text) {
|
|
17794
|
+
return text.replace(/\r\n/g, "\n");
|
|
17795
|
+
}
|
|
17796
|
+
function applyLineRange2(text, startLine, endLine) {
|
|
17797
|
+
if (startLine === void 0 && endLine === void 0) {
|
|
17798
|
+
return {
|
|
17799
|
+
selectedText: text,
|
|
17800
|
+
rangeStart: 0,
|
|
17801
|
+
rangeEnd: text.length
|
|
17802
|
+
};
|
|
17803
|
+
}
|
|
17804
|
+
const lines = text.split(/\r?\n/);
|
|
17805
|
+
const actualStartLine = startLine ?? 1;
|
|
17806
|
+
const actualEndLine = endLine ?? lines.length;
|
|
17807
|
+
assertPositiveInteger(actualStartLine, "startLine");
|
|
17808
|
+
assertPositiveInteger(actualEndLine, "endLine");
|
|
17809
|
+
if (actualEndLine < actualStartLine) {
|
|
17810
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "endLine \u4E0D\u80FD\u5C0F\u4E8E startLine");
|
|
17811
|
+
}
|
|
17812
|
+
let cursor = 0;
|
|
17813
|
+
let rangeStart = 0;
|
|
17814
|
+
let rangeEnd = text.length;
|
|
17815
|
+
for (let lineNumber = 1; lineNumber <= lines.length; lineNumber += 1) {
|
|
17816
|
+
if (lineNumber === actualStartLine) {
|
|
17817
|
+
rangeStart = cursor;
|
|
17818
|
+
}
|
|
17819
|
+
cursor += lines[lineNumber - 1].length;
|
|
17820
|
+
if (lineNumber < lines.length) {
|
|
17821
|
+
const newlineLength = text.startsWith("\r\n", cursor) ? 2 : 1;
|
|
17822
|
+
cursor += newlineLength;
|
|
17823
|
+
}
|
|
17824
|
+
if (lineNumber === actualEndLine) {
|
|
17825
|
+
rangeEnd = cursor;
|
|
17826
|
+
break;
|
|
17827
|
+
}
|
|
17828
|
+
}
|
|
17829
|
+
return {
|
|
17830
|
+
selectedText: text.slice(rangeStart, rangeEnd),
|
|
17831
|
+
rangeStart,
|
|
17832
|
+
rangeEnd
|
|
17833
|
+
};
|
|
17834
|
+
}
|
|
17835
|
+
function applyAnchors2(text, startAnchor, endAnchor) {
|
|
17836
|
+
if (!startAnchor && !endAnchor) {
|
|
17837
|
+
return {
|
|
17838
|
+
selectedText: text,
|
|
17839
|
+
rangeStart: 0,
|
|
17840
|
+
rangeEnd: text.length
|
|
17841
|
+
};
|
|
17842
|
+
}
|
|
17843
|
+
let rangeStart = 0;
|
|
17844
|
+
let rangeEnd = text.length;
|
|
17845
|
+
if (startAnchor) {
|
|
17846
|
+
const found = text.indexOf(startAnchor);
|
|
17847
|
+
if (found === -1) {
|
|
17848
|
+
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\u70B9: ${startAnchor}`);
|
|
17849
|
+
}
|
|
17850
|
+
rangeStart = found + startAnchor.length;
|
|
17851
|
+
}
|
|
17852
|
+
if (endAnchor) {
|
|
17853
|
+
const found = text.indexOf(endAnchor, rangeStart);
|
|
17854
|
+
if (found === -1) {
|
|
17855
|
+
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\u70B9: ${endAnchor}`);
|
|
17856
|
+
}
|
|
17857
|
+
rangeEnd = found;
|
|
17858
|
+
}
|
|
17859
|
+
if (rangeEnd < rangeStart) {
|
|
17860
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
17861
|
+
}
|
|
17862
|
+
return {
|
|
17863
|
+
selectedText: text.slice(rangeStart, rangeEnd),
|
|
17864
|
+
rangeStart,
|
|
17865
|
+
rangeEnd
|
|
17866
|
+
};
|
|
17867
|
+
}
|
|
17868
|
+
function resolveEditScope2(text, input) {
|
|
17869
|
+
const anchored = applyAnchors2(text, input.startAnchor, input.endAnchor);
|
|
17870
|
+
const lineRanged = applyLineRange2(anchored.selectedText, input.startLine, input.endLine);
|
|
17871
|
+
return {
|
|
17872
|
+
selectedText: lineRanged.selectedText,
|
|
17873
|
+
rangeStart: anchored.rangeStart + lineRanged.rangeStart,
|
|
17874
|
+
rangeEnd: anchored.rangeStart + lineRanged.rangeEnd
|
|
17875
|
+
};
|
|
17876
|
+
}
|
|
17877
|
+
function alignTextToNewlineStyle(text, newlineStyle) {
|
|
17878
|
+
assertStringArgument2(text, "text");
|
|
17879
|
+
const normalized = text.replace(/\r\n/g, "\n");
|
|
17880
|
+
if (newlineStyle === "crlf") {
|
|
17881
|
+
return normalized.replace(/\n/g, "\r\n");
|
|
17882
|
+
}
|
|
17883
|
+
if (newlineStyle === "lf") {
|
|
17884
|
+
return normalized;
|
|
17885
|
+
}
|
|
17886
|
+
return text;
|
|
17887
|
+
}
|
|
17888
|
+
function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
|
|
17889
|
+
assertStringArgument2(input, "content");
|
|
17890
|
+
const buffer = encodeText(input, encoding, hasBom);
|
|
17891
|
+
const roundTrip = decodeText(buffer, encoding);
|
|
17892
|
+
if (roundTrip !== input) {
|
|
17893
|
+
throw createTextError("TEXT_ENCODING_LOSSY", `\u5185\u5BB9\u65E0\u6CD5\u65E0\u635F\u5199\u5165\u7F16\u7801 ${encoding}`);
|
|
17894
|
+
}
|
|
17895
|
+
}
|
|
17896
|
+
async function ensureParentDirectory(parent, createDirectories) {
|
|
17897
|
+
try {
|
|
17898
|
+
const parentStat = await fs3.stat(parent);
|
|
17899
|
+
if (!parentStat.isDirectory()) {
|
|
17900
|
+
throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17901
|
+
}
|
|
17902
|
+
} catch (error45) {
|
|
17903
|
+
if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
|
|
17904
|
+
if (!createDirectories) {
|
|
17905
|
+
throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17906
|
+
}
|
|
17907
|
+
await fs3.mkdir(parent, { recursive: true });
|
|
17908
|
+
return;
|
|
17909
|
+
}
|
|
17910
|
+
throw error45;
|
|
17911
|
+
}
|
|
17912
|
+
}
|
|
17913
|
+
async function readTextFile(input) {
|
|
17914
|
+
const offset = normalizeOptionalPositiveInteger(input.offset, "offset") ?? 1;
|
|
17915
|
+
const limit = normalizeOptionalPositiveInteger(input.limit, "limit") ?? 2e3;
|
|
17916
|
+
const tail = input.tail ?? false;
|
|
17917
|
+
const loaded = await readWholeTextFile(input);
|
|
17918
|
+
const lineWindow = tail ? collectTailLines2(loaded.content, limit) : {
|
|
17919
|
+
...splitLinesWithNumbers(loaded.content, offset, limit),
|
|
17920
|
+
tail: false,
|
|
17921
|
+
truncated: false
|
|
17922
|
+
};
|
|
17923
|
+
if (!tail) {
|
|
17924
|
+
lineWindow.truncated = lineWindow.endLine < lineWindow.totalLines;
|
|
17925
|
+
}
|
|
17926
|
+
return {
|
|
17927
|
+
filePath: loaded.filePath,
|
|
17928
|
+
encoding: loaded.encoding,
|
|
17929
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
17930
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
17931
|
+
confidence: loaded.confidence,
|
|
17932
|
+
hasBom: loaded.hasBom,
|
|
17933
|
+
bytesRead: loaded.bytesRead,
|
|
17934
|
+
fileSize: loaded.fileSize,
|
|
17935
|
+
streamed: false,
|
|
17936
|
+
newlineStyle: loaded.newlineStyle,
|
|
17937
|
+
...lineWindow
|
|
17938
|
+
};
|
|
17939
|
+
}
|
|
17940
|
+
async function writeTextFile(input) {
|
|
17941
|
+
assertStringArgument2(input.content, "content");
|
|
17942
|
+
const requestedEncoding = input.encoding ?? "auto";
|
|
17943
|
+
assertTextEncodingSupported(requestedEncoding);
|
|
17944
|
+
const preserveEncoding = input.preserveEncoding ?? true;
|
|
17945
|
+
const preserveNewlineStyle = input.preserveNewlineStyle ?? true;
|
|
17946
|
+
const createDirectories = input.createDirectories ?? true;
|
|
17947
|
+
const overwrite = input.overwrite ?? false;
|
|
17948
|
+
const append = input.append ?? false;
|
|
17949
|
+
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
17950
|
+
const parent = path3.dirname(candidatePath);
|
|
17951
|
+
await ensureParentDirectory(parent, createDirectories);
|
|
17952
|
+
let existing = null;
|
|
17953
|
+
try {
|
|
17954
|
+
existing = await readWholeTextFile({
|
|
17955
|
+
filePath: candidatePath,
|
|
17956
|
+
encoding: requestedEncoding,
|
|
17957
|
+
allowExternal: input.allowExternal,
|
|
17958
|
+
context: input.context
|
|
17959
|
+
});
|
|
17960
|
+
} catch (error45) {
|
|
17961
|
+
if (!(error45 instanceof Error && "code" in error45 && error45.code === "GBK_FILE_NOT_FOUND")) {
|
|
17962
|
+
throw error45;
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17965
|
+
if (existing && !append && !overwrite) {
|
|
17966
|
+
throw createTextError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728: ${candidatePath}`);
|
|
17967
|
+
}
|
|
17968
|
+
const targetEncoding = existing && preserveEncoding ? existing.encoding : requestedEncoding === "auto" ? "utf8" : requestedEncoding;
|
|
17969
|
+
const targetHasBom = targetEncoding === "utf8-bom" ? true : existing && preserveEncoding ? existing.hasBom : targetEncoding === "utf16le" || targetEncoding === "utf16be" ? true : false;
|
|
17970
|
+
const baseContent = append ? existing?.content ?? "" : "";
|
|
17971
|
+
const rawContent = `${baseContent}${input.content}`;
|
|
17972
|
+
const outputContent = existing && preserveNewlineStyle ? alignTextToNewlineStyle(rawContent, existing.newlineStyle) : rawContent;
|
|
17973
|
+
ensureLossless(outputContent, targetEncoding, targetHasBom);
|
|
17974
|
+
const buffer = encodeText(outputContent, targetEncoding, targetHasBom);
|
|
17975
|
+
await fs3.writeFile(candidatePath, buffer);
|
|
17976
|
+
return {
|
|
17977
|
+
filePath: candidatePath,
|
|
17978
|
+
encoding: targetEncoding,
|
|
17979
|
+
requestedEncoding,
|
|
17980
|
+
detectedEncoding: existing?.encoding ?? null,
|
|
17981
|
+
confidence: existing?.confidence ?? null,
|
|
17982
|
+
hasBom: targetHasBom,
|
|
17983
|
+
bytesWritten: buffer.byteLength,
|
|
17984
|
+
created: !existing,
|
|
17985
|
+
overwritten: Boolean(existing) && !append,
|
|
17986
|
+
appended: append,
|
|
17987
|
+
newlineStyle: detectNewlineStyle(outputContent)
|
|
17988
|
+
};
|
|
17989
|
+
}
|
|
17990
|
+
async function replaceTextFileText(input) {
|
|
17991
|
+
const mode = resolveEditMode(input.mode);
|
|
17992
|
+
const normalizedInput = {
|
|
17993
|
+
...input,
|
|
17994
|
+
startLine: normalizeOptionalPositiveInteger(input.startLine, "startLine"),
|
|
17995
|
+
endLine: normalizeOptionalPositiveInteger(input.endLine, "endLine")
|
|
17996
|
+
};
|
|
17997
|
+
const loaded = await readWholeTextFile({
|
|
17998
|
+
filePath: input.filePath,
|
|
17999
|
+
encoding: input.encoding ?? "auto",
|
|
18000
|
+
allowExternal: input.allowExternal,
|
|
18001
|
+
context: input.context
|
|
18002
|
+
});
|
|
18003
|
+
if (mode === "insertAfter" || mode === "insertBefore") {
|
|
18004
|
+
assertInsertArguments2(normalizedInput);
|
|
18005
|
+
const occurrence = normalizeOptionalPositiveInteger(normalizedInput.occurrence, "occurrence") ?? 1;
|
|
18006
|
+
const insertResult = buildInsertOutput(
|
|
18007
|
+
loaded.content,
|
|
18008
|
+
mode,
|
|
18009
|
+
normalizedInput.anchor,
|
|
18010
|
+
normalizedInput.content,
|
|
18011
|
+
occurrence,
|
|
18012
|
+
resolveInsertIfExists(normalizedInput.ifExists),
|
|
18013
|
+
loaded.newlineStyle
|
|
18014
|
+
);
|
|
18015
|
+
if (insertResult.skipped) {
|
|
18016
|
+
return {
|
|
18017
|
+
mode,
|
|
18018
|
+
filePath: loaded.filePath,
|
|
18019
|
+
encoding: loaded.encoding,
|
|
18020
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
18021
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
18022
|
+
confidence: loaded.confidence,
|
|
18023
|
+
hasBom: loaded.hasBom,
|
|
18024
|
+
anchor: insertResult.anchor,
|
|
18025
|
+
occurrence: insertResult.occurrence,
|
|
18026
|
+
anchorMatches: insertResult.anchorMatches,
|
|
18027
|
+
inserted: false,
|
|
18028
|
+
skipped: true,
|
|
18029
|
+
bytesRead: loaded.bytesRead,
|
|
18030
|
+
bytesWritten: 0,
|
|
18031
|
+
newlineStyle: loaded.newlineStyle
|
|
18032
|
+
};
|
|
18033
|
+
}
|
|
18034
|
+
const targetEncoding2 = (normalizedInput.preserveEncoding ?? true) || (normalizedInput.encoding ?? "auto") === "auto" ? loaded.encoding : resolveExplicitTextEncoding(normalizedInput.encoding, loaded.encoding);
|
|
18035
|
+
const targetHasBom2 = normalizedInput.preserveEncoding === false ? targetEncoding2 === "utf8-bom" || targetEncoding2 === "utf16le" || targetEncoding2 === "utf16be" : loaded.hasBom;
|
|
18036
|
+
ensureLossless(insertResult.outputText, targetEncoding2, targetHasBom2);
|
|
18037
|
+
const buffer2 = encodeText(insertResult.outputText, targetEncoding2, targetHasBom2);
|
|
18038
|
+
await fs3.writeFile(loaded.filePath, buffer2);
|
|
18039
|
+
return {
|
|
18040
|
+
mode,
|
|
18041
|
+
filePath: loaded.filePath,
|
|
18042
|
+
encoding: targetEncoding2,
|
|
18043
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
18044
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
18045
|
+
confidence: loaded.confidence,
|
|
18046
|
+
hasBom: targetHasBom2,
|
|
18047
|
+
anchor: insertResult.anchor,
|
|
18048
|
+
occurrence: insertResult.occurrence,
|
|
18049
|
+
anchorMatches: insertResult.anchorMatches,
|
|
18050
|
+
inserted: true,
|
|
18051
|
+
skipped: false,
|
|
18052
|
+
bytesRead: loaded.bytesRead,
|
|
18053
|
+
bytesWritten: buffer2.byteLength,
|
|
18054
|
+
newlineStyle: detectNewlineStyle(insertResult.outputText)
|
|
18055
|
+
};
|
|
18056
|
+
}
|
|
18057
|
+
assertReplaceArguments2(input);
|
|
18058
|
+
if (input.oldString.length === 0) {
|
|
18059
|
+
throw createTextError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18060
|
+
}
|
|
18061
|
+
const scope = resolveEditScope2(loaded.content, normalizedInput);
|
|
18062
|
+
const replaceAll = normalizedInput.replaceAll ?? false;
|
|
18063
|
+
const preserveEncoding = normalizedInput.preserveEncoding ?? true;
|
|
18064
|
+
const requestedEncoding = normalizedInput.encoding ?? "auto";
|
|
18065
|
+
const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
|
|
18066
|
+
if (replaceAll) {
|
|
18067
|
+
if (occurrencesBefore === 0) {
|
|
18068
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText, input.oldString));
|
|
18069
|
+
}
|
|
18070
|
+
} else if (occurrencesBefore === 0) {
|
|
18071
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText, input.oldString));
|
|
18072
|
+
} else if (occurrencesBefore > 1) {
|
|
18073
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
18074
|
+
}
|
|
18075
|
+
const alignedNewString = normalizedInput.preserveNewlineStyle === false ? input.newString : alignTextToNewlineStyle(input.newString, loaded.newlineStyle);
|
|
18076
|
+
const replaced = replaceAll ? scope.selectedText.split(input.oldString).join(alignedNewString) : scope.selectedText.replace(input.oldString, alignedNewString);
|
|
18077
|
+
const outputText = `${loaded.content.slice(0, scope.rangeStart)}${replaced}${loaded.content.slice(scope.rangeEnd)}`;
|
|
18078
|
+
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
18079
|
+
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
18080
|
+
ensureLossless(outputText, targetEncoding, targetHasBom);
|
|
18081
|
+
const buffer = encodeText(outputText, targetEncoding, targetHasBom);
|
|
18082
|
+
await fs3.writeFile(loaded.filePath, buffer);
|
|
18083
|
+
return {
|
|
18084
|
+
mode: "replace",
|
|
18085
|
+
filePath: loaded.filePath,
|
|
18086
|
+
encoding: targetEncoding,
|
|
18087
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
18088
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
18089
|
+
confidence: loaded.confidence,
|
|
18090
|
+
hasBom: targetHasBom,
|
|
18091
|
+
replacements: replaceAll ? occurrencesBefore : 1,
|
|
18092
|
+
occurrencesBefore,
|
|
18093
|
+
bytesRead: loaded.bytesRead,
|
|
18094
|
+
bytesWritten: buffer.byteLength,
|
|
18095
|
+
newlineStyle: detectNewlineStyle(outputText)
|
|
18096
|
+
};
|
|
18097
|
+
}
|
|
18098
|
+
async function createTextDiffPreview(input) {
|
|
18099
|
+
const mode = resolveEditMode(input.mode);
|
|
18100
|
+
const loaded = await readWholeTextFile({
|
|
18101
|
+
filePath: input.filePath,
|
|
18102
|
+
encoding: input.encoding ?? "auto",
|
|
18103
|
+
allowExternal: input.allowExternal,
|
|
18104
|
+
context: input.context
|
|
18105
|
+
});
|
|
18106
|
+
if (mode === "insertAfter" || mode === "insertBefore") {
|
|
18107
|
+
assertInsertArguments2(input);
|
|
18108
|
+
const occurrence = normalizeOptionalPositiveInteger(input.occurrence, "occurrence") ?? 1;
|
|
18109
|
+
const insertResult = buildInsertOutput(
|
|
18110
|
+
loaded.content,
|
|
18111
|
+
mode,
|
|
18112
|
+
input.anchor,
|
|
18113
|
+
input.content,
|
|
18114
|
+
occurrence,
|
|
18115
|
+
resolveInsertIfExists(input.ifExists),
|
|
18116
|
+
loaded.newlineStyle
|
|
18117
|
+
);
|
|
18118
|
+
return {
|
|
18119
|
+
filePath: loaded.filePath,
|
|
18120
|
+
encoding: loaded.encoding,
|
|
18121
|
+
preview: buildLineDiffPreview(loaded.filePath, loaded.encoding, insertResult.previewBefore, insertResult.previewAfter)
|
|
18122
|
+
};
|
|
18123
|
+
}
|
|
18124
|
+
assertReplaceArguments2(input);
|
|
18125
|
+
const scope = resolveEditScope2(loaded.content, input);
|
|
18126
|
+
const alignedNewString = alignTextToNewlineStyle(input.newString, loaded.newlineStyle);
|
|
18127
|
+
const replaced = input.replaceAll ?? false ? scope.selectedText.split(input.oldString).join(alignedNewString) : scope.selectedText.replace(input.oldString, alignedNewString);
|
|
18128
|
+
return {
|
|
18129
|
+
filePath: loaded.filePath,
|
|
18130
|
+
encoding: loaded.encoding,
|
|
18131
|
+
preview: buildLineDiffPreview(loaded.filePath, loaded.encoding, scope.selectedText, replaced)
|
|
18132
|
+
};
|
|
18133
|
+
}
|
|
18134
|
+
|
|
18135
|
+
// src/tools/text_edit.ts
|
|
18136
|
+
var text_edit_default = tool({
|
|
18137
|
+
description: `Edit text files with automatic encoding detection and preservation.
|
|
18138
|
+
|
|
18139
|
+
- Existing files keep their original encoding and BOM by default.
|
|
18140
|
+
- Existing newline style is preserved by default.
|
|
18141
|
+
- Replace mode uses oldString/newString.
|
|
18142
|
+
- Insert mode uses mode=insertAfter or insertBefore with anchor/content.`,
|
|
18143
|
+
args: {
|
|
18144
|
+
filePath: tool.schema.string().describe("Target file path"),
|
|
18145
|
+
mode: tool.schema.enum(["replace", "insertAfter", "insertBefore"]).optional().describe("Edit mode, default replace"),
|
|
18146
|
+
oldString: tool.schema.string().optional().describe("Exact text to replace when mode=replace"),
|
|
18147
|
+
newString: tool.schema.string().optional().describe("Replacement text when mode=replace"),
|
|
18148
|
+
anchor: tool.schema.string().optional().describe("Anchor text used by insertAfter/insertBefore"),
|
|
18149
|
+
content: tool.schema.string().optional().describe("Inserted content used by insertAfter/insertBefore"),
|
|
18150
|
+
occurrence: tool.schema.number().int().positive().optional().describe("1-based anchor occurrence for insert mode"),
|
|
18151
|
+
ifExists: tool.schema.enum(["allow", "skip", "error"]).optional().describe("What to do when inserted content already exists at target position"),
|
|
18152
|
+
replaceAll: tool.schema.boolean().optional().describe("Replace all occurrences"),
|
|
18153
|
+
startLine: tool.schema.number().int().positive().optional().describe("Start line for scoped edit"),
|
|
18154
|
+
endLine: tool.schema.number().int().positive().optional().describe("End line for scoped edit"),
|
|
18155
|
+
startAnchor: tool.schema.string().optional().describe("Start anchor for scoped edit"),
|
|
18156
|
+
endAnchor: tool.schema.string().optional().describe("End anchor for scoped edit"),
|
|
18157
|
+
encoding: tool.schema.enum(["auto", "utf8", "utf8-bom", "utf16le", "utf16be", "gbk", "gb18030"]).optional().describe("Encoding override, default auto"),
|
|
18158
|
+
preserveEncoding: tool.schema.boolean().optional().describe("Preserve detected file encoding"),
|
|
18159
|
+
preserveNewlineStyle: tool.schema.boolean().optional().describe("Preserve detected file newline style"),
|
|
18160
|
+
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
18161
|
+
},
|
|
18162
|
+
async execute(args, context) {
|
|
18163
|
+
const result = await replaceTextFileText({ ...args, context });
|
|
18164
|
+
const preview = await createTextDiffPreview({ ...args, context });
|
|
18165
|
+
const title = result.mode === "replace" ? `\u6587\u672C\u7F16\u8F91 ${result.encoding.toUpperCase()} x${result.replacements}` : result.inserted ? `\u6587\u672C\u63D2\u5165 ${result.encoding.toUpperCase()} #${result.occurrence}` : `\u6587\u672C\u8DF3\u8FC7 ${result.encoding.toUpperCase()} #${result.occurrence}`;
|
|
18166
|
+
context.metadata({
|
|
18167
|
+
title,
|
|
18168
|
+
metadata: {
|
|
18169
|
+
filePath: result.filePath,
|
|
18170
|
+
encoding: result.encoding,
|
|
18171
|
+
requestedEncoding: result.requestedEncoding,
|
|
18172
|
+
confidence: result.confidence,
|
|
18173
|
+
hasBom: result.hasBom,
|
|
18174
|
+
mode: result.mode,
|
|
18175
|
+
replacements: result.mode === "replace" ? result.replacements : void 0,
|
|
18176
|
+
occurrencesBefore: result.mode === "replace" ? result.occurrencesBefore : void 0,
|
|
18177
|
+
anchor: result.mode === "replace" ? void 0 : result.anchor,
|
|
18178
|
+
occurrence: result.mode === "replace" ? void 0 : result.occurrence,
|
|
18179
|
+
anchorMatches: result.mode === "replace" ? void 0 : result.anchorMatches,
|
|
18180
|
+
inserted: result.mode === "replace" ? void 0 : result.inserted,
|
|
18181
|
+
skipped: result.mode === "replace" ? void 0 : result.skipped,
|
|
18182
|
+
newlineStyle: result.newlineStyle,
|
|
18183
|
+
diffPreview: preview.preview
|
|
18184
|
+
}
|
|
18185
|
+
});
|
|
18186
|
+
return JSON.stringify(result, null, 2);
|
|
18187
|
+
}
|
|
18188
|
+
});
|
|
18189
|
+
|
|
18190
|
+
// src/tools/text_read.ts
|
|
18191
|
+
var text_read_default = tool({
|
|
18192
|
+
description: `Read text files with automatic encoding detection and preservation.
|
|
18193
|
+
|
|
18194
|
+
- Default encoding=auto detects the existing file encoding.
|
|
18195
|
+
- Supported encodings: utf8, utf8-bom, utf16le, utf16be, gbk, gb18030.
|
|
18196
|
+
- Returns line-numbered content and detected encoding metadata.`,
|
|
18197
|
+
args: {
|
|
18198
|
+
filePath: tool.schema.string().describe("Target file path"),
|
|
18199
|
+
offset: tool.schema.number().int().positive().optional().describe("1-based start line"),
|
|
18200
|
+
limit: tool.schema.number().int().positive().optional().describe("Maximum lines to return"),
|
|
18201
|
+
tail: tool.schema.boolean().optional().describe("Read from file end instead of start"),
|
|
18202
|
+
encoding: tool.schema.enum(["auto", "utf8", "utf8-bom", "utf16le", "utf16be", "gbk", "gb18030"]).optional().describe("Encoding override, default auto"),
|
|
18203
|
+
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
18204
|
+
},
|
|
18205
|
+
async execute(args, context) {
|
|
18206
|
+
const result = await readTextFile({ ...args, context });
|
|
18207
|
+
const lineRange = `${result.startLine}-${result.endLine}`;
|
|
18208
|
+
context.metadata({
|
|
18209
|
+
title: `\u6587\u672C\u8BFB\u53D6 ${result.encoding.toUpperCase()} ${lineRange}`,
|
|
18210
|
+
metadata: {
|
|
18211
|
+
filePath: result.filePath,
|
|
18212
|
+
encoding: result.encoding,
|
|
18213
|
+
requestedEncoding: result.requestedEncoding,
|
|
18214
|
+
confidence: result.confidence,
|
|
18215
|
+
hasBom: result.hasBom,
|
|
18216
|
+
lineRange,
|
|
18217
|
+
totalLines: result.totalLines,
|
|
18218
|
+
newlineStyle: result.newlineStyle,
|
|
18219
|
+
truncated: result.truncated
|
|
18220
|
+
}
|
|
18221
|
+
});
|
|
18222
|
+
return JSON.stringify(result, null, 2);
|
|
18223
|
+
}
|
|
18224
|
+
});
|
|
18225
|
+
|
|
18226
|
+
// src/tools/text_write.ts
|
|
18227
|
+
var text_write_default = tool({
|
|
18228
|
+
description: `Write text files while preserving detected encoding and newline style by default.
|
|
18229
|
+
|
|
18230
|
+
- Existing files keep their original encoding when encoding=auto.
|
|
18231
|
+
- New files default to utf8 unless encoding is specified.
|
|
18232
|
+
- Supports append and overwrite modes.`,
|
|
18233
|
+
args: {
|
|
18234
|
+
filePath: tool.schema.string().describe("Target file path"),
|
|
18235
|
+
content: tool.schema.string().describe("Text content to write or append"),
|
|
18236
|
+
encoding: tool.schema.enum(["auto", "utf8", "utf8-bom", "utf16le", "utf16be", "gbk", "gb18030"]).optional().describe("Encoding override, default auto"),
|
|
18237
|
+
createDirectories: tool.schema.boolean().optional().describe("Create parent directories"),
|
|
18238
|
+
overwrite: tool.schema.boolean().optional().describe("Overwrite existing file"),
|
|
18239
|
+
append: tool.schema.boolean().optional().describe("Append content to existing file"),
|
|
18240
|
+
preserveEncoding: tool.schema.boolean().optional().describe("Preserve detected file encoding when file exists"),
|
|
18241
|
+
preserveNewlineStyle: tool.schema.boolean().optional().describe("Preserve detected file newline style when file exists"),
|
|
18242
|
+
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
18243
|
+
},
|
|
18244
|
+
async execute(args, context) {
|
|
18245
|
+
const result = await writeTextFile({ ...args, context });
|
|
18246
|
+
const action = result.appended ? "\u8FFD\u52A0" : result.overwritten ? "\u8986\u76D6" : "\u5199\u5165";
|
|
18247
|
+
context.metadata({
|
|
18248
|
+
title: `\u6587\u672C${action} ${result.encoding.toUpperCase()}`,
|
|
18249
|
+
metadata: {
|
|
18250
|
+
filePath: result.filePath,
|
|
18251
|
+
encoding: result.encoding,
|
|
18252
|
+
requestedEncoding: result.requestedEncoding,
|
|
18253
|
+
detectedEncoding: result.detectedEncoding,
|
|
18254
|
+
confidence: result.confidence,
|
|
18255
|
+
hasBom: result.hasBom,
|
|
18256
|
+
created: result.created,
|
|
18257
|
+
overwritten: result.overwritten,
|
|
18258
|
+
appended: result.appended,
|
|
18259
|
+
newlineStyle: result.newlineStyle
|
|
18260
|
+
}
|
|
18261
|
+
});
|
|
17211
18262
|
return JSON.stringify(result, null, 2);
|
|
17212
18263
|
}
|
|
17213
18264
|
});
|
|
@@ -17221,7 +18272,13 @@ var pluginModule = {
|
|
|
17221
18272
|
gbk_read: gbk_read_default,
|
|
17222
18273
|
gbk_write: gbk_write_default,
|
|
17223
18274
|
gbk_edit: gbk_edit_default,
|
|
17224
|
-
gbk_search: gbk_search_default
|
|
18275
|
+
gbk_search: gbk_search_default,
|
|
18276
|
+
text_read: text_read_default,
|
|
18277
|
+
text_write: text_write_default,
|
|
18278
|
+
text_edit: text_edit_default
|
|
18279
|
+
},
|
|
18280
|
+
async "experimental.chat.system.transform"(_input, output) {
|
|
18281
|
+
appendTextToolSystemPrompt(output.system);
|
|
17225
18282
|
}
|
|
17226
18283
|
};
|
|
17227
18284
|
}
|