pi-formatter 0.3.0 → 1.0.1
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 +19 -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 +101 -111
- package/package.json +2 -2
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 prompt — after the agent finishes all its
|
|
7
|
+
work and yields control back to you. You can also format after each individual
|
|
8
|
+
tool call or defer formatting until the session shuts down.
|
|
9
|
+
|
|
10
|
+
To format after every individual edit instead, set `"formatMode": "tool"`.
|
|
9
11
|
|
|
10
12
|
## 📦 Install
|
|
11
13
|
|
|
@@ -20,13 +22,17 @@ 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 call.
|
|
26
|
+
Use this mode when you want files on disk to stay formatted after every edit,
|
|
27
|
+
even while the agent is still working.
|
|
28
|
+
- `prompt`: collect all files touched during the agent run and format them once
|
|
29
|
+
when the agent finishes and yields control back to you. This is the default.
|
|
30
|
+
Use this mode to avoid mid-run formatter interruptions while still getting
|
|
31
|
+
clean files after each response.
|
|
32
|
+
- `session`: collect files touched during the current session and format them
|
|
33
|
+
once at session shutdown, reload, or switch.
|
|
34
|
+
Use this mode when you want the fewest interruptions and are okay with
|
|
35
|
+
formatting only when the session ends.
|
|
30
36
|
|
|
31
37
|
Supported file types:
|
|
32
38
|
|
|
@@ -53,17 +59,14 @@ folder (default: `~/.pi/agent`, overridable via `PI_CODING_AGENT_DIR`):
|
|
|
53
59
|
|
|
54
60
|
```json
|
|
55
61
|
{
|
|
56
|
-
"formatMode": "
|
|
57
|
-
"formatOnAbort": false,
|
|
62
|
+
"formatMode": "prompt",
|
|
58
63
|
"commandTimeoutMs": 10000,
|
|
59
64
|
"hideCallSummariesInTui": false
|
|
60
65
|
}
|
|
61
66
|
```
|
|
62
67
|
|
|
63
|
-
- `formatMode`: formatting strategy
|
|
64
|
-
|
|
65
|
-
- `formatOnAbort`: in deferred mode, also format files after an interrupted or
|
|
66
|
-
canceled run (default: `false`)
|
|
68
|
+
- `formatMode`: formatting strategy (`"tool"` | `"prompt"` | `"session"`,
|
|
69
|
+
default: `"prompt"`). Use `"tool"` to format after every individual edit.
|
|
67
70
|
- `commandTimeoutMs`: timeout (ms) per formatter command (default: `10000`)
|
|
68
71
|
- `hideCallSummariesInTui`: hide formatter pass/fail summaries in the TUI
|
|
69
72
|
(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" | "prompt" | "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
|
+
export const DEFAULT_FORMAT_MODE: FormatMode = "prompt";
|
|
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 === "prompt" || 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 prompt completes, 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", "prompt", "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 pendingPromptPaths = 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,84 @@ 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(pendingPromptPaths, 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 === "prompt") {
|
|
239
|
+
pendingPromptPaths.add(filePath);
|
|
232
240
|
return;
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
243
|
+
pendingSessionPaths.add(filePath);
|
|
244
|
+
});
|
|
238
245
|
|
|
239
|
-
|
|
246
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
247
|
+
if (pendingPromptPaths.size === 0) {
|
|
240
248
|
return;
|
|
241
249
|
}
|
|
242
250
|
|
|
243
|
-
await
|
|
251
|
+
await flushPaths(pendingPromptPaths, ctx);
|
|
244
252
|
});
|
|
245
253
|
|
|
246
|
-
pi.on("
|
|
247
|
-
if (
|
|
248
|
-
candidatePaths.clear();
|
|
249
|
-
successfulPaths.clear();
|
|
254
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
255
|
+
if (pendingPromptPaths.size === 0 && pendingSessionPaths.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 flushPendingPaths(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_shutdown", async (_event, ctx) => {
|
|
263
|
+
if (pendingPromptPaths.size === 0 && pendingSessionPaths.size === 0) {
|
|
264
|
+
return;
|
|
278
265
|
}
|
|
279
266
|
|
|
280
|
-
|
|
281
|
-
successfulPaths.clear();
|
|
282
|
-
|
|
283
|
-
for (const filePath of pathsToFormat) {
|
|
284
|
-
await formatResolvedPath(filePath, ctx);
|
|
285
|
-
}
|
|
267
|
+
await flushPendingPaths(ctx);
|
|
286
268
|
});
|
|
287
269
|
|
|
288
270
|
pi.registerCommand("formatter", {
|
|
@@ -307,10 +289,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
307
289
|
|
|
308
290
|
const syncDraftToSettingsList = (settingsList: SettingsList) => {
|
|
309
291
|
settingsList.updateValue("formatMode", draft.formatMode);
|
|
310
|
-
settingsList.updateValue(
|
|
311
|
-
"formatOnAbort",
|
|
312
|
-
draft.formatOnAbort ? "on" : "off",
|
|
313
|
-
);
|
|
314
292
|
settingsList.updateValue(
|
|
315
293
|
"commandTimeoutMs",
|
|
316
294
|
String(draft.commandTimeoutMs),
|
|
@@ -329,9 +307,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
329
307
|
const previous = cloneFormatterConfig(draft);
|
|
330
308
|
|
|
331
309
|
if (id === "formatMode") {
|
|
332
|
-
draft.formatMode =
|
|
333
|
-
|
|
334
|
-
draft.formatOnAbort = newValue === "on";
|
|
310
|
+
draft.formatMode =
|
|
311
|
+
newValue as FormatterConfigSnapshot["formatMode"];
|
|
335
312
|
} else if (id === "commandTimeoutMs") {
|
|
336
313
|
draft.commandTimeoutMs = Number.parseInt(newValue, 10);
|
|
337
314
|
} else if (id === "hideCallSummariesInTui") {
|
|
@@ -341,13 +318,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
318
|
try {
|
|
342
319
|
writeFormatterConfigSnapshot(draft);
|
|
343
320
|
reloadFormatterConfig();
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
id === "formatMode" &&
|
|
324
|
+
previous.formatMode !== draft.formatMode
|
|
325
|
+
) {
|
|
326
|
+
void flushPendingPaths(ctx).catch((error) => {
|
|
327
|
+
const message =
|
|
328
|
+
error instanceof Error ? error.message : String(error);
|
|
329
|
+
ctx.ui.notify(
|
|
330
|
+
`Failed to flush pending formats: ${message}`,
|
|
331
|
+
"error",
|
|
332
|
+
);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
344
335
|
} catch (error) {
|
|
345
336
|
const message =
|
|
346
337
|
error instanceof Error ? error.message : String(error);
|
|
347
338
|
draft.commandTimeoutMs = previous.commandTimeoutMs;
|
|
348
339
|
draft.hideCallSummariesInTui = previous.hideCallSummariesInTui;
|
|
349
340
|
draft.formatMode = previous.formatMode;
|
|
350
|
-
draft.formatOnAbort = previous.formatOnAbort;
|
|
351
341
|
syncDraftToSettingsList(settingsList);
|
|
352
342
|
ctx.ui.notify(`Failed to save config: ${message}`, "error");
|
|
353
343
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-formatter",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pi extension that auto-formats files
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Pi extension that auto-formats files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"extensions",
|