Skip to content

Commit df24fa0

Browse files
CopilotGordonSmith
authored andcommitted
feat: add pretty print support
Signed-off-by: Gordon Smith <[email protected]>
1 parent b9042ce commit df24fa0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1107
-604
lines changed

package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@
181181
"command": "wit-idl.extractCoreWasm",
182182
"title": "Extract Core Wasm",
183183
"category": "WIT"
184+
},
185+
{
186+
"command": "wit-idl.formatDocument",
187+
"title": "Format Document",
188+
"category": "WIT"
184189
}
185190
],
186191
"submenus": [
@@ -196,6 +201,11 @@
196201
"when": "resourceExtname == .wit",
197202
"group": "navigation"
198203
},
204+
{
205+
"command": "wit-idl.formatDocument",
206+
"when": "resourceExtname == .wit",
207+
"group": "1_modification@10"
208+
},
199209
{
200210
"submenu": "wit-idl.generateBindings.submenu",
201211
"when": "resourceExtname == .wit || witIdl.isWasmComponent",
@@ -289,6 +299,10 @@
289299
{
290300
"command": "wit-idl.extractCoreWasm",
291301
"when": "witIdl.isWasmComponent"
302+
},
303+
{
304+
"command": "wit-idl.formatDocument",
305+
"when": "editorLangId == wit"
292306
}
293307
]
294308
},
@@ -301,6 +315,11 @@
301315
{
302316
"command": "wit-idl.syntaxCheckWorkspace",
303317
"key": "shift+f7"
318+
},
319+
{
320+
"command": "wit-idl.formatDocument",
321+
"key": "shift+alt+f",
322+
"when": "editorTextFocus && editorLangId == wit"
304323
}
305324
],
306325
"customEditors": [

scripts/format-wit-tests.mjs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env node
2+
import fs from "fs";
3+
import path from "path";
4+
5+
function isOpeningBrace(line) {
6+
return line.endsWith("{") && !line.includes("}");
7+
}
8+
9+
function isTypeDecl(line) {
10+
return /^(record|variant|enum|flags|resource)\s+/.test(line);
11+
}
12+
function isFuncDecl(line) {
13+
if (line.startsWith("import ") || line.startsWith("export ")) return false;
14+
return /^[a-zA-Z][\w-]*\s*:\s*func\b/.test(line) || /:\s*func\b/.test(line) || /->/.test(line);
15+
}
16+
function isFieldDecl(line) {
17+
const t = line.trim();
18+
return /^[a-zA-Z][a-zA-Z0-9-]*\s*[:,(]/.test(t) || /^[a-zA-Z][a-zA-Z0-9-]*\s*,?\s*$/.test(t);
19+
}
20+
21+
function formatPackage(line) {
22+
return line.replace(/\s+/g, " ").replace(/\s*;\s*$/, ";");
23+
}
24+
function formatNamedBlock(line) {
25+
return line.replace(/\s+/g, " ").replace(/\s*{\s*$/, " {");
26+
}
27+
function formatFunc(line) {
28+
let f = line;
29+
f = f.replace(/:func/, ": func");
30+
f = f.replace(/:\s*func/, ": func");
31+
f = f.replace(/func\s*\(/, "func(");
32+
f = f.replace(/\)\s*->\s*/, ") -> ");
33+
f = f.replace(/\)->\s*/, ") -> ");
34+
f = f.replace(/\)->/, ") -> ");
35+
f = f.replace(/,\s*/g, ", ");
36+
f = f.replace(/:\s*/g, ": ");
37+
f = f.replace(/\s*;\s*$/, ";");
38+
return f;
39+
}
40+
function formatImportExport(line) {
41+
let f = line.replace(/^(import|export)\s+/, "$1 ");
42+
if (f.includes(": func") || f.includes(":func")) {
43+
f = formatFunc(f);
44+
} else {
45+
f = f.replace(/\s*;\s*$/, ";");
46+
}
47+
return f;
48+
}
49+
function formatUse(line) {
50+
let f = line;
51+
f = f.replace(/^use\s+/, "use ");
52+
f = f.replace(/\s+as\s+/, " as ");
53+
f = f.replace(/\s+from\s+/, " from ");
54+
f = f.replace(/\s*;\s*$/, ";");
55+
return f;
56+
}
57+
function formatTypeAlias(line) {
58+
let f = line;
59+
if (f.startsWith("type ")) f = f.replace(/^type\s+/, "type ");
60+
f = f.replace(/\s*=\s*/, " = ");
61+
f = f.replace(/\s*;\s*$/, ";");
62+
return f;
63+
}
64+
function formatField(line) {
65+
let f = line;
66+
f = f.replace(/:\s*/g, ": ");
67+
f = f.replace(/,\s*/g, ", ");
68+
f = f.replace(/,\s*$/, ",");
69+
return f;
70+
}
71+
72+
function formatWitContent(content, { tabSize = 2, insertSpaces = true } = {}) {
73+
const lines = content.split(/\r?\n/);
74+
const out = [];
75+
let indentLevel = 0;
76+
const indentUnit = insertSpaces ? " ".repeat(tabSize) : "\t";
77+
for (let i = 0; i < lines.length; i++) {
78+
const raw = lines[i];
79+
const trimmed = raw.trim();
80+
if (trimmed === "") {
81+
out.push("");
82+
continue;
83+
}
84+
if (/^\/\//.test(trimmed)) {
85+
out.push(indentUnit.repeat(indentLevel) + trimmed);
86+
continue;
87+
}
88+
if (/^\}/.test(trimmed)) indentLevel = Math.max(0, indentLevel - 1);
89+
let formatted = trimmed;
90+
if (trimmed.startsWith("package ")) formatted = formatPackage(trimmed);
91+
else if (trimmed.startsWith("interface ")) formatted = formatNamedBlock(trimmed);
92+
else if (trimmed.startsWith("world ")) formatted = formatNamedBlock(trimmed);
93+
else if (isTypeDecl(trimmed)) formatted = formatNamedBlock(trimmed);
94+
else if (trimmed.startsWith("type ") && trimmed.includes("=")) formatted = formatTypeAlias(trimmed);
95+
else if (isFuncDecl(trimmed)) formatted = formatFunc(trimmed);
96+
else if (trimmed.startsWith("import ") || trimmed.startsWith("export "))
97+
formatted = formatImportExport(trimmed);
98+
else if (trimmed.startsWith("use ")) formatted = formatUse(trimmed);
99+
else if (isFieldDecl(trimmed)) formatted = formatField(trimmed);
100+
out.push(indentUnit.repeat(indentLevel) + formatted);
101+
if (isOpeningBrace(trimmed)) indentLevel++;
102+
}
103+
return out.join("\n");
104+
}
105+
106+
function collect(dir) {
107+
const acc = [];
108+
const stack = [dir];
109+
while (stack.length) {
110+
const cur = stack.pop();
111+
const ents = fs.readdirSync(cur, { withFileTypes: true });
112+
for (const e of ents) {
113+
const p = path.join(cur, e.name);
114+
if (e.isDirectory()) stack.push(p);
115+
else if (e.isFile() && p.endsWith(".wit")) acc.push(p);
116+
}
117+
}
118+
return acc.sort();
119+
}
120+
121+
const root = path.resolve("tests");
122+
if (!fs.existsSync(root)) {
123+
console.error("tests directory not found");
124+
process.exit(1);
125+
}
126+
const files = collect(root);
127+
let changed = 0;
128+
for (const f of files) {
129+
const original = fs.readFileSync(f, "utf8");
130+
const formatted = formatWitContent(original, { tabSize: 2, insertSpaces: true });
131+
if (formatted !== original) {
132+
fs.writeFileSync(f, formatted + (formatted.endsWith("\n") ? "" : "\n"));
133+
changed++;
134+
console.log("Formatted:", path.relative(process.cwd(), f));
135+
}
136+
}
137+
console.log(`Done. ${changed} file(s) updated.`);

src/extension.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from "vscode";
22
import * as path from "node:path";
33
import * as fs from "node:fs";
44
import { WitSyntaxValidator } from "./validator.js";
5+
import { WitFormatter } from "./formatter.js";
56
import { isWasmComponentFile } from "./wasmDetection.js";
67
import {
78
getWitBindgenVersionFromWasm,
@@ -319,6 +320,34 @@ export function activate(context: vscode.ExtensionContext) {
319320
}
320321
});
321322

323+
// Register WIT formatter
324+
const witFormatter = new WitFormatter();
325+
const formattingProvider = vscode.languages.registerDocumentFormattingEditProvider("wit", witFormatter);
326+
327+
// Register format document command
328+
const formatDocumentCommand = vscode.commands.registerCommand("wit-idl.formatDocument", async () => {
329+
const activeEditor = vscode.window.activeTextEditor;
330+
331+
if (!activeEditor) {
332+
vscode.window.showWarningMessage("No active editor found");
333+
return;
334+
}
335+
336+
if (activeEditor.document.languageId !== "wit") {
337+
vscode.window.showWarningMessage("Active file is not a WIT file");
338+
return;
339+
}
340+
341+
try {
342+
await vscode.commands.executeCommand("editor.action.formatDocument");
343+
} catch (error) {
344+
console.error("Failed to format document:", error);
345+
vscode.window.showErrorMessage(
346+
`Failed to format document: ${error instanceof Error ? error.message : String(error)}`
347+
);
348+
}
349+
});
350+
322351
// Implement extractWit command
323352
const extractWitCommand = vscode.commands.registerCommand("wit-idl.extractWit", async (resource?: vscode.Uri) => {
324353
try {
@@ -725,9 +754,11 @@ export function activate(context: vscode.ExtensionContext) {
725754
providerDisposable,
726755
witExtractProvider,
727756
provider,
757+
formattingProvider,
728758
syntaxCheckCommand,
729759
syntaxCheckWorkspaceCommand,
730760
showVersionCommand,
761+
formatDocumentCommand,
731762
extractWitCommand,
732763
extractCoreWasmCommand,
733764
generateRustBindingsCommand,

0 commit comments

Comments
 (0)