opencode-gbk-tools 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -7
- package/dist/agents/gbk-engine.md +3 -6
- package/dist/opencode-tools/gbk_edit.js +156 -75
- package/dist/opencode-tools/gbk_read.js +3 -3
- package/dist/opencode-tools/gbk_search.js +3 -3
- package/dist/opencode-tools/gbk_write.js +2 -2
- package/dist/opencode-tools/text_edit.js +378 -38
- package/dist/plugin/index.js +512 -120
- package/dist/plugins/opencode-gbk-tools.js +512 -120
- package/dist/release-manifest.json +3 -3
- package/package.json +1 -1
|
@@ -16585,45 +16585,103 @@ function assertInsertArguments(input) {
|
|
|
16585
16585
|
throw createGbkError("GBK_INVALID_ARGUMENT", "content \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16586
16586
|
}
|
|
16587
16587
|
if (input.replaceAll !== void 0) {
|
|
16588
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\
|
|
16588
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 replaceAll");
|
|
16589
16589
|
}
|
|
16590
16590
|
if (input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0) {
|
|
16591
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\
|
|
16591
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 startLine/endLine/startAnchor/endAnchor");
|
|
16592
16592
|
}
|
|
16593
16593
|
}
|
|
16594
|
-
function
|
|
16595
|
-
assertStringArgument(text, "text");
|
|
16596
|
-
assertStringArgument(token, "anchor");
|
|
16597
|
-
assertPositiveInteger(occurrence, "occurrence");
|
|
16594
|
+
function collectOccurrencePositions(text, token) {
|
|
16598
16595
|
if (token.length === 0) {
|
|
16599
|
-
|
|
16596
|
+
return [];
|
|
16600
16597
|
}
|
|
16601
|
-
|
|
16598
|
+
const positions = [];
|
|
16602
16599
|
let searchFrom = 0;
|
|
16603
16600
|
while (true) {
|
|
16604
16601
|
const index = text.indexOf(token, searchFrom);
|
|
16605
16602
|
if (index === -1) {
|
|
16606
|
-
|
|
16607
|
-
}
|
|
16608
|
-
count += 1;
|
|
16609
|
-
if (count === occurrence) {
|
|
16610
|
-
return { index, total: countOccurrences(text, token) };
|
|
16603
|
+
return positions;
|
|
16611
16604
|
}
|
|
16605
|
+
positions.push(index);
|
|
16612
16606
|
searchFrom = index + token.length;
|
|
16613
16607
|
}
|
|
16614
|
-
|
|
16615
|
-
|
|
16608
|
+
}
|
|
16609
|
+
function buildFlexibleSearchVariants(token, newlineStyle) {
|
|
16610
|
+
const variants = /* @__PURE__ */ new Set();
|
|
16611
|
+
const pushVariant = (value) => {
|
|
16612
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16613
|
+
return;
|
|
16614
|
+
}
|
|
16615
|
+
variants.add(value);
|
|
16616
|
+
};
|
|
16617
|
+
pushVariant(token);
|
|
16618
|
+
pushVariant(alignTextToNewlineStyle(token, newlineStyle));
|
|
16619
|
+
const prefixedBlock = parseLineNumberPrefixedBlock(token);
|
|
16620
|
+
if (prefixedBlock) {
|
|
16621
|
+
pushVariant(prefixedBlock.strippedText);
|
|
16622
|
+
pushVariant(alignTextToNewlineStyle(prefixedBlock.strippedText, newlineStyle));
|
|
16623
|
+
}
|
|
16624
|
+
const trimmedLines = trimTrailingEmptyLines(splitNormalizedLines(token));
|
|
16625
|
+
if (trimmedLines.length > 0) {
|
|
16626
|
+
const trimmedToken = trimmedLines.join("\n");
|
|
16627
|
+
pushVariant(trimmedToken);
|
|
16628
|
+
pushVariant(alignTextToNewlineStyle(trimmedToken, newlineStyle));
|
|
16629
|
+
const trimmedPrefixedBlock = parseLineNumberPrefixedBlock(trimmedToken);
|
|
16630
|
+
if (trimmedPrefixedBlock) {
|
|
16631
|
+
pushVariant(trimmedPrefixedBlock.strippedText);
|
|
16632
|
+
pushVariant(alignTextToNewlineStyle(trimmedPrefixedBlock.strippedText, newlineStyle));
|
|
16633
|
+
}
|
|
16634
|
+
}
|
|
16635
|
+
return [...variants];
|
|
16636
|
+
}
|
|
16637
|
+
function findNextFlexibleMatch(text, token, searchFrom, newlineStyle) {
|
|
16638
|
+
let bestMatch = null;
|
|
16639
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16640
|
+
const index = text.indexOf(candidate, searchFrom);
|
|
16641
|
+
if (index === -1) {
|
|
16642
|
+
continue;
|
|
16643
|
+
}
|
|
16644
|
+
if (bestMatch === null || index < bestMatch.index || index === bestMatch.index && candidate.length > bestMatch.matchedToken.length) {
|
|
16645
|
+
bestMatch = { index, matchedToken: candidate };
|
|
16646
|
+
}
|
|
16647
|
+
}
|
|
16648
|
+
return bestMatch;
|
|
16649
|
+
}
|
|
16650
|
+
function findFlexibleOccurrenceIndex(text, token, occurrence, newlineStyle) {
|
|
16651
|
+
assertStringArgument(text, "text");
|
|
16652
|
+
assertStringArgument(token, "anchor");
|
|
16653
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
16654
|
+
if (token.length === 0) {
|
|
16655
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16656
|
+
}
|
|
16657
|
+
let maxMatches = 0;
|
|
16658
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16659
|
+
const positions = collectOccurrencePositions(text, candidate);
|
|
16660
|
+
if (positions.length === 0) {
|
|
16661
|
+
continue;
|
|
16662
|
+
}
|
|
16663
|
+
maxMatches = Math.max(maxMatches, positions.length);
|
|
16664
|
+
if (positions.length >= occurrence) {
|
|
16665
|
+
return {
|
|
16666
|
+
index: positions[occurrence - 1],
|
|
16667
|
+
total: positions.length,
|
|
16668
|
+
matchedToken: candidate
|
|
16669
|
+
};
|
|
16670
|
+
}
|
|
16671
|
+
}
|
|
16672
|
+
if (maxMatches === 0) {
|
|
16673
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
16616
16674
|
}
|
|
16617
|
-
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\
|
|
16675
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${maxMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
16618
16676
|
}
|
|
16619
16677
|
function insertByAnchor(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
16620
16678
|
const alignedContent = newlineStyle === "crlf" ? content.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : newlineStyle === "lf" ? content.replace(/\r\n/g, "\n") : content;
|
|
16621
|
-
const located =
|
|
16622
|
-
const insertionPoint = mode === "insertAfter" ? located.index +
|
|
16679
|
+
const located = findFlexibleOccurrenceIndex(text, anchor, occurrence, newlineStyle);
|
|
16680
|
+
const insertionPoint = mode === "insertAfter" ? located.index + located.matchedToken.length : located.index;
|
|
16623
16681
|
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
16624
16682
|
if (alreadyExists) {
|
|
16625
16683
|
if (ifExists === "error") {
|
|
16626
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
16684
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
16627
16685
|
}
|
|
16628
16686
|
if (ifExists === "skip") {
|
|
16629
16687
|
return {
|
|
@@ -16664,7 +16722,7 @@ function normalizeOptionalPositiveInteger(value, name) {
|
|
|
16664
16722
|
}
|
|
16665
16723
|
function assertNotBinary(buffer) {
|
|
16666
16724
|
if (buffer.includes(0)) {
|
|
16667
|
-
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\
|
|
16725
|
+
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
|
|
16668
16726
|
}
|
|
16669
16727
|
}
|
|
16670
16728
|
function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
|
|
@@ -16791,19 +16849,15 @@ function applyAnchors(text, startAnchor, endAnchor) {
|
|
|
16791
16849
|
}
|
|
16792
16850
|
let rangeStart = 0;
|
|
16793
16851
|
let rangeEnd = text.length;
|
|
16852
|
+
const newlineStyle = detectNewlineStyle(text);
|
|
16794
16853
|
if (startAnchor) {
|
|
16795
|
-
const found = text
|
|
16796
|
-
|
|
16797
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\uFFFD?: ${startAnchor}`);
|
|
16798
|
-
}
|
|
16799
|
-
rangeStart = found + startAnchor.length;
|
|
16854
|
+
const found = findFlexibleOccurrenceIndex(text, startAnchor, 1, newlineStyle);
|
|
16855
|
+
rangeStart = found.index + found.matchedToken.length;
|
|
16800
16856
|
}
|
|
16801
16857
|
if (endAnchor) {
|
|
16802
|
-
const
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
}
|
|
16806
|
-
rangeEnd = found;
|
|
16858
|
+
const searchText = text.slice(rangeStart);
|
|
16859
|
+
const found = findFlexibleOccurrenceIndex(searchText, endAnchor, 1, newlineStyle);
|
|
16860
|
+
rangeEnd = rangeStart + found.index;
|
|
16807
16861
|
}
|
|
16808
16862
|
if (rangeEnd < rangeStart) {
|
|
16809
16863
|
throw createGbkError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
@@ -16854,9 +16908,9 @@ function getNearestContext(content, oldString) {
|
|
|
16854
16908
|
}
|
|
16855
16909
|
function buildNoMatchMessage(content, oldString) {
|
|
16856
16910
|
return [
|
|
16857
|
-
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\
|
|
16858
|
-
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\
|
|
16859
|
-
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\
|
|
16911
|
+
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
16912
|
+
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\u884C/\u5C3E\u968F\u7A7A\u884C\u4E0A\u5B58\u5728\u8F7B\u5FAE\u5DEE\u5F02\u3002",
|
|
16913
|
+
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
16860
16914
|
getNearestContext(content, oldString)
|
|
16861
16915
|
].join("\n");
|
|
16862
16916
|
}
|
|
@@ -16868,8 +16922,10 @@ function stripLineNumberPrefixes(lines) {
|
|
|
16868
16922
|
return lines.map((l) => l.replace(/^\d+: /, ""));
|
|
16869
16923
|
}
|
|
16870
16924
|
function parseLineNumberPrefixedBlock(text) {
|
|
16871
|
-
const
|
|
16872
|
-
|
|
16925
|
+
const normalizedText = normalizeNewlines(text);
|
|
16926
|
+
const hasTrailingNewline = normalizedText.endsWith("\n");
|
|
16927
|
+
const lines = trimTrailingEmptyLines(splitNormalizedLines(text));
|
|
16928
|
+
if (lines.length === 0 || !hasLineNumberPrefixes(lines)) {
|
|
16873
16929
|
return null;
|
|
16874
16930
|
}
|
|
16875
16931
|
const lineNumbers = [];
|
|
@@ -16884,7 +16940,7 @@ function parseLineNumberPrefixedBlock(text) {
|
|
|
16884
16940
|
}
|
|
16885
16941
|
return {
|
|
16886
16942
|
lineNumbers,
|
|
16887
|
-
strippedText: strippedLines.join("\n")
|
|
16943
|
+
strippedText: `${strippedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`,
|
|
16888
16944
|
isContiguous: lineNumbers.every((lineNumber, index) => index === 0 || lineNumber === lineNumbers[index - 1] + 1),
|
|
16889
16945
|
startLine: lineNumbers[0],
|
|
16890
16946
|
endLine: lineNumbers[lineNumbers.length - 1]
|
|
@@ -16979,10 +17035,10 @@ async function resolveReadableGbkFile(input) {
|
|
|
16979
17035
|
try {
|
|
16980
17036
|
stat = await fs2.stat(candidatePath);
|
|
16981
17037
|
} catch (error45) {
|
|
16982
|
-
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\
|
|
17038
|
+
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
16983
17039
|
}
|
|
16984
17040
|
if (stat.isDirectory()) {
|
|
16985
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
17041
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16986
17042
|
}
|
|
16987
17043
|
return {
|
|
16988
17044
|
filePath: candidatePath,
|
|
@@ -17187,7 +17243,7 @@ function replaceScopedTextContent(scopeText, oldString, newString, replaceAll, n
|
|
|
17187
17243
|
} else if (occurrencesBefore === 0) {
|
|
17188
17244
|
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scopeText, oldString));
|
|
17189
17245
|
} else if (occurrencesBefore > 1) {
|
|
17190
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17246
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${oldString}`);
|
|
17191
17247
|
}
|
|
17192
17248
|
const alignedNewString = alignTextToNewlineStyle(newString, newlineStyle);
|
|
17193
17249
|
return {
|
|
@@ -17245,14 +17301,17 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17245
17301
|
}
|
|
17246
17302
|
}
|
|
17247
17303
|
async function replaceLargeGbkFileByAnchor(input) {
|
|
17304
|
+
const lineIndex = await getGbkLineIndex(input);
|
|
17305
|
+
const newlineStyle = lineIndex.newlineStyle;
|
|
17248
17306
|
const tempPath = path2.join(
|
|
17249
17307
|
path2.dirname(input.filePath),
|
|
17250
17308
|
`${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17251
17309
|
);
|
|
17252
17310
|
const handle = await fs2.open(tempPath, "w");
|
|
17253
|
-
const alignedContent = input.content
|
|
17254
|
-
const
|
|
17255
|
-
const
|
|
17311
|
+
const alignedContent = alignTextToNewlineStyle(input.content, newlineStyle);
|
|
17312
|
+
const anchorVariants = buildFlexibleSearchVariants(input.anchor, newlineStyle);
|
|
17313
|
+
const maxAnchorLength = anchorVariants.reduce((maxLength, candidate) => Math.max(maxLength, candidate.length), input.anchor.length);
|
|
17314
|
+
const carryLength = Math.max(maxAnchorLength + alignedContent.length, 1);
|
|
17256
17315
|
let decoded = "";
|
|
17257
17316
|
let scanFrom = 0;
|
|
17258
17317
|
let totalMatches = 0;
|
|
@@ -17276,23 +17335,23 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17276
17335
|
await visitDecodedTextChunks(input, async (text) => {
|
|
17277
17336
|
decoded += text;
|
|
17278
17337
|
while (!inserted) {
|
|
17279
|
-
const
|
|
17280
|
-
if (
|
|
17338
|
+
const located = findNextFlexibleMatch(decoded, input.anchor, scanFrom, newlineStyle);
|
|
17339
|
+
if (located === null) {
|
|
17281
17340
|
break;
|
|
17282
17341
|
}
|
|
17283
17342
|
totalMatches += 1;
|
|
17284
|
-
const afterAnchor =
|
|
17343
|
+
const afterAnchor = located.index + located.matchedToken.length;
|
|
17285
17344
|
if (totalMatches !== input.occurrence) {
|
|
17286
17345
|
scanFrom = afterAnchor;
|
|
17287
17346
|
continue;
|
|
17288
17347
|
}
|
|
17289
|
-
const before = decoded.slice(0,
|
|
17348
|
+
const before = decoded.slice(0, located.index);
|
|
17290
17349
|
const after = decoded.slice(afterAnchor);
|
|
17291
|
-
const anchorAndAfter = decoded.slice(
|
|
17350
|
+
const anchorAndAfter = decoded.slice(located.index);
|
|
17292
17351
|
const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
|
|
17293
17352
|
if (alreadyExists) {
|
|
17294
17353
|
if (input.ifExists === "error") {
|
|
17295
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
17354
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17296
17355
|
}
|
|
17297
17356
|
if (input.ifExists === "skip") {
|
|
17298
17357
|
skipped = true;
|
|
@@ -17304,7 +17363,7 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17304
17363
|
}
|
|
17305
17364
|
if (input.mode === "insertAfter") {
|
|
17306
17365
|
bytesWritten += await writeEncodedText(handle, input.encoding, before);
|
|
17307
|
-
bytesWritten += await writeEncodedText(handle, input.encoding,
|
|
17366
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, located.matchedToken);
|
|
17308
17367
|
bytesWritten += await writeEncodedText(handle, input.encoding, alignedContent);
|
|
17309
17368
|
bytesWritten += await writeEncodedText(handle, input.encoding, after);
|
|
17310
17369
|
} else {
|
|
@@ -17323,23 +17382,23 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17323
17382
|
});
|
|
17324
17383
|
if (!inserted) {
|
|
17325
17384
|
while (true) {
|
|
17326
|
-
const
|
|
17327
|
-
if (
|
|
17385
|
+
const located = findNextFlexibleMatch(decoded, input.anchor, scanFrom, newlineStyle);
|
|
17386
|
+
if (located === null) {
|
|
17328
17387
|
break;
|
|
17329
17388
|
}
|
|
17330
17389
|
totalMatches += 1;
|
|
17331
|
-
const afterAnchor =
|
|
17390
|
+
const afterAnchor = located.index + located.matchedToken.length;
|
|
17332
17391
|
if (totalMatches !== input.occurrence) {
|
|
17333
17392
|
scanFrom = afterAnchor;
|
|
17334
17393
|
continue;
|
|
17335
17394
|
}
|
|
17336
|
-
const before = decoded.slice(0,
|
|
17395
|
+
const before = decoded.slice(0, located.index);
|
|
17337
17396
|
const after = decoded.slice(afterAnchor);
|
|
17338
|
-
const anchorAndAfter = decoded.slice(
|
|
17397
|
+
const anchorAndAfter = decoded.slice(located.index);
|
|
17339
17398
|
const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
|
|
17340
17399
|
if (alreadyExists) {
|
|
17341
17400
|
if (input.ifExists === "error") {
|
|
17342
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
17401
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17343
17402
|
}
|
|
17344
17403
|
if (input.ifExists === "skip") {
|
|
17345
17404
|
skipped = true;
|
|
@@ -17347,16 +17406,16 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17347
17406
|
break;
|
|
17348
17407
|
}
|
|
17349
17408
|
}
|
|
17350
|
-
decoded = input.mode === "insertAfter" ? `${before}${
|
|
17409
|
+
decoded = input.mode === "insertAfter" ? `${before}${located.matchedToken}${alignedContent}${after}` : `${before}${alignedContent}${anchorAndAfter}`;
|
|
17351
17410
|
inserted = true;
|
|
17352
17411
|
break;
|
|
17353
17412
|
}
|
|
17354
17413
|
}
|
|
17355
17414
|
if (!inserted && totalMatches === 0) {
|
|
17356
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\
|
|
17415
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${input.anchor}`);
|
|
17357
17416
|
}
|
|
17358
17417
|
if (!inserted && totalMatches > 0) {
|
|
17359
|
-
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\
|
|
17418
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\u5230 ${totalMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${input.occurrence} \u5904`);
|
|
17360
17419
|
}
|
|
17361
17420
|
await finalizeInserted();
|
|
17362
17421
|
await handle.close();
|
|
@@ -17383,35 +17442,57 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17383
17442
|
}
|
|
17384
17443
|
}
|
|
17385
17444
|
async function replaceLargeGbkFileText(input) {
|
|
17445
|
+
const lineIndex = await getGbkLineIndex(input);
|
|
17446
|
+
const newlineStyle = lineIndex.newlineStyle;
|
|
17386
17447
|
const tempPath = path2.join(
|
|
17387
17448
|
path2.dirname(input.filePath),
|
|
17388
17449
|
`${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17389
17450
|
);
|
|
17390
17451
|
const handle = await fs2.open(tempPath, "w");
|
|
17391
17452
|
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
17453
|
+
const alignedNewString = alignTextToNewlineStyle(input.newString, newlineStyle);
|
|
17392
17454
|
let carry = "";
|
|
17393
17455
|
let occurrencesBefore = 0;
|
|
17394
17456
|
let bytesWritten = 0;
|
|
17457
|
+
let replacedFirstMatch = false;
|
|
17395
17458
|
const flushText = async (text, flush = false) => {
|
|
17396
17459
|
const combined = carry + text;
|
|
17397
|
-
|
|
17398
|
-
const processable = combined.slice(0, splitAt);
|
|
17399
|
-
carry = combined.slice(splitAt);
|
|
17400
|
-
if (processable.length === 0) {
|
|
17460
|
+
if (combined.length === 0) {
|
|
17401
17461
|
return;
|
|
17402
17462
|
}
|
|
17403
|
-
const
|
|
17404
|
-
const
|
|
17405
|
-
|
|
17406
|
-
let
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17463
|
+
const safeEnd = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
17464
|
+
const outputParts = [];
|
|
17465
|
+
let cursor = 0;
|
|
17466
|
+
let flushUpto = safeEnd;
|
|
17467
|
+
while (true) {
|
|
17468
|
+
const index = combined.indexOf(input.oldString, cursor);
|
|
17469
|
+
if (index === -1) {
|
|
17470
|
+
break;
|
|
17471
|
+
}
|
|
17472
|
+
const matchEnd = index + input.oldString.length;
|
|
17473
|
+
if (!flush && matchEnd > safeEnd) {
|
|
17474
|
+
flushUpto = index;
|
|
17475
|
+
break;
|
|
17410
17476
|
}
|
|
17411
|
-
|
|
17412
|
-
|
|
17477
|
+
occurrencesBefore += 1;
|
|
17478
|
+
outputParts.push(combined.slice(cursor, index));
|
|
17479
|
+
if (input.replaceAll) {
|
|
17480
|
+
outputParts.push(alignedNewString);
|
|
17481
|
+
} else if (!replacedFirstMatch) {
|
|
17482
|
+
outputParts.push(alignedNewString);
|
|
17483
|
+
replacedFirstMatch = true;
|
|
17484
|
+
} else {
|
|
17485
|
+
outputParts.push(input.oldString);
|
|
17486
|
+
}
|
|
17487
|
+
cursor = matchEnd;
|
|
17488
|
+
}
|
|
17489
|
+
outputParts.push(combined.slice(cursor, flushUpto));
|
|
17490
|
+
carry = combined.slice(flushUpto);
|
|
17491
|
+
const processable = outputParts.join("");
|
|
17492
|
+
if (processable.length === 0) {
|
|
17493
|
+
return;
|
|
17413
17494
|
}
|
|
17414
|
-
bytesWritten += await writeEncodedText(handle, input.encoding,
|
|
17495
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, processable);
|
|
17415
17496
|
};
|
|
17416
17497
|
try {
|
|
17417
17498
|
await visitDecodedTextChunks(input, async (text) => {
|
|
@@ -17419,10 +17500,10 @@ async function replaceLargeGbkFileText(input) {
|
|
|
17419
17500
|
});
|
|
17420
17501
|
await flushText("", true);
|
|
17421
17502
|
if (occurrencesBefore === 0) {
|
|
17422
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\
|
|
17503
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
17423
17504
|
}
|
|
17424
17505
|
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
17425
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17506
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
17426
17507
|
}
|
|
17427
17508
|
await handle.close();
|
|
17428
17509
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
@@ -17648,7 +17729,7 @@ async function replaceGbkFileText(input) {
|
|
|
17648
17729
|
} else if (occurrencesBefore === 0) {
|
|
17649
17730
|
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, effectiveOldString));
|
|
17650
17731
|
} else if (occurrencesBefore > 1) {
|
|
17651
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17732
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${effectiveOldString}`);
|
|
17652
17733
|
}
|
|
17653
17734
|
const fileNewlineStyle = detectNewlineStyle(current.content);
|
|
17654
17735
|
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;
|
|
@@ -17781,10 +17862,10 @@ async function writeGbkFile(input) {
|
|
|
17781
17862
|
try {
|
|
17782
17863
|
const stat = await fs2.stat(candidatePath);
|
|
17783
17864
|
if (stat.isDirectory()) {
|
|
17784
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
17865
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
17785
17866
|
}
|
|
17786
17867
|
if (!overwrite) {
|
|
17787
|
-
throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\
|
|
17868
|
+
throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728: ${candidatePath}`);
|
|
17788
17869
|
}
|
|
17789
17870
|
} catch (error45) {
|
|
17790
17871
|
if (error45 instanceof Error && "code" in error45) {
|
|
@@ -18034,19 +18115,26 @@ var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
|
|
|
18034
18115
|
var TEXT_TOOL_SYSTEM_PROMPT = [
|
|
18035
18116
|
TEXT_TOOL_SYSTEM_MARKER,
|
|
18036
18117
|
"\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
|
|
18037
|
-
"- \u5904\u7406\u6587\u672C\u6587\u4EF6\u65F6\uFF0C\u4F18\u5148\u4F7F\u7528
|
|
18038
|
-
"-
|
|
18118
|
+
"- \u5904\u7406\u6587\u672C\u6587\u4EF6\u65F6\uFF0C\u9ED8\u8BA4\u4F18\u5148\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\u3002",
|
|
18119
|
+
"- gbk_* \u9002\u5408\u5F53\u524D\u4ED3\u5E93\u7684\u81EA\u52A8\u7F16\u7801\u6587\u672C\u4E0E GBK/GB18030 \u6587\u4EF6\uFF1B\u5373\u4F7F\u4E0D\u662F\u660E\u786E\u7684 GBK \u6587\u4EF6\uFF0C\u4E5F\u4F18\u5148\u8D70\u8FD9\u7EC4\u5DE5\u5177\u3002",
|
|
18039
18120
|
"- \u65B0\u5EFA .txt \u6587\u4EF6\u5728 encoding=auto \u4E0B\u9ED8\u8BA4\u4F7F\u7528 GBK\uFF1B\u5176\u4ED6\u65B0\u6587\u4EF6\u8BF7\u663E\u5F0F\u6307\u5B9A encoding\u3002",
|
|
18040
18121
|
"- \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",
|
|
18041
18122
|
"- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
|
|
18042
|
-
"- \
|
|
18043
|
-
|
|
18123
|
+
"- anchor\u3001startAnchor\u3001endAnchor\u3001oldString \u82E5\u76F4\u63A5\u590D\u5236\u81EA\u8BFB\u53D6\u7ED3\u679C\uFF0C\u53EF\u4FDD\u7559 LF \u6362\u884C\uFF1B\u5DE5\u5177\u4F1A\u5C3D\u91CF\u6309\u6587\u4EF6\u6362\u884C\u98CE\u683C\u81EA\u52A8\u5BF9\u9F50\u3002",
|
|
18124
|
+
'- \u82E5\u8BFB\u53D6\u7ED3\u679C\u5E26\u6709 "N: " \u884C\u53F7\u524D\u7F00\uFF0Cgbk_edit \u4F1A\u5C3D\u91CF\u81EA\u52A8\u5265\u79BB\u8FD9\u4E9B\u524D\u7F00\u540E\u518D\u5339\u914D\u3002',
|
|
18125
|
+
"- text_read\u3001text_write\u3001text_edit \u4EC5\u4F5C\u4E3A\u517C\u5BB9\u5DE5\u5177\uFF0C\u4E0D\u518D\u4F5C\u4E3A\u9ED8\u8BA4\u63A8\u8350\u8DEF\u5F84\u3002"
|
|
18044
18126
|
].join("\n");
|
|
18045
18127
|
function appendTextToolSystemPrompt(system) {
|
|
18046
18128
|
if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
|
|
18047
18129
|
return;
|
|
18048
18130
|
}
|
|
18049
|
-
system.
|
|
18131
|
+
if (system.length === 0) {
|
|
18132
|
+
system.push(TEXT_TOOL_SYSTEM_PROMPT);
|
|
18133
|
+
return;
|
|
18134
|
+
}
|
|
18135
|
+
system[0] = `${system[0]}
|
|
18136
|
+
|
|
18137
|
+
${TEXT_TOOL_SYSTEM_PROMPT}`;
|
|
18050
18138
|
}
|
|
18051
18139
|
|
|
18052
18140
|
// src/lib/text-file.ts
|
|
@@ -18242,35 +18330,126 @@ function buildNoMatchMessage2(content, oldString) {
|
|
|
18242
18330
|
assertStringArgument2(oldString, "oldString");
|
|
18243
18331
|
return [
|
|
18244
18332
|
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
18245
|
-
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\u3002",
|
|
18333
|
+
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\u884C/\u5C3E\u968F\u7A7A\u884C\u4E0A\u5B58\u5728\u8F7B\u5FAE\u5DEE\u5F02\u3002",
|
|
18246
18334
|
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
18247
18335
|
getNearestContext2(content, oldString)
|
|
18248
18336
|
].join("\n");
|
|
18249
18337
|
}
|
|
18250
|
-
function
|
|
18338
|
+
function trimRightSpaces2(text) {
|
|
18251
18339
|
assertStringArgument2(text, "text");
|
|
18252
|
-
|
|
18253
|
-
|
|
18340
|
+
return text.replace(/[ \t]+$/g, "");
|
|
18341
|
+
}
|
|
18342
|
+
function splitNormalizedLines2(text) {
|
|
18343
|
+
return normalizeNewlines2(text).split("\n");
|
|
18344
|
+
}
|
|
18345
|
+
function trimTrailingEmptyLines2(lines) {
|
|
18346
|
+
const result = [...lines];
|
|
18347
|
+
while (result.length > 0 && trimRightSpaces2(result[result.length - 1]) === "") {
|
|
18348
|
+
result.pop();
|
|
18349
|
+
}
|
|
18350
|
+
return result;
|
|
18351
|
+
}
|
|
18352
|
+
function hasLineNumberPrefixes2(lines) {
|
|
18353
|
+
const nonEmpty = lines.filter((line) => line.trim().length > 0);
|
|
18354
|
+
return nonEmpty.length > 0 && nonEmpty.every((line) => /^\d+: /.test(line));
|
|
18355
|
+
}
|
|
18356
|
+
function stripLineNumberPrefixes2(lines) {
|
|
18357
|
+
return lines.map((line) => line.replace(/^\d+: /, ""));
|
|
18358
|
+
}
|
|
18359
|
+
function parseLineNumberPrefixedBlock2(text) {
|
|
18360
|
+
const normalizedText = normalizeNewlines2(text);
|
|
18361
|
+
const hasTrailingNewline = normalizedText.endsWith("\n");
|
|
18362
|
+
const lines = trimTrailingEmptyLines2(splitNormalizedLines2(text));
|
|
18363
|
+
if (lines.length === 0 || !hasLineNumberPrefixes2(lines)) {
|
|
18364
|
+
return null;
|
|
18365
|
+
}
|
|
18366
|
+
const lineNumbers = [];
|
|
18367
|
+
const strippedLines = [];
|
|
18368
|
+
for (const line of lines) {
|
|
18369
|
+
const match = /^(\d+): ?(.*)$/.exec(line);
|
|
18370
|
+
if (!match) {
|
|
18371
|
+
return null;
|
|
18372
|
+
}
|
|
18373
|
+
lineNumbers.push(Number(match[1]));
|
|
18374
|
+
strippedLines.push(match[2]);
|
|
18375
|
+
}
|
|
18376
|
+
return {
|
|
18377
|
+
lineNumbers,
|
|
18378
|
+
strippedText: `${strippedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`,
|
|
18379
|
+
isContiguous: lineNumbers.every((lineNumber, index) => index === 0 || lineNumber === lineNumbers[index - 1] + 1),
|
|
18380
|
+
startLine: lineNumbers[0],
|
|
18381
|
+
endLine: lineNumbers[lineNumbers.length - 1]
|
|
18382
|
+
};
|
|
18383
|
+
}
|
|
18384
|
+
function collectOccurrencePositions2(text, token) {
|
|
18254
18385
|
if (token.length === 0) {
|
|
18255
|
-
|
|
18386
|
+
return [];
|
|
18256
18387
|
}
|
|
18257
|
-
|
|
18388
|
+
const positions = [];
|
|
18258
18389
|
let searchFrom = 0;
|
|
18259
18390
|
while (true) {
|
|
18260
18391
|
const index = text.indexOf(token, searchFrom);
|
|
18261
18392
|
if (index === -1) {
|
|
18262
|
-
|
|
18263
|
-
}
|
|
18264
|
-
count += 1;
|
|
18265
|
-
if (count === occurrence) {
|
|
18266
|
-
return { index, total: countOccurrences(text, token) };
|
|
18393
|
+
return positions;
|
|
18267
18394
|
}
|
|
18395
|
+
positions.push(index);
|
|
18268
18396
|
searchFrom = index + token.length;
|
|
18269
18397
|
}
|
|
18270
|
-
|
|
18398
|
+
}
|
|
18399
|
+
function buildFlexibleSearchVariants2(token, newlineStyle) {
|
|
18400
|
+
const variants = /* @__PURE__ */ new Set();
|
|
18401
|
+
const pushVariant = (value) => {
|
|
18402
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
18403
|
+
return;
|
|
18404
|
+
}
|
|
18405
|
+
variants.add(value);
|
|
18406
|
+
};
|
|
18407
|
+
pushVariant(token);
|
|
18408
|
+
pushVariant(alignTextToNewlineStyle2(token, newlineStyle));
|
|
18409
|
+
const prefixedBlock = parseLineNumberPrefixedBlock2(token);
|
|
18410
|
+
if (prefixedBlock) {
|
|
18411
|
+
pushVariant(prefixedBlock.strippedText);
|
|
18412
|
+
pushVariant(alignTextToNewlineStyle2(prefixedBlock.strippedText, newlineStyle));
|
|
18413
|
+
}
|
|
18414
|
+
const trimmedLines = trimTrailingEmptyLines2(splitNormalizedLines2(token));
|
|
18415
|
+
if (trimmedLines.length > 0) {
|
|
18416
|
+
const trimmedToken = trimmedLines.join("\n");
|
|
18417
|
+
pushVariant(trimmedToken);
|
|
18418
|
+
pushVariant(alignTextToNewlineStyle2(trimmedToken, newlineStyle));
|
|
18419
|
+
const trimmedPrefixedBlock = parseLineNumberPrefixedBlock2(trimmedToken);
|
|
18420
|
+
if (trimmedPrefixedBlock) {
|
|
18421
|
+
pushVariant(trimmedPrefixedBlock.strippedText);
|
|
18422
|
+
pushVariant(alignTextToNewlineStyle2(trimmedPrefixedBlock.strippedText, newlineStyle));
|
|
18423
|
+
}
|
|
18424
|
+
}
|
|
18425
|
+
return [...variants];
|
|
18426
|
+
}
|
|
18427
|
+
function findOccurrenceIndex(text, token, occurrence, newlineStyle) {
|
|
18428
|
+
assertStringArgument2(text, "text");
|
|
18429
|
+
assertStringArgument2(token, "anchor");
|
|
18430
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
18431
|
+
if (token.length === 0) {
|
|
18432
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18433
|
+
}
|
|
18434
|
+
let maxMatches = 0;
|
|
18435
|
+
for (const candidate of buildFlexibleSearchVariants2(token, newlineStyle)) {
|
|
18436
|
+
const positions = collectOccurrencePositions2(text, candidate);
|
|
18437
|
+
if (positions.length === 0) {
|
|
18438
|
+
continue;
|
|
18439
|
+
}
|
|
18440
|
+
maxMatches = Math.max(maxMatches, positions.length);
|
|
18441
|
+
if (positions.length >= occurrence) {
|
|
18442
|
+
return {
|
|
18443
|
+
index: positions[occurrence - 1],
|
|
18444
|
+
total: positions.length,
|
|
18445
|
+
matchedToken: candidate
|
|
18446
|
+
};
|
|
18447
|
+
}
|
|
18448
|
+
}
|
|
18449
|
+
if (maxMatches === 0) {
|
|
18271
18450
|
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
18272
18451
|
}
|
|
18273
|
-
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${
|
|
18452
|
+
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${maxMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
18274
18453
|
}
|
|
18275
18454
|
function assertInsertArguments2(input) {
|
|
18276
18455
|
assertStringArgument2(input.anchor, "anchor");
|
|
@@ -18322,8 +18501,8 @@ function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
18322
18501
|
}
|
|
18323
18502
|
function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
18324
18503
|
const alignedContent = alignTextToNewlineStyle2(content, newlineStyle);
|
|
18325
|
-
const located =
|
|
18326
|
-
const insertionPoint = mode === "insertAfter" ? located.index +
|
|
18504
|
+
const located = findOccurrenceIndex(text, anchor, occurrence, newlineStyle);
|
|
18505
|
+
const insertionPoint = mode === "insertAfter" ? located.index + located.matchedToken.length : located.index;
|
|
18327
18506
|
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
18328
18507
|
if (alreadyExists) {
|
|
18329
18508
|
if (ifExists === "error") {
|
|
@@ -18337,8 +18516,8 @@ function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, ne
|
|
|
18337
18516
|
anchorMatches: located.total,
|
|
18338
18517
|
occurrence,
|
|
18339
18518
|
anchor,
|
|
18340
|
-
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index +
|
|
18341
|
-
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index +
|
|
18519
|
+
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + located.matchedToken.length + 80)),
|
|
18520
|
+
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + located.matchedToken.length + 80))
|
|
18342
18521
|
};
|
|
18343
18522
|
}
|
|
18344
18523
|
}
|
|
@@ -18584,6 +18763,114 @@ async function visitDecodedTextChunks2(resolved, visitor) {
|
|
|
18584
18763
|
stream.destroy();
|
|
18585
18764
|
}
|
|
18586
18765
|
}
|
|
18766
|
+
async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
|
|
18767
|
+
const tempPath = path3.join(path3.dirname(filePath), `${path3.basename(filePath)}.opencode-text-${crypto2.randomUUID()}.tmp`);
|
|
18768
|
+
const handle = await fs3.open(tempPath, "w");
|
|
18769
|
+
try {
|
|
18770
|
+
if (hasBom) {
|
|
18771
|
+
const bom = getBomPrefix(encoding, hasBom);
|
|
18772
|
+
if (bom.length > 0) {
|
|
18773
|
+
await handle.writeFile(bom);
|
|
18774
|
+
}
|
|
18775
|
+
}
|
|
18776
|
+
const bytesWritten = await producer(handle);
|
|
18777
|
+
await handle.close();
|
|
18778
|
+
await fs3.rename(tempPath, filePath);
|
|
18779
|
+
return bytesWritten + getBomPrefix(encoding, hasBom).length;
|
|
18780
|
+
} catch (error45) {
|
|
18781
|
+
await handle.close().catch(() => void 0);
|
|
18782
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
18783
|
+
throw error45;
|
|
18784
|
+
}
|
|
18785
|
+
}
|
|
18786
|
+
async function writeEncodedTextChunk(handle, encoding, text) {
|
|
18787
|
+
if (text.length === 0) {
|
|
18788
|
+
return 0;
|
|
18789
|
+
}
|
|
18790
|
+
const buffer = encodeTextBody(text, encoding);
|
|
18791
|
+
await handle.writeFile(buffer);
|
|
18792
|
+
return buffer.byteLength;
|
|
18793
|
+
}
|
|
18794
|
+
async function replaceLargeTextFileText(input) {
|
|
18795
|
+
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
18796
|
+
const fileNewlineStyle = input.loaded.newlineStyle;
|
|
18797
|
+
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;
|
|
18798
|
+
let carry = "";
|
|
18799
|
+
let occurrencesBefore = 0;
|
|
18800
|
+
let replacedFirstMatch = false;
|
|
18801
|
+
const bytesWritten = await writeLargeTextFile(input.loaded.filePath, input.loaded.encoding, input.loaded.hasBom, async (handle) => {
|
|
18802
|
+
let written = 0;
|
|
18803
|
+
const flushText = async (text, flush = false) => {
|
|
18804
|
+
const combined = carry + text;
|
|
18805
|
+
if (combined.length === 0) {
|
|
18806
|
+
return;
|
|
18807
|
+
}
|
|
18808
|
+
const safeEnd = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
18809
|
+
const outputParts = [];
|
|
18810
|
+
let cursor = 0;
|
|
18811
|
+
let flushUpto = safeEnd;
|
|
18812
|
+
while (true) {
|
|
18813
|
+
const index = combined.indexOf(input.oldString, cursor);
|
|
18814
|
+
if (index === -1) {
|
|
18815
|
+
break;
|
|
18816
|
+
}
|
|
18817
|
+
const matchEnd = index + input.oldString.length;
|
|
18818
|
+
if (!flush && matchEnd > safeEnd) {
|
|
18819
|
+
flushUpto = index;
|
|
18820
|
+
break;
|
|
18821
|
+
}
|
|
18822
|
+
occurrencesBefore += 1;
|
|
18823
|
+
outputParts.push(combined.slice(cursor, index));
|
|
18824
|
+
if (input.replaceAll) {
|
|
18825
|
+
outputParts.push(alignedNewString);
|
|
18826
|
+
} else if (!replacedFirstMatch) {
|
|
18827
|
+
outputParts.push(alignedNewString);
|
|
18828
|
+
replacedFirstMatch = true;
|
|
18829
|
+
} else {
|
|
18830
|
+
outputParts.push(input.oldString);
|
|
18831
|
+
}
|
|
18832
|
+
cursor = matchEnd;
|
|
18833
|
+
}
|
|
18834
|
+
outputParts.push(combined.slice(cursor, flushUpto));
|
|
18835
|
+
carry = combined.slice(flushUpto);
|
|
18836
|
+
const processable = outputParts.join("");
|
|
18837
|
+
if (processable.length === 0) {
|
|
18838
|
+
return;
|
|
18839
|
+
}
|
|
18840
|
+
written += await writeEncodedTextChunk(handle, input.loaded.encoding, processable);
|
|
18841
|
+
};
|
|
18842
|
+
await visitDecodedTextChunks2({
|
|
18843
|
+
filePath: input.loaded.filePath,
|
|
18844
|
+
encoding: input.loaded.encoding,
|
|
18845
|
+
hasBom: input.loaded.hasBom
|
|
18846
|
+
}, async (text) => {
|
|
18847
|
+
await flushText(text);
|
|
18848
|
+
});
|
|
18849
|
+
await flushText("", true);
|
|
18850
|
+
if (occurrencesBefore === 0) {
|
|
18851
|
+
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
18852
|
+
}
|
|
18853
|
+
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
18854
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
18855
|
+
}
|
|
18856
|
+
return written;
|
|
18857
|
+
});
|
|
18858
|
+
return {
|
|
18859
|
+
mode: "replace",
|
|
18860
|
+
filePath: input.loaded.filePath,
|
|
18861
|
+
encoding: input.loaded.encoding,
|
|
18862
|
+
requestedEncoding: input.loaded.requestedEncoding,
|
|
18863
|
+
detectedEncoding: input.loaded.detectedEncoding,
|
|
18864
|
+
confidence: input.loaded.confidence,
|
|
18865
|
+
hasBom: input.loaded.hasBom,
|
|
18866
|
+
replacements: input.replaceAll ? occurrencesBefore : 1,
|
|
18867
|
+
occurrencesBefore,
|
|
18868
|
+
bytesRead: input.loaded.bytesRead,
|
|
18869
|
+
bytesWritten,
|
|
18870
|
+
newlineStyle: input.loaded.newlineStyle,
|
|
18871
|
+
diffPreview: void 0
|
|
18872
|
+
};
|
|
18873
|
+
}
|
|
18587
18874
|
async function readWholeTextFile(input) {
|
|
18588
18875
|
const resolved = await resolveReadableTextFile(input);
|
|
18589
18876
|
try {
|
|
@@ -18677,19 +18964,14 @@ function applyAnchors2(text, startAnchor, endAnchor) {
|
|
|
18677
18964
|
}
|
|
18678
18965
|
let rangeStart = 0;
|
|
18679
18966
|
let rangeEnd = text.length;
|
|
18967
|
+
const newlineStyle = detectNewlineStyle(text);
|
|
18680
18968
|
if (startAnchor) {
|
|
18681
|
-
const found = text
|
|
18682
|
-
|
|
18683
|
-
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\u70B9: ${startAnchor}`);
|
|
18684
|
-
}
|
|
18685
|
-
rangeStart = found + startAnchor.length;
|
|
18969
|
+
const found = findOccurrenceIndex(text, startAnchor, 1, newlineStyle);
|
|
18970
|
+
rangeStart = found.index + found.matchedToken.length;
|
|
18686
18971
|
}
|
|
18687
18972
|
if (endAnchor) {
|
|
18688
|
-
const found = text.
|
|
18689
|
-
|
|
18690
|
-
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\u70B9: ${endAnchor}`);
|
|
18691
|
-
}
|
|
18692
|
-
rangeEnd = found;
|
|
18973
|
+
const found = findOccurrenceIndex(text.slice(rangeStart), endAnchor, 1, newlineStyle);
|
|
18974
|
+
rangeEnd = rangeStart + found.index;
|
|
18693
18975
|
}
|
|
18694
18976
|
if (rangeEnd < rangeStart) {
|
|
18695
18977
|
throw createTextError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
@@ -18720,6 +19002,64 @@ function alignTextToNewlineStyle2(text, newlineStyle) {
|
|
|
18720
19002
|
}
|
|
18721
19003
|
return text;
|
|
18722
19004
|
}
|
|
19005
|
+
function matchLooseBlock2(contentLines, oldLines, newLines, newlineStyle) {
|
|
19006
|
+
for (let start = 0; start < contentLines.length; start += 1) {
|
|
19007
|
+
let contentIndex = start;
|
|
19008
|
+
let oldIndex = 0;
|
|
19009
|
+
while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
|
|
19010
|
+
const expected = trimRightSpaces2(oldLines[oldIndex]);
|
|
19011
|
+
const actual = trimRightSpaces2(contentLines[contentIndex]);
|
|
19012
|
+
if (expected === actual) {
|
|
19013
|
+
oldIndex += 1;
|
|
19014
|
+
contentIndex += 1;
|
|
19015
|
+
continue;
|
|
19016
|
+
}
|
|
19017
|
+
if (actual === "") {
|
|
19018
|
+
contentIndex += 1;
|
|
19019
|
+
continue;
|
|
19020
|
+
}
|
|
19021
|
+
if (expected === "") {
|
|
19022
|
+
oldIndex += 1;
|
|
19023
|
+
continue;
|
|
19024
|
+
}
|
|
19025
|
+
break;
|
|
19026
|
+
}
|
|
19027
|
+
if (oldIndex === oldLines.length) {
|
|
19028
|
+
const matchedOriginal = contentLines.slice(start, contentIndex).join("\n");
|
|
19029
|
+
const replacedNormalized = [
|
|
19030
|
+
...contentLines.slice(0, start),
|
|
19031
|
+
...newLines,
|
|
19032
|
+
...contentLines.slice(contentIndex)
|
|
19033
|
+
].join("\n");
|
|
19034
|
+
return {
|
|
19035
|
+
occurrencesBefore: 1,
|
|
19036
|
+
matchedOriginal,
|
|
19037
|
+
content: newlineStyle === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
|
|
19038
|
+
};
|
|
19039
|
+
}
|
|
19040
|
+
}
|
|
19041
|
+
return null;
|
|
19042
|
+
}
|
|
19043
|
+
function tryLooseBlockReplace2(content, oldString, newString) {
|
|
19044
|
+
const contentLines = splitNormalizedLines2(content);
|
|
19045
|
+
const oldLines = trimTrailingEmptyLines2(splitNormalizedLines2(oldString));
|
|
19046
|
+
const newLines = splitNormalizedLines2(newString);
|
|
19047
|
+
const newlineStyle = detectNewlineStyle(content);
|
|
19048
|
+
if (oldLines.length === 0) {
|
|
19049
|
+
return null;
|
|
19050
|
+
}
|
|
19051
|
+
const direct = matchLooseBlock2(contentLines, oldLines, newLines, newlineStyle);
|
|
19052
|
+
if (direct !== null) {
|
|
19053
|
+
return direct;
|
|
19054
|
+
}
|
|
19055
|
+
if (hasLineNumberPrefixes2(oldLines)) {
|
|
19056
|
+
const strippedOldLines = trimTrailingEmptyLines2(stripLineNumberPrefixes2(oldLines));
|
|
19057
|
+
if (strippedOldLines.length > 0) {
|
|
19058
|
+
return matchLooseBlock2(contentLines, strippedOldLines, newLines, newlineStyle);
|
|
19059
|
+
}
|
|
19060
|
+
}
|
|
19061
|
+
return null;
|
|
19062
|
+
}
|
|
18723
19063
|
function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
|
|
18724
19064
|
assertStringArgument2(input, "content");
|
|
18725
19065
|
const buffer = encodeText(input, encoding, hasBom);
|
|
@@ -18919,37 +19259,89 @@ async function replaceTextFileText(input) {
|
|
|
18919
19259
|
if (input.oldString.length === 0) {
|
|
18920
19260
|
throw createTextError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18921
19261
|
}
|
|
19262
|
+
const prefixedBlock = parseLineNumberPrefixedBlock2(input.oldString);
|
|
19263
|
+
const derivedScopedRange = prefixedBlock?.isContiguous ? {
|
|
19264
|
+
startLine: normalizedInput.startLine ?? prefixedBlock.startLine,
|
|
19265
|
+
endLine: normalizedInput.endLine ?? prefixedBlock.endLine,
|
|
19266
|
+
oldString: prefixedBlock.strippedText
|
|
19267
|
+
} : null;
|
|
19268
|
+
const effectiveOldString = derivedScopedRange?.oldString ?? input.oldString;
|
|
18922
19269
|
const resolved = await detectReadableTextFile({
|
|
18923
19270
|
filePath: input.filePath,
|
|
18924
19271
|
encoding: input.encoding ?? "auto",
|
|
18925
19272
|
allowExternal: input.allowExternal,
|
|
18926
19273
|
context: input.context
|
|
18927
19274
|
});
|
|
19275
|
+
const hasScopedRange = normalizedInput.startLine !== void 0 || normalizedInput.endLine !== void 0 || normalizedInput.startAnchor !== void 0 || normalizedInput.endAnchor !== void 0 || derivedScopedRange !== null;
|
|
19276
|
+
const preserveEncoding = normalizedInput.preserveEncoding ?? true;
|
|
19277
|
+
const preserveNewlineStyle = normalizedInput.preserveNewlineStyle ?? true;
|
|
19278
|
+
const requestedEncoding = normalizedInput.encoding ?? "auto";
|
|
19279
|
+
if (resolved.stat.size >= TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES && !hasScopedRange && preserveEncoding && preserveNewlineStyle && requestedEncoding === "auto") {
|
|
19280
|
+
const loadedForStreaming = await readWholeTextFile({
|
|
19281
|
+
filePath: input.filePath,
|
|
19282
|
+
encoding: requestedEncoding,
|
|
19283
|
+
allowExternal: input.allowExternal,
|
|
19284
|
+
context: input.context
|
|
19285
|
+
});
|
|
19286
|
+
return await replaceLargeTextFileText({
|
|
19287
|
+
loaded: loadedForStreaming,
|
|
19288
|
+
oldString: effectiveOldString,
|
|
19289
|
+
newString: input.newString,
|
|
19290
|
+
replaceAll: normalizedInput.replaceAll ?? false
|
|
19291
|
+
});
|
|
19292
|
+
}
|
|
18928
19293
|
const loaded = await readWholeTextFile({
|
|
18929
19294
|
filePath: input.filePath,
|
|
18930
19295
|
encoding: input.encoding ?? "auto",
|
|
18931
19296
|
allowExternal: input.allowExternal,
|
|
18932
19297
|
context: input.context
|
|
18933
19298
|
});
|
|
18934
|
-
const
|
|
19299
|
+
const scopedInput = derivedScopedRange === null ? normalizedInput : {
|
|
19300
|
+
...normalizedInput,
|
|
19301
|
+
startLine: derivedScopedRange.startLine,
|
|
19302
|
+
endLine: derivedScopedRange.endLine
|
|
19303
|
+
};
|
|
19304
|
+
const scope = resolveEditScope2(loaded.content, scopedInput);
|
|
18935
19305
|
const replaceAll = normalizedInput.replaceAll ?? false;
|
|
18936
|
-
const
|
|
18937
|
-
const
|
|
18938
|
-
const occurrencesBefore = countOccurrences(scope.selectedText,
|
|
19306
|
+
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
19307
|
+
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
19308
|
+
const occurrencesBefore = countOccurrences(scope.selectedText, effectiveOldString);
|
|
19309
|
+
if (!replaceAll && occurrencesBefore === 0) {
|
|
19310
|
+
const loose = tryLooseBlockReplace2(scope.selectedText, effectiveOldString, input.newString);
|
|
19311
|
+
if (loose !== null) {
|
|
19312
|
+
const outputText2 = `${loaded.content.slice(0, scope.rangeStart)}${loose.content}${loaded.content.slice(scope.rangeEnd)}`;
|
|
19313
|
+
ensureLossless(outputText2, targetEncoding, targetHasBom);
|
|
19314
|
+
const buffer2 = encodeText(outputText2, targetEncoding, targetHasBom);
|
|
19315
|
+
await fs3.writeFile(loaded.filePath, buffer2);
|
|
19316
|
+
return {
|
|
19317
|
+
mode: "replace",
|
|
19318
|
+
filePath: loaded.filePath,
|
|
19319
|
+
encoding: targetEncoding,
|
|
19320
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
19321
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
19322
|
+
confidence: loaded.confidence,
|
|
19323
|
+
hasBom: targetHasBom,
|
|
19324
|
+
replacements: 1,
|
|
19325
|
+
occurrencesBefore: loose.occurrencesBefore,
|
|
19326
|
+
bytesRead: loaded.bytesRead,
|
|
19327
|
+
bytesWritten: buffer2.byteLength,
|
|
19328
|
+
newlineStyle: detectNewlineStyle(outputText2),
|
|
19329
|
+
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, loose.matchedOriginal, input.newString)
|
|
19330
|
+
};
|
|
19331
|
+
}
|
|
19332
|
+
}
|
|
18939
19333
|
if (replaceAll) {
|
|
18940
19334
|
if (occurrencesBefore === 0) {
|
|
18941
|
-
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText,
|
|
19335
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText, effectiveOldString));
|
|
18942
19336
|
}
|
|
18943
19337
|
} else if (occurrencesBefore === 0) {
|
|
18944
|
-
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText,
|
|
19338
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText, effectiveOldString));
|
|
18945
19339
|
} else if (occurrencesBefore > 1) {
|
|
18946
|
-
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${
|
|
19340
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${effectiveOldString}`);
|
|
18947
19341
|
}
|
|
18948
19342
|
const alignedNewString = normalizedInput.preserveNewlineStyle === false ? input.newString : alignTextToNewlineStyle2(input.newString, loaded.newlineStyle);
|
|
18949
|
-
const replaced = replaceAll ? scope.selectedText.split(
|
|
19343
|
+
const replaced = replaceAll ? scope.selectedText.split(effectiveOldString).join(alignedNewString) : scope.selectedText.replace(effectiveOldString, alignedNewString);
|
|
18950
19344
|
const outputText = `${loaded.content.slice(0, scope.rangeStart)}${replaced}${loaded.content.slice(scope.rangeEnd)}`;
|
|
18951
|
-
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
18952
|
-
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
18953
19345
|
ensureLossless(outputText, targetEncoding, targetHasBom);
|
|
18954
19346
|
const buffer = encodeText(outputText, targetEncoding, targetHasBom);
|
|
18955
19347
|
await fs3.writeFile(loaded.filePath, buffer);
|
|
@@ -18966,7 +19358,7 @@ async function replaceTextFileText(input) {
|
|
|
18966
19358
|
bytesRead: loaded.bytesRead,
|
|
18967
19359
|
bytesWritten: buffer.byteLength,
|
|
18968
19360
|
newlineStyle: detectNewlineStyle(outputText),
|
|
18969
|
-
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding,
|
|
19361
|
+
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, effectiveOldString, alignedNewString)
|
|
18970
19362
|
};
|
|
18971
19363
|
}
|
|
18972
19364
|
|