@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.2
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/CHANGELOG.md +266 -1
- package/package.json +86 -20
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +91 -0
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +83 -125
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -5
- package/src/commit/agentic/index.ts +3 -4
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +3 -4
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +54 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +102 -46
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/settings-defs.ts +5 -0
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +12 -8
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +94 -37
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +2 -2
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +190 -154
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +306 -256
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +21 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -4
- package/src/task/template.ts +2 -2
- package/src/task/worktree.ts +4 -4
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +2 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +10 -11
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +198 -67
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +4 -4
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
|
@@ -70,12 +70,16 @@ export function getLeadingWhitespace(line: string): string {
|
|
|
70
70
|
return line.slice(0, countLeadingWhitespace(line));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function isNonEmptyLine(line: string): boolean {
|
|
74
|
+
return line.trim().length > 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
73
77
|
/** Compute minimum indentation of non-empty lines */
|
|
74
78
|
export function minIndent(text: string): number {
|
|
75
79
|
const lines = text.split("\n");
|
|
76
80
|
let min = Infinity;
|
|
77
81
|
for (const line of lines) {
|
|
78
|
-
if (line
|
|
82
|
+
if (isNonEmptyLine(line)) {
|
|
79
83
|
min = Math.min(min, countLeadingWhitespace(line));
|
|
80
84
|
}
|
|
81
85
|
}
|
|
@@ -107,9 +111,7 @@ function gcd(a: number, b: number): number {
|
|
|
107
111
|
|
|
108
112
|
interface IndentProfile {
|
|
109
113
|
lines: string[];
|
|
110
|
-
indentStrings: string[];
|
|
111
114
|
indentCounts: number[];
|
|
112
|
-
min: number;
|
|
113
115
|
char: " " | "\t" | undefined;
|
|
114
116
|
spaceOnly: boolean;
|
|
115
117
|
tabOnly: boolean;
|
|
@@ -120,9 +122,7 @@ interface IndentProfile {
|
|
|
120
122
|
|
|
121
123
|
function buildIndentProfile(text: string): IndentProfile {
|
|
122
124
|
const lines = text.split("\n");
|
|
123
|
-
const indentStrings: string[] = [];
|
|
124
125
|
const indentCounts: number[] = [];
|
|
125
|
-
let min = Infinity;
|
|
126
126
|
let char: " " | "\t" | undefined;
|
|
127
127
|
let spaceOnly = true;
|
|
128
128
|
let tabOnly = true;
|
|
@@ -131,12 +131,10 @@ function buildIndentProfile(text: string): IndentProfile {
|
|
|
131
131
|
let unit = 0;
|
|
132
132
|
|
|
133
133
|
for (const line of lines) {
|
|
134
|
-
if (line
|
|
134
|
+
if (!isNonEmptyLine(line)) continue;
|
|
135
135
|
nonEmptyCount++;
|
|
136
136
|
const indent = getLeadingWhitespace(line);
|
|
137
|
-
indentStrings.push(indent);
|
|
138
137
|
indentCounts.push(indent.length);
|
|
139
|
-
min = Math.min(min, indent.length);
|
|
140
138
|
if (indent.includes(" ")) {
|
|
141
139
|
tabOnly = false;
|
|
142
140
|
}
|
|
@@ -156,10 +154,6 @@ function buildIndentProfile(text: string): IndentProfile {
|
|
|
156
154
|
}
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
if (min === Infinity) {
|
|
160
|
-
min = 0;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
157
|
if (spaceOnly && nonEmptyCount > 0) {
|
|
164
158
|
let current = 0;
|
|
165
159
|
for (const count of indentCounts) {
|
|
@@ -175,9 +169,7 @@ function buildIndentProfile(text: string): IndentProfile {
|
|
|
175
169
|
|
|
176
170
|
return {
|
|
177
171
|
lines,
|
|
178
|
-
indentStrings,
|
|
179
172
|
indentCounts,
|
|
180
|
-
min,
|
|
181
173
|
char,
|
|
182
174
|
spaceOnly,
|
|
183
175
|
tabOnly,
|
|
@@ -246,6 +238,87 @@ export function normalizeForFuzzy(line: string): string {
|
|
|
246
238
|
.replace(/[ \t]+/g, " ");
|
|
247
239
|
}
|
|
248
240
|
|
|
241
|
+
function isIndentationOnlyRewrite(oldText: string, newText: string): boolean {
|
|
242
|
+
const oldLines = oldText.split("\n");
|
|
243
|
+
const newLines = newText.split("\n");
|
|
244
|
+
if (oldLines.length !== newLines.length) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
for (let i = 0; i < oldLines.length; i++) {
|
|
248
|
+
if (oldLines[i].trim() !== newLines[i].trim()) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function maybeConvertTabIndentation(
|
|
256
|
+
oldProfile: IndentProfile,
|
|
257
|
+
actualProfile: IndentProfile,
|
|
258
|
+
newProfile: IndentProfile,
|
|
259
|
+
newText: string,
|
|
260
|
+
): string | undefined {
|
|
261
|
+
if (!actualProfile.spaceOnly || !oldProfile.tabOnly || !newProfile.tabOnly || actualProfile.unit <= 0) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const lineCount = Math.min(oldProfile.lines.length, actualProfile.lines.length);
|
|
266
|
+
for (let i = 0; i < lineCount; i++) {
|
|
267
|
+
const oldLine = oldProfile.lines[i];
|
|
268
|
+
const actualLine = actualProfile.lines[i];
|
|
269
|
+
if (!isNonEmptyLine(oldLine) || !isNonEmptyLine(actualLine)) continue;
|
|
270
|
+
const oldIndent = getLeadingWhitespace(oldLine);
|
|
271
|
+
if (oldIndent.length === 0) continue;
|
|
272
|
+
const actualIndent = getLeadingWhitespace(actualLine);
|
|
273
|
+
if (actualIndent.length !== oldIndent.length * actualProfile.unit) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return convertLeadingTabsToSpaces(newText, actualProfile.unit);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function computeUniformIndentDelta(oldProfile: IndentProfile, actualProfile: IndentProfile): number | undefined {
|
|
282
|
+
const lineCount = Math.min(oldProfile.lines.length, actualProfile.lines.length);
|
|
283
|
+
const deltas: number[] = [];
|
|
284
|
+
for (let i = 0; i < lineCount; i++) {
|
|
285
|
+
const oldLine = oldProfile.lines[i];
|
|
286
|
+
const actualLine = actualProfile.lines[i];
|
|
287
|
+
if (!isNonEmptyLine(oldLine) || !isNonEmptyLine(actualLine)) continue;
|
|
288
|
+
deltas.push(countLeadingWhitespace(actualLine) - countLeadingWhitespace(oldLine));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (deltas.length === 0) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const delta = deltas[0];
|
|
296
|
+
return deltas.every(value => value === delta) ? delta : undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function applyIndentDelta(text: string, delta: number, indentChar: string): string {
|
|
300
|
+
const adjusted = text.split("\n").map(line => {
|
|
301
|
+
if (!isNonEmptyLine(line)) {
|
|
302
|
+
return line;
|
|
303
|
+
}
|
|
304
|
+
if (delta > 0) {
|
|
305
|
+
return indentChar.repeat(delta) + line;
|
|
306
|
+
}
|
|
307
|
+
const toRemove = Math.min(-delta, countLeadingWhitespace(line));
|
|
308
|
+
return line.slice(toRemove);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return adjusted.join("\n");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function hasNonEmptyIndentProfiles(...profiles: IndentProfile[]): boolean {
|
|
315
|
+
return profiles.every(profile => profile.nonEmptyCount > 0);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function hasMixedIndentation(...profiles: IndentProfile[]): boolean {
|
|
319
|
+
return profiles.some(profile => profile.mixed);
|
|
320
|
+
}
|
|
321
|
+
|
|
249
322
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
323
|
// Indentation Adjustment
|
|
251
324
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -264,73 +337,32 @@ export function adjustIndentation(oldText: string, actualText: string, newText:
|
|
|
264
337
|
}
|
|
265
338
|
|
|
266
339
|
// If the patch is purely an indentation change (same trimmed content), apply exactly as specified
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (oldLines.length === newLines.length) {
|
|
270
|
-
let indentationOnly = true;
|
|
271
|
-
for (let i = 0; i < oldLines.length; i++) {
|
|
272
|
-
if (oldLines[i].trim() !== newLines[i].trim()) {
|
|
273
|
-
indentationOnly = false;
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
if (indentationOnly) {
|
|
278
|
-
return newText;
|
|
279
|
-
}
|
|
340
|
+
if (isIndentationOnlyRewrite(oldText, newText)) {
|
|
341
|
+
return newText;
|
|
280
342
|
}
|
|
281
343
|
|
|
282
344
|
const oldProfile = buildIndentProfile(oldText);
|
|
283
345
|
const actualProfile = buildIndentProfile(actualText);
|
|
284
346
|
const newProfile = buildIndentProfile(newText);
|
|
285
347
|
|
|
286
|
-
if (
|
|
348
|
+
if (!hasNonEmptyIndentProfiles(oldProfile, actualProfile, newProfile)) {
|
|
287
349
|
return newText;
|
|
288
350
|
}
|
|
289
351
|
|
|
290
|
-
if (oldProfile
|
|
352
|
+
if (hasMixedIndentation(oldProfile, actualProfile, newProfile)) {
|
|
291
353
|
return newText;
|
|
292
354
|
}
|
|
293
355
|
|
|
294
356
|
if (oldProfile.char && actualProfile.char && oldProfile.char !== actualProfile.char) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
for (let i = 0; i < lineCount; i++) {
|
|
299
|
-
const oldLine = oldProfile.lines[i];
|
|
300
|
-
const actualLine = actualProfile.lines[i];
|
|
301
|
-
if (oldLine.trim().length === 0 || actualLine.trim().length === 0) continue;
|
|
302
|
-
const oldIndent = getLeadingWhitespace(oldLine);
|
|
303
|
-
const actualIndent = getLeadingWhitespace(actualLine);
|
|
304
|
-
if (oldIndent.length === 0) continue;
|
|
305
|
-
if (actualIndent.length !== oldIndent.length * actualProfile.unit) {
|
|
306
|
-
consistent = false;
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return consistent ? convertLeadingTabsToSpaces(newText, actualProfile.unit) : newText;
|
|
357
|
+
const converted = maybeConvertTabIndentation(oldProfile, actualProfile, newProfile, newText);
|
|
358
|
+
if (converted !== undefined) {
|
|
359
|
+
return converted;
|
|
311
360
|
}
|
|
312
361
|
return newText;
|
|
313
362
|
}
|
|
314
363
|
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
for (let i = 0; i < lineCount; i++) {
|
|
318
|
-
const oldLine = oldProfile.lines[i];
|
|
319
|
-
const actualLine = actualProfile.lines[i];
|
|
320
|
-
if (oldLine.trim().length === 0 || actualLine.trim().length === 0) continue;
|
|
321
|
-
deltas.push(countLeadingWhitespace(actualLine) - countLeadingWhitespace(oldLine));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (deltas.length === 0) {
|
|
325
|
-
return newText;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const delta = deltas[0];
|
|
329
|
-
if (!deltas.every(value => value === delta)) {
|
|
330
|
-
return newText;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (delta === 0) {
|
|
364
|
+
const delta = computeUniformIndentDelta(oldProfile, actualProfile);
|
|
365
|
+
if (delta === undefined || delta === 0) {
|
|
334
366
|
return newText;
|
|
335
367
|
}
|
|
336
368
|
|
|
@@ -339,16 +371,5 @@ export function adjustIndentation(oldText: string, actualText: string, newText:
|
|
|
339
371
|
}
|
|
340
372
|
|
|
341
373
|
const indentChar = actualProfile.char ?? oldProfile.char ?? detectIndentChar(actualText);
|
|
342
|
-
|
|
343
|
-
if (line.trim().length === 0) {
|
|
344
|
-
return line;
|
|
345
|
-
}
|
|
346
|
-
if (delta > 0) {
|
|
347
|
-
return indentChar.repeat(delta) + line;
|
|
348
|
-
}
|
|
349
|
-
const toRemove = Math.min(-delta, countLeadingWhitespace(line));
|
|
350
|
-
return line.slice(toRemove);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
return adjusted.join("\n");
|
|
374
|
+
return applyIndentDelta(newText, delta, indentChar);
|
|
354
375
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Edit tool renderer and LSP batching helpers.
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolCallContext } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
truncateDiffByHunk,
|
|
23
23
|
} from "../tools/render-utils";
|
|
24
24
|
import { Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
25
|
-
import type {
|
|
26
|
-
import type {
|
|
25
|
+
import type { DiffError, DiffResult } from "./diff";
|
|
26
|
+
import type { ChunkToolEdit } from "./modes/chunk";
|
|
27
|
+
import type { HashlineToolEdit } from "./modes/hashline";
|
|
28
|
+
import type { Operation } from "./modes/patch";
|
|
27
29
|
|
|
28
30
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
31
|
// LSP Batching
|
|
@@ -31,7 +33,12 @@ import type { DiffError, DiffResult, Operation } from "./types";
|
|
|
31
33
|
|
|
32
34
|
const LSP_BATCH_TOOLS = new Set(["edit", "write"]);
|
|
33
35
|
|
|
34
|
-
export
|
|
36
|
+
export interface LspBatchRequest {
|
|
37
|
+
id: string;
|
|
38
|
+
flush: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getLspBatchRequest(toolCall: ToolCallContext | undefined): LspBatchRequest | undefined {
|
|
35
42
|
if (!toolCall) {
|
|
36
43
|
return undefined;
|
|
37
44
|
}
|
|
@@ -83,8 +90,8 @@ interface EditRenderArgs {
|
|
|
83
90
|
* Computed preview diff (used when tool args don't include a diff, e.g. hashline mode).
|
|
84
91
|
*/
|
|
85
92
|
previewDiff?: string;
|
|
86
|
-
// Hashline mode fields
|
|
87
|
-
edits?: Partial<HashlineToolEdit>[];
|
|
93
|
+
// Hashline / chunk mode fields
|
|
94
|
+
edits?: Partial<HashlineToolEdit | ChunkToolEdit>[];
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
/** Extended context for edit tool rendering */
|
|
@@ -96,12 +103,69 @@ export interface EditRenderContext {
|
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
const EDIT_STREAMING_PREVIEW_LINES = 12;
|
|
106
|
+
const CALL_TEXT_PREVIEW_LINES = 6;
|
|
107
|
+
const CALL_TEXT_PREVIEW_WIDTH = 80;
|
|
108
|
+
const STREAMING_EDIT_PREVIEW_WIDTH = 120;
|
|
109
|
+
const STREAMING_EDIT_PREVIEW_LIMIT = 4;
|
|
110
|
+
const STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT = 8;
|
|
111
|
+
|
|
112
|
+
interface FormattedStreamingEdit {
|
|
113
|
+
srcLabel: string;
|
|
114
|
+
dst: string;
|
|
115
|
+
}
|
|
99
116
|
|
|
100
117
|
function countLines(text: string): number {
|
|
101
118
|
if (!text) return 0;
|
|
102
119
|
return text.split("\n").length;
|
|
103
120
|
}
|
|
104
121
|
|
|
122
|
+
function getOperationTitle(op: Operation | undefined): string {
|
|
123
|
+
return op === "create" ? "Create" : op === "delete" ? "Delete" : "Edit";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function formatEditPathDisplay(
|
|
127
|
+
rawPath: string,
|
|
128
|
+
uiTheme: Theme,
|
|
129
|
+
options?: { rename?: string; firstChangedLine?: number },
|
|
130
|
+
): string {
|
|
131
|
+
let pathDisplay = rawPath ? uiTheme.fg("accent", shortenPath(rawPath)) : uiTheme.fg("toolOutput", "…");
|
|
132
|
+
|
|
133
|
+
if (options?.firstChangedLine) {
|
|
134
|
+
pathDisplay += uiTheme.fg("warning", `:${options.firstChangedLine}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (options?.rename) {
|
|
138
|
+
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${uiTheme.fg("accent", shortenPath(options.rename))}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return pathDisplay;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatEditDescription(
|
|
145
|
+
rawPath: string,
|
|
146
|
+
uiTheme: Theme,
|
|
147
|
+
options?: { rename?: string; firstChangedLine?: number },
|
|
148
|
+
): { language: string; description: string } {
|
|
149
|
+
const language = getLanguageFromPath(rawPath) ?? "text";
|
|
150
|
+
const icon = uiTheme.fg("muted", uiTheme.getLangIcon(language));
|
|
151
|
+
return {
|
|
152
|
+
language,
|
|
153
|
+
description: `${icon} ${formatEditPathDisplay(rawPath, uiTheme, options)}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderPlainTextPreview(text: string, uiTheme: Theme): string {
|
|
158
|
+
const previewLines = text.split("\n");
|
|
159
|
+
let preview = "\n\n";
|
|
160
|
+
for (const line of previewLines.slice(0, CALL_TEXT_PREVIEW_LINES)) {
|
|
161
|
+
preview += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), CALL_TEXT_PREVIEW_WIDTH))}\n`;
|
|
162
|
+
}
|
|
163
|
+
if (previewLines.length > CALL_TEXT_PREVIEW_LINES) {
|
|
164
|
+
preview += uiTheme.fg("dim", `… ${previewLines.length - CALL_TEXT_PREVIEW_LINES} more lines`);
|
|
165
|
+
}
|
|
166
|
+
return preview.trimEnd();
|
|
167
|
+
}
|
|
168
|
+
|
|
105
169
|
function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, label = "streaming"): string {
|
|
106
170
|
if (!diff) return "";
|
|
107
171
|
const lines = diff.split("\n");
|
|
@@ -117,69 +181,111 @@ function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, labe
|
|
|
117
181
|
return text;
|
|
118
182
|
}
|
|
119
183
|
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
184
|
+
function isChunkStreamingEdit(edit: Partial<HashlineToolEdit | ChunkToolEdit>): edit is Partial<ChunkToolEdit> {
|
|
185
|
+
return "sel" in edit;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getStreamingEditContent(content: unknown): string {
|
|
189
|
+
if (Array.isArray(content)) {
|
|
190
|
+
return content.join("\n");
|
|
191
|
+
}
|
|
192
|
+
return typeof content === "string" ? content : "";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function formatHashlineStreamingEdit(edit: Partial<HashlineToolEdit>): FormattedStreamingEdit {
|
|
196
|
+
if (typeof edit !== "object" || !edit) {
|
|
197
|
+
return { srcLabel: "\u2022 (incomplete edit)", dst: "" };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const contentLines = getStreamingEditContent(edit.content);
|
|
201
|
+
const loc = edit.loc;
|
|
202
|
+
|
|
203
|
+
if (loc === "append" || loc === "prepend") {
|
|
204
|
+
return { srcLabel: `\u2022 ${loc} (file-level)`, dst: contentLines };
|
|
205
|
+
}
|
|
206
|
+
if (typeof loc === "object" && loc) {
|
|
207
|
+
if ("range" in loc && typeof loc.range === "object" && loc.range) {
|
|
208
|
+
return { srcLabel: `\u2022 range ${loc.range.pos ?? "?"}\u2026${loc.range.end ?? "?"}`, dst: contentLines };
|
|
209
|
+
}
|
|
210
|
+
if ("line" in loc) {
|
|
211
|
+
return { srcLabel: `\u2022 line ${(loc as { line: string }).line}`, dst: contentLines };
|
|
212
|
+
}
|
|
213
|
+
if ("append" in loc) {
|
|
214
|
+
return { srcLabel: `\u2022 append ${(loc as { append: string }).append}`, dst: contentLines };
|
|
215
|
+
}
|
|
216
|
+
if ("prepend" in loc) {
|
|
217
|
+
return { srcLabel: `\u2022 prepend ${(loc as { prepend: string }).prepend}`, dst: contentLines };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { srcLabel: "\u2022 (unknown edit)", dst: contentLines };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function formatChunkStreamingEdit(edit: Partial<ChunkToolEdit>): FormattedStreamingEdit {
|
|
224
|
+
if (typeof edit !== "object" || !edit) {
|
|
225
|
+
return { srcLabel: "\u2022 (incomplete edit)", dst: "" };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const contentLines = getStreamingEditContent(edit.content);
|
|
229
|
+
const target = edit.sel ?? "?";
|
|
230
|
+
const op = edit.op ?? "replace";
|
|
231
|
+
|
|
232
|
+
switch (op) {
|
|
233
|
+
case "append":
|
|
234
|
+
return { srcLabel: `\u2022 append ${target}`, dst: contentLines };
|
|
235
|
+
case "prepend":
|
|
236
|
+
return { srcLabel: `\u2022 prepend ${target}`, dst: contentLines };
|
|
237
|
+
case "after":
|
|
238
|
+
return { srcLabel: `\u2022 insert after ${target}`, dst: contentLines };
|
|
239
|
+
case "before":
|
|
240
|
+
return { srcLabel: `\u2022 insert before ${target}`, dst: contentLines };
|
|
241
|
+
default:
|
|
242
|
+
return {
|
|
243
|
+
srcLabel: contentLines.length === 0 ? `\u2022 remove ${target}` : `\u2022 replace ${target}`,
|
|
244
|
+
dst: contentLines,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function formatStreamingHashlineEdits(edits: Partial<HashlineToolEdit | ChunkToolEdit>[], uiTheme: Theme): string {
|
|
123
250
|
let text = "\n\n";
|
|
124
|
-
|
|
251
|
+
|
|
252
|
+
// Detect whether these are chunk edits (target field) or hashline edits (loc field)
|
|
253
|
+
const isChunk = edits.length > 0 && isChunkStreamingEdit(edits[0]);
|
|
254
|
+
const label = isChunk ? "chunk edit" : "hashline edit";
|
|
255
|
+
const formatEdit = isChunk ? formatChunkStreamingEdit : formatHashlineStreamingEdit;
|
|
256
|
+
text += uiTheme.fg("dim", `[${edits.length} ${label}${edits.length === 1 ? "" : "s"}]`);
|
|
125
257
|
text += "\n";
|
|
126
258
|
let shownEdits = 0;
|
|
127
259
|
let shownDstLines = 0;
|
|
128
260
|
for (const edit of edits) {
|
|
129
261
|
shownEdits++;
|
|
130
|
-
if (shownEdits >
|
|
131
|
-
const formatted =
|
|
132
|
-
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(formatted.srcLabel),
|
|
262
|
+
if (shownEdits > STREAMING_EDIT_PREVIEW_LIMIT) break;
|
|
263
|
+
const formatted = formatEdit(edit as never);
|
|
264
|
+
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(formatted.srcLabel), STREAMING_EDIT_PREVIEW_WIDTH));
|
|
133
265
|
text += "\n";
|
|
134
266
|
if (formatted.dst === "") {
|
|
135
|
-
text += uiTheme.fg("dim", truncateToWidth(" (delete)",
|
|
267
|
+
text += uiTheme.fg("dim", truncateToWidth(" (delete)", STREAMING_EDIT_PREVIEW_WIDTH));
|
|
136
268
|
text += "\n";
|
|
137
269
|
continue;
|
|
138
270
|
}
|
|
139
271
|
for (const dstLine of formatted.dst.split("\n")) {
|
|
140
272
|
shownDstLines++;
|
|
141
|
-
if (shownDstLines >
|
|
142
|
-
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(`+ ${dstLine}`),
|
|
273
|
+
if (shownDstLines > STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT) break;
|
|
274
|
+
text += uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(`+ ${dstLine}`), STREAMING_EDIT_PREVIEW_WIDTH));
|
|
143
275
|
text += "\n";
|
|
144
276
|
}
|
|
145
|
-
if (shownDstLines >
|
|
277
|
+
if (shownDstLines > STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT) break;
|
|
146
278
|
}
|
|
147
|
-
if (edits.length >
|
|
148
|
-
text += uiTheme.fg("dim",
|
|
279
|
+
if (edits.length > STREAMING_EDIT_PREVIEW_LIMIT) {
|
|
280
|
+
text += uiTheme.fg("dim", `\u2026 (${edits.length - STREAMING_EDIT_PREVIEW_LIMIT} more edits)`);
|
|
149
281
|
}
|
|
150
|
-
if (shownDstLines >
|
|
151
|
-
text += uiTheme.fg("dim", `\n
|
|
282
|
+
if (shownDstLines > STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT) {
|
|
283
|
+
text += uiTheme.fg("dim", `\n\u2026 (${shownDstLines - STREAMING_EDIT_PREVIEW_DST_LINE_LIMIT} more dst lines)`);
|
|
152
284
|
}
|
|
153
285
|
|
|
154
286
|
return text.trimEnd();
|
|
155
|
-
function formatHashlineEdit(edit: Partial<HashlineToolEdit>): { srcLabel: string; dst: string } {
|
|
156
|
-
if (typeof edit !== "object" || !edit) {
|
|
157
|
-
return { srcLabel: "• (incomplete edit)", dst: "" };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const contentLines = Array.isArray(edit.content) ? (edit.content as string[]).join("\n") : "";
|
|
161
|
-
const loc = edit.loc;
|
|
162
|
-
|
|
163
|
-
if (loc === "append" || loc === "prepend") {
|
|
164
|
-
return { srcLabel: `• ${loc} (file-level)`, dst: contentLines };
|
|
165
|
-
}
|
|
166
|
-
if (typeof loc === "object" && loc) {
|
|
167
|
-
if ("range" in loc && typeof loc.range === "object" && loc.range) {
|
|
168
|
-
return { srcLabel: `• range ${loc.range.pos ?? "?"}…${loc.range.end ?? "?"}`, dst: contentLines };
|
|
169
|
-
}
|
|
170
|
-
if ("line" in loc) {
|
|
171
|
-
return { srcLabel: `• line ${(loc as { line: string }).line}`, dst: contentLines };
|
|
172
|
-
}
|
|
173
|
-
if ("append" in loc) {
|
|
174
|
-
return { srcLabel: `• append ${(loc as { append: string }).append}`, dst: contentLines };
|
|
175
|
-
}
|
|
176
|
-
if ("prepend" in loc) {
|
|
177
|
-
return { srcLabel: `• prepend ${(loc as { prepend: string }).prepend}`, dst: contentLines };
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return { srcLabel: "• (unknown edit)", dst: contentLines };
|
|
181
|
-
}
|
|
182
287
|
}
|
|
288
|
+
|
|
183
289
|
function formatMetadataLine(lineCount: number | null, language: string | undefined, uiTheme: Theme): string {
|
|
184
290
|
const icon = uiTheme.getLangIcon(language);
|
|
185
291
|
if (lineCount !== null) {
|
|
@@ -188,6 +294,25 @@ function formatMetadataLine(lineCount: number | null, language: string | undefin
|
|
|
188
294
|
return uiTheme.fg("dim", `${icon}`);
|
|
189
295
|
}
|
|
190
296
|
|
|
297
|
+
function getCallPreview(args: EditRenderArgs, rawPath: string, uiTheme: Theme): string {
|
|
298
|
+
if (args.previewDiff) {
|
|
299
|
+
return formatStreamingDiff(args.previewDiff, rawPath, uiTheme, "preview");
|
|
300
|
+
}
|
|
301
|
+
if (args.diff && args.op) {
|
|
302
|
+
return formatStreamingDiff(args.diff, rawPath, uiTheme);
|
|
303
|
+
}
|
|
304
|
+
if (args.edits && args.edits.length > 0) {
|
|
305
|
+
return formatStreamingHashlineEdits(args.edits, uiTheme);
|
|
306
|
+
}
|
|
307
|
+
if (args.diff) {
|
|
308
|
+
return renderPlainTextPreview(args.diff, uiTheme);
|
|
309
|
+
}
|
|
310
|
+
if (args.newText || args.patch) {
|
|
311
|
+
return renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme);
|
|
312
|
+
}
|
|
313
|
+
return "";
|
|
314
|
+
}
|
|
315
|
+
|
|
191
316
|
function renderDiffSection(
|
|
192
317
|
diff: string,
|
|
193
318
|
rawPath: string,
|
|
@@ -252,50 +377,11 @@ export const editToolRenderer = {
|
|
|
252
377
|
|
|
253
378
|
renderCall(args: EditRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
254
379
|
const rawPath = args.file_path || args.path || "";
|
|
255
|
-
const
|
|
256
|
-
const editLanguage = getLanguageFromPath(rawPath) ?? "text";
|
|
257
|
-
const editIcon = uiTheme.fg("muted", uiTheme.getLangIcon(editLanguage));
|
|
258
|
-
let pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
259
|
-
|
|
260
|
-
// Add arrow for move/rename operations
|
|
261
|
-
if (args.rename) {
|
|
262
|
-
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${uiTheme.fg("accent", shortenPath(args.rename))}`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Show operation type for patch mode
|
|
266
|
-
const opTitle = args.op === "create" ? "Create" : args.op === "delete" ? "Delete" : "Edit";
|
|
380
|
+
const { description } = formatEditDescription(rawPath, uiTheme, { rename: args.rename });
|
|
267
381
|
const spinner =
|
|
268
382
|
options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
|
|
269
|
-
let text = `${formatTitle(
|
|
270
|
-
|
|
271
|
-
// Show streaming preview of diff/content
|
|
272
|
-
if (args.previewDiff) {
|
|
273
|
-
text += formatStreamingDiff(args.previewDiff, rawPath, uiTheme, "preview");
|
|
274
|
-
} else if (args.diff && args.op) {
|
|
275
|
-
text += formatStreamingDiff(args.diff, rawPath, uiTheme);
|
|
276
|
-
} else if (args.edits && args.edits.length > 0) {
|
|
277
|
-
text += formatStreamingHashlineEdits(args.edits, uiTheme);
|
|
278
|
-
} else if (args.diff) {
|
|
279
|
-
const previewLines = args.diff.split("\n");
|
|
280
|
-
const maxLines = 6;
|
|
281
|
-
text += "\n\n";
|
|
282
|
-
for (const line of previewLines.slice(0, maxLines)) {
|
|
283
|
-
text += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), 80))}\n`;
|
|
284
|
-
}
|
|
285
|
-
if (previewLines.length > maxLines) {
|
|
286
|
-
text += uiTheme.fg("dim", `… ${previewLines.length - maxLines} more lines`);
|
|
287
|
-
}
|
|
288
|
-
} else if (args.newText || args.patch) {
|
|
289
|
-
const previewLines = (args.newText ?? args.patch ?? "").split("\n");
|
|
290
|
-
const maxLines = 6;
|
|
291
|
-
text += "\n\n";
|
|
292
|
-
for (const line of previewLines.slice(0, maxLines)) {
|
|
293
|
-
text += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), 80))}\n`;
|
|
294
|
-
}
|
|
295
|
-
if (previewLines.length > maxLines) {
|
|
296
|
-
text += uiTheme.fg("dim", `… ${previewLines.length - maxLines} more lines`);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
383
|
+
let text = `${formatTitle(getOperationTitle(args.op), uiTheme)} ${spinner ? `${spinner} ` : ""}${description}`;
|
|
384
|
+
text += getCallPreview(args, rawPath, uiTheme);
|
|
299
385
|
|
|
300
386
|
return new Text(text, 0, 0);
|
|
301
387
|
},
|
|
@@ -307,18 +393,14 @@ export const editToolRenderer = {
|
|
|
307
393
|
args?: EditRenderArgs,
|
|
308
394
|
): Component {
|
|
309
395
|
const rawPath = args?.file_path || args?.path || "";
|
|
310
|
-
const filePath = shortenPath(rawPath);
|
|
311
|
-
const editLanguage = getLanguageFromPath(rawPath) ?? "text";
|
|
312
|
-
const editIcon = uiTheme.fg("muted", uiTheme.getLangIcon(editLanguage));
|
|
313
|
-
|
|
314
396
|
const op = args?.op || result.details?.op;
|
|
315
397
|
const rename = args?.rename || result.details?.move;
|
|
316
|
-
const
|
|
398
|
+
const { language } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
317
399
|
|
|
318
400
|
// Pre-compute metadata line (static across renders)
|
|
319
401
|
const metadataLine =
|
|
320
402
|
op !== "delete"
|
|
321
|
-
? `\n${formatMetadataLine(countLines(args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch ?? ""),
|
|
403
|
+
? `\n${formatMetadataLine(countLines(args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch ?? ""), language, uiTheme)}`
|
|
322
404
|
: "";
|
|
323
405
|
|
|
324
406
|
// Pre-compute error text (static)
|
|
@@ -334,26 +416,17 @@ export const editToolRenderer = {
|
|
|
334
416
|
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
335
417
|
if (cached?.key === key) return cached.lines;
|
|
336
418
|
|
|
337
|
-
// Build path display with line number
|
|
338
|
-
let pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
339
419
|
const firstChangedLine =
|
|
340
420
|
(editDiffPreview && "firstChangedLine" in editDiffPreview
|
|
341
421
|
? editDiffPreview.firstChangedLine
|
|
342
422
|
: undefined) || (result.details && !result.isError ? result.details.firstChangedLine : undefined);
|
|
343
|
-
|
|
344
|
-
pathDisplay += uiTheme.fg("warning", `:${firstChangedLine}`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Add arrow for rename operations
|
|
348
|
-
if (rename) {
|
|
349
|
-
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${uiTheme.fg("accent", shortenPath(rename))}`;
|
|
350
|
-
}
|
|
423
|
+
const { description } = formatEditDescription(rawPath, uiTheme, { rename, firstChangedLine });
|
|
351
424
|
|
|
352
425
|
const header = renderStatusLine(
|
|
353
426
|
{
|
|
354
427
|
icon: result.isError ? "error" : "success",
|
|
355
|
-
title:
|
|
356
|
-
description
|
|
428
|
+
title: getOperationTitle(op),
|
|
429
|
+
description,
|
|
357
430
|
},
|
|
358
431
|
uiTheme,
|
|
359
432
|
);
|
|
@@ -155,8 +155,10 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
155
155
|
timeoutMs: options?.timeout,
|
|
156
156
|
signal: runAbortController.signal,
|
|
157
157
|
},
|
|
158
|
-
chunk => {
|
|
159
|
-
|
|
158
|
+
(err, chunk) => {
|
|
159
|
+
if (!err) {
|
|
160
|
+
enqueueChunk(chunk);
|
|
161
|
+
}
|
|
160
162
|
},
|
|
161
163
|
);
|
|
162
164
|
|