@trim21/personal-pi-extensions 0.0.385 → 0.0.388
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/package.json
CHANGED
|
@@ -114,3 +114,12 @@ export function preserveQuoteStyle(
|
|
|
114
114
|
if (hasSingleQuotes) result = applyCurlySingleQuotes(result);
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* patch 显示用:把行首 tab 转成 2 空格(对齐 Claude Code 的
|
|
120
|
+
* convertLeadingTabsToSpaces)。仅用于 details 里展示的 diff,不影响写盘内容。
|
|
121
|
+
*/
|
|
122
|
+
export function convertLeadingTabsToSpaces(content: string): string {
|
|
123
|
+
if (!content.includes("\t")) return content;
|
|
124
|
+
return content.replaceAll(/^\t+/gm, (tabs) => " ".repeat(tabs.length));
|
|
125
|
+
}
|
package/src/claude-code/files.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
snapshotsEqual,
|
|
27
27
|
throwIfAborted,
|
|
28
28
|
} from "./common.js";
|
|
29
|
-
import { findActualString, preserveQuoteStyle } from "./edit-utils.js";
|
|
29
|
+
import { convertLeadingTabsToSpaces, findActualString, preserveQuoteStyle } from "./edit-utils.js";
|
|
30
30
|
|
|
31
31
|
const SAMPLE_BYTES = 4096;
|
|
32
32
|
|
|
@@ -388,7 +388,7 @@ export function registerFileTools(
|
|
|
388
388
|
const snapshot = snapshotOf(newString);
|
|
389
389
|
const key = await readStateKey(filePath);
|
|
390
390
|
state.reads.set(key, snapshot);
|
|
391
|
-
const diff = generateDiffString("", newString);
|
|
391
|
+
const diff = generateDiffString("", convertLeadingTabsToSpaces(newString));
|
|
392
392
|
throwIfAborted(signal);
|
|
393
393
|
const diagnosticText = await service.lspDiagnosticsForFile(filePath, ctx.cwd, {
|
|
394
394
|
notify: (message, level) => ctx.ui.notify(message, level),
|
|
@@ -398,7 +398,7 @@ export function registerFileTools(
|
|
|
398
398
|
`The file ${filePath} has been updated successfully.`,
|
|
399
399
|
{
|
|
400
400
|
diff: diff.diff,
|
|
401
|
-
patch: generateUnifiedPatch(filePath, "", newString),
|
|
401
|
+
patch: generateUnifiedPatch(filePath, "", convertLeadingTabsToSpaces(newString)),
|
|
402
402
|
firstChangedLine: diff.firstChangedLine,
|
|
403
403
|
reads: { [key]: snapshot },
|
|
404
404
|
},
|
|
@@ -459,15 +459,30 @@ export function registerFileTools(
|
|
|
459
459
|
);
|
|
460
460
|
}
|
|
461
461
|
const actualNewString = preserveQuoteStyle(oldString, actualOldString, newString);
|
|
462
|
+
// 删除场景(new_string 为空):old_string 不以换行结尾且文件里是
|
|
463
|
+
// "old_string\n" 时连换行一起删,避免留下空行(对齐 Claude Code
|
|
464
|
+
// applyEditToFile 的 stripTrailingNewline 语义)
|
|
465
|
+
let searchString = actualOldString;
|
|
466
|
+
if (
|
|
467
|
+
actualNewString === "" &&
|
|
468
|
+
!actualOldString.endsWith("\n") &&
|
|
469
|
+
normalized.includes(actualOldString + "\n")
|
|
470
|
+
) {
|
|
471
|
+
searchString = actualOldString + "\n";
|
|
472
|
+
}
|
|
462
473
|
// split/join 与函数替换:replacement 含 $ 时不会触发 $& 等特殊语义
|
|
463
474
|
const updated = replaceAll
|
|
464
|
-
? normalized.split(
|
|
465
|
-
: normalized.replace(
|
|
475
|
+
? normalized.split(searchString).join(actualNewString)
|
|
476
|
+
: normalized.replace(searchString, () => actualNewString);
|
|
466
477
|
const restored = lineEnding === "\r\n" ? updated.replaceAll("\n", "\r\n") : updated;
|
|
467
478
|
await writeFile(filePath, restored, "utf8");
|
|
468
479
|
const snapshot = snapshotOf(restored);
|
|
469
480
|
state.reads.set(key, snapshot);
|
|
470
|
-
|
|
481
|
+
// patch/diff 仅供显示:前导 tab 转空格,避免 UI 渲染错位(对齐 Claude Code)
|
|
482
|
+
const diff = generateDiffString(
|
|
483
|
+
convertLeadingTabsToSpaces(original),
|
|
484
|
+
convertLeadingTabsToSpaces(restored),
|
|
485
|
+
);
|
|
471
486
|
const text = replaceAll
|
|
472
487
|
? `The file ${filePath} has been updated. All occurrences were successfully replaced.`
|
|
473
488
|
: `The file ${filePath} has been updated successfully.`;
|
|
@@ -479,7 +494,11 @@ export function registerFileTools(
|
|
|
479
494
|
text,
|
|
480
495
|
{
|
|
481
496
|
diff: diff.diff,
|
|
482
|
-
patch: generateUnifiedPatch(
|
|
497
|
+
patch: generateUnifiedPatch(
|
|
498
|
+
filePath,
|
|
499
|
+
convertLeadingTabsToSpaces(original),
|
|
500
|
+
convertLeadingTabsToSpaces(restored),
|
|
501
|
+
),
|
|
483
502
|
firstChangedLine: diff.firstChangedLine,
|
|
484
503
|
reads: { [key]: snapshot },
|
|
485
504
|
},
|
package/src/claude-code/glob.ts
CHANGED
|
@@ -88,9 +88,13 @@ export async function globFiles(
|
|
|
88
88
|
stdout = result.stdout;
|
|
89
89
|
} catch (error) {
|
|
90
90
|
if (signal?.aborted) throwIfAborted(signal);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
91
|
+
// rg exit code 1 = 搜索完成但无匹配,对齐 Claude Code 的 ripGrep(正常空结果)
|
|
92
|
+
const code = (error as { code?: unknown }).code;
|
|
93
|
+
if (code === 1) return { files: [], truncated: false };
|
|
94
|
+
const detail =
|
|
95
|
+
(error as { stderr?: string }).stderr?.trim() ||
|
|
96
|
+
(error instanceof Error ? error.message : String(error));
|
|
97
|
+
throw new Error(`ripgrep failed: ${detail}`, { cause: error });
|
|
94
98
|
}
|
|
95
99
|
// rg 输出相对 searchDir 的路径,转成绝对路径
|
|
96
100
|
const lines = stdout ? stdout.replace(/\n$/, "").split("\n") : [];
|
|
@@ -149,7 +153,7 @@ export function registerGlobTool(pi: ExtensionAPI): void {
|
|
|
149
153
|
: filenames;
|
|
150
154
|
return {
|
|
151
155
|
content: [{ type: "text", text: lines.length > 0 ? lines.join("\n") : "No files found" }],
|
|
152
|
-
details:
|
|
156
|
+
details: undefined,
|
|
153
157
|
};
|
|
154
158
|
},
|
|
155
159
|
});
|
package/src/claude-code/grep.ts
CHANGED
|
@@ -284,7 +284,7 @@ export function registerGrepTool(pi: ExtensionAPI): void {
|
|
|
284
284
|
|
|
285
285
|
if (mode === "files_with_matches") {
|
|
286
286
|
if (stdout === "") {
|
|
287
|
-
return { content: [{ type: "text", text: "No files found" }], details:
|
|
287
|
+
return { content: [{ type: "text", text: "No files found" }], details: undefined };
|
|
288
288
|
}
|
|
289
289
|
const sorted = await sortFilesByMtime(stdout);
|
|
290
290
|
const { lines, appliedLimit, appliedOffset } = pageGrepOutput(sorted, offset, headLimit);
|
|
@@ -293,7 +293,10 @@ export function registerGrepTool(pi: ExtensionAPI): void {
|
|
|
293
293
|
const text = truncateOutput(
|
|
294
294
|
`Found ${filenames.length} ${filenames.length === 1 ? "file" : "files"}${limitInfo ? ` ${limitInfo}` : ""}\n${filenames.join("\n")}`,
|
|
295
295
|
);
|
|
296
|
-
return {
|
|
296
|
+
return {
|
|
297
|
+
content: [{ type: "text", text }],
|
|
298
|
+
details: undefined,
|
|
299
|
+
};
|
|
297
300
|
}
|
|
298
301
|
|
|
299
302
|
if (mode === "count") {
|