opencode-gbk-tools 0.1.27 → 0.1.29
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 +8 -6
- 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 +759 -192
- package/dist/plugins/opencode-gbk-tools.js +759 -192
- package/dist/release-manifest.json +3 -3
- package/package.json +1 -1
|
@@ -16576,35 +16576,126 @@ function buildNoMatchMessage(content, oldString) {
|
|
|
16576
16576
|
assertStringArgument2(oldString, "oldString");
|
|
16577
16577
|
return [
|
|
16578
16578
|
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
16579
|
-
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\u3002",
|
|
16579
|
+
"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",
|
|
16580
16580
|
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
16581
16581
|
getNearestContext(content, oldString)
|
|
16582
16582
|
].join("\n");
|
|
16583
16583
|
}
|
|
16584
|
-
function
|
|
16584
|
+
function trimRightSpaces(text) {
|
|
16585
16585
|
assertStringArgument2(text, "text");
|
|
16586
|
-
|
|
16587
|
-
|
|
16586
|
+
return text.replace(/[ \t]+$/g, "");
|
|
16587
|
+
}
|
|
16588
|
+
function splitNormalizedLines(text) {
|
|
16589
|
+
return normalizeNewlines(text).split("\n");
|
|
16590
|
+
}
|
|
16591
|
+
function trimTrailingEmptyLines(lines) {
|
|
16592
|
+
const result = [...lines];
|
|
16593
|
+
while (result.length > 0 && trimRightSpaces(result[result.length - 1]) === "") {
|
|
16594
|
+
result.pop();
|
|
16595
|
+
}
|
|
16596
|
+
return result;
|
|
16597
|
+
}
|
|
16598
|
+
function hasLineNumberPrefixes(lines) {
|
|
16599
|
+
const nonEmpty = lines.filter((line) => line.trim().length > 0);
|
|
16600
|
+
return nonEmpty.length > 0 && nonEmpty.every((line) => /^\d+: /.test(line));
|
|
16601
|
+
}
|
|
16602
|
+
function stripLineNumberPrefixes(lines) {
|
|
16603
|
+
return lines.map((line) => line.replace(/^\d+: /, ""));
|
|
16604
|
+
}
|
|
16605
|
+
function parseLineNumberPrefixedBlock(text) {
|
|
16606
|
+
const normalizedText = normalizeNewlines(text);
|
|
16607
|
+
const hasTrailingNewline = normalizedText.endsWith("\n");
|
|
16608
|
+
const lines = trimTrailingEmptyLines(splitNormalizedLines(text));
|
|
16609
|
+
if (lines.length === 0 || !hasLineNumberPrefixes(lines)) {
|
|
16610
|
+
return null;
|
|
16611
|
+
}
|
|
16612
|
+
const lineNumbers = [];
|
|
16613
|
+
const strippedLines = [];
|
|
16614
|
+
for (const line of lines) {
|
|
16615
|
+
const match = /^(\d+): ?(.*)$/.exec(line);
|
|
16616
|
+
if (!match) {
|
|
16617
|
+
return null;
|
|
16618
|
+
}
|
|
16619
|
+
lineNumbers.push(Number(match[1]));
|
|
16620
|
+
strippedLines.push(match[2]);
|
|
16621
|
+
}
|
|
16622
|
+
return {
|
|
16623
|
+
lineNumbers,
|
|
16624
|
+
strippedText: `${strippedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`,
|
|
16625
|
+
isContiguous: lineNumbers.every((lineNumber, index) => index === 0 || lineNumber === lineNumbers[index - 1] + 1),
|
|
16626
|
+
startLine: lineNumbers[0],
|
|
16627
|
+
endLine: lineNumbers[lineNumbers.length - 1]
|
|
16628
|
+
};
|
|
16629
|
+
}
|
|
16630
|
+
function collectOccurrencePositions(text, token) {
|
|
16588
16631
|
if (token.length === 0) {
|
|
16589
|
-
|
|
16632
|
+
return [];
|
|
16590
16633
|
}
|
|
16591
|
-
|
|
16634
|
+
const positions = [];
|
|
16592
16635
|
let searchFrom = 0;
|
|
16593
16636
|
while (true) {
|
|
16594
16637
|
const index = text.indexOf(token, searchFrom);
|
|
16595
16638
|
if (index === -1) {
|
|
16596
|
-
|
|
16597
|
-
}
|
|
16598
|
-
count += 1;
|
|
16599
|
-
if (count === occurrence) {
|
|
16600
|
-
return { index, total: countOccurrences(text, token) };
|
|
16639
|
+
return positions;
|
|
16601
16640
|
}
|
|
16641
|
+
positions.push(index);
|
|
16602
16642
|
searchFrom = index + token.length;
|
|
16603
16643
|
}
|
|
16604
|
-
|
|
16644
|
+
}
|
|
16645
|
+
function buildFlexibleSearchVariants(token, newlineStyle) {
|
|
16646
|
+
const variants = /* @__PURE__ */ new Set();
|
|
16647
|
+
const pushVariant = (value) => {
|
|
16648
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16649
|
+
return;
|
|
16650
|
+
}
|
|
16651
|
+
variants.add(value);
|
|
16652
|
+
};
|
|
16653
|
+
pushVariant(token);
|
|
16654
|
+
pushVariant(alignTextToNewlineStyle(token, newlineStyle));
|
|
16655
|
+
const prefixedBlock = parseLineNumberPrefixedBlock(token);
|
|
16656
|
+
if (prefixedBlock) {
|
|
16657
|
+
pushVariant(prefixedBlock.strippedText);
|
|
16658
|
+
pushVariant(alignTextToNewlineStyle(prefixedBlock.strippedText, newlineStyle));
|
|
16659
|
+
}
|
|
16660
|
+
const trimmedLines = trimTrailingEmptyLines(splitNormalizedLines(token));
|
|
16661
|
+
if (trimmedLines.length > 0) {
|
|
16662
|
+
const trimmedToken = trimmedLines.join("\n");
|
|
16663
|
+
pushVariant(trimmedToken);
|
|
16664
|
+
pushVariant(alignTextToNewlineStyle(trimmedToken, newlineStyle));
|
|
16665
|
+
const trimmedPrefixedBlock = parseLineNumberPrefixedBlock(trimmedToken);
|
|
16666
|
+
if (trimmedPrefixedBlock) {
|
|
16667
|
+
pushVariant(trimmedPrefixedBlock.strippedText);
|
|
16668
|
+
pushVariant(alignTextToNewlineStyle(trimmedPrefixedBlock.strippedText, newlineStyle));
|
|
16669
|
+
}
|
|
16670
|
+
}
|
|
16671
|
+
return [...variants];
|
|
16672
|
+
}
|
|
16673
|
+
function findOccurrenceIndex(text, token, occurrence, newlineStyle) {
|
|
16674
|
+
assertStringArgument2(text, "text");
|
|
16675
|
+
assertStringArgument2(token, "anchor");
|
|
16676
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
16677
|
+
if (token.length === 0) {
|
|
16678
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16679
|
+
}
|
|
16680
|
+
let maxMatches = 0;
|
|
16681
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16682
|
+
const positions = collectOccurrencePositions(text, candidate);
|
|
16683
|
+
if (positions.length === 0) {
|
|
16684
|
+
continue;
|
|
16685
|
+
}
|
|
16686
|
+
maxMatches = Math.max(maxMatches, positions.length);
|
|
16687
|
+
if (positions.length >= occurrence) {
|
|
16688
|
+
return {
|
|
16689
|
+
index: positions[occurrence - 1],
|
|
16690
|
+
total: positions.length,
|
|
16691
|
+
matchedToken: candidate
|
|
16692
|
+
};
|
|
16693
|
+
}
|
|
16694
|
+
}
|
|
16695
|
+
if (maxMatches === 0) {
|
|
16605
16696
|
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
16606
16697
|
}
|
|
16607
|
-
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${
|
|
16698
|
+
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${maxMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
16608
16699
|
}
|
|
16609
16700
|
function assertInsertArguments(input) {
|
|
16610
16701
|
assertStringArgument2(input.anchor, "anchor");
|
|
@@ -16656,8 +16747,8 @@ function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
16656
16747
|
}
|
|
16657
16748
|
function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
16658
16749
|
const alignedContent = alignTextToNewlineStyle(content, newlineStyle);
|
|
16659
|
-
const located = findOccurrenceIndex(text, anchor, occurrence);
|
|
16660
|
-
const insertionPoint = mode === "insertAfter" ? located.index +
|
|
16750
|
+
const located = findOccurrenceIndex(text, anchor, occurrence, newlineStyle);
|
|
16751
|
+
const insertionPoint = mode === "insertAfter" ? located.index + located.matchedToken.length : located.index;
|
|
16661
16752
|
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
16662
16753
|
if (alreadyExists) {
|
|
16663
16754
|
if (ifExists === "error") {
|
|
@@ -16671,8 +16762,8 @@ function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, ne
|
|
|
16671
16762
|
anchorMatches: located.total,
|
|
16672
16763
|
occurrence,
|
|
16673
16764
|
anchor,
|
|
16674
|
-
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index +
|
|
16675
|
-
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index +
|
|
16765
|
+
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + located.matchedToken.length + 80)),
|
|
16766
|
+
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + located.matchedToken.length + 80))
|
|
16676
16767
|
};
|
|
16677
16768
|
}
|
|
16678
16769
|
}
|
|
@@ -16779,6 +16870,150 @@ async function detectReadableTextFile(input) {
|
|
|
16779
16870
|
hasBom: detected.hasBom
|
|
16780
16871
|
};
|
|
16781
16872
|
}
|
|
16873
|
+
async function visitDecodedTextChunks(resolved, visitor) {
|
|
16874
|
+
const stream = createReadStream2(resolved.filePath);
|
|
16875
|
+
const bomLength = getBomPrefix(resolved.encoding, resolved.hasBom).length;
|
|
16876
|
+
const decoderEncoding = resolved.encoding === "utf8-bom" ? "utf8" : resolved.encoding;
|
|
16877
|
+
const decoder = import_iconv_lite2.default.getDecoder(decoderEncoding);
|
|
16878
|
+
let skippedBom = false;
|
|
16879
|
+
try {
|
|
16880
|
+
for await (const chunk of stream) {
|
|
16881
|
+
let buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
16882
|
+
if (!skippedBom && bomLength > 0) {
|
|
16883
|
+
buffer = buffer.subarray(Math.min(bomLength, buffer.length));
|
|
16884
|
+
skippedBom = true;
|
|
16885
|
+
} else {
|
|
16886
|
+
skippedBom = true;
|
|
16887
|
+
}
|
|
16888
|
+
if (buffer.length === 0) {
|
|
16889
|
+
continue;
|
|
16890
|
+
}
|
|
16891
|
+
const text = decoder.write(buffer);
|
|
16892
|
+
if (text.length > 0) {
|
|
16893
|
+
await visitor(text);
|
|
16894
|
+
}
|
|
16895
|
+
}
|
|
16896
|
+
const trailingText = decoder.end() ?? "";
|
|
16897
|
+
if (trailingText.length > 0) {
|
|
16898
|
+
await visitor(trailingText);
|
|
16899
|
+
}
|
|
16900
|
+
} catch (error45) {
|
|
16901
|
+
if (error45 instanceof Error && "code" in error45) {
|
|
16902
|
+
throw error45;
|
|
16903
|
+
}
|
|
16904
|
+
throw createTextError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${resolved.filePath}`, error45);
|
|
16905
|
+
} finally {
|
|
16906
|
+
stream.destroy();
|
|
16907
|
+
}
|
|
16908
|
+
}
|
|
16909
|
+
async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
|
|
16910
|
+
const tempPath = path3.join(path3.dirname(filePath), `${path3.basename(filePath)}.opencode-text-${crypto2.randomUUID()}.tmp`);
|
|
16911
|
+
const handle = await fs3.open(tempPath, "w");
|
|
16912
|
+
try {
|
|
16913
|
+
if (hasBom) {
|
|
16914
|
+
const bom = getBomPrefix(encoding, hasBom);
|
|
16915
|
+
if (bom.length > 0) {
|
|
16916
|
+
await handle.writeFile(bom);
|
|
16917
|
+
}
|
|
16918
|
+
}
|
|
16919
|
+
const bytesWritten = await producer(handle);
|
|
16920
|
+
await handle.close();
|
|
16921
|
+
await fs3.rename(tempPath, filePath);
|
|
16922
|
+
return bytesWritten + getBomPrefix(encoding, hasBom).length;
|
|
16923
|
+
} catch (error45) {
|
|
16924
|
+
await handle.close().catch(() => void 0);
|
|
16925
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
16926
|
+
throw error45;
|
|
16927
|
+
}
|
|
16928
|
+
}
|
|
16929
|
+
async function writeEncodedTextChunk(handle, encoding, text) {
|
|
16930
|
+
if (text.length === 0) {
|
|
16931
|
+
return 0;
|
|
16932
|
+
}
|
|
16933
|
+
const buffer = encodeTextBody(text, encoding);
|
|
16934
|
+
await handle.writeFile(buffer);
|
|
16935
|
+
return buffer.byteLength;
|
|
16936
|
+
}
|
|
16937
|
+
async function replaceLargeTextFileText(input) {
|
|
16938
|
+
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
16939
|
+
const fileNewlineStyle = input.loaded.newlineStyle;
|
|
16940
|
+
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;
|
|
16941
|
+
let carry = "";
|
|
16942
|
+
let occurrencesBefore = 0;
|
|
16943
|
+
let replacedFirstMatch = false;
|
|
16944
|
+
const bytesWritten = await writeLargeTextFile(input.loaded.filePath, input.loaded.encoding, input.loaded.hasBom, async (handle) => {
|
|
16945
|
+
let written = 0;
|
|
16946
|
+
const flushText = async (text, flush = false) => {
|
|
16947
|
+
const combined = carry + text;
|
|
16948
|
+
if (combined.length === 0) {
|
|
16949
|
+
return;
|
|
16950
|
+
}
|
|
16951
|
+
const safeEnd = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
16952
|
+
const outputParts = [];
|
|
16953
|
+
let cursor = 0;
|
|
16954
|
+
let flushUpto = safeEnd;
|
|
16955
|
+
while (true) {
|
|
16956
|
+
const index = combined.indexOf(input.oldString, cursor);
|
|
16957
|
+
if (index === -1) {
|
|
16958
|
+
break;
|
|
16959
|
+
}
|
|
16960
|
+
const matchEnd = index + input.oldString.length;
|
|
16961
|
+
if (!flush && matchEnd > safeEnd) {
|
|
16962
|
+
flushUpto = index;
|
|
16963
|
+
break;
|
|
16964
|
+
}
|
|
16965
|
+
occurrencesBefore += 1;
|
|
16966
|
+
outputParts.push(combined.slice(cursor, index));
|
|
16967
|
+
if (input.replaceAll) {
|
|
16968
|
+
outputParts.push(alignedNewString);
|
|
16969
|
+
} else if (!replacedFirstMatch) {
|
|
16970
|
+
outputParts.push(alignedNewString);
|
|
16971
|
+
replacedFirstMatch = true;
|
|
16972
|
+
} else {
|
|
16973
|
+
outputParts.push(input.oldString);
|
|
16974
|
+
}
|
|
16975
|
+
cursor = matchEnd;
|
|
16976
|
+
}
|
|
16977
|
+
outputParts.push(combined.slice(cursor, flushUpto));
|
|
16978
|
+
carry = combined.slice(flushUpto);
|
|
16979
|
+
const processable = outputParts.join("");
|
|
16980
|
+
if (processable.length === 0) {
|
|
16981
|
+
return;
|
|
16982
|
+
}
|
|
16983
|
+
written += await writeEncodedTextChunk(handle, input.loaded.encoding, processable);
|
|
16984
|
+
};
|
|
16985
|
+
await visitDecodedTextChunks({
|
|
16986
|
+
filePath: input.loaded.filePath,
|
|
16987
|
+
encoding: input.loaded.encoding,
|
|
16988
|
+
hasBom: input.loaded.hasBom
|
|
16989
|
+
}, async (text) => {
|
|
16990
|
+
await flushText(text);
|
|
16991
|
+
});
|
|
16992
|
+
await flushText("", true);
|
|
16993
|
+
if (occurrencesBefore === 0) {
|
|
16994
|
+
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
16995
|
+
}
|
|
16996
|
+
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
16997
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
16998
|
+
}
|
|
16999
|
+
return written;
|
|
17000
|
+
});
|
|
17001
|
+
return {
|
|
17002
|
+
mode: "replace",
|
|
17003
|
+
filePath: input.loaded.filePath,
|
|
17004
|
+
encoding: input.loaded.encoding,
|
|
17005
|
+
requestedEncoding: input.loaded.requestedEncoding,
|
|
17006
|
+
detectedEncoding: input.loaded.detectedEncoding,
|
|
17007
|
+
confidence: input.loaded.confidence,
|
|
17008
|
+
hasBom: input.loaded.hasBom,
|
|
17009
|
+
replacements: input.replaceAll ? occurrencesBefore : 1,
|
|
17010
|
+
occurrencesBefore,
|
|
17011
|
+
bytesRead: input.loaded.bytesRead,
|
|
17012
|
+
bytesWritten,
|
|
17013
|
+
newlineStyle: input.loaded.newlineStyle,
|
|
17014
|
+
diffPreview: void 0
|
|
17015
|
+
};
|
|
17016
|
+
}
|
|
16782
17017
|
async function readWholeTextFile(input) {
|
|
16783
17018
|
const resolved = await resolveReadableTextFile(input);
|
|
16784
17019
|
try {
|
|
@@ -16857,19 +17092,14 @@ function applyAnchors(text, startAnchor, endAnchor) {
|
|
|
16857
17092
|
}
|
|
16858
17093
|
let rangeStart = 0;
|
|
16859
17094
|
let rangeEnd = text.length;
|
|
17095
|
+
const newlineStyle = detectNewlineStyle(text);
|
|
16860
17096
|
if (startAnchor) {
|
|
16861
|
-
const found = text
|
|
16862
|
-
|
|
16863
|
-
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\u70B9: ${startAnchor}`);
|
|
16864
|
-
}
|
|
16865
|
-
rangeStart = found + startAnchor.length;
|
|
17097
|
+
const found = findOccurrenceIndex(text, startAnchor, 1, newlineStyle);
|
|
17098
|
+
rangeStart = found.index + found.matchedToken.length;
|
|
16866
17099
|
}
|
|
16867
17100
|
if (endAnchor) {
|
|
16868
|
-
const found = text.
|
|
16869
|
-
|
|
16870
|
-
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\u70B9: ${endAnchor}`);
|
|
16871
|
-
}
|
|
16872
|
-
rangeEnd = found;
|
|
17101
|
+
const found = findOccurrenceIndex(text.slice(rangeStart), endAnchor, 1, newlineStyle);
|
|
17102
|
+
rangeEnd = rangeStart + found.index;
|
|
16873
17103
|
}
|
|
16874
17104
|
if (rangeEnd < rangeStart) {
|
|
16875
17105
|
throw createTextError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
@@ -16900,6 +17130,64 @@ function alignTextToNewlineStyle(text, newlineStyle) {
|
|
|
16900
17130
|
}
|
|
16901
17131
|
return text;
|
|
16902
17132
|
}
|
|
17133
|
+
function matchLooseBlock(contentLines, oldLines, newLines, newlineStyle) {
|
|
17134
|
+
for (let start = 0; start < contentLines.length; start += 1) {
|
|
17135
|
+
let contentIndex = start;
|
|
17136
|
+
let oldIndex = 0;
|
|
17137
|
+
while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
|
|
17138
|
+
const expected = trimRightSpaces(oldLines[oldIndex]);
|
|
17139
|
+
const actual = trimRightSpaces(contentLines[contentIndex]);
|
|
17140
|
+
if (expected === actual) {
|
|
17141
|
+
oldIndex += 1;
|
|
17142
|
+
contentIndex += 1;
|
|
17143
|
+
continue;
|
|
17144
|
+
}
|
|
17145
|
+
if (actual === "") {
|
|
17146
|
+
contentIndex += 1;
|
|
17147
|
+
continue;
|
|
17148
|
+
}
|
|
17149
|
+
if (expected === "") {
|
|
17150
|
+
oldIndex += 1;
|
|
17151
|
+
continue;
|
|
17152
|
+
}
|
|
17153
|
+
break;
|
|
17154
|
+
}
|
|
17155
|
+
if (oldIndex === oldLines.length) {
|
|
17156
|
+
const matchedOriginal = contentLines.slice(start, contentIndex).join("\n");
|
|
17157
|
+
const replacedNormalized = [
|
|
17158
|
+
...contentLines.slice(0, start),
|
|
17159
|
+
...newLines,
|
|
17160
|
+
...contentLines.slice(contentIndex)
|
|
17161
|
+
].join("\n");
|
|
17162
|
+
return {
|
|
17163
|
+
occurrencesBefore: 1,
|
|
17164
|
+
matchedOriginal,
|
|
17165
|
+
content: newlineStyle === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
|
|
17166
|
+
};
|
|
17167
|
+
}
|
|
17168
|
+
}
|
|
17169
|
+
return null;
|
|
17170
|
+
}
|
|
17171
|
+
function tryLooseBlockReplace(content, oldString, newString) {
|
|
17172
|
+
const contentLines = splitNormalizedLines(content);
|
|
17173
|
+
const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
|
|
17174
|
+
const newLines = splitNormalizedLines(newString);
|
|
17175
|
+
const newlineStyle = detectNewlineStyle(content);
|
|
17176
|
+
if (oldLines.length === 0) {
|
|
17177
|
+
return null;
|
|
17178
|
+
}
|
|
17179
|
+
const direct = matchLooseBlock(contentLines, oldLines, newLines, newlineStyle);
|
|
17180
|
+
if (direct !== null) {
|
|
17181
|
+
return direct;
|
|
17182
|
+
}
|
|
17183
|
+
if (hasLineNumberPrefixes(oldLines)) {
|
|
17184
|
+
const strippedOldLines = trimTrailingEmptyLines(stripLineNumberPrefixes(oldLines));
|
|
17185
|
+
if (strippedOldLines.length > 0) {
|
|
17186
|
+
return matchLooseBlock(contentLines, strippedOldLines, newLines, newlineStyle);
|
|
17187
|
+
}
|
|
17188
|
+
}
|
|
17189
|
+
return null;
|
|
17190
|
+
}
|
|
16903
17191
|
function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
|
|
16904
17192
|
assertStringArgument2(input, "content");
|
|
16905
17193
|
const buffer = encodeText(input, encoding, hasBom);
|
|
@@ -16981,37 +17269,89 @@ async function replaceTextFileText(input) {
|
|
|
16981
17269
|
if (input.oldString.length === 0) {
|
|
16982
17270
|
throw createTextError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16983
17271
|
}
|
|
17272
|
+
const prefixedBlock = parseLineNumberPrefixedBlock(input.oldString);
|
|
17273
|
+
const derivedScopedRange = prefixedBlock?.isContiguous ? {
|
|
17274
|
+
startLine: normalizedInput.startLine ?? prefixedBlock.startLine,
|
|
17275
|
+
endLine: normalizedInput.endLine ?? prefixedBlock.endLine,
|
|
17276
|
+
oldString: prefixedBlock.strippedText
|
|
17277
|
+
} : null;
|
|
17278
|
+
const effectiveOldString = derivedScopedRange?.oldString ?? input.oldString;
|
|
16984
17279
|
const resolved = await detectReadableTextFile({
|
|
16985
17280
|
filePath: input.filePath,
|
|
16986
17281
|
encoding: input.encoding ?? "auto",
|
|
16987
17282
|
allowExternal: input.allowExternal,
|
|
16988
17283
|
context: input.context
|
|
16989
17284
|
});
|
|
17285
|
+
const hasScopedRange = normalizedInput.startLine !== void 0 || normalizedInput.endLine !== void 0 || normalizedInput.startAnchor !== void 0 || normalizedInput.endAnchor !== void 0 || derivedScopedRange !== null;
|
|
17286
|
+
const preserveEncoding = normalizedInput.preserveEncoding ?? true;
|
|
17287
|
+
const preserveNewlineStyle = normalizedInput.preserveNewlineStyle ?? true;
|
|
17288
|
+
const requestedEncoding = normalizedInput.encoding ?? "auto";
|
|
17289
|
+
if (resolved.stat.size >= TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES && !hasScopedRange && preserveEncoding && preserveNewlineStyle && requestedEncoding === "auto") {
|
|
17290
|
+
const loadedForStreaming = await readWholeTextFile({
|
|
17291
|
+
filePath: input.filePath,
|
|
17292
|
+
encoding: requestedEncoding,
|
|
17293
|
+
allowExternal: input.allowExternal,
|
|
17294
|
+
context: input.context
|
|
17295
|
+
});
|
|
17296
|
+
return await replaceLargeTextFileText({
|
|
17297
|
+
loaded: loadedForStreaming,
|
|
17298
|
+
oldString: effectiveOldString,
|
|
17299
|
+
newString: input.newString,
|
|
17300
|
+
replaceAll: normalizedInput.replaceAll ?? false
|
|
17301
|
+
});
|
|
17302
|
+
}
|
|
16990
17303
|
const loaded = await readWholeTextFile({
|
|
16991
17304
|
filePath: input.filePath,
|
|
16992
17305
|
encoding: input.encoding ?? "auto",
|
|
16993
17306
|
allowExternal: input.allowExternal,
|
|
16994
17307
|
context: input.context
|
|
16995
17308
|
});
|
|
16996
|
-
const
|
|
17309
|
+
const scopedInput = derivedScopedRange === null ? normalizedInput : {
|
|
17310
|
+
...normalizedInput,
|
|
17311
|
+
startLine: derivedScopedRange.startLine,
|
|
17312
|
+
endLine: derivedScopedRange.endLine
|
|
17313
|
+
};
|
|
17314
|
+
const scope = resolveEditScope(loaded.content, scopedInput);
|
|
16997
17315
|
const replaceAll = normalizedInput.replaceAll ?? false;
|
|
16998
|
-
const
|
|
16999
|
-
const
|
|
17000
|
-
const occurrencesBefore = countOccurrences(scope.selectedText,
|
|
17316
|
+
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
17317
|
+
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
17318
|
+
const occurrencesBefore = countOccurrences(scope.selectedText, effectiveOldString);
|
|
17319
|
+
if (!replaceAll && occurrencesBefore === 0) {
|
|
17320
|
+
const loose = tryLooseBlockReplace(scope.selectedText, effectiveOldString, input.newString);
|
|
17321
|
+
if (loose !== null) {
|
|
17322
|
+
const outputText2 = `${loaded.content.slice(0, scope.rangeStart)}${loose.content}${loaded.content.slice(scope.rangeEnd)}`;
|
|
17323
|
+
ensureLossless(outputText2, targetEncoding, targetHasBom);
|
|
17324
|
+
const buffer2 = encodeText(outputText2, targetEncoding, targetHasBom);
|
|
17325
|
+
await fs3.writeFile(loaded.filePath, buffer2);
|
|
17326
|
+
return {
|
|
17327
|
+
mode: "replace",
|
|
17328
|
+
filePath: loaded.filePath,
|
|
17329
|
+
encoding: targetEncoding,
|
|
17330
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
17331
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
17332
|
+
confidence: loaded.confidence,
|
|
17333
|
+
hasBom: targetHasBom,
|
|
17334
|
+
replacements: 1,
|
|
17335
|
+
occurrencesBefore: loose.occurrencesBefore,
|
|
17336
|
+
bytesRead: loaded.bytesRead,
|
|
17337
|
+
bytesWritten: buffer2.byteLength,
|
|
17338
|
+
newlineStyle: detectNewlineStyle(outputText2),
|
|
17339
|
+
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, loose.matchedOriginal, input.newString)
|
|
17340
|
+
};
|
|
17341
|
+
}
|
|
17342
|
+
}
|
|
17001
17343
|
if (replaceAll) {
|
|
17002
17344
|
if (occurrencesBefore === 0) {
|
|
17003
|
-
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText,
|
|
17345
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, effectiveOldString));
|
|
17004
17346
|
}
|
|
17005
17347
|
} else if (occurrencesBefore === 0) {
|
|
17006
|
-
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText,
|
|
17348
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, effectiveOldString));
|
|
17007
17349
|
} else if (occurrencesBefore > 1) {
|
|
17008
|
-
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${
|
|
17350
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${effectiveOldString}`);
|
|
17009
17351
|
}
|
|
17010
17352
|
const alignedNewString = normalizedInput.preserveNewlineStyle === false ? input.newString : alignTextToNewlineStyle(input.newString, loaded.newlineStyle);
|
|
17011
|
-
const replaced = replaceAll ? scope.selectedText.split(
|
|
17353
|
+
const replaced = replaceAll ? scope.selectedText.split(effectiveOldString).join(alignedNewString) : scope.selectedText.replace(effectiveOldString, alignedNewString);
|
|
17012
17354
|
const outputText = `${loaded.content.slice(0, scope.rangeStart)}${replaced}${loaded.content.slice(scope.rangeEnd)}`;
|
|
17013
|
-
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
17014
|
-
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
17015
17355
|
ensureLossless(outputText, targetEncoding, targetHasBom);
|
|
17016
17356
|
const buffer = encodeText(outputText, targetEncoding, targetHasBom);
|
|
17017
17357
|
await fs3.writeFile(loaded.filePath, buffer);
|
|
@@ -17028,7 +17368,7 @@ async function replaceTextFileText(input) {
|
|
|
17028
17368
|
bytesRead: loaded.bytesRead,
|
|
17029
17369
|
bytesWritten: buffer.byteLength,
|
|
17030
17370
|
newlineStyle: detectNewlineStyle(outputText),
|
|
17031
|
-
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding,
|
|
17371
|
+
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, effectiveOldString, alignedNewString)
|
|
17032
17372
|
};
|
|
17033
17373
|
}
|
|
17034
17374
|
|