pi-formatter 0.3.0 → 1.0.0
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 +21 -16
- package/extensions/formatter/config.ts +4 -18
- package/extensions/formatter/context.ts +8 -4
- package/extensions/formatter/path.ts +5 -2
- package/extensions/index.ts +107 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
A [pi](https://pi.dev) extension that auto-formats files after `write` and
|
|
4
4
|
`edit` tool calls.
|
|
5
5
|
|
|
6
|
-
By default, formatting runs
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
By default, formatting runs once per turn. You can also format after each tool
|
|
7
|
+
call or defer formatting until the current session shuts down.
|
|
8
|
+
|
|
9
|
+
This default changed from the previous immediate-per-tool behavior. If you want
|
|
10
|
+
the old default, set `"formatMode": "tool"`.
|
|
9
11
|
|
|
10
12
|
## 📦 Install
|
|
11
13
|
|
|
@@ -20,13 +22,19 @@ best-effort post-processing. Formatter failures never block tool results.
|
|
|
20
22
|
|
|
21
23
|
Formatting modes:
|
|
22
24
|
|
|
23
|
-
- `
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
- `tool`: format immediately after each successful `write` or `edit` tool
|
|
26
|
+
result.
|
|
27
|
+
Use this mode when you want the file on disk to stay formatted after every
|
|
28
|
+
edit, even while the agent is still working.
|
|
29
|
+
- `turn`: collect files touched during the current turn and format them once at
|
|
30
|
+
`turn_end`. This is the default.
|
|
31
|
+
Use this mode when you want to avoid mid-turn formatter drift while still
|
|
32
|
+
keeping files formatted throughout the run.
|
|
33
|
+
- `session`: collect files touched during the current session and format them
|
|
34
|
+
once at `session_shutdown`.
|
|
35
|
+
Use this mode when you want the fewest formatter interruptions and are okay
|
|
36
|
+
with formatting only when the session exits, reloads, or switches. Interrupted
|
|
37
|
+
runs stay pending until the session ends or changes.
|
|
30
38
|
|
|
31
39
|
Supported file types:
|
|
32
40
|
|
|
@@ -53,17 +61,14 @@ folder (default: `~/.pi/agent`, overridable via `PI_CODING_AGENT_DIR`):
|
|
|
53
61
|
|
|
54
62
|
```json
|
|
55
63
|
{
|
|
56
|
-
"formatMode": "
|
|
57
|
-
"formatOnAbort": false,
|
|
64
|
+
"formatMode": "turn",
|
|
58
65
|
"commandTimeoutMs": 10000,
|
|
59
66
|
"hideCallSummariesInTui": false
|
|
60
67
|
}
|
|
61
68
|
```
|
|
62
69
|
|
|
63
|
-
- `formatMode`: formatting strategy
|
|
64
|
-
|
|
65
|
-
- `formatOnAbort`: in deferred mode, also format files after an interrupted or
|
|
66
|
-
canceled run (default: `false`)
|
|
70
|
+
- `formatMode`: formatting strategy (`"tool"` | `"turn"` | `"session"`,
|
|
71
|
+
default: `"turn"`). Use `"tool"` to restore the old immediate default.
|
|
67
72
|
- `commandTimeoutMs`: timeout (ms) per formatter command (default: `10000`)
|
|
68
73
|
- `hideCallSummariesInTui`: hide formatter pass/fail summaries in the TUI
|
|
69
74
|
(default: `false`)
|
|
@@ -1,32 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
writeFileSync,
|
|
6
|
-
} from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
2
|
import { join } from "node:path";
|
|
8
3
|
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
9
4
|
|
|
10
|
-
export type FormatMode = "
|
|
5
|
+
export type FormatMode = "tool" | "turn" | "session";
|
|
11
6
|
|
|
12
7
|
const DEFAULT_COMMAND_TIMEOUT_MS = 10_000;
|
|
13
8
|
const DEFAULT_HIDE_CALL_SUMMARIES_IN_TUI = false;
|
|
14
|
-
const DEFAULT_FORMAT_MODE: FormatMode = "
|
|
15
|
-
const DEFAULT_FORMAT_ON_ABORT = false;
|
|
9
|
+
const DEFAULT_FORMAT_MODE: FormatMode = "turn";
|
|
16
10
|
const FORMATTER_CONFIG_FILE = "formatter.json";
|
|
17
11
|
|
|
18
12
|
export type FormatterConfigSnapshot = {
|
|
19
13
|
commandTimeoutMs: number;
|
|
20
14
|
hideCallSummariesInTui: boolean;
|
|
21
15
|
formatMode: FormatMode;
|
|
22
|
-
formatOnAbort: boolean;
|
|
23
16
|
};
|
|
24
17
|
|
|
25
18
|
export const DEFAULT_FORMATTER_CONFIG: FormatterConfigSnapshot = {
|
|
26
19
|
commandTimeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,
|
|
27
20
|
hideCallSummariesInTui: DEFAULT_HIDE_CALL_SUMMARIES_IN_TUI,
|
|
28
21
|
formatMode: DEFAULT_FORMAT_MODE,
|
|
29
|
-
formatOnAbort: DEFAULT_FORMAT_ON_ABORT,
|
|
30
22
|
};
|
|
31
23
|
|
|
32
24
|
export function getFormatterConfigPath(): string {
|
|
@@ -73,7 +65,7 @@ function parseBooleanValue(value: unknown, defaultValue: boolean): boolean {
|
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
function parseFormatMode(value: unknown, defaultValue: FormatMode): FormatMode {
|
|
76
|
-
if (value === "
|
|
68
|
+
if (value === "tool" || value === "turn" || value === "session") {
|
|
77
69
|
return value;
|
|
78
70
|
}
|
|
79
71
|
|
|
@@ -87,7 +79,6 @@ function toFormatterConfigObject(
|
|
|
87
79
|
commandTimeoutMs: config.commandTimeoutMs,
|
|
88
80
|
hideCallSummariesInTui: config.hideCallSummariesInTui,
|
|
89
81
|
formatMode: config.formatMode,
|
|
90
|
-
formatOnAbort: config.formatOnAbort,
|
|
91
82
|
};
|
|
92
83
|
}
|
|
93
84
|
|
|
@@ -113,10 +104,6 @@ export function loadFormatterConfig(): FormatterConfigSnapshot {
|
|
|
113
104
|
DEFAULT_HIDE_CALL_SUMMARIES_IN_TUI,
|
|
114
105
|
),
|
|
115
106
|
formatMode: parseFormatMode(config.formatMode, DEFAULT_FORMAT_MODE),
|
|
116
|
-
formatOnAbort: parseBooleanValue(
|
|
117
|
-
config.formatOnAbort,
|
|
118
|
-
DEFAULT_FORMAT_ON_ABORT,
|
|
119
|
-
),
|
|
120
107
|
};
|
|
121
108
|
}
|
|
122
109
|
|
|
@@ -127,7 +114,6 @@ export function cloneFormatterConfig(
|
|
|
127
114
|
commandTimeoutMs: config.commandTimeoutMs,
|
|
128
115
|
hideCallSummariesInTui: config.hideCallSummariesInTui,
|
|
129
116
|
formatMode: config.formatMode,
|
|
130
|
-
formatOnAbort: config.formatOnAbort,
|
|
131
117
|
};
|
|
132
118
|
}
|
|
133
119
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getRelativePathOrAbsolute,
|
|
6
|
+
isWithinDirectory,
|
|
7
|
+
pathExists,
|
|
8
|
+
} from "./path.js";
|
|
5
9
|
import { hasCommand } from "./system.js";
|
|
6
10
|
import type { FileKind, RequiredMajorVersion, RunnerContext } from "./types.js";
|
|
7
11
|
|
|
@@ -239,12 +243,12 @@ export class FormatRunContext implements RunnerContext {
|
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
private async resolveChangedLines(): Promise<string[]> {
|
|
242
|
-
const
|
|
246
|
+
const diffPath = getRelativePathOrAbsolute(this.filePath, this.cwd);
|
|
243
247
|
const rangeSet = new Set<string>();
|
|
244
248
|
|
|
245
249
|
const diffArgSets = [
|
|
246
|
-
["diff", "--unified=0", "--",
|
|
247
|
-
["diff", "--cached", "--unified=0", "--",
|
|
250
|
+
["diff", "--unified=0", "--", diffPath],
|
|
251
|
+
["diff", "--cached", "--unified=0", "--", diffPath],
|
|
248
252
|
];
|
|
249
253
|
|
|
250
254
|
for (const args of diffArgSets) {
|
|
@@ -54,8 +54,11 @@ export function isWithinDirectory(
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export function
|
|
58
|
-
|
|
57
|
+
export function getRelativePathOrAbsolute(
|
|
58
|
+
filePath: string,
|
|
59
|
+
directory: string,
|
|
60
|
+
): string {
|
|
61
|
+
const relPath = relative(directory, filePath);
|
|
59
62
|
if (
|
|
60
63
|
!relPath ||
|
|
61
64
|
relPath === "." ||
|
package/extensions/index.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
1
|
import {
|
|
3
2
|
type ExtensionAPI,
|
|
4
3
|
getSettingsListTheme,
|
|
5
4
|
isEditToolResult,
|
|
6
5
|
isWriteToolResult,
|
|
7
6
|
} from "@mariozechner/pi-coding-agent";
|
|
8
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Container,
|
|
9
|
+
type SettingItem,
|
|
10
|
+
SettingsList,
|
|
11
|
+
Text,
|
|
12
|
+
} from "@mariozechner/pi-tui";
|
|
9
13
|
import {
|
|
10
14
|
cloneFormatterConfig,
|
|
11
15
|
type FormatterConfigSnapshot,
|
|
@@ -15,36 +19,39 @@ import {
|
|
|
15
19
|
} from "./formatter/config.js";
|
|
16
20
|
import { type FormatCallSummary, formatFile } from "./formatter/dispatch.js";
|
|
17
21
|
import {
|
|
18
|
-
|
|
22
|
+
getRelativePathOrAbsolute,
|
|
19
23
|
pathExists,
|
|
20
24
|
resolveToolPath,
|
|
21
25
|
} from "./formatter/path.js";
|
|
22
26
|
|
|
27
|
+
function normalizeSummaryMessage(message: string): string {
|
|
28
|
+
return message.replace(/\s+/g, " ").trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
function formatError(error: unknown): string {
|
|
24
32
|
return error instanceof Error ? error.message : String(error);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
35
|
+
function formatCallSummary(
|
|
36
|
+
summary: FormatCallSummary,
|
|
37
|
+
fileLabel: string,
|
|
38
|
+
): string {
|
|
39
|
+
const prefix = summary.status === "succeeded" ? "✔︎" : "✘";
|
|
40
|
+
const base = `${prefix} ${summary.runnerId}: ${fileLabel}`;
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
42
|
+
if (summary.status === "succeeded") {
|
|
43
|
+
return base;
|
|
44
|
+
}
|
|
37
45
|
|
|
38
|
-
function formatCallFailureSummary(summary: FormatCallSummary): string {
|
|
39
46
|
if (summary.failureMessage) {
|
|
40
|
-
return
|
|
47
|
+
return `${base}: ${normalizeSummaryMessage(summary.failureMessage)}`;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
if (summary.exitCode !== undefined) {
|
|
44
|
-
return
|
|
51
|
+
return `${base} (exit ${summary.exitCode})`;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
return
|
|
54
|
+
return base;
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
function resolveEventPath(rawPath: unknown, cwd: string): string | undefined {
|
|
@@ -72,17 +79,9 @@ function getFormatterSettingItems(
|
|
|
72
79
|
id: "formatMode",
|
|
73
80
|
label: "Format mode",
|
|
74
81
|
description:
|
|
75
|
-
"Choose whether formatting runs after each successful write/edit tool call or once
|
|
82
|
+
"Choose whether formatting runs after each successful write/edit tool call, once after each turn, or once when the session shuts down.",
|
|
76
83
|
currentValue: config.formatMode,
|
|
77
|
-
values: ["
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
id: "formatOnAbort",
|
|
81
|
-
label: "Format on abort",
|
|
82
|
-
description:
|
|
83
|
-
"When enabled, deferred formatting also runs if the model is interrupted or cancelled.",
|
|
84
|
-
currentValue: config.formatOnAbort ? "on" : "off",
|
|
85
|
-
values: ["off", "on"],
|
|
84
|
+
values: ["tool", "turn", "session"],
|
|
86
85
|
},
|
|
87
86
|
{
|
|
88
87
|
id: "commandTimeoutMs",
|
|
@@ -102,11 +101,19 @@ function getFormatterSettingItems(
|
|
|
102
101
|
];
|
|
103
102
|
}
|
|
104
103
|
|
|
104
|
+
type FormatterContext = {
|
|
105
|
+
cwd: string;
|
|
106
|
+
hasUI: boolean;
|
|
107
|
+
ui: {
|
|
108
|
+
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
105
112
|
export default function (pi: ExtensionAPI) {
|
|
106
113
|
let formatterConfig = loadFormatterConfig();
|
|
107
114
|
const formatQueueByPath = new Map<string, Promise<void>>();
|
|
108
|
-
const
|
|
109
|
-
const
|
|
115
|
+
const pendingTurnPaths = new Set<string>();
|
|
116
|
+
const pendingSessionPaths = new Set<string>();
|
|
110
117
|
|
|
111
118
|
const enqueueFormat = async (
|
|
112
119
|
filePath: string,
|
|
@@ -130,13 +137,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
130
137
|
|
|
131
138
|
const formatResolvedPath = async (
|
|
132
139
|
filePath: string,
|
|
133
|
-
ctx:
|
|
134
|
-
cwd: string;
|
|
135
|
-
hasUI: boolean;
|
|
136
|
-
ui: {
|
|
137
|
-
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
138
|
-
};
|
|
139
|
-
},
|
|
140
|
+
ctx: FormatterContext,
|
|
140
141
|
): Promise<void> => {
|
|
141
142
|
if (!(await pathExists(filePath))) {
|
|
142
143
|
return;
|
|
@@ -144,7 +145,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
144
145
|
|
|
145
146
|
const showSummaries = !formatterConfig.hideCallSummariesInTui && ctx.hasUI;
|
|
146
147
|
const notifyWarning = (message: string) => {
|
|
147
|
-
const normalizedMessage = message
|
|
148
|
+
const normalizedMessage = normalizeSummaryMessage(message);
|
|
148
149
|
|
|
149
150
|
if (ctx.hasUI) {
|
|
150
151
|
ctx.ui.notify(normalizedMessage, "warning");
|
|
@@ -162,12 +163,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
162
163
|
}
|
|
163
164
|
: undefined;
|
|
164
165
|
|
|
165
|
-
const runnerWarningReporter =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
: notifyWarning;
|
|
166
|
+
const runnerWarningReporter = showSummaries
|
|
167
|
+
? () => {
|
|
168
|
+
// Summary mode already reports failures compactly.
|
|
169
|
+
}
|
|
170
|
+
: notifyWarning;
|
|
171
171
|
|
|
172
172
|
try {
|
|
173
173
|
await formatFile(
|
|
@@ -179,7 +179,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
179
179
|
runnerWarningReporter,
|
|
180
180
|
);
|
|
181
181
|
} catch (error) {
|
|
182
|
-
const fileLabel =
|
|
182
|
+
const fileLabel = getRelativePathOrAbsolute(filePath, ctx.cwd);
|
|
183
183
|
notifyWarning(`Failed to format ${fileLabel}: ${formatError(error)}`);
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -187,102 +187,92 @@ export default function (pi: ExtensionAPI) {
|
|
|
187
187
|
return;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
if (summary.status === "succeeded") {
|
|
192
|
-
ctx.ui.notify(formatCallSuccessSummary(summary), "info");
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
190
|
+
const fileLabel = getRelativePathOrAbsolute(filePath, ctx.cwd);
|
|
195
191
|
|
|
196
|
-
|
|
192
|
+
for (const summary of summaries) {
|
|
193
|
+
ctx.ui.notify(formatCallSummary(summary, fileLabel), "info");
|
|
197
194
|
}
|
|
198
195
|
});
|
|
199
196
|
};
|
|
200
197
|
|
|
198
|
+
const flushPaths = async (
|
|
199
|
+
paths: Set<string>,
|
|
200
|
+
ctx: FormatterContext,
|
|
201
|
+
): Promise<void> => {
|
|
202
|
+
const batch = [...paths];
|
|
203
|
+
paths.clear();
|
|
204
|
+
|
|
205
|
+
for (const filePath of batch) {
|
|
206
|
+
await formatResolvedPath(filePath, ctx);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const flushPendingPaths = async (ctx: FormatterContext): Promise<void> => {
|
|
211
|
+
await flushPaths(pendingTurnPaths, ctx);
|
|
212
|
+
await flushPaths(pendingSessionPaths, ctx);
|
|
213
|
+
};
|
|
214
|
+
|
|
201
215
|
const reloadFormatterConfig = () => {
|
|
202
216
|
formatterConfig = loadFormatterConfig();
|
|
203
217
|
};
|
|
204
218
|
|
|
205
|
-
pi.on("
|
|
206
|
-
|
|
207
|
-
successfulPaths.clear();
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
pi.on("tool_call", async (event, ctx) => {
|
|
211
|
-
if (formatterConfig.formatMode !== "afterAgentStop") {
|
|
219
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
220
|
+
if (!isWriteToolResult(event) && !isEditToolResult(event)) {
|
|
212
221
|
return;
|
|
213
222
|
}
|
|
214
223
|
|
|
215
|
-
if (event.
|
|
224
|
+
if (event.isError) {
|
|
216
225
|
return;
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
const filePath = resolveEventPath(event.input.path, ctx.cwd);
|
|
220
|
-
if (filePath) {
|
|
221
|
-
|
|
229
|
+
if (!filePath) {
|
|
230
|
+
return;
|
|
222
231
|
}
|
|
223
|
-
});
|
|
224
232
|
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
if (formatterConfig.formatMode === "tool") {
|
|
234
|
+
await formatResolvedPath(filePath, ctx);
|
|
227
235
|
return;
|
|
228
236
|
}
|
|
229
237
|
|
|
230
|
-
|
|
231
|
-
|
|
238
|
+
if (formatterConfig.formatMode === "turn") {
|
|
239
|
+
pendingTurnPaths.add(filePath);
|
|
232
240
|
return;
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
243
|
+
pendingSessionPaths.add(filePath);
|
|
244
|
+
});
|
|
238
245
|
|
|
239
|
-
|
|
246
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
247
|
+
if (pendingTurnPaths.size === 0) {
|
|
240
248
|
return;
|
|
241
249
|
}
|
|
242
250
|
|
|
243
|
-
await
|
|
251
|
+
await flushPaths(pendingTurnPaths, ctx);
|
|
244
252
|
});
|
|
245
253
|
|
|
246
|
-
pi.on("agent_end", async (
|
|
247
|
-
if (
|
|
248
|
-
candidatePaths.clear();
|
|
249
|
-
successfulPaths.clear();
|
|
254
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
255
|
+
if (pendingTurnPaths.size === 0) {
|
|
250
256
|
return;
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const message = event.messages[index];
|
|
256
|
-
if (message.role !== "assistant") {
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
lastAssistantStopReason = message.stopReason;
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
259
|
+
await flushPaths(pendingTurnPaths, ctx);
|
|
260
|
+
});
|
|
263
261
|
|
|
264
|
-
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
for (const filePath of candidatePaths) {
|
|
268
|
-
pathsToFormat.add(filePath);
|
|
269
|
-
}
|
|
270
|
-
for (const filePath of successfulPaths) {
|
|
271
|
-
pathsToFormat.add(filePath);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
} else {
|
|
275
|
-
for (const filePath of successfulPaths) {
|
|
276
|
-
pathsToFormat.add(filePath);
|
|
277
|
-
}
|
|
262
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
263
|
+
if (pendingTurnPaths.size === 0 && pendingSessionPaths.size === 0) {
|
|
264
|
+
return;
|
|
278
265
|
}
|
|
279
266
|
|
|
280
|
-
|
|
281
|
-
|
|
267
|
+
await flushPendingPaths(ctx);
|
|
268
|
+
});
|
|
282
269
|
|
|
283
|
-
|
|
284
|
-
|
|
270
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
271
|
+
if (pendingTurnPaths.size === 0 && pendingSessionPaths.size === 0) {
|
|
272
|
+
return;
|
|
285
273
|
}
|
|
274
|
+
|
|
275
|
+
await flushPendingPaths(ctx);
|
|
286
276
|
});
|
|
287
277
|
|
|
288
278
|
pi.registerCommand("formatter", {
|
|
@@ -307,10 +297,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
307
297
|
|
|
308
298
|
const syncDraftToSettingsList = (settingsList: SettingsList) => {
|
|
309
299
|
settingsList.updateValue("formatMode", draft.formatMode);
|
|
310
|
-
settingsList.updateValue(
|
|
311
|
-
"formatOnAbort",
|
|
312
|
-
draft.formatOnAbort ? "on" : "off",
|
|
313
|
-
);
|
|
314
300
|
settingsList.updateValue(
|
|
315
301
|
"commandTimeoutMs",
|
|
316
302
|
String(draft.commandTimeoutMs),
|
|
@@ -329,9 +315,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
329
315
|
const previous = cloneFormatterConfig(draft);
|
|
330
316
|
|
|
331
317
|
if (id === "formatMode") {
|
|
332
|
-
draft.formatMode =
|
|
333
|
-
|
|
334
|
-
draft.formatOnAbort = newValue === "on";
|
|
318
|
+
draft.formatMode =
|
|
319
|
+
newValue as FormatterConfigSnapshot["formatMode"];
|
|
335
320
|
} else if (id === "commandTimeoutMs") {
|
|
336
321
|
draft.commandTimeoutMs = Number.parseInt(newValue, 10);
|
|
337
322
|
} else if (id === "hideCallSummariesInTui") {
|
|
@@ -341,13 +326,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
326
|
try {
|
|
342
327
|
writeFormatterConfigSnapshot(draft);
|
|
343
328
|
reloadFormatterConfig();
|
|
329
|
+
|
|
330
|
+
if (
|
|
331
|
+
id === "formatMode" &&
|
|
332
|
+
previous.formatMode !== draft.formatMode
|
|
333
|
+
) {
|
|
334
|
+
void flushPendingPaths(ctx).catch((error) => {
|
|
335
|
+
const message =
|
|
336
|
+
error instanceof Error ? error.message : String(error);
|
|
337
|
+
ctx.ui.notify(
|
|
338
|
+
`Failed to flush pending formats: ${message}`,
|
|
339
|
+
"error",
|
|
340
|
+
);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
344
343
|
} catch (error) {
|
|
345
344
|
const message =
|
|
346
345
|
error instanceof Error ? error.message : String(error);
|
|
347
346
|
draft.commandTimeoutMs = previous.commandTimeoutMs;
|
|
348
347
|
draft.hideCallSummariesInTui = previous.hideCallSummariesInTui;
|
|
349
348
|
draft.formatMode = previous.formatMode;
|
|
350
|
-
draft.formatOnAbort = previous.formatOnAbort;
|
|
351
349
|
syncDraftToSettingsList(settingsList);
|
|
352
350
|
ctx.ui.notify(`Failed to save config: ${message}`, "error");
|
|
353
351
|
}
|