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
|
@@ -3910,6 +3910,139 @@ function estimateSessionTokens(messages) {
|
|
|
3910
3910
|
return Math.ceil(totalChars / 4);
|
|
3911
3911
|
}
|
|
3912
3912
|
|
|
3913
|
+
// src/lib/encoding-memory.ts
|
|
3914
|
+
import fs from "fs/promises";
|
|
3915
|
+
import os from "os";
|
|
3916
|
+
import path from "path";
|
|
3917
|
+
var ENCODING_MEMORY_VERSION = 1;
|
|
3918
|
+
var ENCODING_MEMORY_FILE_NAME = "encoding-memory.json";
|
|
3919
|
+
var CONFIG_DIR_ENV = "OPENCODE_GBK_TOOLS_CONFIG_DIR";
|
|
3920
|
+
var memoryCache = null;
|
|
3921
|
+
var loadingPromise = null;
|
|
3922
|
+
function resolveConfigDirectory() {
|
|
3923
|
+
const override = process.env[CONFIG_DIR_ENV];
|
|
3924
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
3925
|
+
return path.resolve(override);
|
|
3926
|
+
}
|
|
3927
|
+
if (process.platform === "win32") {
|
|
3928
|
+
return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "opencode-gbk-tools");
|
|
3929
|
+
}
|
|
3930
|
+
if (process.platform === "darwin") {
|
|
3931
|
+
return path.join(os.homedir(), "Library", "Application Support", "opencode-gbk-tools");
|
|
3932
|
+
}
|
|
3933
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "opencode-gbk-tools");
|
|
3934
|
+
}
|
|
3935
|
+
function normalizeFilePath(filePath) {
|
|
3936
|
+
const resolved = path.normalize(path.resolve(filePath));
|
|
3937
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
3938
|
+
}
|
|
3939
|
+
function toStoredFilePath(filePath) {
|
|
3940
|
+
return path.normalize(path.resolve(filePath));
|
|
3941
|
+
}
|
|
3942
|
+
function isRememberedEntry(value) {
|
|
3943
|
+
if (!value || typeof value !== "object") {
|
|
3944
|
+
return false;
|
|
3945
|
+
}
|
|
3946
|
+
const entry = value;
|
|
3947
|
+
return typeof entry.filePath === "string" && isRememberedGbkEncoding(entry.encoding) && typeof entry.mtimeMs === "number" && Number.isFinite(entry.mtimeMs) && typeof entry.size === "number" && Number.isFinite(entry.size) && typeof entry.lastConfirmedAt === "number" && Number.isFinite(entry.lastConfirmedAt);
|
|
3948
|
+
}
|
|
3949
|
+
async function readEncodingMemoryMap() {
|
|
3950
|
+
if (memoryCache) {
|
|
3951
|
+
return memoryCache;
|
|
3952
|
+
}
|
|
3953
|
+
if (loadingPromise) {
|
|
3954
|
+
return loadingPromise;
|
|
3955
|
+
}
|
|
3956
|
+
loadingPromise = (async () => {
|
|
3957
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
3958
|
+
try {
|
|
3959
|
+
const raw = await fs.readFile(getEncodingMemoryFilePath(), "utf8");
|
|
3960
|
+
const parsed = JSON.parse(raw);
|
|
3961
|
+
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
3962
|
+
for (const entry of entries) {
|
|
3963
|
+
if (isRememberedEntry(entry)) {
|
|
3964
|
+
map2.set(normalizeFilePath(entry.filePath), entry);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
} catch (error45) {
|
|
3968
|
+
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
3969
|
+
throw error45;
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
memoryCache = map2;
|
|
3973
|
+
loadingPromise = null;
|
|
3974
|
+
return map2;
|
|
3975
|
+
})();
|
|
3976
|
+
return loadingPromise;
|
|
3977
|
+
}
|
|
3978
|
+
async function persistEncodingMemoryMap(map2) {
|
|
3979
|
+
const entries = [...map2.values()].sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
3980
|
+
const payload = {
|
|
3981
|
+
version: ENCODING_MEMORY_VERSION,
|
|
3982
|
+
entries
|
|
3983
|
+
};
|
|
3984
|
+
const filePath = getEncodingMemoryFilePath();
|
|
3985
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
3986
|
+
await fs.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
3987
|
+
}
|
|
3988
|
+
async function statRegularFile(filePath) {
|
|
3989
|
+
try {
|
|
3990
|
+
const stat = await fs.stat(filePath);
|
|
3991
|
+
return stat.isFile() ? stat : null;
|
|
3992
|
+
} catch (error45) {
|
|
3993
|
+
if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
|
|
3994
|
+
return null;
|
|
3995
|
+
}
|
|
3996
|
+
throw error45;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
function isRememberedGbkEncoding(encoding) {
|
|
4000
|
+
return encoding === "gbk" || encoding === "gb18030";
|
|
4001
|
+
}
|
|
4002
|
+
function getEncodingMemoryFilePath() {
|
|
4003
|
+
return path.join(resolveConfigDirectory(), ENCODING_MEMORY_FILE_NAME);
|
|
4004
|
+
}
|
|
4005
|
+
async function rememberGbkEncoding(filePath, encoding) {
|
|
4006
|
+
const stat = await statRegularFile(filePath);
|
|
4007
|
+
if (!stat) {
|
|
4008
|
+
return null;
|
|
4009
|
+
}
|
|
4010
|
+
const map2 = await readEncodingMemoryMap();
|
|
4011
|
+
const entry = {
|
|
4012
|
+
filePath: toStoredFilePath(filePath),
|
|
4013
|
+
encoding,
|
|
4014
|
+
mtimeMs: stat.mtimeMs,
|
|
4015
|
+
size: Number(stat.size),
|
|
4016
|
+
lastConfirmedAt: Date.now()
|
|
4017
|
+
};
|
|
4018
|
+
map2.set(normalizeFilePath(filePath), entry);
|
|
4019
|
+
await persistEncodingMemoryMap(map2);
|
|
4020
|
+
return entry;
|
|
4021
|
+
}
|
|
4022
|
+
async function forgetRememberedEncoding(filePath) {
|
|
4023
|
+
const map2 = await readEncodingMemoryMap();
|
|
4024
|
+
const deleted = map2.delete(normalizeFilePath(filePath));
|
|
4025
|
+
if (deleted) {
|
|
4026
|
+
await persistEncodingMemoryMap(map2);
|
|
4027
|
+
}
|
|
4028
|
+
return deleted;
|
|
4029
|
+
}
|
|
4030
|
+
async function getRememberedGbkEncoding(filePath) {
|
|
4031
|
+
const map2 = await readEncodingMemoryMap();
|
|
4032
|
+
const key = normalizeFilePath(filePath);
|
|
4033
|
+
const entry = map2.get(key);
|
|
4034
|
+
if (!entry) {
|
|
4035
|
+
return null;
|
|
4036
|
+
}
|
|
4037
|
+
const stat = await statRegularFile(filePath);
|
|
4038
|
+
if (!stat || stat.mtimeMs !== entry.mtimeMs || Number(stat.size) !== entry.size) {
|
|
4039
|
+
map2.delete(key);
|
|
4040
|
+
await persistEncodingMemoryMap(map2);
|
|
4041
|
+
return null;
|
|
4042
|
+
}
|
|
4043
|
+
return entry;
|
|
4044
|
+
}
|
|
4045
|
+
|
|
3913
4046
|
// node_modules/zod/v4/classic/external.js
|
|
3914
4047
|
var external_exports = {};
|
|
3915
4048
|
__export(external_exports, {
|
|
@@ -4641,10 +4774,10 @@ function mergeDefs(...defs) {
|
|
|
4641
4774
|
function cloneDef(schema) {
|
|
4642
4775
|
return mergeDefs(schema._zod.def);
|
|
4643
4776
|
}
|
|
4644
|
-
function getElementAtPath(obj,
|
|
4645
|
-
if (!
|
|
4777
|
+
function getElementAtPath(obj, path5) {
|
|
4778
|
+
if (!path5)
|
|
4646
4779
|
return obj;
|
|
4647
|
-
return
|
|
4780
|
+
return path5.reduce((acc, key) => acc?.[key], obj);
|
|
4648
4781
|
}
|
|
4649
4782
|
function promiseAllObject(promisesObj) {
|
|
4650
4783
|
const keys = Object.keys(promisesObj);
|
|
@@ -5005,11 +5138,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5005
5138
|
}
|
|
5006
5139
|
return false;
|
|
5007
5140
|
}
|
|
5008
|
-
function prefixIssues(
|
|
5141
|
+
function prefixIssues(path5, issues) {
|
|
5009
5142
|
return issues.map((iss) => {
|
|
5010
5143
|
var _a;
|
|
5011
5144
|
(_a = iss).path ?? (_a.path = []);
|
|
5012
|
-
iss.path.unshift(
|
|
5145
|
+
iss.path.unshift(path5);
|
|
5013
5146
|
return iss;
|
|
5014
5147
|
});
|
|
5015
5148
|
}
|
|
@@ -5177,7 +5310,7 @@ function treeifyError(error45, _mapper) {
|
|
|
5177
5310
|
return issue2.message;
|
|
5178
5311
|
};
|
|
5179
5312
|
const result = { errors: [] };
|
|
5180
|
-
const processError = (error46,
|
|
5313
|
+
const processError = (error46, path5 = []) => {
|
|
5181
5314
|
var _a, _b;
|
|
5182
5315
|
for (const issue2 of error46.issues) {
|
|
5183
5316
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -5187,7 +5320,7 @@ function treeifyError(error45, _mapper) {
|
|
|
5187
5320
|
} else if (issue2.code === "invalid_element") {
|
|
5188
5321
|
processError({ issues: issue2.issues }, issue2.path);
|
|
5189
5322
|
} else {
|
|
5190
|
-
const fullpath = [...
|
|
5323
|
+
const fullpath = [...path5, ...issue2.path];
|
|
5191
5324
|
if (fullpath.length === 0) {
|
|
5192
5325
|
result.errors.push(mapper(issue2));
|
|
5193
5326
|
continue;
|
|
@@ -5219,8 +5352,8 @@ function treeifyError(error45, _mapper) {
|
|
|
5219
5352
|
}
|
|
5220
5353
|
function toDotPath(_path) {
|
|
5221
5354
|
const segs = [];
|
|
5222
|
-
const
|
|
5223
|
-
for (const seg of
|
|
5355
|
+
const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
5356
|
+
for (const seg of path5) {
|
|
5224
5357
|
if (typeof seg === "number")
|
|
5225
5358
|
segs.push(`[${seg}]`);
|
|
5226
5359
|
else if (typeof seg === "symbol")
|
|
@@ -16462,8 +16595,8 @@ function truncateToolOutput(content, sessionID, fallback = FALLBACK_MAX_OUTPUT_C
|
|
|
16462
16595
|
var import_iconv_lite = __toESM(require_lib(), 1);
|
|
16463
16596
|
import crypto from "crypto";
|
|
16464
16597
|
import { createReadStream } from "fs";
|
|
16465
|
-
import
|
|
16466
|
-
import
|
|
16598
|
+
import fs3 from "fs/promises";
|
|
16599
|
+
import path3 from "path";
|
|
16467
16600
|
|
|
16468
16601
|
// src/lib/errors.ts
|
|
16469
16602
|
var GbkToolError = class extends Error {
|
|
@@ -16482,26 +16615,26 @@ function createGbkError(code, message, cause) {
|
|
|
16482
16615
|
var createTextError = createGbkError;
|
|
16483
16616
|
|
|
16484
16617
|
// src/lib/path-sandbox.ts
|
|
16485
|
-
import
|
|
16486
|
-
import
|
|
16618
|
+
import fs2 from "fs/promises";
|
|
16619
|
+
import path2 from "path";
|
|
16487
16620
|
function resolveBaseDirectory(context) {
|
|
16488
16621
|
const value = context.directory ?? process.cwd();
|
|
16489
|
-
return
|
|
16622
|
+
return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
|
|
16490
16623
|
}
|
|
16491
16624
|
function resolveWorkspaceRoot(context) {
|
|
16492
16625
|
const value = context.worktree ?? context.directory ?? process.cwd();
|
|
16493
|
-
return
|
|
16626
|
+
return path2.isAbsolute(value) ? value : path2.resolve(process.cwd(), value);
|
|
16494
16627
|
}
|
|
16495
16628
|
function resolveCandidatePath(filePath, context) {
|
|
16496
|
-
return
|
|
16629
|
+
return path2.resolve(resolveBaseDirectory(context), filePath);
|
|
16497
16630
|
}
|
|
16498
16631
|
async function resolveExistingAnchor(filePath) {
|
|
16499
16632
|
let current = filePath;
|
|
16500
16633
|
while (true) {
|
|
16501
16634
|
try {
|
|
16502
|
-
return await
|
|
16635
|
+
return await fs2.realpath(current);
|
|
16503
16636
|
} catch {
|
|
16504
|
-
const parent =
|
|
16637
|
+
const parent = path2.dirname(current);
|
|
16505
16638
|
if (parent === current) {
|
|
16506
16639
|
throw createGbkError("GBK_IO_ERROR", `\u65E0\u6CD5\u89E3\u6790\u8DEF\u5F84\u951A\u70B9: ${filePath}`);
|
|
16507
16640
|
}
|
|
@@ -16513,10 +16646,10 @@ async function assertPathAllowed(filePath, context, allowExternal = false) {
|
|
|
16513
16646
|
const candidatePath = resolveCandidatePath(filePath, context);
|
|
16514
16647
|
const workspaceRoot = resolveWorkspaceRoot(context);
|
|
16515
16648
|
if (!allowExternal) {
|
|
16516
|
-
const realWorkspaceRoot = await
|
|
16649
|
+
const realWorkspaceRoot = await fs2.realpath(workspaceRoot);
|
|
16517
16650
|
const realCandidateAnchor = await resolveExistingAnchor(candidatePath);
|
|
16518
|
-
const relative =
|
|
16519
|
-
if (relative === "" || !relative.startsWith("..") && !
|
|
16651
|
+
const relative = path2.relative(realWorkspaceRoot, realCandidateAnchor);
|
|
16652
|
+
if (relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative)) {
|
|
16520
16653
|
return { candidatePath, workspaceRoot };
|
|
16521
16654
|
}
|
|
16522
16655
|
throw createGbkError("GBK_PATH_OUTSIDE_ROOT", `\u76EE\u6807\u8DEF\u5F84\u8D85\u51FA\u5DE5\u4F5C\u76EE\u5F55\u8303\u56F4: ${candidatePath}`);
|
|
@@ -16538,8 +16671,8 @@ function buildGbkLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
16538
16671
|
const afterLines = normalizeNewlines(afterText).split("\n");
|
|
16539
16672
|
const maxLines = Math.max(beforeLines.length, afterLines.length);
|
|
16540
16673
|
const header = [
|
|
16541
|
-
`${ANSI_DIM}--- ${
|
|
16542
|
-
`${ANSI_DIM}+++ ${
|
|
16674
|
+
`${ANSI_DIM}--- ${path3.basename(filePath)} (${encoding})${ANSI_RESET}`,
|
|
16675
|
+
`${ANSI_DIM}+++ ${path3.basename(filePath)} (${encoding})${ANSI_RESET}`
|
|
16543
16676
|
];
|
|
16544
16677
|
const body = [];
|
|
16545
16678
|
for (let index = 0; index < maxLines; index += 1) {
|
|
@@ -16585,45 +16718,103 @@ function assertInsertArguments(input) {
|
|
|
16585
16718
|
throw createGbkError("GBK_INVALID_ARGUMENT", "content \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16586
16719
|
}
|
|
16587
16720
|
if (input.replaceAll !== void 0) {
|
|
16588
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\
|
|
16721
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 replaceAll");
|
|
16589
16722
|
}
|
|
16590
16723
|
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\
|
|
16724
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 startLine/endLine/startAnchor/endAnchor");
|
|
16592
16725
|
}
|
|
16593
16726
|
}
|
|
16594
|
-
function
|
|
16595
|
-
assertStringArgument(text, "text");
|
|
16596
|
-
assertStringArgument(token, "anchor");
|
|
16597
|
-
assertPositiveInteger(occurrence, "occurrence");
|
|
16727
|
+
function collectOccurrencePositions(text, token) {
|
|
16598
16728
|
if (token.length === 0) {
|
|
16599
|
-
|
|
16729
|
+
return [];
|
|
16600
16730
|
}
|
|
16601
|
-
|
|
16731
|
+
const positions = [];
|
|
16602
16732
|
let searchFrom = 0;
|
|
16603
16733
|
while (true) {
|
|
16604
16734
|
const index = text.indexOf(token, searchFrom);
|
|
16605
16735
|
if (index === -1) {
|
|
16606
|
-
|
|
16607
|
-
}
|
|
16608
|
-
count += 1;
|
|
16609
|
-
if (count === occurrence) {
|
|
16610
|
-
return { index, total: countOccurrences(text, token) };
|
|
16736
|
+
return positions;
|
|
16611
16737
|
}
|
|
16738
|
+
positions.push(index);
|
|
16612
16739
|
searchFrom = index + token.length;
|
|
16613
16740
|
}
|
|
16614
|
-
|
|
16615
|
-
|
|
16741
|
+
}
|
|
16742
|
+
function buildFlexibleSearchVariants(token, newlineStyle) {
|
|
16743
|
+
const variants = /* @__PURE__ */ new Set();
|
|
16744
|
+
const pushVariant = (value) => {
|
|
16745
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16746
|
+
return;
|
|
16747
|
+
}
|
|
16748
|
+
variants.add(value);
|
|
16749
|
+
};
|
|
16750
|
+
pushVariant(token);
|
|
16751
|
+
pushVariant(alignTextToNewlineStyle(token, newlineStyle));
|
|
16752
|
+
const prefixedBlock = parseLineNumberPrefixedBlock(token);
|
|
16753
|
+
if (prefixedBlock) {
|
|
16754
|
+
pushVariant(prefixedBlock.strippedText);
|
|
16755
|
+
pushVariant(alignTextToNewlineStyle(prefixedBlock.strippedText, newlineStyle));
|
|
16756
|
+
}
|
|
16757
|
+
const trimmedLines = trimTrailingEmptyLines(splitNormalizedLines(token));
|
|
16758
|
+
if (trimmedLines.length > 0) {
|
|
16759
|
+
const trimmedToken = trimmedLines.join("\n");
|
|
16760
|
+
pushVariant(trimmedToken);
|
|
16761
|
+
pushVariant(alignTextToNewlineStyle(trimmedToken, newlineStyle));
|
|
16762
|
+
const trimmedPrefixedBlock = parseLineNumberPrefixedBlock(trimmedToken);
|
|
16763
|
+
if (trimmedPrefixedBlock) {
|
|
16764
|
+
pushVariant(trimmedPrefixedBlock.strippedText);
|
|
16765
|
+
pushVariant(alignTextToNewlineStyle(trimmedPrefixedBlock.strippedText, newlineStyle));
|
|
16766
|
+
}
|
|
16767
|
+
}
|
|
16768
|
+
return [...variants];
|
|
16769
|
+
}
|
|
16770
|
+
function findNextFlexibleMatch(text, token, searchFrom, newlineStyle) {
|
|
16771
|
+
let bestMatch = null;
|
|
16772
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16773
|
+
const index = text.indexOf(candidate, searchFrom);
|
|
16774
|
+
if (index === -1) {
|
|
16775
|
+
continue;
|
|
16776
|
+
}
|
|
16777
|
+
if (bestMatch === null || index < bestMatch.index || index === bestMatch.index && candidate.length > bestMatch.matchedToken.length) {
|
|
16778
|
+
bestMatch = { index, matchedToken: candidate };
|
|
16779
|
+
}
|
|
16780
|
+
}
|
|
16781
|
+
return bestMatch;
|
|
16782
|
+
}
|
|
16783
|
+
function findFlexibleOccurrenceIndex(text, token, occurrence, newlineStyle) {
|
|
16784
|
+
assertStringArgument(text, "text");
|
|
16785
|
+
assertStringArgument(token, "anchor");
|
|
16786
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
16787
|
+
if (token.length === 0) {
|
|
16788
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16789
|
+
}
|
|
16790
|
+
let maxMatches = 0;
|
|
16791
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16792
|
+
const positions = collectOccurrencePositions(text, candidate);
|
|
16793
|
+
if (positions.length === 0) {
|
|
16794
|
+
continue;
|
|
16795
|
+
}
|
|
16796
|
+
maxMatches = Math.max(maxMatches, positions.length);
|
|
16797
|
+
if (positions.length >= occurrence) {
|
|
16798
|
+
return {
|
|
16799
|
+
index: positions[occurrence - 1],
|
|
16800
|
+
total: positions.length,
|
|
16801
|
+
matchedToken: candidate
|
|
16802
|
+
};
|
|
16803
|
+
}
|
|
16804
|
+
}
|
|
16805
|
+
if (maxMatches === 0) {
|
|
16806
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
16616
16807
|
}
|
|
16617
|
-
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\
|
|
16808
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${maxMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
16618
16809
|
}
|
|
16619
16810
|
function insertByAnchor(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
16620
16811
|
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 +
|
|
16812
|
+
const located = findFlexibleOccurrenceIndex(text, anchor, occurrence, newlineStyle);
|
|
16813
|
+
const insertionPoint = mode === "insertAfter" ? located.index + located.matchedToken.length : located.index;
|
|
16623
16814
|
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
16624
16815
|
if (alreadyExists) {
|
|
16625
16816
|
if (ifExists === "error") {
|
|
16626
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
16817
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
16627
16818
|
}
|
|
16628
16819
|
if (ifExists === "skip") {
|
|
16629
16820
|
return {
|
|
@@ -16664,7 +16855,7 @@ function normalizeOptionalPositiveInteger(value, name) {
|
|
|
16664
16855
|
}
|
|
16665
16856
|
function assertNotBinary(buffer) {
|
|
16666
16857
|
if (buffer.includes(0)) {
|
|
16667
|
-
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\
|
|
16858
|
+
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
|
|
16668
16859
|
}
|
|
16669
16860
|
}
|
|
16670
16861
|
function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
|
|
@@ -16791,19 +16982,15 @@ function applyAnchors(text, startAnchor, endAnchor) {
|
|
|
16791
16982
|
}
|
|
16792
16983
|
let rangeStart = 0;
|
|
16793
16984
|
let rangeEnd = text.length;
|
|
16985
|
+
const newlineStyle = detectNewlineStyle(text);
|
|
16794
16986
|
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;
|
|
16987
|
+
const found = findFlexibleOccurrenceIndex(text, startAnchor, 1, newlineStyle);
|
|
16988
|
+
rangeStart = found.index + found.matchedToken.length;
|
|
16800
16989
|
}
|
|
16801
16990
|
if (endAnchor) {
|
|
16802
|
-
const
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
}
|
|
16806
|
-
rangeEnd = found;
|
|
16991
|
+
const searchText = text.slice(rangeStart);
|
|
16992
|
+
const found = findFlexibleOccurrenceIndex(searchText, endAnchor, 1, newlineStyle);
|
|
16993
|
+
rangeEnd = rangeStart + found.index;
|
|
16807
16994
|
}
|
|
16808
16995
|
if (rangeEnd < rangeStart) {
|
|
16809
16996
|
throw createGbkError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
@@ -16854,9 +17041,9 @@ function getNearestContext(content, oldString) {
|
|
|
16854
17041
|
}
|
|
16855
17042
|
function buildNoMatchMessage(content, oldString) {
|
|
16856
17043
|
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\
|
|
17044
|
+
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
17045
|
+
"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",
|
|
17046
|
+
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
16860
17047
|
getNearestContext(content, oldString)
|
|
16861
17048
|
].join("\n");
|
|
16862
17049
|
}
|
|
@@ -16868,8 +17055,10 @@ function stripLineNumberPrefixes(lines) {
|
|
|
16868
17055
|
return lines.map((l) => l.replace(/^\d+: /, ""));
|
|
16869
17056
|
}
|
|
16870
17057
|
function parseLineNumberPrefixedBlock(text) {
|
|
16871
|
-
const
|
|
16872
|
-
|
|
17058
|
+
const normalizedText = normalizeNewlines(text);
|
|
17059
|
+
const hasTrailingNewline = normalizedText.endsWith("\n");
|
|
17060
|
+
const lines = trimTrailingEmptyLines(splitNormalizedLines(text));
|
|
17061
|
+
if (lines.length === 0 || !hasLineNumberPrefixes(lines)) {
|
|
16873
17062
|
return null;
|
|
16874
17063
|
}
|
|
16875
17064
|
const lineNumbers = [];
|
|
@@ -16884,7 +17073,7 @@ function parseLineNumberPrefixedBlock(text) {
|
|
|
16884
17073
|
}
|
|
16885
17074
|
return {
|
|
16886
17075
|
lineNumbers,
|
|
16887
|
-
strippedText: strippedLines.join("\n")
|
|
17076
|
+
strippedText: `${strippedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`,
|
|
16888
17077
|
isContiguous: lineNumbers.every((lineNumber, index) => index === 0 || lineNumber === lineNumbers[index - 1] + 1),
|
|
16889
17078
|
startLine: lineNumbers[0],
|
|
16890
17079
|
endLine: lineNumbers[lineNumbers.length - 1]
|
|
@@ -16977,12 +17166,12 @@ async function resolveReadableGbkFile(input) {
|
|
|
16977
17166
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
16978
17167
|
let stat;
|
|
16979
17168
|
try {
|
|
16980
|
-
stat = await
|
|
17169
|
+
stat = await fs3.stat(candidatePath);
|
|
16981
17170
|
} catch (error45) {
|
|
16982
|
-
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\
|
|
17171
|
+
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
16983
17172
|
}
|
|
16984
17173
|
if (stat.isDirectory()) {
|
|
16985
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
17174
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16986
17175
|
}
|
|
16987
17176
|
return {
|
|
16988
17177
|
filePath: candidatePath,
|
|
@@ -16992,7 +17181,7 @@ async function resolveReadableGbkFile(input) {
|
|
|
16992
17181
|
}
|
|
16993
17182
|
async function readWholeGbkTextFile(input) {
|
|
16994
17183
|
try {
|
|
16995
|
-
const buffer = await
|
|
17184
|
+
const buffer = await fs3.readFile(input.filePath);
|
|
16996
17185
|
const content = await readBufferAsText(buffer, input.encoding);
|
|
16997
17186
|
return {
|
|
16998
17187
|
filePath: input.filePath,
|
|
@@ -17136,7 +17325,7 @@ async function appendEncodedText(filePath, encoding, text) {
|
|
|
17136
17325
|
return 0;
|
|
17137
17326
|
}
|
|
17138
17327
|
const buffer = import_iconv_lite.default.encode(text, encoding);
|
|
17139
|
-
await
|
|
17328
|
+
await fs3.appendFile(filePath, buffer);
|
|
17140
17329
|
return buffer.byteLength;
|
|
17141
17330
|
}
|
|
17142
17331
|
async function copyFileByteRangeToHandle(sourcePath, handle, start, endExclusive) {
|
|
@@ -17187,7 +17376,7 @@ function replaceScopedTextContent(scopeText, oldString, newString, replaceAll, n
|
|
|
17187
17376
|
} else if (occurrencesBefore === 0) {
|
|
17188
17377
|
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scopeText, oldString));
|
|
17189
17378
|
} else if (occurrencesBefore > 1) {
|
|
17190
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17379
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${oldString}`);
|
|
17191
17380
|
}
|
|
17192
17381
|
const alignedNewString = alignTextToNewlineStyle(newString, newlineStyle);
|
|
17193
17382
|
return {
|
|
@@ -17215,11 +17404,11 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17215
17404
|
input.replaceAll,
|
|
17216
17405
|
lineIndex.newlineStyle
|
|
17217
17406
|
);
|
|
17218
|
-
const tempPath =
|
|
17219
|
-
|
|
17220
|
-
`${
|
|
17407
|
+
const tempPath = path3.join(
|
|
17408
|
+
path3.dirname(input.filePath),
|
|
17409
|
+
`${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17221
17410
|
);
|
|
17222
|
-
const handle = await
|
|
17411
|
+
const handle = await fs3.open(tempPath, "w");
|
|
17223
17412
|
let bytesWritten = 0;
|
|
17224
17413
|
try {
|
|
17225
17414
|
bytesWritten += await copyFileByteRangeToHandle(input.filePath, handle, 0, rangeStart);
|
|
@@ -17227,8 +17416,8 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17227
17416
|
bytesWritten += await copyFileByteRangeToHandle(input.filePath, handle, rangeEnd, toSafeNumber(input.stat.size));
|
|
17228
17417
|
await handle.close();
|
|
17229
17418
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
17230
|
-
await
|
|
17231
|
-
await
|
|
17419
|
+
await fs3.chmod(tempPath, mode);
|
|
17420
|
+
await fs3.rename(tempPath, input.filePath);
|
|
17232
17421
|
return {
|
|
17233
17422
|
mode: "replace",
|
|
17234
17423
|
filePath: input.filePath,
|
|
@@ -17240,19 +17429,22 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17240
17429
|
};
|
|
17241
17430
|
} catch (error45) {
|
|
17242
17431
|
await handle.close().catch(() => void 0);
|
|
17243
|
-
await
|
|
17432
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
17244
17433
|
throw error45;
|
|
17245
17434
|
}
|
|
17246
17435
|
}
|
|
17247
17436
|
async function replaceLargeGbkFileByAnchor(input) {
|
|
17248
|
-
const
|
|
17249
|
-
|
|
17250
|
-
|
|
17437
|
+
const lineIndex = await getGbkLineIndex(input);
|
|
17438
|
+
const newlineStyle = lineIndex.newlineStyle;
|
|
17439
|
+
const tempPath = path3.join(
|
|
17440
|
+
path3.dirname(input.filePath),
|
|
17441
|
+
`${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17251
17442
|
);
|
|
17252
|
-
const handle = await
|
|
17253
|
-
const alignedContent = input.content
|
|
17254
|
-
const
|
|
17255
|
-
const
|
|
17443
|
+
const handle = await fs3.open(tempPath, "w");
|
|
17444
|
+
const alignedContent = alignTextToNewlineStyle(input.content, newlineStyle);
|
|
17445
|
+
const anchorVariants = buildFlexibleSearchVariants(input.anchor, newlineStyle);
|
|
17446
|
+
const maxAnchorLength = anchorVariants.reduce((maxLength, candidate) => Math.max(maxLength, candidate.length), input.anchor.length);
|
|
17447
|
+
const carryLength = Math.max(maxAnchorLength + alignedContent.length, 1);
|
|
17256
17448
|
let decoded = "";
|
|
17257
17449
|
let scanFrom = 0;
|
|
17258
17450
|
let totalMatches = 0;
|
|
@@ -17276,23 +17468,23 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17276
17468
|
await visitDecodedTextChunks(input, async (text) => {
|
|
17277
17469
|
decoded += text;
|
|
17278
17470
|
while (!inserted) {
|
|
17279
|
-
const
|
|
17280
|
-
if (
|
|
17471
|
+
const located = findNextFlexibleMatch(decoded, input.anchor, scanFrom, newlineStyle);
|
|
17472
|
+
if (located === null) {
|
|
17281
17473
|
break;
|
|
17282
17474
|
}
|
|
17283
17475
|
totalMatches += 1;
|
|
17284
|
-
const afterAnchor =
|
|
17476
|
+
const afterAnchor = located.index + located.matchedToken.length;
|
|
17285
17477
|
if (totalMatches !== input.occurrence) {
|
|
17286
17478
|
scanFrom = afterAnchor;
|
|
17287
17479
|
continue;
|
|
17288
17480
|
}
|
|
17289
|
-
const before = decoded.slice(0,
|
|
17481
|
+
const before = decoded.slice(0, located.index);
|
|
17290
17482
|
const after = decoded.slice(afterAnchor);
|
|
17291
|
-
const anchorAndAfter = decoded.slice(
|
|
17483
|
+
const anchorAndAfter = decoded.slice(located.index);
|
|
17292
17484
|
const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
|
|
17293
17485
|
if (alreadyExists) {
|
|
17294
17486
|
if (input.ifExists === "error") {
|
|
17295
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
17487
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17296
17488
|
}
|
|
17297
17489
|
if (input.ifExists === "skip") {
|
|
17298
17490
|
skipped = true;
|
|
@@ -17304,7 +17496,7 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17304
17496
|
}
|
|
17305
17497
|
if (input.mode === "insertAfter") {
|
|
17306
17498
|
bytesWritten += await writeEncodedText(handle, input.encoding, before);
|
|
17307
|
-
bytesWritten += await writeEncodedText(handle, input.encoding,
|
|
17499
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, located.matchedToken);
|
|
17308
17500
|
bytesWritten += await writeEncodedText(handle, input.encoding, alignedContent);
|
|
17309
17501
|
bytesWritten += await writeEncodedText(handle, input.encoding, after);
|
|
17310
17502
|
} else {
|
|
@@ -17323,23 +17515,23 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17323
17515
|
});
|
|
17324
17516
|
if (!inserted) {
|
|
17325
17517
|
while (true) {
|
|
17326
|
-
const
|
|
17327
|
-
if (
|
|
17518
|
+
const located = findNextFlexibleMatch(decoded, input.anchor, scanFrom, newlineStyle);
|
|
17519
|
+
if (located === null) {
|
|
17328
17520
|
break;
|
|
17329
17521
|
}
|
|
17330
17522
|
totalMatches += 1;
|
|
17331
|
-
const afterAnchor =
|
|
17523
|
+
const afterAnchor = located.index + located.matchedToken.length;
|
|
17332
17524
|
if (totalMatches !== input.occurrence) {
|
|
17333
17525
|
scanFrom = afterAnchor;
|
|
17334
17526
|
continue;
|
|
17335
17527
|
}
|
|
17336
|
-
const before = decoded.slice(0,
|
|
17528
|
+
const before = decoded.slice(0, located.index);
|
|
17337
17529
|
const after = decoded.slice(afterAnchor);
|
|
17338
|
-
const anchorAndAfter = decoded.slice(
|
|
17530
|
+
const anchorAndAfter = decoded.slice(located.index);
|
|
17339
17531
|
const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
|
|
17340
17532
|
if (alreadyExists) {
|
|
17341
17533
|
if (input.ifExists === "error") {
|
|
17342
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
17534
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17343
17535
|
}
|
|
17344
17536
|
if (input.ifExists === "skip") {
|
|
17345
17537
|
skipped = true;
|
|
@@ -17347,22 +17539,22 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17347
17539
|
break;
|
|
17348
17540
|
}
|
|
17349
17541
|
}
|
|
17350
|
-
decoded = input.mode === "insertAfter" ? `${before}${
|
|
17542
|
+
decoded = input.mode === "insertAfter" ? `${before}${located.matchedToken}${alignedContent}${after}` : `${before}${alignedContent}${anchorAndAfter}`;
|
|
17351
17543
|
inserted = true;
|
|
17352
17544
|
break;
|
|
17353
17545
|
}
|
|
17354
17546
|
}
|
|
17355
17547
|
if (!inserted && totalMatches === 0) {
|
|
17356
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\
|
|
17548
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${input.anchor}`);
|
|
17357
17549
|
}
|
|
17358
17550
|
if (!inserted && totalMatches > 0) {
|
|
17359
|
-
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\
|
|
17551
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\u5230 ${totalMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${input.occurrence} \u5904`);
|
|
17360
17552
|
}
|
|
17361
17553
|
await finalizeInserted();
|
|
17362
17554
|
await handle.close();
|
|
17363
17555
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
17364
|
-
await
|
|
17365
|
-
await
|
|
17556
|
+
await fs3.chmod(tempPath, mode);
|
|
17557
|
+
await fs3.rename(tempPath, input.filePath);
|
|
17366
17558
|
invalidateGbkLineIndex(input.filePath);
|
|
17367
17559
|
return {
|
|
17368
17560
|
mode: input.mode,
|
|
@@ -17378,40 +17570,62 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17378
17570
|
};
|
|
17379
17571
|
} catch (error45) {
|
|
17380
17572
|
await handle.close().catch(() => void 0);
|
|
17381
|
-
await
|
|
17573
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
17382
17574
|
throw error45;
|
|
17383
17575
|
}
|
|
17384
17576
|
}
|
|
17385
17577
|
async function replaceLargeGbkFileText(input) {
|
|
17386
|
-
const
|
|
17387
|
-
|
|
17388
|
-
|
|
17578
|
+
const lineIndex = await getGbkLineIndex(input);
|
|
17579
|
+
const newlineStyle = lineIndex.newlineStyle;
|
|
17580
|
+
const tempPath = path3.join(
|
|
17581
|
+
path3.dirname(input.filePath),
|
|
17582
|
+
`${path3.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17389
17583
|
);
|
|
17390
|
-
const handle = await
|
|
17584
|
+
const handle = await fs3.open(tempPath, "w");
|
|
17391
17585
|
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
17586
|
+
const alignedNewString = alignTextToNewlineStyle(input.newString, newlineStyle);
|
|
17392
17587
|
let carry = "";
|
|
17393
17588
|
let occurrencesBefore = 0;
|
|
17394
17589
|
let bytesWritten = 0;
|
|
17590
|
+
let replacedFirstMatch = false;
|
|
17395
17591
|
const flushText = async (text, flush = false) => {
|
|
17396
17592
|
const combined = carry + text;
|
|
17397
|
-
|
|
17398
|
-
const processable = combined.slice(0, splitAt);
|
|
17399
|
-
carry = combined.slice(splitAt);
|
|
17400
|
-
if (processable.length === 0) {
|
|
17593
|
+
if (combined.length === 0) {
|
|
17401
17594
|
return;
|
|
17402
17595
|
}
|
|
17403
|
-
const
|
|
17404
|
-
const
|
|
17405
|
-
|
|
17406
|
-
let
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17596
|
+
const safeEnd = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
17597
|
+
const outputParts = [];
|
|
17598
|
+
let cursor = 0;
|
|
17599
|
+
let flushUpto = safeEnd;
|
|
17600
|
+
while (true) {
|
|
17601
|
+
const index = combined.indexOf(input.oldString, cursor);
|
|
17602
|
+
if (index === -1) {
|
|
17603
|
+
break;
|
|
17604
|
+
}
|
|
17605
|
+
const matchEnd = index + input.oldString.length;
|
|
17606
|
+
if (!flush && matchEnd > safeEnd) {
|
|
17607
|
+
flushUpto = index;
|
|
17608
|
+
break;
|
|
17609
|
+
}
|
|
17610
|
+
occurrencesBefore += 1;
|
|
17611
|
+
outputParts.push(combined.slice(cursor, index));
|
|
17612
|
+
if (input.replaceAll) {
|
|
17613
|
+
outputParts.push(alignedNewString);
|
|
17614
|
+
} else if (!replacedFirstMatch) {
|
|
17615
|
+
outputParts.push(alignedNewString);
|
|
17616
|
+
replacedFirstMatch = true;
|
|
17617
|
+
} else {
|
|
17618
|
+
outputParts.push(input.oldString);
|
|
17410
17619
|
}
|
|
17411
|
-
|
|
17412
|
-
output = processable.replace(input.oldString, input.newString);
|
|
17620
|
+
cursor = matchEnd;
|
|
17413
17621
|
}
|
|
17414
|
-
|
|
17622
|
+
outputParts.push(combined.slice(cursor, flushUpto));
|
|
17623
|
+
carry = combined.slice(flushUpto);
|
|
17624
|
+
const processable = outputParts.join("");
|
|
17625
|
+
if (processable.length === 0) {
|
|
17626
|
+
return;
|
|
17627
|
+
}
|
|
17628
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, processable);
|
|
17415
17629
|
};
|
|
17416
17630
|
try {
|
|
17417
17631
|
await visitDecodedTextChunks(input, async (text) => {
|
|
@@ -17419,15 +17633,15 @@ async function replaceLargeGbkFileText(input) {
|
|
|
17419
17633
|
});
|
|
17420
17634
|
await flushText("", true);
|
|
17421
17635
|
if (occurrencesBefore === 0) {
|
|
17422
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\
|
|
17636
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
17423
17637
|
}
|
|
17424
17638
|
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
17425
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17639
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
17426
17640
|
}
|
|
17427
17641
|
await handle.close();
|
|
17428
17642
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
17429
|
-
await
|
|
17430
|
-
await
|
|
17643
|
+
await fs3.chmod(tempPath, mode);
|
|
17644
|
+
await fs3.rename(tempPath, input.filePath);
|
|
17431
17645
|
invalidateGbkLineIndex(input.filePath);
|
|
17432
17646
|
return {
|
|
17433
17647
|
filePath: input.filePath,
|
|
@@ -17439,7 +17653,7 @@ async function replaceLargeGbkFileText(input) {
|
|
|
17439
17653
|
};
|
|
17440
17654
|
} catch (error45) {
|
|
17441
17655
|
await handle.close().catch(() => void 0);
|
|
17442
|
-
await
|
|
17656
|
+
await fs3.rm(tempPath, { force: true }).catch(() => void 0);
|
|
17443
17657
|
throw error45;
|
|
17444
17658
|
}
|
|
17445
17659
|
}
|
|
@@ -17567,7 +17781,7 @@ async function replaceGbkFileText(input) {
|
|
|
17567
17781
|
};
|
|
17568
17782
|
}
|
|
17569
17783
|
const buffer2 = import_iconv_lite.default.encode(insertResult.outputText, current2.encoding);
|
|
17570
|
-
await
|
|
17784
|
+
await fs3.writeFile(current2.filePath, buffer2);
|
|
17571
17785
|
return {
|
|
17572
17786
|
mode,
|
|
17573
17787
|
filePath: current2.filePath,
|
|
@@ -17627,7 +17841,7 @@ async function replaceGbkFileText(input) {
|
|
|
17627
17841
|
if (loose !== null) {
|
|
17628
17842
|
const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
|
|
17629
17843
|
const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
|
|
17630
|
-
await
|
|
17844
|
+
await fs3.writeFile(current.filePath, buffer2);
|
|
17631
17845
|
invalidateGbkLineIndex(current.filePath);
|
|
17632
17846
|
return {
|
|
17633
17847
|
mode: "replace",
|
|
@@ -17648,14 +17862,14 @@ async function replaceGbkFileText(input) {
|
|
|
17648
17862
|
} else if (occurrencesBefore === 0) {
|
|
17649
17863
|
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, effectiveOldString));
|
|
17650
17864
|
} else if (occurrencesBefore > 1) {
|
|
17651
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17865
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${effectiveOldString}`);
|
|
17652
17866
|
}
|
|
17653
17867
|
const fileNewlineStyle = detectNewlineStyle(current.content);
|
|
17654
17868
|
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;
|
|
17655
17869
|
const replaced = replaceAll ? scope.selectedText.split(effectiveOldString).join(alignedNewString) : scope.selectedText.replace(effectiveOldString, alignedNewString);
|
|
17656
17870
|
const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
|
|
17657
17871
|
const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
|
|
17658
|
-
await
|
|
17872
|
+
await fs3.writeFile(current.filePath, buffer);
|
|
17659
17873
|
invalidateGbkLineIndex(current.filePath);
|
|
17660
17874
|
return {
|
|
17661
17875
|
mode: "replace",
|
|
@@ -17740,11 +17954,11 @@ async function writeGbkFile(input) {
|
|
|
17740
17954
|
const overwrite = input.overwrite ?? false;
|
|
17741
17955
|
const append = input.append ?? false;
|
|
17742
17956
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
17743
|
-
const parent =
|
|
17957
|
+
const parent = path3.dirname(candidatePath);
|
|
17744
17958
|
assertEncodingSupported(encoding);
|
|
17745
17959
|
if (append) {
|
|
17746
17960
|
try {
|
|
17747
|
-
const parentStat = await
|
|
17961
|
+
const parentStat = await fs3.stat(parent);
|
|
17748
17962
|
if (!parentStat.isDirectory()) {
|
|
17749
17963
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17750
17964
|
}
|
|
@@ -17753,14 +17967,14 @@ async function writeGbkFile(input) {
|
|
|
17753
17967
|
if (!createDirectories) {
|
|
17754
17968
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17755
17969
|
}
|
|
17756
|
-
await
|
|
17970
|
+
await fs3.mkdir(parent, { recursive: true });
|
|
17757
17971
|
} else if (error45 instanceof Error && "code" in error45) {
|
|
17758
17972
|
throw error45;
|
|
17759
17973
|
}
|
|
17760
17974
|
}
|
|
17761
17975
|
let existed = false;
|
|
17762
17976
|
try {
|
|
17763
|
-
await
|
|
17977
|
+
await fs3.stat(candidatePath);
|
|
17764
17978
|
existed = true;
|
|
17765
17979
|
} catch (error45) {
|
|
17766
17980
|
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
@@ -17779,12 +17993,12 @@ async function writeGbkFile(input) {
|
|
|
17779
17993
|
};
|
|
17780
17994
|
}
|
|
17781
17995
|
try {
|
|
17782
|
-
const stat = await
|
|
17996
|
+
const stat = await fs3.stat(candidatePath);
|
|
17783
17997
|
if (stat.isDirectory()) {
|
|
17784
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
17998
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
17785
17999
|
}
|
|
17786
18000
|
if (!overwrite) {
|
|
17787
|
-
throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\
|
|
18001
|
+
throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728: ${candidatePath}`);
|
|
17788
18002
|
}
|
|
17789
18003
|
} catch (error45) {
|
|
17790
18004
|
if (error45 instanceof Error && "code" in error45) {
|
|
@@ -17796,7 +18010,7 @@ async function writeGbkFile(input) {
|
|
|
17796
18010
|
}
|
|
17797
18011
|
}
|
|
17798
18012
|
try {
|
|
17799
|
-
const parentStat = await
|
|
18013
|
+
const parentStat = await fs3.stat(parent);
|
|
17800
18014
|
if (!parentStat.isDirectory()) {
|
|
17801
18015
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17802
18016
|
}
|
|
@@ -17805,15 +18019,15 @@ async function writeGbkFile(input) {
|
|
|
17805
18019
|
if (!createDirectories) {
|
|
17806
18020
|
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17807
18021
|
}
|
|
17808
|
-
await
|
|
18022
|
+
await fs3.mkdir(parent, { recursive: true });
|
|
17809
18023
|
} else if (error45 instanceof Error && "code" in error45) {
|
|
17810
18024
|
throw error45;
|
|
17811
18025
|
}
|
|
17812
18026
|
}
|
|
17813
18027
|
try {
|
|
17814
|
-
const existed = await
|
|
18028
|
+
const existed = await fs3.stat(candidatePath).then(() => true).catch(() => false);
|
|
17815
18029
|
const buffer = import_iconv_lite.default.encode(input.content, encoding);
|
|
17816
|
-
await
|
|
18030
|
+
await fs3.writeFile(candidatePath, buffer);
|
|
17817
18031
|
invalidateGbkLineIndex(candidatePath);
|
|
17818
18032
|
return {
|
|
17819
18033
|
filePath: candidatePath,
|
|
@@ -18034,13 +18248,16 @@ var TEXT_TOOL_SYSTEM_MARKER = "[opencode-gbk-tools:text-rules]";
|
|
|
18034
18248
|
var TEXT_TOOL_SYSTEM_PROMPT = [
|
|
18035
18249
|
TEXT_TOOL_SYSTEM_MARKER,
|
|
18036
18250
|
"\u6587\u672C\u6587\u4EF6\u5904\u7406\u89C4\u5219\uFF1A",
|
|
18037
|
-
"- \
|
|
18038
|
-
"-
|
|
18251
|
+
"- \u666E\u901A UTF-8 / UTF-8 BOM / UTF-16 \u6587\u672C\uFF0C\u4F18\u5148\u4F7F\u7528 OpenCode \u5185\u7F6E read\u3001write\u3001edit\u3002",
|
|
18252
|
+
"- \u9047\u5230 GBK / GB18030 \u6587\u4EF6\u3001\u4E2D\u6587\u4E71\u7801\u3001\u975E UTF-8 \u65E7\u6587\u672C\uFF0C\u4F18\u5148\u4F7F\u7528 gbk_read\u3001gbk_write\u3001gbk_edit\u3001gbk_search\u3002",
|
|
18039
18253
|
"- \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",
|
|
18254
|
+
"- \u65E0\u6CD5\u786E\u5B9A\u7F16\u7801\u65F6\uFF0C\u5148\u5C1D\u8BD5\u5185\u7F6E read\uFF1B\u82E5\u51FA\u73B0\u4E2D\u6587\u4E71\u7801\u3001\u66FF\u6362\u5931\u8D25\u6216\u7F16\u7801\u98CE\u9669\uFF0C\u518D\u5207\u6362\u5230 gbk_*\u3002",
|
|
18255
|
+
"- \u5DF2\u786E\u8BA4\u662F GBK/GB18030 \u7684\u6587\u4EF6\u4F1A\u88AB\u63D2\u4EF6\u6301\u4E45\u8BB0\u5FC6\uFF1B\u518D\u6B21\u64CD\u4F5C\u540C\u4E00\u8DEF\u5F84\u65F6\uFF0C\u4F18\u5148\u7EE7\u7EED\u6309 GBK \u5904\u7406\u3002",
|
|
18040
18256
|
"- \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
18257
|
"- \u53EA\u6709\u5728\u660E\u786E\u505A\u7CBE\u786E\u66FF\u6362\u65F6\uFF0C\u624D\u4F7F\u7528 oldString/newString\u3002",
|
|
18042
|
-
"- \u82E5\
|
|
18043
|
-
|
|
18258
|
+
"- anchor\u3001startAnchor\u3001endAnchor\u3001oldString \u82E5\u76F4\u63A5\u590D\u5236\u81EA\u8BFB\u53D6\u7ED3\u679C\uFF0C\u53EF\u4FDD\u7559 LF \u6362\u884C\uFF1Bgbk_edit / text_edit \u4F1A\u5C3D\u91CF\u6309\u6587\u4EF6\u6362\u884C\u98CE\u683C\u81EA\u52A8\u5BF9\u9F50\u3002",
|
|
18259
|
+
'- \u82E5\u8BFB\u53D6\u7ED3\u679C\u5E26\u6709 "N: " \u884C\u53F7\u524D\u7F00\uFF0Cgbk_edit / text_edit \u4F1A\u5C3D\u91CF\u81EA\u52A8\u5265\u79BB\u8FD9\u4E9B\u524D\u7F00\u540E\u518D\u5339\u914D\u3002',
|
|
18260
|
+
"- gbk-engine \u662F\u5F3A\u5236 GBK \u4E13\u5C5E\u6A21\u5F0F\uFF1A\u53EA\u5141\u8BB8 gbk_*\uFF0C\u4E0D\u8D70\u5185\u7F6E\u8BFB\u5199\u7F16\u8F91\u5DE5\u5177\u3002"
|
|
18044
18261
|
].join("\n");
|
|
18045
18262
|
function appendTextToolSystemPrompt(system) {
|
|
18046
18263
|
if (system.some((item) => item.includes(TEXT_TOOL_SYSTEM_MARKER))) {
|
|
@@ -18059,8 +18276,8 @@ ${TEXT_TOOL_SYSTEM_PROMPT}`;
|
|
|
18059
18276
|
var import_iconv_lite2 = __toESM(require_lib(), 1);
|
|
18060
18277
|
import crypto2 from "crypto";
|
|
18061
18278
|
import { createReadStream as createReadStream2 } from "fs";
|
|
18062
|
-
import
|
|
18063
|
-
import
|
|
18279
|
+
import fs4 from "fs/promises";
|
|
18280
|
+
import path4 from "path";
|
|
18064
18281
|
var TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES = 1024 * 1024;
|
|
18065
18282
|
var TEXT_DETECTION_SAMPLE_BYTES = 64 * 1024;
|
|
18066
18283
|
var UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -18096,7 +18313,7 @@ function resolveExplicitTextEncoding(value, fallback) {
|
|
|
18096
18313
|
return requested === "auto" ? fallback : requested;
|
|
18097
18314
|
}
|
|
18098
18315
|
function shouldDefaultNewTextFileToGbk(filePath) {
|
|
18099
|
-
return
|
|
18316
|
+
return path4.extname(filePath).toLowerCase() === ".txt";
|
|
18100
18317
|
}
|
|
18101
18318
|
function getBomPrefix(encoding, hasBom) {
|
|
18102
18319
|
if (!hasBom) {
|
|
@@ -18248,35 +18465,126 @@ function buildNoMatchMessage2(content, oldString) {
|
|
|
18248
18465
|
assertStringArgument2(oldString, "oldString");
|
|
18249
18466
|
return [
|
|
18250
18467
|
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
18251
|
-
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\u3002",
|
|
18468
|
+
"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",
|
|
18252
18469
|
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
18253
18470
|
getNearestContext2(content, oldString)
|
|
18254
18471
|
].join("\n");
|
|
18255
18472
|
}
|
|
18256
|
-
function
|
|
18473
|
+
function trimRightSpaces2(text) {
|
|
18257
18474
|
assertStringArgument2(text, "text");
|
|
18258
|
-
|
|
18259
|
-
|
|
18475
|
+
return text.replace(/[ \t]+$/g, "");
|
|
18476
|
+
}
|
|
18477
|
+
function splitNormalizedLines2(text) {
|
|
18478
|
+
return normalizeNewlines2(text).split("\n");
|
|
18479
|
+
}
|
|
18480
|
+
function trimTrailingEmptyLines2(lines) {
|
|
18481
|
+
const result = [...lines];
|
|
18482
|
+
while (result.length > 0 && trimRightSpaces2(result[result.length - 1]) === "") {
|
|
18483
|
+
result.pop();
|
|
18484
|
+
}
|
|
18485
|
+
return result;
|
|
18486
|
+
}
|
|
18487
|
+
function hasLineNumberPrefixes2(lines) {
|
|
18488
|
+
const nonEmpty = lines.filter((line) => line.trim().length > 0);
|
|
18489
|
+
return nonEmpty.length > 0 && nonEmpty.every((line) => /^\d+: /.test(line));
|
|
18490
|
+
}
|
|
18491
|
+
function stripLineNumberPrefixes2(lines) {
|
|
18492
|
+
return lines.map((line) => line.replace(/^\d+: /, ""));
|
|
18493
|
+
}
|
|
18494
|
+
function parseLineNumberPrefixedBlock2(text) {
|
|
18495
|
+
const normalizedText = normalizeNewlines2(text);
|
|
18496
|
+
const hasTrailingNewline = normalizedText.endsWith("\n");
|
|
18497
|
+
const lines = trimTrailingEmptyLines2(splitNormalizedLines2(text));
|
|
18498
|
+
if (lines.length === 0 || !hasLineNumberPrefixes2(lines)) {
|
|
18499
|
+
return null;
|
|
18500
|
+
}
|
|
18501
|
+
const lineNumbers = [];
|
|
18502
|
+
const strippedLines = [];
|
|
18503
|
+
for (const line of lines) {
|
|
18504
|
+
const match = /^(\d+): ?(.*)$/.exec(line);
|
|
18505
|
+
if (!match) {
|
|
18506
|
+
return null;
|
|
18507
|
+
}
|
|
18508
|
+
lineNumbers.push(Number(match[1]));
|
|
18509
|
+
strippedLines.push(match[2]);
|
|
18510
|
+
}
|
|
18511
|
+
return {
|
|
18512
|
+
lineNumbers,
|
|
18513
|
+
strippedText: `${strippedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`,
|
|
18514
|
+
isContiguous: lineNumbers.every((lineNumber, index) => index === 0 || lineNumber === lineNumbers[index - 1] + 1),
|
|
18515
|
+
startLine: lineNumbers[0],
|
|
18516
|
+
endLine: lineNumbers[lineNumbers.length - 1]
|
|
18517
|
+
};
|
|
18518
|
+
}
|
|
18519
|
+
function collectOccurrencePositions2(text, token) {
|
|
18260
18520
|
if (token.length === 0) {
|
|
18261
|
-
|
|
18521
|
+
return [];
|
|
18262
18522
|
}
|
|
18263
|
-
|
|
18523
|
+
const positions = [];
|
|
18264
18524
|
let searchFrom = 0;
|
|
18265
18525
|
while (true) {
|
|
18266
18526
|
const index = text.indexOf(token, searchFrom);
|
|
18267
18527
|
if (index === -1) {
|
|
18268
|
-
|
|
18269
|
-
}
|
|
18270
|
-
count += 1;
|
|
18271
|
-
if (count === occurrence) {
|
|
18272
|
-
return { index, total: countOccurrences(text, token) };
|
|
18528
|
+
return positions;
|
|
18273
18529
|
}
|
|
18530
|
+
positions.push(index);
|
|
18274
18531
|
searchFrom = index + token.length;
|
|
18275
18532
|
}
|
|
18276
|
-
|
|
18533
|
+
}
|
|
18534
|
+
function buildFlexibleSearchVariants2(token, newlineStyle) {
|
|
18535
|
+
const variants = /* @__PURE__ */ new Set();
|
|
18536
|
+
const pushVariant = (value) => {
|
|
18537
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
18538
|
+
return;
|
|
18539
|
+
}
|
|
18540
|
+
variants.add(value);
|
|
18541
|
+
};
|
|
18542
|
+
pushVariant(token);
|
|
18543
|
+
pushVariant(alignTextToNewlineStyle2(token, newlineStyle));
|
|
18544
|
+
const prefixedBlock = parseLineNumberPrefixedBlock2(token);
|
|
18545
|
+
if (prefixedBlock) {
|
|
18546
|
+
pushVariant(prefixedBlock.strippedText);
|
|
18547
|
+
pushVariant(alignTextToNewlineStyle2(prefixedBlock.strippedText, newlineStyle));
|
|
18548
|
+
}
|
|
18549
|
+
const trimmedLines = trimTrailingEmptyLines2(splitNormalizedLines2(token));
|
|
18550
|
+
if (trimmedLines.length > 0) {
|
|
18551
|
+
const trimmedToken = trimmedLines.join("\n");
|
|
18552
|
+
pushVariant(trimmedToken);
|
|
18553
|
+
pushVariant(alignTextToNewlineStyle2(trimmedToken, newlineStyle));
|
|
18554
|
+
const trimmedPrefixedBlock = parseLineNumberPrefixedBlock2(trimmedToken);
|
|
18555
|
+
if (trimmedPrefixedBlock) {
|
|
18556
|
+
pushVariant(trimmedPrefixedBlock.strippedText);
|
|
18557
|
+
pushVariant(alignTextToNewlineStyle2(trimmedPrefixedBlock.strippedText, newlineStyle));
|
|
18558
|
+
}
|
|
18559
|
+
}
|
|
18560
|
+
return [...variants];
|
|
18561
|
+
}
|
|
18562
|
+
function findOccurrenceIndex(text, token, occurrence, newlineStyle) {
|
|
18563
|
+
assertStringArgument2(text, "text");
|
|
18564
|
+
assertStringArgument2(token, "anchor");
|
|
18565
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
18566
|
+
if (token.length === 0) {
|
|
18567
|
+
throw createTextError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18568
|
+
}
|
|
18569
|
+
let maxMatches = 0;
|
|
18570
|
+
for (const candidate of buildFlexibleSearchVariants2(token, newlineStyle)) {
|
|
18571
|
+
const positions = collectOccurrencePositions2(text, candidate);
|
|
18572
|
+
if (positions.length === 0) {
|
|
18573
|
+
continue;
|
|
18574
|
+
}
|
|
18575
|
+
maxMatches = Math.max(maxMatches, positions.length);
|
|
18576
|
+
if (positions.length >= occurrence) {
|
|
18577
|
+
return {
|
|
18578
|
+
index: positions[occurrence - 1],
|
|
18579
|
+
total: positions.length,
|
|
18580
|
+
matchedToken: candidate
|
|
18581
|
+
};
|
|
18582
|
+
}
|
|
18583
|
+
}
|
|
18584
|
+
if (maxMatches === 0) {
|
|
18277
18585
|
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
18278
18586
|
}
|
|
18279
|
-
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${
|
|
18587
|
+
throw createTextError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${maxMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
18280
18588
|
}
|
|
18281
18589
|
function assertInsertArguments2(input) {
|
|
18282
18590
|
assertStringArgument2(input.anchor, "anchor");
|
|
@@ -18301,8 +18609,8 @@ function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
18301
18609
|
const afterLines = normalizeNewlines2(afterText).split("\n");
|
|
18302
18610
|
const maxLines = Math.max(beforeLines.length, afterLines.length);
|
|
18303
18611
|
const header = [
|
|
18304
|
-
`${ANSI_DIM2}--- ${
|
|
18305
|
-
`${ANSI_DIM2}+++ ${
|
|
18612
|
+
`${ANSI_DIM2}--- ${path4.basename(filePath)} (${encoding})${ANSI_RESET2}`,
|
|
18613
|
+
`${ANSI_DIM2}+++ ${path4.basename(filePath)} (${encoding})${ANSI_RESET2}`
|
|
18306
18614
|
];
|
|
18307
18615
|
const body = [];
|
|
18308
18616
|
for (let index = 0; index < maxLines; index += 1) {
|
|
@@ -18328,8 +18636,8 @@ function buildLineDiffPreview(filePath, encoding, beforeText, afterText) {
|
|
|
18328
18636
|
}
|
|
18329
18637
|
function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
18330
18638
|
const alignedContent = alignTextToNewlineStyle2(content, newlineStyle);
|
|
18331
|
-
const located =
|
|
18332
|
-
const insertionPoint = mode === "insertAfter" ? located.index +
|
|
18639
|
+
const located = findOccurrenceIndex(text, anchor, occurrence, newlineStyle);
|
|
18640
|
+
const insertionPoint = mode === "insertAfter" ? located.index + located.matchedToken.length : located.index;
|
|
18333
18641
|
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
18334
18642
|
if (alreadyExists) {
|
|
18335
18643
|
if (ifExists === "error") {
|
|
@@ -18343,8 +18651,8 @@ function buildInsertOutput(text, mode, anchor, content, occurrence, ifExists, ne
|
|
|
18343
18651
|
anchorMatches: located.total,
|
|
18344
18652
|
occurrence,
|
|
18345
18653
|
anchor,
|
|
18346
|
-
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index +
|
|
18347
|
-
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index +
|
|
18654
|
+
previewBefore: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + located.matchedToken.length + 80)),
|
|
18655
|
+
previewAfter: text.slice(Math.max(0, located.index - 80), Math.min(text.length, located.index + located.matchedToken.length + 80))
|
|
18348
18656
|
};
|
|
18349
18657
|
}
|
|
18350
18658
|
}
|
|
@@ -18418,7 +18726,7 @@ async function resolveReadableTextFile(input) {
|
|
|
18418
18726
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
18419
18727
|
let stat;
|
|
18420
18728
|
try {
|
|
18421
|
-
stat = await
|
|
18729
|
+
stat = await fs4.stat(candidatePath);
|
|
18422
18730
|
} catch (error45) {
|
|
18423
18731
|
throw createTextError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
18424
18732
|
}
|
|
@@ -18428,7 +18736,7 @@ async function resolveReadableTextFile(input) {
|
|
|
18428
18736
|
return { filePath: candidatePath, stat };
|
|
18429
18737
|
}
|
|
18430
18738
|
async function readDetectionBuffer(filePath, sampleSize = TEXT_DETECTION_SAMPLE_BYTES) {
|
|
18431
|
-
const handle = await
|
|
18739
|
+
const handle = await fs4.open(filePath, "r");
|
|
18432
18740
|
try {
|
|
18433
18741
|
const buffer = Buffer.alloc(sampleSize);
|
|
18434
18742
|
const { bytesRead } = await handle.read(buffer, 0, sampleSize, 0);
|
|
@@ -18590,10 +18898,118 @@ async function visitDecodedTextChunks2(resolved, visitor) {
|
|
|
18590
18898
|
stream.destroy();
|
|
18591
18899
|
}
|
|
18592
18900
|
}
|
|
18901
|
+
async function writeLargeTextFile(filePath, encoding, hasBom, producer) {
|
|
18902
|
+
const tempPath = path4.join(path4.dirname(filePath), `${path4.basename(filePath)}.opencode-text-${crypto2.randomUUID()}.tmp`);
|
|
18903
|
+
const handle = await fs4.open(tempPath, "w");
|
|
18904
|
+
try {
|
|
18905
|
+
if (hasBom) {
|
|
18906
|
+
const bom = getBomPrefix(encoding, hasBom);
|
|
18907
|
+
if (bom.length > 0) {
|
|
18908
|
+
await handle.writeFile(bom);
|
|
18909
|
+
}
|
|
18910
|
+
}
|
|
18911
|
+
const bytesWritten = await producer(handle);
|
|
18912
|
+
await handle.close();
|
|
18913
|
+
await fs4.rename(tempPath, filePath);
|
|
18914
|
+
return bytesWritten + getBomPrefix(encoding, hasBom).length;
|
|
18915
|
+
} catch (error45) {
|
|
18916
|
+
await handle.close().catch(() => void 0);
|
|
18917
|
+
await fs4.rm(tempPath, { force: true }).catch(() => void 0);
|
|
18918
|
+
throw error45;
|
|
18919
|
+
}
|
|
18920
|
+
}
|
|
18921
|
+
async function writeEncodedTextChunk(handle, encoding, text) {
|
|
18922
|
+
if (text.length === 0) {
|
|
18923
|
+
return 0;
|
|
18924
|
+
}
|
|
18925
|
+
const buffer = encodeTextBody(text, encoding);
|
|
18926
|
+
await handle.writeFile(buffer);
|
|
18927
|
+
return buffer.byteLength;
|
|
18928
|
+
}
|
|
18929
|
+
async function replaceLargeTextFileText(input) {
|
|
18930
|
+
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
18931
|
+
const fileNewlineStyle = input.loaded.newlineStyle;
|
|
18932
|
+
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;
|
|
18933
|
+
let carry = "";
|
|
18934
|
+
let occurrencesBefore = 0;
|
|
18935
|
+
let replacedFirstMatch = false;
|
|
18936
|
+
const bytesWritten = await writeLargeTextFile(input.loaded.filePath, input.loaded.encoding, input.loaded.hasBom, async (handle) => {
|
|
18937
|
+
let written = 0;
|
|
18938
|
+
const flushText = async (text, flush = false) => {
|
|
18939
|
+
const combined = carry + text;
|
|
18940
|
+
if (combined.length === 0) {
|
|
18941
|
+
return;
|
|
18942
|
+
}
|
|
18943
|
+
const safeEnd = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
18944
|
+
const outputParts = [];
|
|
18945
|
+
let cursor = 0;
|
|
18946
|
+
let flushUpto = safeEnd;
|
|
18947
|
+
while (true) {
|
|
18948
|
+
const index = combined.indexOf(input.oldString, cursor);
|
|
18949
|
+
if (index === -1) {
|
|
18950
|
+
break;
|
|
18951
|
+
}
|
|
18952
|
+
const matchEnd = index + input.oldString.length;
|
|
18953
|
+
if (!flush && matchEnd > safeEnd) {
|
|
18954
|
+
flushUpto = index;
|
|
18955
|
+
break;
|
|
18956
|
+
}
|
|
18957
|
+
occurrencesBefore += 1;
|
|
18958
|
+
outputParts.push(combined.slice(cursor, index));
|
|
18959
|
+
if (input.replaceAll) {
|
|
18960
|
+
outputParts.push(alignedNewString);
|
|
18961
|
+
} else if (!replacedFirstMatch) {
|
|
18962
|
+
outputParts.push(alignedNewString);
|
|
18963
|
+
replacedFirstMatch = true;
|
|
18964
|
+
} else {
|
|
18965
|
+
outputParts.push(input.oldString);
|
|
18966
|
+
}
|
|
18967
|
+
cursor = matchEnd;
|
|
18968
|
+
}
|
|
18969
|
+
outputParts.push(combined.slice(cursor, flushUpto));
|
|
18970
|
+
carry = combined.slice(flushUpto);
|
|
18971
|
+
const processable = outputParts.join("");
|
|
18972
|
+
if (processable.length === 0) {
|
|
18973
|
+
return;
|
|
18974
|
+
}
|
|
18975
|
+
written += await writeEncodedTextChunk(handle, input.loaded.encoding, processable);
|
|
18976
|
+
};
|
|
18977
|
+
await visitDecodedTextChunks2({
|
|
18978
|
+
filePath: input.loaded.filePath,
|
|
18979
|
+
encoding: input.loaded.encoding,
|
|
18980
|
+
hasBom: input.loaded.hasBom
|
|
18981
|
+
}, async (text) => {
|
|
18982
|
+
await flushText(text);
|
|
18983
|
+
});
|
|
18984
|
+
await flushText("", true);
|
|
18985
|
+
if (occurrencesBefore === 0) {
|
|
18986
|
+
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
18987
|
+
}
|
|
18988
|
+
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
18989
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
18990
|
+
}
|
|
18991
|
+
return written;
|
|
18992
|
+
});
|
|
18993
|
+
return {
|
|
18994
|
+
mode: "replace",
|
|
18995
|
+
filePath: input.loaded.filePath,
|
|
18996
|
+
encoding: input.loaded.encoding,
|
|
18997
|
+
requestedEncoding: input.loaded.requestedEncoding,
|
|
18998
|
+
detectedEncoding: input.loaded.detectedEncoding,
|
|
18999
|
+
confidence: input.loaded.confidence,
|
|
19000
|
+
hasBom: input.loaded.hasBom,
|
|
19001
|
+
replacements: input.replaceAll ? occurrencesBefore : 1,
|
|
19002
|
+
occurrencesBefore,
|
|
19003
|
+
bytesRead: input.loaded.bytesRead,
|
|
19004
|
+
bytesWritten,
|
|
19005
|
+
newlineStyle: input.loaded.newlineStyle,
|
|
19006
|
+
diffPreview: void 0
|
|
19007
|
+
};
|
|
19008
|
+
}
|
|
18593
19009
|
async function readWholeTextFile(input) {
|
|
18594
19010
|
const resolved = await resolveReadableTextFile(input);
|
|
18595
19011
|
try {
|
|
18596
|
-
const buffer = await
|
|
19012
|
+
const buffer = await fs4.readFile(resolved.filePath);
|
|
18597
19013
|
assertLikelyTextBuffer(buffer);
|
|
18598
19014
|
const detected = detectTextEncodingFromBuffer(buffer, input.encoding ?? "auto");
|
|
18599
19015
|
const content = decodeText(buffer, detected.detectedEncoding);
|
|
@@ -18683,19 +19099,14 @@ function applyAnchors2(text, startAnchor, endAnchor) {
|
|
|
18683
19099
|
}
|
|
18684
19100
|
let rangeStart = 0;
|
|
18685
19101
|
let rangeEnd = text.length;
|
|
19102
|
+
const newlineStyle = detectNewlineStyle(text);
|
|
18686
19103
|
if (startAnchor) {
|
|
18687
|
-
const found = text
|
|
18688
|
-
|
|
18689
|
-
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\u70B9: ${startAnchor}`);
|
|
18690
|
-
}
|
|
18691
|
-
rangeStart = found + startAnchor.length;
|
|
19104
|
+
const found = findOccurrenceIndex(text, startAnchor, 1, newlineStyle);
|
|
19105
|
+
rangeStart = found.index + found.matchedToken.length;
|
|
18692
19106
|
}
|
|
18693
19107
|
if (endAnchor) {
|
|
18694
|
-
const found = text.
|
|
18695
|
-
|
|
18696
|
-
throw createTextError("GBK_NO_MATCH", `\u672A\u627E\u5230\u7ED3\u675F\u951A\u70B9: ${endAnchor}`);
|
|
18697
|
-
}
|
|
18698
|
-
rangeEnd = found;
|
|
19108
|
+
const found = findOccurrenceIndex(text.slice(rangeStart), endAnchor, 1, newlineStyle);
|
|
19109
|
+
rangeEnd = rangeStart + found.index;
|
|
18699
19110
|
}
|
|
18700
19111
|
if (rangeEnd < rangeStart) {
|
|
18701
19112
|
throw createTextError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
@@ -18726,6 +19137,64 @@ function alignTextToNewlineStyle2(text, newlineStyle) {
|
|
|
18726
19137
|
}
|
|
18727
19138
|
return text;
|
|
18728
19139
|
}
|
|
19140
|
+
function matchLooseBlock2(contentLines, oldLines, newLines, newlineStyle) {
|
|
19141
|
+
for (let start = 0; start < contentLines.length; start += 1) {
|
|
19142
|
+
let contentIndex = start;
|
|
19143
|
+
let oldIndex = 0;
|
|
19144
|
+
while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
|
|
19145
|
+
const expected = trimRightSpaces2(oldLines[oldIndex]);
|
|
19146
|
+
const actual = trimRightSpaces2(contentLines[contentIndex]);
|
|
19147
|
+
if (expected === actual) {
|
|
19148
|
+
oldIndex += 1;
|
|
19149
|
+
contentIndex += 1;
|
|
19150
|
+
continue;
|
|
19151
|
+
}
|
|
19152
|
+
if (actual === "") {
|
|
19153
|
+
contentIndex += 1;
|
|
19154
|
+
continue;
|
|
19155
|
+
}
|
|
19156
|
+
if (expected === "") {
|
|
19157
|
+
oldIndex += 1;
|
|
19158
|
+
continue;
|
|
19159
|
+
}
|
|
19160
|
+
break;
|
|
19161
|
+
}
|
|
19162
|
+
if (oldIndex === oldLines.length) {
|
|
19163
|
+
const matchedOriginal = contentLines.slice(start, contentIndex).join("\n");
|
|
19164
|
+
const replacedNormalized = [
|
|
19165
|
+
...contentLines.slice(0, start),
|
|
19166
|
+
...newLines,
|
|
19167
|
+
...contentLines.slice(contentIndex)
|
|
19168
|
+
].join("\n");
|
|
19169
|
+
return {
|
|
19170
|
+
occurrencesBefore: 1,
|
|
19171
|
+
matchedOriginal,
|
|
19172
|
+
content: newlineStyle === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
|
|
19173
|
+
};
|
|
19174
|
+
}
|
|
19175
|
+
}
|
|
19176
|
+
return null;
|
|
19177
|
+
}
|
|
19178
|
+
function tryLooseBlockReplace2(content, oldString, newString) {
|
|
19179
|
+
const contentLines = splitNormalizedLines2(content);
|
|
19180
|
+
const oldLines = trimTrailingEmptyLines2(splitNormalizedLines2(oldString));
|
|
19181
|
+
const newLines = splitNormalizedLines2(newString);
|
|
19182
|
+
const newlineStyle = detectNewlineStyle(content);
|
|
19183
|
+
if (oldLines.length === 0) {
|
|
19184
|
+
return null;
|
|
19185
|
+
}
|
|
19186
|
+
const direct = matchLooseBlock2(contentLines, oldLines, newLines, newlineStyle);
|
|
19187
|
+
if (direct !== null) {
|
|
19188
|
+
return direct;
|
|
19189
|
+
}
|
|
19190
|
+
if (hasLineNumberPrefixes2(oldLines)) {
|
|
19191
|
+
const strippedOldLines = trimTrailingEmptyLines2(stripLineNumberPrefixes2(oldLines));
|
|
19192
|
+
if (strippedOldLines.length > 0) {
|
|
19193
|
+
return matchLooseBlock2(contentLines, strippedOldLines, newLines, newlineStyle);
|
|
19194
|
+
}
|
|
19195
|
+
}
|
|
19196
|
+
return null;
|
|
19197
|
+
}
|
|
18729
19198
|
function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
|
|
18730
19199
|
assertStringArgument2(input, "content");
|
|
18731
19200
|
const buffer = encodeText(input, encoding, hasBom);
|
|
@@ -18736,7 +19205,7 @@ function ensureLossless(input, encoding, hasBom = encoding === "utf8-bom") {
|
|
|
18736
19205
|
}
|
|
18737
19206
|
async function ensureParentDirectory(parent, createDirectories) {
|
|
18738
19207
|
try {
|
|
18739
|
-
const parentStat = await
|
|
19208
|
+
const parentStat = await fs4.stat(parent);
|
|
18740
19209
|
if (!parentStat.isDirectory()) {
|
|
18741
19210
|
throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
18742
19211
|
}
|
|
@@ -18745,7 +19214,7 @@ async function ensureParentDirectory(parent, createDirectories) {
|
|
|
18745
19214
|
if (!createDirectories) {
|
|
18746
19215
|
throw createTextError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
18747
19216
|
}
|
|
18748
|
-
await
|
|
19217
|
+
await fs4.mkdir(parent, { recursive: true });
|
|
18749
19218
|
return;
|
|
18750
19219
|
}
|
|
18751
19220
|
throw error45;
|
|
@@ -18808,7 +19277,7 @@ async function writeTextFile(input) {
|
|
|
18808
19277
|
const overwrite = input.overwrite ?? false;
|
|
18809
19278
|
const append = input.append ?? false;
|
|
18810
19279
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
18811
|
-
const parent =
|
|
19280
|
+
const parent = path4.dirname(candidatePath);
|
|
18812
19281
|
await ensureParentDirectory(parent, createDirectories);
|
|
18813
19282
|
let existing = null;
|
|
18814
19283
|
try {
|
|
@@ -18837,7 +19306,7 @@ async function writeTextFile(input) {
|
|
|
18837
19306
|
const outputContent = existing && preserveNewlineStyle ? alignTextToNewlineStyle2(rawContent, existing.newlineStyle) : rawContent;
|
|
18838
19307
|
ensureLossless(outputContent, targetEncoding, targetHasBom);
|
|
18839
19308
|
const buffer = encodeText(outputContent, targetEncoding, targetHasBom);
|
|
18840
|
-
await
|
|
19309
|
+
await fs4.writeFile(candidatePath, buffer);
|
|
18841
19310
|
return {
|
|
18842
19311
|
filePath: candidatePath,
|
|
18843
19312
|
encoding: targetEncoding,
|
|
@@ -18901,7 +19370,7 @@ async function replaceTextFileText(input) {
|
|
|
18901
19370
|
const targetHasBom2 = normalizedInput.preserveEncoding === false ? targetEncoding2 === "utf8-bom" || targetEncoding2 === "utf16le" || targetEncoding2 === "utf16be" : loaded2.hasBom;
|
|
18902
19371
|
ensureLossless(insertResult.outputText, targetEncoding2, targetHasBom2);
|
|
18903
19372
|
const buffer2 = encodeText(insertResult.outputText, targetEncoding2, targetHasBom2);
|
|
18904
|
-
await
|
|
19373
|
+
await fs4.writeFile(loaded2.filePath, buffer2);
|
|
18905
19374
|
return {
|
|
18906
19375
|
mode,
|
|
18907
19376
|
filePath: loaded2.filePath,
|
|
@@ -18925,40 +19394,92 @@ async function replaceTextFileText(input) {
|
|
|
18925
19394
|
if (input.oldString.length === 0) {
|
|
18926
19395
|
throw createTextError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
|
|
18927
19396
|
}
|
|
19397
|
+
const prefixedBlock = parseLineNumberPrefixedBlock2(input.oldString);
|
|
19398
|
+
const derivedScopedRange = prefixedBlock?.isContiguous ? {
|
|
19399
|
+
startLine: normalizedInput.startLine ?? prefixedBlock.startLine,
|
|
19400
|
+
endLine: normalizedInput.endLine ?? prefixedBlock.endLine,
|
|
19401
|
+
oldString: prefixedBlock.strippedText
|
|
19402
|
+
} : null;
|
|
19403
|
+
const effectiveOldString = derivedScopedRange?.oldString ?? input.oldString;
|
|
18928
19404
|
const resolved = await detectReadableTextFile({
|
|
18929
19405
|
filePath: input.filePath,
|
|
18930
19406
|
encoding: input.encoding ?? "auto",
|
|
18931
19407
|
allowExternal: input.allowExternal,
|
|
18932
19408
|
context: input.context
|
|
18933
19409
|
});
|
|
19410
|
+
const hasScopedRange = normalizedInput.startLine !== void 0 || normalizedInput.endLine !== void 0 || normalizedInput.startAnchor !== void 0 || normalizedInput.endAnchor !== void 0 || derivedScopedRange !== null;
|
|
19411
|
+
const preserveEncoding = normalizedInput.preserveEncoding ?? true;
|
|
19412
|
+
const preserveNewlineStyle = normalizedInput.preserveNewlineStyle ?? true;
|
|
19413
|
+
const requestedEncoding = normalizedInput.encoding ?? "auto";
|
|
19414
|
+
if (resolved.stat.size >= TEXT_STREAMING_FILE_SIZE_THRESHOLD_BYTES && !hasScopedRange && preserveEncoding && preserveNewlineStyle && requestedEncoding === "auto") {
|
|
19415
|
+
const loadedForStreaming = await readWholeTextFile({
|
|
19416
|
+
filePath: input.filePath,
|
|
19417
|
+
encoding: requestedEncoding,
|
|
19418
|
+
allowExternal: input.allowExternal,
|
|
19419
|
+
context: input.context
|
|
19420
|
+
});
|
|
19421
|
+
return await replaceLargeTextFileText({
|
|
19422
|
+
loaded: loadedForStreaming,
|
|
19423
|
+
oldString: effectiveOldString,
|
|
19424
|
+
newString: input.newString,
|
|
19425
|
+
replaceAll: normalizedInput.replaceAll ?? false
|
|
19426
|
+
});
|
|
19427
|
+
}
|
|
18934
19428
|
const loaded = await readWholeTextFile({
|
|
18935
19429
|
filePath: input.filePath,
|
|
18936
19430
|
encoding: input.encoding ?? "auto",
|
|
18937
19431
|
allowExternal: input.allowExternal,
|
|
18938
19432
|
context: input.context
|
|
18939
19433
|
});
|
|
18940
|
-
const
|
|
19434
|
+
const scopedInput = derivedScopedRange === null ? normalizedInput : {
|
|
19435
|
+
...normalizedInput,
|
|
19436
|
+
startLine: derivedScopedRange.startLine,
|
|
19437
|
+
endLine: derivedScopedRange.endLine
|
|
19438
|
+
};
|
|
19439
|
+
const scope = resolveEditScope2(loaded.content, scopedInput);
|
|
18941
19440
|
const replaceAll = normalizedInput.replaceAll ?? false;
|
|
18942
|
-
const
|
|
18943
|
-
const
|
|
18944
|
-
const occurrencesBefore = countOccurrences(scope.selectedText,
|
|
19441
|
+
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
19442
|
+
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
19443
|
+
const occurrencesBefore = countOccurrences(scope.selectedText, effectiveOldString);
|
|
19444
|
+
if (!replaceAll && occurrencesBefore === 0) {
|
|
19445
|
+
const loose = tryLooseBlockReplace2(scope.selectedText, effectiveOldString, input.newString);
|
|
19446
|
+
if (loose !== null) {
|
|
19447
|
+
const outputText2 = `${loaded.content.slice(0, scope.rangeStart)}${loose.content}${loaded.content.slice(scope.rangeEnd)}`;
|
|
19448
|
+
ensureLossless(outputText2, targetEncoding, targetHasBom);
|
|
19449
|
+
const buffer2 = encodeText(outputText2, targetEncoding, targetHasBom);
|
|
19450
|
+
await fs4.writeFile(loaded.filePath, buffer2);
|
|
19451
|
+
return {
|
|
19452
|
+
mode: "replace",
|
|
19453
|
+
filePath: loaded.filePath,
|
|
19454
|
+
encoding: targetEncoding,
|
|
19455
|
+
requestedEncoding: loaded.requestedEncoding,
|
|
19456
|
+
detectedEncoding: loaded.detectedEncoding,
|
|
19457
|
+
confidence: loaded.confidence,
|
|
19458
|
+
hasBom: targetHasBom,
|
|
19459
|
+
replacements: 1,
|
|
19460
|
+
occurrencesBefore: loose.occurrencesBefore,
|
|
19461
|
+
bytesRead: loaded.bytesRead,
|
|
19462
|
+
bytesWritten: buffer2.byteLength,
|
|
19463
|
+
newlineStyle: detectNewlineStyle(outputText2),
|
|
19464
|
+
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, loose.matchedOriginal, input.newString)
|
|
19465
|
+
};
|
|
19466
|
+
}
|
|
19467
|
+
}
|
|
18945
19468
|
if (replaceAll) {
|
|
18946
19469
|
if (occurrencesBefore === 0) {
|
|
18947
|
-
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText,
|
|
19470
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText, effectiveOldString));
|
|
18948
19471
|
}
|
|
18949
19472
|
} else if (occurrencesBefore === 0) {
|
|
18950
|
-
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText,
|
|
19473
|
+
throw createTextError("GBK_NO_MATCH", buildNoMatchMessage2(scope.selectedText, effectiveOldString));
|
|
18951
19474
|
} else if (occurrencesBefore > 1) {
|
|
18952
|
-
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${
|
|
19475
|
+
throw createTextError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${effectiveOldString}`);
|
|
18953
19476
|
}
|
|
18954
19477
|
const alignedNewString = normalizedInput.preserveNewlineStyle === false ? input.newString : alignTextToNewlineStyle2(input.newString, loaded.newlineStyle);
|
|
18955
|
-
const replaced = replaceAll ? scope.selectedText.split(
|
|
19478
|
+
const replaced = replaceAll ? scope.selectedText.split(effectiveOldString).join(alignedNewString) : scope.selectedText.replace(effectiveOldString, alignedNewString);
|
|
18956
19479
|
const outputText = `${loaded.content.slice(0, scope.rangeStart)}${replaced}${loaded.content.slice(scope.rangeEnd)}`;
|
|
18957
|
-
const targetEncoding = preserveEncoding || requestedEncoding === "auto" ? loaded.encoding : resolveExplicitTextEncoding(requestedEncoding, loaded.encoding);
|
|
18958
|
-
const targetHasBom = preserveEncoding ? loaded.hasBom : targetEncoding === "utf8-bom" || targetEncoding === "utf16le" || targetEncoding === "utf16be";
|
|
18959
19480
|
ensureLossless(outputText, targetEncoding, targetHasBom);
|
|
18960
19481
|
const buffer = encodeText(outputText, targetEncoding, targetHasBom);
|
|
18961
|
-
await
|
|
19482
|
+
await fs4.writeFile(loaded.filePath, buffer);
|
|
18962
19483
|
return {
|
|
18963
19484
|
mode: "replace",
|
|
18964
19485
|
filePath: loaded.filePath,
|
|
@@ -18972,7 +19493,7 @@ async function replaceTextFileText(input) {
|
|
|
18972
19493
|
bytesRead: loaded.bytesRead,
|
|
18973
19494
|
bytesWritten: buffer.byteLength,
|
|
18974
19495
|
newlineStyle: detectNewlineStyle(outputText),
|
|
18975
|
-
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding,
|
|
19496
|
+
diffPreview: buildLineDiffPreview(loaded.filePath, targetEncoding, effectiveOldString, alignedNewString)
|
|
18976
19497
|
};
|
|
18977
19498
|
}
|
|
18978
19499
|
|
|
@@ -19119,6 +19640,28 @@ var MANAGED_TOOL_IDS = /* @__PURE__ */ new Set([
|
|
|
19119
19640
|
"text_write",
|
|
19120
19641
|
"text_edit"
|
|
19121
19642
|
]);
|
|
19643
|
+
var BUILTIN_TEXT_TOOL_IDS = /* @__PURE__ */ new Set(["read", "write", "edit"]);
|
|
19644
|
+
var REMEMBERABLE_TEXT_TOOL_IDS = /* @__PURE__ */ new Set(["text_read", "text_write", "text_edit"]);
|
|
19645
|
+
function getToolFilePath(args) {
|
|
19646
|
+
if (!args || typeof args !== "object") {
|
|
19647
|
+
return null;
|
|
19648
|
+
}
|
|
19649
|
+
const filePath = args.filePath;
|
|
19650
|
+
return typeof filePath === "string" ? filePath : null;
|
|
19651
|
+
}
|
|
19652
|
+
async function maybePersistRememberedEncoding(metadata) {
|
|
19653
|
+
const filePath = typeof metadata.filePath === "string" ? metadata.filePath : null;
|
|
19654
|
+
const encoding = metadata.encoding;
|
|
19655
|
+
if (!filePath) {
|
|
19656
|
+
return;
|
|
19657
|
+
}
|
|
19658
|
+
if (isRememberedGbkEncoding(encoding)) {
|
|
19659
|
+
await rememberGbkEncoding(filePath, encoding);
|
|
19660
|
+
metadata.rememberedEncoding = encoding;
|
|
19661
|
+
return;
|
|
19662
|
+
}
|
|
19663
|
+
await forgetRememberedEncoding(filePath);
|
|
19664
|
+
}
|
|
19122
19665
|
function truncateMetadataPreview(value, sessionID) {
|
|
19123
19666
|
const previewMaxChars = Math.max(800, Math.min(2e3, Math.floor(getMaxOutputChars(sessionID) / 2)));
|
|
19124
19667
|
if (value.length <= previewMaxChars) return value;
|
|
@@ -19195,6 +19738,25 @@ function createOpencodeGbkHooks(client, directory) {
|
|
|
19195
19738
|
await maybeAutoSummarizeSession(client, directory, input);
|
|
19196
19739
|
}
|
|
19197
19740
|
},
|
|
19741
|
+
async "tool.execute.before"(input, output) {
|
|
19742
|
+
const filePath = getToolFilePath(output.args);
|
|
19743
|
+
if (!filePath) {
|
|
19744
|
+
return;
|
|
19745
|
+
}
|
|
19746
|
+
const remembered = await getRememberedGbkEncoding(filePath);
|
|
19747
|
+
if (!remembered) {
|
|
19748
|
+
return;
|
|
19749
|
+
}
|
|
19750
|
+
if (REMEMBERABLE_TEXT_TOOL_IDS.has(input.tool)) {
|
|
19751
|
+
if (output.args && (output.args.encoding === void 0 || output.args.encoding === "auto")) {
|
|
19752
|
+
output.args.encoding = remembered.encoding;
|
|
19753
|
+
}
|
|
19754
|
+
return;
|
|
19755
|
+
}
|
|
19756
|
+
if (BUILTIN_TEXT_TOOL_IDS.has(input.tool)) {
|
|
19757
|
+
throw new Error(`\u6587\u4EF6\u5DF2\u8BB0\u5FC6\u4E3A ${remembered.encoding.toUpperCase()} \u7F16\u7801\uFF0C\u8BF7\u6539\u7528 gbk_read\u3001gbk_write\u3001gbk_edit \u6216 gbk_search\uFF1A${filePath}`);
|
|
19758
|
+
}
|
|
19759
|
+
},
|
|
19198
19760
|
async "tool.execute.after"(input, output) {
|
|
19199
19761
|
if (!MANAGED_TOOL_IDS.has(input.tool)) return;
|
|
19200
19762
|
const maxOutputChars = getMaxOutputChars(input.sessionID);
|
|
@@ -19210,6 +19772,11 @@ function createOpencodeGbkHooks(client, directory) {
|
|
|
19210
19772
|
if (typeof metadata.diffPreview === "string") {
|
|
19211
19773
|
metadata.diffPreview = truncateMetadataPreview(metadata.diffPreview, input.sessionID);
|
|
19212
19774
|
}
|
|
19775
|
+
try {
|
|
19776
|
+
await maybePersistRememberedEncoding(metadata);
|
|
19777
|
+
} catch {
|
|
19778
|
+
metadata.encodingMemoryWarning = "\u8BB0\u5FC6\u6587\u4EF6\u7F16\u7801\u5931\u8D25\uFF0C\u5DF2\u5FFD\u7565";
|
|
19779
|
+
}
|
|
19213
19780
|
metadata.maxOutputChars = maxOutputChars;
|
|
19214
19781
|
if (compactionCount > 0) {
|
|
19215
19782
|
metadata.sessionCompactions = compactionCount;
|