opencode-gbk-tools 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -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 +505 -119
- package/dist/plugins/opencode-gbk-tools.js +505 -119
- package/dist/release-manifest.json +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
为 OpenCode 提供一套自动识别编码的文本工具,以及面向 `GBK` / `GB18030` 的专用工具。
|
|
4
4
|
|
|
5
|
-
解决 OpenCode 内置工具难以稳定处理非 UTF-8 文本文件的问题,并通过 plugin 让所有 agents 默认获得这些工具与规则;同时提供一个只允许调用 `
|
|
5
|
+
解决 OpenCode 内置工具难以稳定处理非 UTF-8 文本文件的问题,并通过 plugin 让所有 agents 默认获得这些工具与规则;同时提供一个只允许调用 `gbk_*` 自定义工具的 `gbk-engine` agent。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
| `gbk_write` | 写入或追加内容到 GBK 文件(`append=true` 支持追加) |
|
|
18
18
|
| `gbk_edit` | 精确替换 GBK 文件中的指定文本块 |
|
|
19
19
|
| `gbk_search` | 在 GBK 文件中搜索关键词,返回行号和上下文 |
|
|
20
|
-
| 本地/全局 plugin 规则 | 给所有 agents
|
|
21
|
-
| `gbk-engine` agent | 禁用 OpenCode 内置读写编辑工具,只保留 `
|
|
20
|
+
| 本地/全局 plugin 规则 | 给所有 agents 注入“默认优先使用 `gbk_*`,`text_*` 作为兼容工具”的系统提示,并统一开放 `text_*` / `gbk_*` 工具 |
|
|
21
|
+
| `gbk-engine` agent | 禁用 OpenCode 内置读写编辑工具,只保留 `gbk_*` 自定义工具 |
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -34,7 +34,7 @@ npx opencode-gbk-tools install
|
|
|
34
34
|
|
|
35
35
|
- `text_read` / `text_write` / `text_edit`
|
|
36
36
|
- `gbk_read` / `gbk_write` / `gbk_edit` / `gbk_search`
|
|
37
|
-
-
|
|
37
|
+
- 默认优先使用 `gbk_*`、`text_*` 作为兼容工具的系统提示
|
|
38
38
|
- `.opencode/agents/gbk-engine.md` 或 `~/.config/opencode/agents/gbk-engine.md`
|
|
39
39
|
|
|
40
40
|
如果你希望强制禁用内置 `read` / `write` / `edit` / `apply_patch`,可以直接切换到专属 `gbk-engine` agent。
|
|
@@ -77,8 +77,8 @@ npx opencode-gbk-tools uninstall
|
|
|
77
77
|
优先使用建议:
|
|
78
78
|
|
|
79
79
|
```text
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
默认优先:gbk_read / gbk_write / gbk_edit / gbk_search
|
|
81
|
+
兼容性场景:text_read / text_write / text_edit
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
编辑动作建议:
|
|
@@ -208,6 +208,7 @@ A:不要。现在优先用 `mode="insertAfter"` 或 `mode="insertBefore"`,
|
|
|
208
208
|
|
|
209
209
|
| 版本 | 说明 |
|
|
210
210
|
|------|------|
|
|
211
|
+
| 0.1.28 | 对齐 plugin/agent 文档说明到当前产品行为:默认优先推荐 `gbk_*`、`text_*` 作为兼容工具,`gbk-engine` 仅保留 `gbk_*`;修复 `src/lib/gbk-file.ts` 中多处用户可见乱码错误文案;修复 `text_*` 大文件流式替换跨块命中遗漏问题并补充回归测试 |
|
|
211
212
|
| 0.1.27 | 重新发布当前稳定内容,包含:新建 `.txt` 文件在 `encoding=auto` 下默认使用 `gbk`;保留已有文件的原编码检测与保持逻辑;修复 plugin 追加 system 规则时可能触发部分 Jinja/chat template 的“System message must be at the beginning”异常;同步更新工具提示、README 与测试覆盖 |
|
|
212
213
|
| 0.1.26 | 仅提升发布版本号,用于重新发布当时的稳定内容 |
|
|
213
214
|
| 0.1.25 | 恢复并正式发布专属 `gbk-engine` agent;通过 agent 白名单禁用内置 `read` / `write` / `edit` / `apply_patch`,只保留 `text_*` / `gbk_*` 自定义工具,并把 agent 纳入构建、安装与 release manifest |
|
|
@@ -8,9 +8,6 @@ tools:
|
|
|
8
8
|
write: false
|
|
9
9
|
edit: false
|
|
10
10
|
apply_patch: false
|
|
11
|
-
text_read: true
|
|
12
|
-
text_write: true
|
|
13
|
-
text_edit: true
|
|
14
11
|
gbk_read: true
|
|
15
12
|
gbk_write: true
|
|
16
13
|
gbk_edit: true
|
|
@@ -21,12 +18,12 @@ permission:
|
|
|
21
18
|
---
|
|
22
19
|
你是 `gbk-engine`。
|
|
23
20
|
|
|
24
|
-
你只能使用 `opencode-gbk-tools` 提供的 `
|
|
21
|
+
你只能使用 `opencode-gbk-tools` 提供的 `gbk_*` 工具,不允许退回到 OpenCode 内置的 `read`、`write`、`edit`、`apply_patch` 或其他内置工具。
|
|
25
22
|
|
|
26
23
|
工作规则:
|
|
27
24
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
25
|
+
- 所有文本文件默认优先使用 `gbk_read`、`gbk_write`、`gbk_edit`、`gbk_search`
|
|
26
|
+
- 不再使用 `text_read`、`text_write`、`text_edit`
|
|
30
27
|
- 编辑前先读取目标文件;对大文件先搜索,再局部读取
|
|
31
28
|
- 插入内容优先使用 `mode="insertAfter"` 或 `mode="insertBefore"` 搭配 `anchor` / `content`
|
|
32
29
|
- 只有在精确替换现有内容时,才使用 `oldString` / `newString`
|
|
@@ -16406,45 +16406,103 @@ function assertInsertArguments(input) {
|
|
|
16406
16406
|
throw createGbkError("GBK_INVALID_ARGUMENT", "content \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16407
16407
|
}
|
|
16408
16408
|
if (input.replaceAll !== void 0) {
|
|
16409
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\
|
|
16409
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 replaceAll");
|
|
16410
16410
|
}
|
|
16411
16411
|
if (input.startLine !== void 0 || input.endLine !== void 0 || input.startAnchor !== void 0 || input.endAnchor !== void 0) {
|
|
16412
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\
|
|
16412
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u6A21\u5F0F\u4E0D\u652F\u6301 startLine/endLine/startAnchor/endAnchor");
|
|
16413
16413
|
}
|
|
16414
16414
|
}
|
|
16415
|
-
function
|
|
16416
|
-
assertStringArgument(text, "text");
|
|
16417
|
-
assertStringArgument(token, "anchor");
|
|
16418
|
-
assertPositiveInteger(occurrence, "occurrence");
|
|
16415
|
+
function collectOccurrencePositions(text, token) {
|
|
16419
16416
|
if (token.length === 0) {
|
|
16420
|
-
|
|
16417
|
+
return [];
|
|
16421
16418
|
}
|
|
16422
|
-
|
|
16419
|
+
const positions = [];
|
|
16423
16420
|
let searchFrom = 0;
|
|
16424
16421
|
while (true) {
|
|
16425
16422
|
const index = text.indexOf(token, searchFrom);
|
|
16426
16423
|
if (index === -1) {
|
|
16427
|
-
|
|
16428
|
-
}
|
|
16429
|
-
count += 1;
|
|
16430
|
-
if (count === occurrence) {
|
|
16431
|
-
return { index, total: countOccurrences(text, token) };
|
|
16424
|
+
return positions;
|
|
16432
16425
|
}
|
|
16426
|
+
positions.push(index);
|
|
16433
16427
|
searchFrom = index + token.length;
|
|
16434
16428
|
}
|
|
16435
|
-
|
|
16436
|
-
|
|
16429
|
+
}
|
|
16430
|
+
function buildFlexibleSearchVariants(token, newlineStyle) {
|
|
16431
|
+
const variants = /* @__PURE__ */ new Set();
|
|
16432
|
+
const pushVariant = (value) => {
|
|
16433
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16434
|
+
return;
|
|
16435
|
+
}
|
|
16436
|
+
variants.add(value);
|
|
16437
|
+
};
|
|
16438
|
+
pushVariant(token);
|
|
16439
|
+
pushVariant(alignTextToNewlineStyle(token, newlineStyle));
|
|
16440
|
+
const prefixedBlock = parseLineNumberPrefixedBlock(token);
|
|
16441
|
+
if (prefixedBlock) {
|
|
16442
|
+
pushVariant(prefixedBlock.strippedText);
|
|
16443
|
+
pushVariant(alignTextToNewlineStyle(prefixedBlock.strippedText, newlineStyle));
|
|
16444
|
+
}
|
|
16445
|
+
const trimmedLines = trimTrailingEmptyLines(splitNormalizedLines(token));
|
|
16446
|
+
if (trimmedLines.length > 0) {
|
|
16447
|
+
const trimmedToken = trimmedLines.join("\n");
|
|
16448
|
+
pushVariant(trimmedToken);
|
|
16449
|
+
pushVariant(alignTextToNewlineStyle(trimmedToken, newlineStyle));
|
|
16450
|
+
const trimmedPrefixedBlock = parseLineNumberPrefixedBlock(trimmedToken);
|
|
16451
|
+
if (trimmedPrefixedBlock) {
|
|
16452
|
+
pushVariant(trimmedPrefixedBlock.strippedText);
|
|
16453
|
+
pushVariant(alignTextToNewlineStyle(trimmedPrefixedBlock.strippedText, newlineStyle));
|
|
16454
|
+
}
|
|
16455
|
+
}
|
|
16456
|
+
return [...variants];
|
|
16457
|
+
}
|
|
16458
|
+
function findNextFlexibleMatch(text, token, searchFrom, newlineStyle) {
|
|
16459
|
+
let bestMatch = null;
|
|
16460
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16461
|
+
const index = text.indexOf(candidate, searchFrom);
|
|
16462
|
+
if (index === -1) {
|
|
16463
|
+
continue;
|
|
16464
|
+
}
|
|
16465
|
+
if (bestMatch === null || index < bestMatch.index || index === bestMatch.index && candidate.length > bestMatch.matchedToken.length) {
|
|
16466
|
+
bestMatch = { index, matchedToken: candidate };
|
|
16467
|
+
}
|
|
16437
16468
|
}
|
|
16438
|
-
|
|
16469
|
+
return bestMatch;
|
|
16470
|
+
}
|
|
16471
|
+
function findFlexibleOccurrenceIndex(text, token, occurrence, newlineStyle) {
|
|
16472
|
+
assertStringArgument(text, "text");
|
|
16473
|
+
assertStringArgument(token, "anchor");
|
|
16474
|
+
assertPositiveInteger(occurrence, "occurrence");
|
|
16475
|
+
if (token.length === 0) {
|
|
16476
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "anchor \u4E0D\u80FD\u4E3A\u7A7A");
|
|
16477
|
+
}
|
|
16478
|
+
let maxMatches = 0;
|
|
16479
|
+
for (const candidate of buildFlexibleSearchVariants(token, newlineStyle)) {
|
|
16480
|
+
const positions = collectOccurrencePositions(text, candidate);
|
|
16481
|
+
if (positions.length === 0) {
|
|
16482
|
+
continue;
|
|
16483
|
+
}
|
|
16484
|
+
maxMatches = Math.max(maxMatches, positions.length);
|
|
16485
|
+
if (positions.length >= occurrence) {
|
|
16486
|
+
return {
|
|
16487
|
+
index: positions[occurrence - 1],
|
|
16488
|
+
total: positions.length,
|
|
16489
|
+
matchedToken: candidate
|
|
16490
|
+
};
|
|
16491
|
+
}
|
|
16492
|
+
}
|
|
16493
|
+
if (maxMatches === 0) {
|
|
16494
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${token}`);
|
|
16495
|
+
}
|
|
16496
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${token} \u53EA\u627E\u5230 ${maxMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${occurrence} \u5904`);
|
|
16439
16497
|
}
|
|
16440
16498
|
function insertByAnchor(text, mode, anchor, content, occurrence, ifExists, newlineStyle) {
|
|
16441
16499
|
const alignedContent = newlineStyle === "crlf" ? content.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : newlineStyle === "lf" ? content.replace(/\r\n/g, "\n") : content;
|
|
16442
|
-
const located =
|
|
16443
|
-
const insertionPoint = mode === "insertAfter" ? located.index +
|
|
16500
|
+
const located = findFlexibleOccurrenceIndex(text, anchor, occurrence, newlineStyle);
|
|
16501
|
+
const insertionPoint = mode === "insertAfter" ? located.index + located.matchedToken.length : located.index;
|
|
16444
16502
|
const alreadyExists = mode === "insertAfter" ? text.slice(insertionPoint, insertionPoint + alignedContent.length) === alignedContent : text.slice(Math.max(0, insertionPoint - alignedContent.length), insertionPoint) === alignedContent;
|
|
16445
16503
|
if (alreadyExists) {
|
|
16446
16504
|
if (ifExists === "error") {
|
|
16447
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
16505
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
16448
16506
|
}
|
|
16449
16507
|
if (ifExists === "skip") {
|
|
16450
16508
|
return {
|
|
@@ -16485,7 +16543,7 @@ function normalizeOptionalPositiveInteger(value, name) {
|
|
|
16485
16543
|
}
|
|
16486
16544
|
function assertNotBinary(buffer) {
|
|
16487
16545
|
if (buffer.includes(0)) {
|
|
16488
|
-
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\
|
|
16546
|
+
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
|
|
16489
16547
|
}
|
|
16490
16548
|
}
|
|
16491
16549
|
function detectNewlineStyle(text) {
|
|
@@ -16563,19 +16621,15 @@ function applyAnchors(text, startAnchor, endAnchor) {
|
|
|
16563
16621
|
}
|
|
16564
16622
|
let rangeStart = 0;
|
|
16565
16623
|
let rangeEnd = text.length;
|
|
16624
|
+
const newlineStyle = detectNewlineStyle(text);
|
|
16566
16625
|
if (startAnchor) {
|
|
16567
|
-
const found = text
|
|
16568
|
-
|
|
16569
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8D77\u59CB\u951A\uFFFD?: ${startAnchor}`);
|
|
16570
|
-
}
|
|
16571
|
-
rangeStart = found + startAnchor.length;
|
|
16626
|
+
const found = findFlexibleOccurrenceIndex(text, startAnchor, 1, newlineStyle);
|
|
16627
|
+
rangeStart = found.index + found.matchedToken.length;
|
|
16572
16628
|
}
|
|
16573
16629
|
if (endAnchor) {
|
|
16574
|
-
const
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
}
|
|
16578
|
-
rangeEnd = found;
|
|
16630
|
+
const searchText = text.slice(rangeStart);
|
|
16631
|
+
const found = findFlexibleOccurrenceIndex(searchText, endAnchor, 1, newlineStyle);
|
|
16632
|
+
rangeEnd = rangeStart + found.index;
|
|
16579
16633
|
}
|
|
16580
16634
|
if (rangeEnd < rangeStart) {
|
|
16581
16635
|
throw createGbkError("GBK_INVALID_ARGUMENT", "\u951A\u70B9\u8303\u56F4\u65E0\u6548");
|
|
@@ -16626,9 +16680,9 @@ function getNearestContext(content, oldString) {
|
|
|
16626
16680
|
}
|
|
16627
16681
|
function buildNoMatchMessage(content, oldString) {
|
|
16628
16682
|
return [
|
|
16629
|
-
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\
|
|
16630
|
-
"oldString \u5FC5\u987B\u4E0E\u6587\u4EF6\u5B9E\u9645\u5185\u5BB9\u5B8C\u5168\u5BF9\u5E94\uFF0C\u6216\u4EC5\u5728\u6362\
|
|
16631
|
-
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\
|
|
16683
|
+
"\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
|
|
16684
|
+
"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",
|
|
16685
|
+
"\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
|
|
16632
16686
|
getNearestContext(content, oldString)
|
|
16633
16687
|
].join("\n");
|
|
16634
16688
|
}
|
|
@@ -16640,8 +16694,10 @@ function stripLineNumberPrefixes(lines) {
|
|
|
16640
16694
|
return lines.map((l) => l.replace(/^\d+: /, ""));
|
|
16641
16695
|
}
|
|
16642
16696
|
function parseLineNumberPrefixedBlock(text) {
|
|
16643
|
-
const
|
|
16644
|
-
|
|
16697
|
+
const normalizedText = normalizeNewlines(text);
|
|
16698
|
+
const hasTrailingNewline = normalizedText.endsWith("\n");
|
|
16699
|
+
const lines = trimTrailingEmptyLines(splitNormalizedLines(text));
|
|
16700
|
+
if (lines.length === 0 || !hasLineNumberPrefixes(lines)) {
|
|
16645
16701
|
return null;
|
|
16646
16702
|
}
|
|
16647
16703
|
const lineNumbers = [];
|
|
@@ -16656,7 +16712,7 @@ function parseLineNumberPrefixedBlock(text) {
|
|
|
16656
16712
|
}
|
|
16657
16713
|
return {
|
|
16658
16714
|
lineNumbers,
|
|
16659
|
-
strippedText: strippedLines.join("\n")
|
|
16715
|
+
strippedText: `${strippedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`,
|
|
16660
16716
|
isContiguous: lineNumbers.every((lineNumber, index) => index === 0 || lineNumber === lineNumbers[index - 1] + 1),
|
|
16661
16717
|
startLine: lineNumbers[0],
|
|
16662
16718
|
endLine: lineNumbers[lineNumbers.length - 1]
|
|
@@ -16751,10 +16807,10 @@ async function resolveReadableGbkFile(input) {
|
|
|
16751
16807
|
try {
|
|
16752
16808
|
stat = await fs2.stat(candidatePath);
|
|
16753
16809
|
} catch (error45) {
|
|
16754
|
-
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\
|
|
16810
|
+
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
16755
16811
|
}
|
|
16756
16812
|
if (stat.isDirectory()) {
|
|
16757
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
16813
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16758
16814
|
}
|
|
16759
16815
|
return {
|
|
16760
16816
|
filePath: candidatePath,
|
|
@@ -16951,7 +17007,7 @@ function replaceScopedTextContent(scopeText, oldString, newString, replaceAll, n
|
|
|
16951
17007
|
} else if (occurrencesBefore === 0) {
|
|
16952
17008
|
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scopeText, oldString));
|
|
16953
17009
|
} else if (occurrencesBefore > 1) {
|
|
16954
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17010
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${oldString}`);
|
|
16955
17011
|
}
|
|
16956
17012
|
const alignedNewString = alignTextToNewlineStyle(newString, newlineStyle);
|
|
16957
17013
|
return {
|
|
@@ -17009,14 +17065,17 @@ async function replaceLargeGbkFileTextInLineRange(input) {
|
|
|
17009
17065
|
}
|
|
17010
17066
|
}
|
|
17011
17067
|
async function replaceLargeGbkFileByAnchor(input) {
|
|
17068
|
+
const lineIndex = await getGbkLineIndex(input);
|
|
17069
|
+
const newlineStyle = lineIndex.newlineStyle;
|
|
17012
17070
|
const tempPath = path2.join(
|
|
17013
17071
|
path2.dirname(input.filePath),
|
|
17014
17072
|
`${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17015
17073
|
);
|
|
17016
17074
|
const handle = await fs2.open(tempPath, "w");
|
|
17017
|
-
const alignedContent = input.content
|
|
17018
|
-
const
|
|
17019
|
-
const
|
|
17075
|
+
const alignedContent = alignTextToNewlineStyle(input.content, newlineStyle);
|
|
17076
|
+
const anchorVariants = buildFlexibleSearchVariants(input.anchor, newlineStyle);
|
|
17077
|
+
const maxAnchorLength = anchorVariants.reduce((maxLength, candidate) => Math.max(maxLength, candidate.length), input.anchor.length);
|
|
17078
|
+
const carryLength = Math.max(maxAnchorLength + alignedContent.length, 1);
|
|
17020
17079
|
let decoded = "";
|
|
17021
17080
|
let scanFrom = 0;
|
|
17022
17081
|
let totalMatches = 0;
|
|
@@ -17040,23 +17099,23 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17040
17099
|
await visitDecodedTextChunks(input, async (text) => {
|
|
17041
17100
|
decoded += text;
|
|
17042
17101
|
while (!inserted) {
|
|
17043
|
-
const
|
|
17044
|
-
if (
|
|
17102
|
+
const located = findNextFlexibleMatch(decoded, input.anchor, scanFrom, newlineStyle);
|
|
17103
|
+
if (located === null) {
|
|
17045
17104
|
break;
|
|
17046
17105
|
}
|
|
17047
17106
|
totalMatches += 1;
|
|
17048
|
-
const afterAnchor =
|
|
17107
|
+
const afterAnchor = located.index + located.matchedToken.length;
|
|
17049
17108
|
if (totalMatches !== input.occurrence) {
|
|
17050
17109
|
scanFrom = afterAnchor;
|
|
17051
17110
|
continue;
|
|
17052
17111
|
}
|
|
17053
|
-
const before = decoded.slice(0,
|
|
17112
|
+
const before = decoded.slice(0, located.index);
|
|
17054
17113
|
const after = decoded.slice(afterAnchor);
|
|
17055
|
-
const anchorAndAfter = decoded.slice(
|
|
17114
|
+
const anchorAndAfter = decoded.slice(located.index);
|
|
17056
17115
|
const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
|
|
17057
17116
|
if (alreadyExists) {
|
|
17058
17117
|
if (input.ifExists === "error") {
|
|
17059
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
17118
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17060
17119
|
}
|
|
17061
17120
|
if (input.ifExists === "skip") {
|
|
17062
17121
|
skipped = true;
|
|
@@ -17068,7 +17127,7 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17068
17127
|
}
|
|
17069
17128
|
if (input.mode === "insertAfter") {
|
|
17070
17129
|
bytesWritten += await writeEncodedText(handle, input.encoding, before);
|
|
17071
|
-
bytesWritten += await writeEncodedText(handle, input.encoding,
|
|
17130
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, located.matchedToken);
|
|
17072
17131
|
bytesWritten += await writeEncodedText(handle, input.encoding, alignedContent);
|
|
17073
17132
|
bytesWritten += await writeEncodedText(handle, input.encoding, after);
|
|
17074
17133
|
} else {
|
|
@@ -17087,23 +17146,23 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17087
17146
|
});
|
|
17088
17147
|
if (!inserted) {
|
|
17089
17148
|
while (true) {
|
|
17090
|
-
const
|
|
17091
|
-
if (
|
|
17149
|
+
const located = findNextFlexibleMatch(decoded, input.anchor, scanFrom, newlineStyle);
|
|
17150
|
+
if (located === null) {
|
|
17092
17151
|
break;
|
|
17093
17152
|
}
|
|
17094
17153
|
totalMatches += 1;
|
|
17095
|
-
const afterAnchor =
|
|
17154
|
+
const afterAnchor = located.index + located.matchedToken.length;
|
|
17096
17155
|
if (totalMatches !== input.occurrence) {
|
|
17097
17156
|
scanFrom = afterAnchor;
|
|
17098
17157
|
continue;
|
|
17099
17158
|
}
|
|
17100
|
-
const before = decoded.slice(0,
|
|
17159
|
+
const before = decoded.slice(0, located.index);
|
|
17101
17160
|
const after = decoded.slice(afterAnchor);
|
|
17102
|
-
const anchorAndAfter = decoded.slice(
|
|
17161
|
+
const anchorAndAfter = decoded.slice(located.index);
|
|
17103
17162
|
const alreadyExists = input.mode === "insertAfter" ? after.startsWith(alignedContent) : before.endsWith(alignedContent);
|
|
17104
17163
|
if (alreadyExists) {
|
|
17105
17164
|
if (input.ifExists === "error") {
|
|
17106
|
-
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\
|
|
17165
|
+
throw createGbkError("GBK_INVALID_ARGUMENT", "\u63D2\u5165\u4F4D\u7F6E\u5DF2\u5B58\u5728\u76F8\u540C\u5185\u5BB9");
|
|
17107
17166
|
}
|
|
17108
17167
|
if (input.ifExists === "skip") {
|
|
17109
17168
|
skipped = true;
|
|
@@ -17111,16 +17170,16 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17111
17170
|
break;
|
|
17112
17171
|
}
|
|
17113
17172
|
}
|
|
17114
|
-
decoded = input.mode === "insertAfter" ? `${before}${
|
|
17173
|
+
decoded = input.mode === "insertAfter" ? `${before}${located.matchedToken}${alignedContent}${after}` : `${before}${alignedContent}${anchorAndAfter}`;
|
|
17115
17174
|
inserted = true;
|
|
17116
17175
|
break;
|
|
17117
17176
|
}
|
|
17118
17177
|
}
|
|
17119
17178
|
if (!inserted && totalMatches === 0) {
|
|
17120
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\
|
|
17179
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u951A\u70B9: ${input.anchor}`);
|
|
17121
17180
|
}
|
|
17122
17181
|
if (!inserted && totalMatches > 0) {
|
|
17123
|
-
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\
|
|
17182
|
+
throw createGbkError("GBK_NO_MATCH", `\u951A\u70B9 ${input.anchor} \u53EA\u627E\u5230 ${totalMatches} \u5904\uFF0C\u65E0\u6CD5\u4F7F\u7528\u7B2C ${input.occurrence} \u5904`);
|
|
17124
17183
|
}
|
|
17125
17184
|
await finalizeInserted();
|
|
17126
17185
|
await handle.close();
|
|
@@ -17147,35 +17206,57 @@ async function replaceLargeGbkFileByAnchor(input) {
|
|
|
17147
17206
|
}
|
|
17148
17207
|
}
|
|
17149
17208
|
async function replaceLargeGbkFileText(input) {
|
|
17209
|
+
const lineIndex = await getGbkLineIndex(input);
|
|
17210
|
+
const newlineStyle = lineIndex.newlineStyle;
|
|
17150
17211
|
const tempPath = path2.join(
|
|
17151
17212
|
path2.dirname(input.filePath),
|
|
17152
17213
|
`${path2.basename(input.filePath)}.opencode-gbk-${crypto.randomUUID()}.tmp`
|
|
17153
17214
|
);
|
|
17154
17215
|
const handle = await fs2.open(tempPath, "w");
|
|
17155
17216
|
const carryLength = Math.max(input.oldString.length - 1, 0);
|
|
17217
|
+
const alignedNewString = alignTextToNewlineStyle(input.newString, newlineStyle);
|
|
17156
17218
|
let carry = "";
|
|
17157
17219
|
let occurrencesBefore = 0;
|
|
17158
17220
|
let bytesWritten = 0;
|
|
17221
|
+
let replacedFirstMatch = false;
|
|
17159
17222
|
const flushText = async (text, flush = false) => {
|
|
17160
17223
|
const combined = carry + text;
|
|
17161
|
-
|
|
17162
|
-
const processable = combined.slice(0, splitAt);
|
|
17163
|
-
carry = combined.slice(splitAt);
|
|
17164
|
-
if (processable.length === 0) {
|
|
17224
|
+
if (combined.length === 0) {
|
|
17165
17225
|
return;
|
|
17166
17226
|
}
|
|
17167
|
-
const
|
|
17168
|
-
const
|
|
17169
|
-
|
|
17170
|
-
let
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17227
|
+
const safeEnd = flush ? combined.length : Math.max(0, combined.length - carryLength);
|
|
17228
|
+
const outputParts = [];
|
|
17229
|
+
let cursor = 0;
|
|
17230
|
+
let flushUpto = safeEnd;
|
|
17231
|
+
while (true) {
|
|
17232
|
+
const index = combined.indexOf(input.oldString, cursor);
|
|
17233
|
+
if (index === -1) {
|
|
17234
|
+
break;
|
|
17235
|
+
}
|
|
17236
|
+
const matchEnd = index + input.oldString.length;
|
|
17237
|
+
if (!flush && matchEnd > safeEnd) {
|
|
17238
|
+
flushUpto = index;
|
|
17239
|
+
break;
|
|
17174
17240
|
}
|
|
17175
|
-
|
|
17176
|
-
|
|
17241
|
+
occurrencesBefore += 1;
|
|
17242
|
+
outputParts.push(combined.slice(cursor, index));
|
|
17243
|
+
if (input.replaceAll) {
|
|
17244
|
+
outputParts.push(alignedNewString);
|
|
17245
|
+
} else if (!replacedFirstMatch) {
|
|
17246
|
+
outputParts.push(alignedNewString);
|
|
17247
|
+
replacedFirstMatch = true;
|
|
17248
|
+
} else {
|
|
17249
|
+
outputParts.push(input.oldString);
|
|
17250
|
+
}
|
|
17251
|
+
cursor = matchEnd;
|
|
17252
|
+
}
|
|
17253
|
+
outputParts.push(combined.slice(cursor, flushUpto));
|
|
17254
|
+
carry = combined.slice(flushUpto);
|
|
17255
|
+
const processable = outputParts.join("");
|
|
17256
|
+
if (processable.length === 0) {
|
|
17257
|
+
return;
|
|
17177
17258
|
}
|
|
17178
|
-
bytesWritten += await writeEncodedText(handle, input.encoding,
|
|
17259
|
+
bytesWritten += await writeEncodedText(handle, input.encoding, processable);
|
|
17179
17260
|
};
|
|
17180
17261
|
try {
|
|
17181
17262
|
await visitDecodedTextChunks(input, async (text) => {
|
|
@@ -17183,10 +17264,10 @@ async function replaceLargeGbkFileText(input) {
|
|
|
17183
17264
|
});
|
|
17184
17265
|
await flushText("", true);
|
|
17185
17266
|
if (occurrencesBefore === 0) {
|
|
17186
|
-
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\
|
|
17267
|
+
throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
|
|
17187
17268
|
}
|
|
17188
17269
|
if (!input.replaceAll && occurrencesBefore > 1) {
|
|
17189
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17270
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
|
|
17190
17271
|
}
|
|
17191
17272
|
await handle.close();
|
|
17192
17273
|
const mode = typeof input.stat.mode === "bigint" ? Number(input.stat.mode) : input.stat.mode;
|
|
@@ -17335,7 +17416,7 @@ async function replaceGbkFileText(input) {
|
|
|
17335
17416
|
} else if (occurrencesBefore === 0) {
|
|
17336
17417
|
throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, effectiveOldString));
|
|
17337
17418
|
} else if (occurrencesBefore > 1) {
|
|
17338
|
-
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\
|
|
17419
|
+
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${effectiveOldString}`);
|
|
17339
17420
|
}
|
|
17340
17421
|
const fileNewlineStyle = detectNewlineStyle(current.content);
|
|
17341
17422
|
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;
|
|
@@ -16329,7 +16329,7 @@ function normalizeOptionalPositiveInteger(value, name) {
|
|
|
16329
16329
|
}
|
|
16330
16330
|
function assertNotBinary(buffer) {
|
|
16331
16331
|
if (buffer.includes(0)) {
|
|
16332
|
-
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\
|
|
16332
|
+
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
|
|
16333
16333
|
}
|
|
16334
16334
|
}
|
|
16335
16335
|
function splitLinesWithNumbers(text, offset = 1, limit = 2e3) {
|
|
@@ -16420,10 +16420,10 @@ async function resolveReadableGbkFile(input) {
|
|
|
16420
16420
|
try {
|
|
16421
16421
|
stat = await fs2.stat(candidatePath);
|
|
16422
16422
|
} catch (error45) {
|
|
16423
|
-
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\
|
|
16423
|
+
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
16424
16424
|
}
|
|
16425
16425
|
if (stat.isDirectory()) {
|
|
16426
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
16426
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16427
16427
|
}
|
|
16428
16428
|
return {
|
|
16429
16429
|
filePath: candidatePath,
|
|
@@ -16313,7 +16313,7 @@ function assertEncodingSupported(encoding) {
|
|
|
16313
16313
|
}
|
|
16314
16314
|
function assertNotBinary(buffer) {
|
|
16315
16315
|
if (buffer.includes(0)) {
|
|
16316
|
-
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\
|
|
16316
|
+
throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
|
|
16317
16317
|
}
|
|
16318
16318
|
}
|
|
16319
16319
|
async function resolveReadableGbkFile(input) {
|
|
@@ -16324,10 +16324,10 @@ async function resolveReadableGbkFile(input) {
|
|
|
16324
16324
|
try {
|
|
16325
16325
|
stat = await fs2.stat(candidatePath);
|
|
16326
16326
|
} catch (error45) {
|
|
16327
|
-
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\
|
|
16327
|
+
throw createGbkError("GBK_FILE_NOT_FOUND", `\u6587\u4EF6\u4E0D\u5B58\u5728: ${candidatePath}`, error45);
|
|
16328
16328
|
}
|
|
16329
16329
|
if (stat.isDirectory()) {
|
|
16330
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
16330
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16331
16331
|
}
|
|
16332
16332
|
return {
|
|
16333
16333
|
filePath: candidatePath,
|
|
@@ -16370,10 +16370,10 @@ async function writeGbkFile(input) {
|
|
|
16370
16370
|
try {
|
|
16371
16371
|
const stat = await fs2.stat(candidatePath);
|
|
16372
16372
|
if (stat.isDirectory()) {
|
|
16373
|
-
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\
|
|
16373
|
+
throw createGbkError("GBK_IS_DIRECTORY", `\u76EE\u6807\u8DEF\u5F84\u662F\u76EE\u5F55: ${candidatePath}`);
|
|
16374
16374
|
}
|
|
16375
16375
|
if (!overwrite) {
|
|
16376
|
-
throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\
|
|
16376
|
+
throw createGbkError("GBK_FILE_EXISTS", `\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728: ${candidatePath}`);
|
|
16377
16377
|
}
|
|
16378
16378
|
} catch (error45) {
|
|
16379
16379
|
if (error45 instanceof Error && "code" in error45) {
|