opencode-gbk-tools 0.1.26 → 0.1.28

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