pi-formatter 0.1.1 → 0.3.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 +55 -10
- package/extensions/formatter/config.ts +140 -0
- package/extensions/{format → formatter}/context.ts +58 -37
- package/extensions/{format → formatter}/dispatch.ts +114 -32
- package/extensions/{format → formatter}/path.ts +19 -3
- package/extensions/{format → formatter}/plan.ts +5 -5
- package/extensions/{format/runners/biome-check-write.ts → formatter/runners/biome.ts} +4 -4
- package/extensions/formatter/runners/config-patterns.ts +5 -0
- package/extensions/{format/runners/eslint-fix.ts → formatter/runners/eslint.ts} +3 -3
- package/extensions/{format → formatter}/runners/index.ts +10 -12
- package/extensions/{format/runners/markdownlint-fix.ts → formatter/runners/markdownlint.ts} +3 -3
- package/extensions/{format/runners/prettier-markdown.ts → formatter/runners/prettier.ts} +3 -3
- package/extensions/{format/runners/ruff-check-fix.ts → formatter/runners/ruff-check.ts} +3 -3
- package/extensions/{format → formatter}/types.ts +4 -6
- package/extensions/index.ts +332 -20
- package/package.json +1 -2
- package/DOCUMENTATION.md +0 -120
- package/extensions/format/config.ts +0 -11
- package/extensions/format/runners/config-patterns.ts +0 -6
- package/extensions/format/runners/prettier-config-write.ts +0 -11
- /package/extensions/{format → formatter}/runners/clang-format.ts +0 -0
- /package/extensions/{format → formatter}/runners/cmake-format.ts +0 -0
- /package/extensions/{format → formatter}/runners/helpers.ts +0 -0
- /package/extensions/{format → formatter}/runners/ruff-format.ts +0 -0
- /package/extensions/{format → formatter}/runners/shfmt.ts +0 -0
- /package/extensions/{format → formatter}/system.ts +0 -0
|
@@ -5,22 +5,22 @@ export const FORMAT_PLAN: Record<FileKind, RunnerGroup[]> = {
|
|
|
5
5
|
cmake: [{ mode: "all", runnerIds: ["cmake-format"] }],
|
|
6
6
|
markdown: [
|
|
7
7
|
{
|
|
8
|
-
mode: "
|
|
9
|
-
runnerIds: ["
|
|
8
|
+
mode: "fallback",
|
|
9
|
+
runnerIds: ["prettier", "markdownlint"],
|
|
10
10
|
},
|
|
11
11
|
],
|
|
12
12
|
json: [
|
|
13
13
|
{
|
|
14
14
|
mode: "fallback",
|
|
15
|
-
runnerIds: ["biome
|
|
15
|
+
runnerIds: ["biome", "prettier"],
|
|
16
16
|
},
|
|
17
17
|
],
|
|
18
18
|
shell: [{ mode: "all", runnerIds: ["shfmt"] }],
|
|
19
|
-
python: [{ mode: "all", runnerIds: ["ruff-format", "ruff-check
|
|
19
|
+
python: [{ mode: "all", runnerIds: ["ruff-format", "ruff-check"] }],
|
|
20
20
|
jsts: [
|
|
21
21
|
{
|
|
22
22
|
mode: "fallback",
|
|
23
|
-
runnerIds: ["biome
|
|
23
|
+
runnerIds: ["biome", "eslint", "prettier"],
|
|
24
24
|
},
|
|
25
25
|
],
|
|
26
26
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineRunner, direct } from "./helpers.js";
|
|
2
1
|
import { BIOME_CONFIG_PATTERNS } from "./config-patterns.js";
|
|
2
|
+
import { defineRunner, direct } from "./helpers.js";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
id: "biome
|
|
4
|
+
const biomeRunner = defineRunner({
|
|
5
|
+
id: "biome",
|
|
6
6
|
launcher: direct("biome"),
|
|
7
7
|
when: (ctx) => ctx.hasConfig(BIOME_CONFIG_PATTERNS),
|
|
8
8
|
args: ["check", "--write"],
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
export default
|
|
11
|
+
export default biomeRunner;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ESLINT_CONFIG_PATTERNS } from "./config-patterns.js";
|
|
2
2
|
import { defineRunner, direct } from "./helpers.js";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
id: "eslint
|
|
4
|
+
const eslintRunner = defineRunner({
|
|
5
|
+
id: "eslint",
|
|
6
6
|
launcher: direct("eslint"),
|
|
7
7
|
when: (ctx) => ctx.hasConfig(ESLINT_CONFIG_PATTERNS),
|
|
8
8
|
args: ["--fix"],
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
export default
|
|
11
|
+
export default eslintRunner;
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import type { RunnerDefinition } from "../types.js";
|
|
2
|
-
import
|
|
2
|
+
import biomeRunner from "./biome.js";
|
|
3
3
|
import clangFormatRunner from "./clang-format.js";
|
|
4
4
|
import cmakeFormatRunner from "./cmake-format.js";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import ruffCheckFixRunner from "./ruff-check-fix.js";
|
|
5
|
+
import eslintRunner from "./eslint.js";
|
|
6
|
+
import markdownlintRunner from "./markdownlint.js";
|
|
7
|
+
import prettierRunner from "./prettier.js";
|
|
8
|
+
import ruffCheckRunner from "./ruff-check.js";
|
|
10
9
|
import ruffFormatRunner from "./ruff-format.js";
|
|
11
10
|
import shfmtRunner from "./shfmt.js";
|
|
12
11
|
|
|
13
12
|
export const RUNNER_DEFINITIONS: RunnerDefinition[] = [
|
|
14
13
|
clangFormatRunner,
|
|
15
14
|
cmakeFormatRunner,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
prettierConfigWriteRunner,
|
|
15
|
+
markdownlintRunner,
|
|
16
|
+
biomeRunner,
|
|
17
|
+
eslintRunner,
|
|
18
|
+
prettierRunner,
|
|
21
19
|
shfmtRunner,
|
|
22
20
|
ruffFormatRunner,
|
|
23
|
-
|
|
21
|
+
ruffCheckRunner,
|
|
24
22
|
];
|
|
25
23
|
|
|
26
24
|
function buildRunnerRegistry(
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineRunner, direct } from "./helpers.js";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
id: "markdownlint
|
|
3
|
+
const markdownlintRunner = defineRunner({
|
|
4
|
+
id: "markdownlint",
|
|
5
5
|
launcher: direct("markdownlint"),
|
|
6
6
|
args: ["--fix"],
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
export default
|
|
9
|
+
export default markdownlintRunner;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineRunner, direct } from "./helpers.js";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
id: "prettier
|
|
3
|
+
const prettierRunner = defineRunner({
|
|
4
|
+
id: "prettier",
|
|
5
5
|
launcher: direct("prettier"),
|
|
6
6
|
args: ["--write"],
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
export default
|
|
9
|
+
export default prettierRunner;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineRunner, pypi } from "./helpers.js";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
id: "ruff-check
|
|
3
|
+
const ruffCheckRunner = defineRunner({
|
|
4
|
+
id: "ruff-check",
|
|
5
5
|
launcher: pypi("ruff"),
|
|
6
6
|
args: ["check", "--fix"],
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
export default
|
|
9
|
+
export default ruffCheckRunner;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ExecResult } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
export type SourceTool = "write" | "edit";
|
|
4
|
-
|
|
5
3
|
export type FileKind =
|
|
6
4
|
| "cxx"
|
|
7
5
|
| "cmake"
|
|
@@ -40,7 +38,6 @@ export interface ResolvedLauncher {
|
|
|
40
38
|
export interface RunnerContext {
|
|
41
39
|
readonly filePath: string;
|
|
42
40
|
readonly cwd: string;
|
|
43
|
-
readonly sourceTool: SourceTool;
|
|
44
41
|
readonly kind: FileKind;
|
|
45
42
|
|
|
46
43
|
hasCommand(command: string): Promise<boolean>;
|
|
@@ -48,7 +45,7 @@ export interface RunnerContext {
|
|
|
48
45
|
findConfigFile(patterns: readonly string[]): Promise<string | undefined>;
|
|
49
46
|
hasEditorConfigInCwd(): Promise<boolean>;
|
|
50
47
|
|
|
51
|
-
exec(command: string, args: string[]): Promise<ExecResult
|
|
48
|
+
exec(command: string, args: string[]): Promise<ExecResult>;
|
|
52
49
|
getChangedLines(): Promise<string[]>;
|
|
53
50
|
getRequiredMajorVersionFromConfig(
|
|
54
51
|
patterns: readonly string[],
|
|
@@ -94,8 +91,9 @@ export interface StaticRunnerDefinition extends RunnerBase {
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
export interface DynamicRunnerDefinition extends RunnerBase {
|
|
97
|
-
buildArgs:
|
|
98
|
-
|
|
94
|
+
buildArgs: (
|
|
95
|
+
ctx: RunnerContext,
|
|
96
|
+
) => Promise<string[] | undefined> | string[] | undefined;
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
export type RunnerDefinition = StaticRunnerDefinition | DynamicRunnerDefinition;
|
package/extensions/index.ts
CHANGED
|
@@ -1,15 +1,112 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
type ExtensionAPI,
|
|
4
|
+
getSettingsListTheme,
|
|
5
|
+
isEditToolResult,
|
|
6
|
+
isWriteToolResult,
|
|
7
|
+
} from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import { Container, type SettingItem, SettingsList, Text } from "@mariozechner/pi-tui";
|
|
9
|
+
import {
|
|
10
|
+
cloneFormatterConfig,
|
|
11
|
+
type FormatterConfigSnapshot,
|
|
12
|
+
getFormatterConfigPath,
|
|
13
|
+
loadFormatterConfig,
|
|
14
|
+
writeFormatterConfigSnapshot,
|
|
15
|
+
} from "./formatter/config.js";
|
|
16
|
+
import { type FormatCallSummary, formatFile } from "./formatter/dispatch.js";
|
|
17
|
+
import {
|
|
18
|
+
getPathForGit,
|
|
19
|
+
pathExists,
|
|
20
|
+
resolveToolPath,
|
|
21
|
+
} from "./formatter/path.js";
|
|
6
22
|
|
|
7
23
|
function formatError(error: unknown): string {
|
|
8
24
|
return error instanceof Error ? error.message : String(error);
|
|
9
25
|
}
|
|
10
26
|
|
|
27
|
+
function formatSummaryPath(filePath: string, cwd: string): string {
|
|
28
|
+
const pathForDisplay = getPathForGit(filePath, cwd);
|
|
29
|
+
return pathForDisplay.startsWith("/")
|
|
30
|
+
? basename(pathForDisplay)
|
|
31
|
+
: pathForDisplay;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatCallSuccessSummary(summary: FormatCallSummary): string {
|
|
35
|
+
return `✔︎ ${summary.runnerId}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatCallFailureSummary(summary: FormatCallSummary): string {
|
|
39
|
+
if (summary.failureMessage) {
|
|
40
|
+
return `✘ ${summary.runnerId}: ${summary.failureMessage}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (summary.exitCode !== undefined) {
|
|
44
|
+
return `✘ ${summary.runnerId} (exit ${summary.exitCode})`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return `✘ ${summary.runnerId}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveEventPath(rawPath: unknown, cwd: string): string | undefined {
|
|
51
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return resolveToolPath(rawPath, cwd);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getFormatterSettingItems(
|
|
59
|
+
config: FormatterConfigSnapshot,
|
|
60
|
+
): SettingItem[] {
|
|
61
|
+
const timeoutValues = ["2000", "5000", "10000", "30000", "60000"];
|
|
62
|
+
|
|
63
|
+
if (!timeoutValues.includes(String(config.commandTimeoutMs))) {
|
|
64
|
+
timeoutValues.push(String(config.commandTimeoutMs));
|
|
65
|
+
timeoutValues.sort(
|
|
66
|
+
(a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
id: "formatMode",
|
|
73
|
+
label: "Format mode",
|
|
74
|
+
description:
|
|
75
|
+
"Choose whether formatting runs after each successful write/edit tool call or once after the agent stops.",
|
|
76
|
+
currentValue: config.formatMode,
|
|
77
|
+
values: ["afterEachToolCall", "afterAgentStop"],
|
|
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"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "commandTimeoutMs",
|
|
89
|
+
label: "Command timeout",
|
|
90
|
+
description: "Maximum runtime per formatter command in milliseconds.",
|
|
91
|
+
currentValue: String(config.commandTimeoutMs),
|
|
92
|
+
values: timeoutValues,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "hideCallSummariesInTui",
|
|
96
|
+
label: "Hide TUI summaries",
|
|
97
|
+
description:
|
|
98
|
+
"Hide per-run formatter pass/fail summaries in the interactive UI.",
|
|
99
|
+
currentValue: config.hideCallSummariesInTui ? "on" : "off",
|
|
100
|
+
values: ["off", "on"],
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
11
105
|
export default function (pi: ExtensionAPI) {
|
|
106
|
+
let formatterConfig = loadFormatterConfig();
|
|
12
107
|
const formatQueueByPath = new Map<string, Promise<void>>();
|
|
108
|
+
const candidatePaths = new Set<string>();
|
|
109
|
+
const successfulPaths = new Set<string>();
|
|
13
110
|
|
|
14
111
|
const enqueueFormat = async (
|
|
15
112
|
filePath: string,
|
|
@@ -31,8 +128,87 @@ export default function (pi: ExtensionAPI) {
|
|
|
31
128
|
await next;
|
|
32
129
|
};
|
|
33
130
|
|
|
34
|
-
|
|
35
|
-
|
|
131
|
+
const formatResolvedPath = async (
|
|
132
|
+
filePath: string,
|
|
133
|
+
ctx: {
|
|
134
|
+
cwd: string;
|
|
135
|
+
hasUI: boolean;
|
|
136
|
+
ui: {
|
|
137
|
+
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
): Promise<void> => {
|
|
141
|
+
if (!(await pathExists(filePath))) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const showSummaries = !formatterConfig.hideCallSummariesInTui && ctx.hasUI;
|
|
146
|
+
const notifyWarning = (message: string) => {
|
|
147
|
+
const normalizedMessage = message.replace(/\s+/g, " ").trim();
|
|
148
|
+
|
|
149
|
+
if (ctx.hasUI) {
|
|
150
|
+
ctx.ui.notify(normalizedMessage, "warning");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.warn(normalizedMessage);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
await enqueueFormat(filePath, async () => {
|
|
158
|
+
const summaries: FormatCallSummary[] = [];
|
|
159
|
+
const summaryReporter = showSummaries
|
|
160
|
+
? (summary: FormatCallSummary) => {
|
|
161
|
+
summaries.push(summary);
|
|
162
|
+
}
|
|
163
|
+
: undefined;
|
|
164
|
+
|
|
165
|
+
const runnerWarningReporter =
|
|
166
|
+
showSummaries && ctx.hasUI
|
|
167
|
+
? () => {
|
|
168
|
+
// Summary mode already reports failures compactly.
|
|
169
|
+
}
|
|
170
|
+
: notifyWarning;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await formatFile(
|
|
174
|
+
pi,
|
|
175
|
+
ctx.cwd,
|
|
176
|
+
filePath,
|
|
177
|
+
formatterConfig.commandTimeoutMs,
|
|
178
|
+
summaryReporter,
|
|
179
|
+
runnerWarningReporter,
|
|
180
|
+
);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const fileLabel = formatSummaryPath(filePath, ctx.cwd);
|
|
183
|
+
notifyWarning(`Failed to format ${fileLabel}: ${formatError(error)}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!showSummaries || summaries.length === 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const summary of summaries) {
|
|
191
|
+
if (summary.status === "succeeded") {
|
|
192
|
+
ctx.ui.notify(formatCallSuccessSummary(summary), "info");
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
ctx.ui.notify(formatCallFailureSummary(summary), "info");
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const reloadFormatterConfig = () => {
|
|
202
|
+
formatterConfig = loadFormatterConfig();
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
pi.on("agent_start", async () => {
|
|
206
|
+
candidatePaths.clear();
|
|
207
|
+
successfulPaths.clear();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
211
|
+
if (formatterConfig.formatMode !== "afterAgentStop") {
|
|
36
212
|
return;
|
|
37
213
|
}
|
|
38
214
|
|
|
@@ -40,26 +216,162 @@ export default function (pi: ExtensionAPI) {
|
|
|
40
216
|
return;
|
|
41
217
|
}
|
|
42
218
|
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
219
|
+
const filePath = resolveEventPath(event.input.path, ctx.cwd);
|
|
220
|
+
if (filePath) {
|
|
221
|
+
candidatePaths.add(filePath);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
226
|
+
if (!isWriteToolResult(event) && !isEditToolResult(event)) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const filePath = resolveEventPath(event.input.path, ctx.cwd);
|
|
231
|
+
if (!filePath) {
|
|
45
232
|
return;
|
|
46
233
|
}
|
|
47
234
|
|
|
48
|
-
|
|
49
|
-
|
|
235
|
+
if (!event.isError) {
|
|
236
|
+
successfulPaths.add(filePath);
|
|
237
|
+
}
|
|
50
238
|
|
|
51
|
-
if (
|
|
239
|
+
if (formatterConfig.formatMode !== "afterEachToolCall" || event.isError) {
|
|
52
240
|
return;
|
|
53
241
|
}
|
|
54
242
|
|
|
55
|
-
await
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
243
|
+
await formatResolvedPath(filePath, ctx);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
247
|
+
if (formatterConfig.formatMode !== "afterAgentStop") {
|
|
248
|
+
candidatePaths.clear();
|
|
249
|
+
successfulPaths.clear();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let lastAssistantStopReason: string | undefined;
|
|
254
|
+
for (let index = event.messages.length - 1; index >= 0; index -= 1) {
|
|
255
|
+
const message = event.messages[index];
|
|
256
|
+
if (message.role !== "assistant") {
|
|
257
|
+
continue;
|
|
62
258
|
}
|
|
63
|
-
|
|
259
|
+
|
|
260
|
+
lastAssistantStopReason = message.stopReason;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const pathsToFormat = new Set<string>();
|
|
265
|
+
if (lastAssistantStopReason === "aborted") {
|
|
266
|
+
if (formatterConfig.formatOnAbort) {
|
|
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
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
candidatePaths.clear();
|
|
281
|
+
successfulPaths.clear();
|
|
282
|
+
|
|
283
|
+
for (const filePath of pathsToFormat) {
|
|
284
|
+
await formatResolvedPath(filePath, ctx);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
pi.registerCommand("formatter", {
|
|
289
|
+
description: "Configure formatter behavior.",
|
|
290
|
+
handler: async (_args, ctx) => {
|
|
291
|
+
if (!ctx.hasUI) {
|
|
292
|
+
console.warn("/formatter requires interactive UI mode");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const configPath = getFormatterConfigPath();
|
|
297
|
+
reloadFormatterConfig();
|
|
298
|
+
const draft = cloneFormatterConfig(formatterConfig);
|
|
299
|
+
|
|
300
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
301
|
+
const container = new Container();
|
|
302
|
+
container.addChild(
|
|
303
|
+
new Text(theme.fg("accent", theme.bold("Formatter Settings")), 1, 0),
|
|
304
|
+
);
|
|
305
|
+
container.addChild(new Text(theme.fg("dim", configPath), 1, 0));
|
|
306
|
+
container.addChild(new Text("", 0, 0));
|
|
307
|
+
|
|
308
|
+
const syncDraftToSettingsList = (settingsList: SettingsList) => {
|
|
309
|
+
settingsList.updateValue("formatMode", draft.formatMode);
|
|
310
|
+
settingsList.updateValue(
|
|
311
|
+
"formatOnAbort",
|
|
312
|
+
draft.formatOnAbort ? "on" : "off",
|
|
313
|
+
);
|
|
314
|
+
settingsList.updateValue(
|
|
315
|
+
"commandTimeoutMs",
|
|
316
|
+
String(draft.commandTimeoutMs),
|
|
317
|
+
);
|
|
318
|
+
settingsList.updateValue(
|
|
319
|
+
"hideCallSummariesInTui",
|
|
320
|
+
draft.hideCallSummariesInTui ? "on" : "off",
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const settingsList = new SettingsList(
|
|
325
|
+
getFormatterSettingItems(draft),
|
|
326
|
+
8,
|
|
327
|
+
getSettingsListTheme(),
|
|
328
|
+
(id, newValue) => {
|
|
329
|
+
const previous = cloneFormatterConfig(draft);
|
|
330
|
+
|
|
331
|
+
if (id === "formatMode") {
|
|
332
|
+
draft.formatMode = newValue as FormatterConfigSnapshot["formatMode"];
|
|
333
|
+
} else if (id === "formatOnAbort") {
|
|
334
|
+
draft.formatOnAbort = newValue === "on";
|
|
335
|
+
} else if (id === "commandTimeoutMs") {
|
|
336
|
+
draft.commandTimeoutMs = Number.parseInt(newValue, 10);
|
|
337
|
+
} else if (id === "hideCallSummariesInTui") {
|
|
338
|
+
draft.hideCallSummariesInTui = newValue === "on";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
writeFormatterConfigSnapshot(draft);
|
|
343
|
+
reloadFormatterConfig();
|
|
344
|
+
} catch (error) {
|
|
345
|
+
const message =
|
|
346
|
+
error instanceof Error ? error.message : String(error);
|
|
347
|
+
draft.commandTimeoutMs = previous.commandTimeoutMs;
|
|
348
|
+
draft.hideCallSummariesInTui = previous.hideCallSummariesInTui;
|
|
349
|
+
draft.formatMode = previous.formatMode;
|
|
350
|
+
draft.formatOnAbort = previous.formatOnAbort;
|
|
351
|
+
syncDraftToSettingsList(settingsList);
|
|
352
|
+
ctx.ui.notify(`Failed to save config: ${message}`, "error");
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
() => {
|
|
356
|
+
done(undefined);
|
|
357
|
+
},
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
container.addChild(settingsList);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
render(width: number) {
|
|
364
|
+
return container.render(width);
|
|
365
|
+
},
|
|
366
|
+
invalidate() {
|
|
367
|
+
container.invalidate();
|
|
368
|
+
},
|
|
369
|
+
handleInput(data: string) {
|
|
370
|
+
settingsList.handleInput?.(data);
|
|
371
|
+
tui.requestRender();
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
},
|
|
64
376
|
});
|
|
65
377
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-formatter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Pi extension that auto-formats files after write/edit tool calls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"extensions",
|
|
8
|
-
"DOCUMENTATION.md",
|
|
9
8
|
"README.md",
|
|
10
9
|
"LICENSE"
|
|
11
10
|
],
|