opencode-gbk-tools 0.1.2 → 0.1.3

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.
@@ -16320,6 +16320,20 @@ function assertNotBinary(buffer) {
16320
16320
  throw createGbkError("GBK_BINARY_FILE", "\u7591\u4F3C\u4E8C\u8FDB\u5236\u6587\u4EF6\uFF0C\u65E0\u6CD5\u6309 GBK \u6587\u672C\u5904\u7406");
16321
16321
  }
16322
16322
  }
16323
+ function detectNewlineStyle(text) {
16324
+ const crlfCount = (text.match(/\r\n/g) ?? []).length;
16325
+ const lfCount = (text.match(/(^|[^\r])\n/g) ?? []).length;
16326
+ if (crlfCount > 0 && lfCount === 0) {
16327
+ return "crlf";
16328
+ }
16329
+ if (lfCount > 0 && crlfCount === 0) {
16330
+ return "lf";
16331
+ }
16332
+ if (crlfCount > 0 && lfCount > 0) {
16333
+ return "mixed";
16334
+ }
16335
+ return "none";
16336
+ }
16323
16337
  function applyLineRange(text, startLine, endLine) {
16324
16338
  if (startLine === void 0 && endLine === void 0) {
16325
16339
  return {
@@ -16401,6 +16415,84 @@ function resolveEditScope(text, input) {
16401
16415
  rangeEnd: anchored.rangeStart + lineRanged.rangeEnd
16402
16416
  };
16403
16417
  }
16418
+ function normalizeNewlines(text) {
16419
+ return text.replace(/\r\n/g, "\n");
16420
+ }
16421
+ function trimRightSpaces(text) {
16422
+ return text.replace(/[ \t]+$/g, "");
16423
+ }
16424
+ function splitNormalizedLines(text) {
16425
+ return normalizeNewlines(text).split("\n");
16426
+ }
16427
+ function trimTrailingEmptyLines(lines) {
16428
+ const result = [...lines];
16429
+ while (result.length > 0 && trimRightSpaces(result[result.length - 1]) === "") {
16430
+ result.pop();
16431
+ }
16432
+ return result;
16433
+ }
16434
+ function getNearestContext(content, oldString) {
16435
+ const lines = content.split(/\r?\n/);
16436
+ const oldLines = normalizeNewlines(oldString).split("\n").filter(Boolean);
16437
+ const firstToken = oldLines[0] || oldString.trim();
16438
+ for (let index = 0; index < lines.length; index += 1) {
16439
+ if (lines[index].includes(firstToken)) {
16440
+ return lines.slice(index, index + 4).join("\n");
16441
+ }
16442
+ }
16443
+ return lines.slice(0, 4).join("\n");
16444
+ }
16445
+ function buildNoMatchMessage(content, oldString) {
16446
+ return [
16447
+ "\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
16448
+ "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",
16449
+ "\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
16450
+ getNearestContext(content, oldString)
16451
+ ].join("\n");
16452
+ }
16453
+ function tryLooseBlockReplace(content, oldString, newString) {
16454
+ const normalizedContent = normalizeNewlines(content);
16455
+ const contentLines = splitNormalizedLines(content);
16456
+ const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16457
+ const newLines = splitNormalizedLines(newString);
16458
+ if (oldLines.length === 0) {
16459
+ return null;
16460
+ }
16461
+ for (let start = 0; start < contentLines.length; start += 1) {
16462
+ let contentIndex = start;
16463
+ let oldIndex = 0;
16464
+ while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
16465
+ const expected = trimRightSpaces(oldLines[oldIndex]);
16466
+ const actual = trimRightSpaces(contentLines[contentIndex]);
16467
+ if (expected === actual) {
16468
+ oldIndex += 1;
16469
+ contentIndex += 1;
16470
+ continue;
16471
+ }
16472
+ if (actual === "") {
16473
+ contentIndex += 1;
16474
+ continue;
16475
+ }
16476
+ if (expected === "") {
16477
+ oldIndex += 1;
16478
+ continue;
16479
+ }
16480
+ break;
16481
+ }
16482
+ if (oldIndex === oldLines.length) {
16483
+ const replacedNormalized = [
16484
+ ...contentLines.slice(0, start),
16485
+ ...newLines,
16486
+ ...contentLines.slice(contentIndex)
16487
+ ].join("\n");
16488
+ return {
16489
+ occurrencesBefore: 1,
16490
+ content: detectNewlineStyle(content) === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16491
+ };
16492
+ }
16493
+ }
16494
+ return null;
16495
+ }
16404
16496
  function countOccurrences(text, target) {
16405
16497
  if (target.length === 0) {
16406
16498
  throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
@@ -16575,12 +16667,28 @@ async function replaceGbkFileText(input) {
16575
16667
  const current = await readWholeGbkTextFile(resolved);
16576
16668
  const scope = resolveEditScope(current.content, input);
16577
16669
  const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
16670
+ if (!replaceAll && occurrencesBefore === 0) {
16671
+ const loose = tryLooseBlockReplace(scope.selectedText, input.oldString, input.newString);
16672
+ if (loose !== null) {
16673
+ const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
16674
+ const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
16675
+ await fs2.writeFile(current.filePath, buffer2);
16676
+ return {
16677
+ filePath: current.filePath,
16678
+ encoding: current.encoding,
16679
+ replacements: 1,
16680
+ occurrencesBefore: loose.occurrencesBefore,
16681
+ bytesRead: current.bytesRead,
16682
+ bytesWritten: buffer2.byteLength
16683
+ };
16684
+ }
16685
+ }
16578
16686
  if (replaceAll) {
16579
16687
  if (occurrencesBefore === 0) {
16580
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16688
+ throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
16581
16689
  }
16582
16690
  } else if (occurrencesBefore === 0) {
16583
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16691
+ throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
16584
16692
  } else if (occurrencesBefore > 1) {
16585
16693
  throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
16586
16694
  }
@@ -16466,6 +16466,84 @@ function resolveEditScope(text, input) {
16466
16466
  rangeEnd: anchored.rangeStart + lineRanged.rangeEnd
16467
16467
  };
16468
16468
  }
16469
+ function normalizeNewlines(text) {
16470
+ return text.replace(/\r\n/g, "\n");
16471
+ }
16472
+ function trimRightSpaces(text) {
16473
+ return text.replace(/[ \t]+$/g, "");
16474
+ }
16475
+ function splitNormalizedLines(text) {
16476
+ return normalizeNewlines(text).split("\n");
16477
+ }
16478
+ function trimTrailingEmptyLines(lines) {
16479
+ const result = [...lines];
16480
+ while (result.length > 0 && trimRightSpaces(result[result.length - 1]) === "") {
16481
+ result.pop();
16482
+ }
16483
+ return result;
16484
+ }
16485
+ function getNearestContext(content, oldString) {
16486
+ const lines = content.split(/\r?\n/);
16487
+ const oldLines = normalizeNewlines(oldString).split("\n").filter(Boolean);
16488
+ const firstToken = oldLines[0] || oldString.trim();
16489
+ for (let index = 0; index < lines.length; index += 1) {
16490
+ if (lines[index].includes(firstToken)) {
16491
+ return lines.slice(index, index + 4).join("\n");
16492
+ }
16493
+ }
16494
+ return lines.slice(0, 4).join("\n");
16495
+ }
16496
+ function buildNoMatchMessage(content, oldString) {
16497
+ return [
16498
+ "\u672A\u627E\u5230\u9700\u8981\u66FF\u6362\u7684\u6587\u672C\u3002",
16499
+ "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",
16500
+ "\u6700\u63A5\u8FD1\u7684\u4E0A\u4E0B\u6587\uFF1A",
16501
+ getNearestContext(content, oldString)
16502
+ ].join("\n");
16503
+ }
16504
+ function tryLooseBlockReplace(content, oldString, newString) {
16505
+ const normalizedContent = normalizeNewlines(content);
16506
+ const contentLines = splitNormalizedLines(content);
16507
+ const oldLines = trimTrailingEmptyLines(splitNormalizedLines(oldString));
16508
+ const newLines = splitNormalizedLines(newString);
16509
+ if (oldLines.length === 0) {
16510
+ return null;
16511
+ }
16512
+ for (let start = 0; start < contentLines.length; start += 1) {
16513
+ let contentIndex = start;
16514
+ let oldIndex = 0;
16515
+ while (oldIndex < oldLines.length && contentIndex < contentLines.length) {
16516
+ const expected = trimRightSpaces(oldLines[oldIndex]);
16517
+ const actual = trimRightSpaces(contentLines[contentIndex]);
16518
+ if (expected === actual) {
16519
+ oldIndex += 1;
16520
+ contentIndex += 1;
16521
+ continue;
16522
+ }
16523
+ if (actual === "") {
16524
+ contentIndex += 1;
16525
+ continue;
16526
+ }
16527
+ if (expected === "") {
16528
+ oldIndex += 1;
16529
+ continue;
16530
+ }
16531
+ break;
16532
+ }
16533
+ if (oldIndex === oldLines.length) {
16534
+ const replacedNormalized = [
16535
+ ...contentLines.slice(0, start),
16536
+ ...newLines,
16537
+ ...contentLines.slice(contentIndex)
16538
+ ].join("\n");
16539
+ return {
16540
+ occurrencesBefore: 1,
16541
+ content: detectNewlineStyle(content) === "crlf" ? replacedNormalized.replace(/\n/g, "\r\n") : replacedNormalized
16542
+ };
16543
+ }
16544
+ }
16545
+ return null;
16546
+ }
16469
16547
  function countOccurrences(text, target) {
16470
16548
  if (target.length === 0) {
16471
16549
  throw createGbkError("GBK_EMPTY_OLD_STRING", "oldString \u4E0D\u80FD\u4E3A\u7A7A");
@@ -16783,12 +16861,28 @@ async function replaceGbkFileText(input) {
16783
16861
  const current = await readWholeGbkTextFile(resolved);
16784
16862
  const scope = resolveEditScope(current.content, input);
16785
16863
  const occurrencesBefore = countOccurrences(scope.selectedText, input.oldString);
16864
+ if (!replaceAll && occurrencesBefore === 0) {
16865
+ const loose = tryLooseBlockReplace(scope.selectedText, input.oldString, input.newString);
16866
+ if (loose !== null) {
16867
+ const outputText2 = `${current.content.slice(0, scope.rangeStart)}${loose.content}${current.content.slice(scope.rangeEnd)}`;
16868
+ const buffer2 = import_iconv_lite.default.encode(outputText2, current.encoding);
16869
+ await fs2.writeFile(current.filePath, buffer2);
16870
+ return {
16871
+ filePath: current.filePath,
16872
+ encoding: current.encoding,
16873
+ replacements: 1,
16874
+ occurrencesBefore: loose.occurrencesBefore,
16875
+ bytesRead: current.bytesRead,
16876
+ bytesWritten: buffer2.byteLength
16877
+ };
16878
+ }
16879
+ }
16786
16880
  if (replaceAll) {
16787
16881
  if (occurrencesBefore === 0) {
16788
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16882
+ throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
16789
16883
  }
16790
16884
  } else if (occurrencesBefore === 0) {
16791
- throw createGbkError("GBK_NO_MATCH", `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u5185\u5BB9: ${input.oldString}`);
16885
+ throw createGbkError("GBK_NO_MATCH", buildNoMatchMessage(scope.selectedText, input.oldString));
16792
16886
  } else if (occurrencesBefore > 1) {
16793
16887
  throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${input.oldString}`);
16794
16888
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "packageName": "opencode-gbk-tools",
4
- "packageVersion": "0.1.2",
4
+ "packageVersion": "0.1.3",
5
5
  "artifacts": [
6
6
  {
7
7
  "relativePath": "tools/gbk_edit.js",
8
8
  "kind": "tool",
9
- "expectedHash": "c90cdc75ece2c66d72750cb9243d20235a7dad82829e8f519a7c31f7432ef3ff",
9
+ "expectedHash": "c23a8f2fe0dea3f6099cf5ecc27eecc7f5acd85b5f5beb41da09418646a4225a",
10
10
  "hashAlgorithm": "sha256"
11
11
  },
12
12
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gbk-tools",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "GBK/GB18030 custom tools and agent for OpenCode",
5
5
  "type": "module",
6
6
  "license": "MIT",