feat: add LSP server, VSCode + DataGrip extensions, release infra, autofix
CI / Test (push) Failing after 47s
CI / Build snapshot (push) Has been skipped

- pkg/lsp: JSON-RPC 2.0 LSP server (formatting, diagnostics, codeAction quick-fixes)
- cmd/pgtidy: lsp and config subcommands
- pkg/diagnostics: TextFix struct for byte-range autofixes
- pkg/lint: MIG001/MIG003 autofixes, ApplyFixes helper, --fix flag on lint command
- editors/vscode: TypeScript extension with LanguageClient, showVersion/showConfig/formatDocument commands, logo
- editors/datagrip: Gradle JetBrains plugin via LSP4IJ, pluginIcon
- .goreleaser.yaml, .github/workflows: CI + release pipeline
- Makefile: snapshot, release, vscode-compile, vscode-package targets
- go.mod + all imports: module path updated to git.warky.dev/wdevs/pgtidy
- assets: logo files (256px, 128px, 1024px, ico)
This commit is contained in:
2026-06-28 12:48:28 +02:00
parent 7fb76bae3d
commit e88d32f281
49 changed files with 3228 additions and 49 deletions
+4
View File
@@ -0,0 +1,4 @@
out/
node_modules/
*.vsix
assets/
+10
View File
@@ -0,0 +1,10 @@
.vscode/**
src/**
node_modules/**
.gitignore
.gitattributes
tsconfig.json
pnpm-lock.yaml
pnpm-workspace.yaml
**/*.ts
**/*.map
+1
View File
@@ -0,0 +1 @@
../../LICENSE
+75
View File
@@ -0,0 +1,75 @@
# PgTidy — VSCode Extension
Formats and lints PostgreSQL/PL-pgSQL files using the `pgtidy` language server.
## Requirements
- `pgtidy` binary on `PATH` (see [Build the binary](#build-the-binary) below)
- Go 1.21+ (to build from source)
- VSCode 1.85+
## Build the binary
**Option A — install directly with Go (recommended):**
```sh
go install git.warky.dev/wdevs/pgtidy/cmd/pgtidy@latest
```
The binary lands in `$(go env GOPATH)/bin/`. Make sure that directory is on your `PATH`.
**Option B — build from source and place manually:**
```sh
git clone https://git.warky.dev/wdevs/PgTidy
cd PgTidy
go build -o pgtidy ./cmd/pgtidy
# Linux / macOS — move to a directory on PATH
mv pgtidy /usr/local/bin/
# Windows — move to a directory on PATH, e.g.
# move pgtidy.exe C:\tools\
```
Verify the binary is reachable:
```sh
pgtidy version
```
If you place it somewhere not on `PATH`, set the full path in VSCode settings:
```json
"pgtidy.path": "/path/to/pgtidy"
```
## Install from `.vsix`
1. Build the package: `pnpm run build && pnpm run package`
2. In VSCode: **Extensions**`···` menu → **Install from VSIX…** → select `pgtidy-*.vsix`
Or via CLI:
```sh
code --install-extension pgtidy-0.1.0.vsix
```
## Settings
| Setting | Default | Description |
|---|---|---|
| `pgtidy.path` | `"pgtidy"` | Path to the binary if not on `PATH` |
| `pgtidy.enable` | `true` | Enable/disable the language server |
## Features
- **Format on save** — configure via VSCode's `editor.formatOnSave`
- **Diagnostics** — lint findings shown inline as you type (MIG001MIG003, COR001003, NAM001003)
- **Quick fixes** — lightbulb actions for MIG001 (add `CONCURRENTLY`) and MIG003 (add `NOT VALID`)
`.sql` and `.pgsql` files are both handled.
## Troubleshooting
**No formatting / diagnostics:** Open the Output panel → select **PgTidy** to see server errors. Most common cause: `pgtidy` not found — set `pgtidy.path` to the full binary path.
+88
View File
@@ -0,0 +1,88 @@
{
"name": "pgtidy",
"displayName": "PgTidy",
"description": "PostgreSQL formatter and linter powered by pgtidy",
"version": "0.1.0",
"publisher": "warky-devs",
"author": {
"name": "Hein Puth",
"email": "hein.puth@gmail.com"
},
"icon": "assets/logo_256.png",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://git.warky.dev/wdevs/PgTidy"
},
"engines": {
"vscode": "^1.85.0"
},
"categories": [
"Formatters",
"Linters"
],
"activationEvents": [
"onCommand:pgtidy.showVersion",
"onCommand:pgtidy.showConfig",
"onCommand:pgtidy.formatDocument"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "pgtidy.showVersion",
"title": "Show Version",
"category": "PgTidy"
},
{
"command": "pgtidy.showConfig",
"title": "Show Config",
"category": "PgTidy"
},
{
"command": "pgtidy.formatDocument",
"title": "Format Document",
"category": "PgTidy"
}
],
"languages": [
{
"id": "sql",
"extensions": [
".pgsql"
]
}
],
"configuration": {
"title": "PgTidy",
"properties": {
"pgtidy.path": {
"type": "string",
"default": "pgtidy",
"description": "Path to the pgtidy binary. Defaults to 'pgtidy' (must be on PATH)."
},
"pgtidy.enable": {
"type": "boolean",
"default": true,
"description": "Enable the PgTidy language server."
}
}
}
},
"scripts": {
"build": "tsc -p ./",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"package": "vsce package --no-dependencies",
"lint": "eslint src --ext ts"
},
"dependencies": {
"vscode-languageclient": "^9.0.1"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.85.0",
"@vscode/vsce": "^2.24.0",
"typescript": "^5.3.0"
}
}
+1589
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
allowBuilds:
'@vscode/vsce-sign': true
keytar: true
+105
View File
@@ -0,0 +1,105 @@
import * as cp from 'child_process';
import * as path from 'path';
import * as vscode from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from 'vscode-languageclient/node';
let client: LanguageClient | undefined;
let outputChannel: vscode.OutputChannel;
export function activate(context: vscode.ExtensionContext): void {
outputChannel = vscode.window.createOutputChannel('PgTidy');
context.subscriptions.push(outputChannel);
// Commands are always registered so they work even when the LSP is disabled.
context.subscriptions.push(
vscode.commands.registerCommand('pgtidy.showVersion', cmdShowVersion),
vscode.commands.registerCommand('pgtidy.showConfig', cmdShowConfig),
vscode.commands.registerCommand('pgtidy.formatDocument', cmdFormatDocument),
);
const cfg = vscode.workspace.getConfiguration('pgtidy');
if (!cfg.get<boolean>('enable', true)) {
return;
}
const bin = cfg.get<string>('path', 'pgtidy');
const serverOptions: ServerOptions = {
command: bin,
args: ['lsp'],
transport: TransportKind.stdio,
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'sql' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{sql,pgsql}'),
},
};
client = new LanguageClient('pgtidy', 'PgTidy', serverOptions, clientOptions);
context.subscriptions.push(client);
client.start();
}
export function deactivate(): Thenable<void> | undefined {
return client?.stop();
}
function binary(): string {
return vscode.workspace.getConfiguration('pgtidy').get<string>('path', 'pgtidy');
}
function contextDir(): string | undefined {
const folders = vscode.workspace.workspaceFolders;
if (folders && folders.length > 0) {
return folders[0].uri.fsPath;
}
const doc = vscode.window.activeTextEditor?.document;
if (doc && !doc.isUntitled) {
return path.dirname(doc.uri.fsPath);
}
return undefined;
}
function cmdShowVersion(): void {
cp.execFile(binary(), ['version'], (err, stdout, stderr) => {
if (err) {
vscode.window.showErrorMessage(`PgTidy: ${stderr.trim() || err.message}`);
return;
}
vscode.window.showInformationMessage(stdout.trim());
});
}
function cmdShowConfig(): void {
const cwd = contextDir();
cp.execFile(binary(), ['config'], { cwd }, (err, stdout, stderr) => {
if (err) {
vscode.window.showErrorMessage(`PgTidy: ${stderr.trim() || err.message}`);
return;
}
outputChannel.clear();
outputChannel.appendLine('PgTidy — effective configuration');
if (cwd) {
outputChannel.appendLine(`(resolved from: ${cwd})`);
}
outputChannel.appendLine('');
outputChannel.append(stdout);
outputChannel.show(true);
});
}
async function cmdFormatDocument(): Promise<void> {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('PgTidy: no active editor');
return;
}
await vscode.commands.executeCommand('editor.action.formatDocument');
}
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": ["ES2020"],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"skipLibCheck": true
},
"exclude": ["node_modules", ".vscode-test"]
}