From f575bdf4ba42b3d3cf159285348051b477b8ec78 Mon Sep 17 00:00:00 2001 From: Alex McKinney Date: Tue, 29 Mar 2022 17:32:25 -0700 Subject: [PATCH 1/4] Add 'buf format' --- src/extension.ts | 2 ++ src/formatter.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/formatter.ts diff --git a/src/extension.ts b/src/extension.ts index e5dcf6b..39576dd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { downloadPage, lint, minimumVersion, version } from "./buf"; import { isError } from "./errors"; +import { Formatter } from "./formatter"; import { parseLines, Warning } from "./parser"; import { format, less } from "./version"; @@ -124,6 +125,7 @@ export function activate(context: vscode.ExtensionContext) { diagnosticCollection.set(document.uri, diagnostics); }; + context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider('proto', new Formatter(binaryPath))); context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(doLint)); context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(doLint)); context.subscriptions.push( diff --git a/src/formatter.ts b/src/formatter.ts new file mode 100644 index 0000000..552bd0f --- /dev/null +++ b/src/formatter.ts @@ -0,0 +1,67 @@ +import * as cp from "child_process"; +import * as path from "path"; +import * as vscode from "vscode"; + +export class Formatter implements vscode.DocumentFormattingEditProvider { + readonly binaryPath: string = ''; + + constructor(binaryPath: string) { + if (!binaryPath || binaryPath.length === 0) { + throw new Error('binaryPath is required to construct a formatter'); + } + this.binaryPath = binaryPath; + } + + public provideDocumentFormattingEdits( + document: vscode.TextDocument, + // @ts-ignore: The formatter doesn't have any options, but we need to implement the interface. + options: vscode.FormattingOptions, + token: vscode.CancellationToken + ): vscode.ProviderResult { + if (vscode.window.visibleTextEditors.every((e) => e.document.fileName !== document.fileName)) { + return []; + } + return this.runFormatter(document, token).then( + (edits) => edits, + (err) => { + console.log(err); + return Promise.reject('Check the console in dev tools to find errors when formatting.'); + } + ); + } + + private runFormatter( + document: vscode.TextDocument, + // @ts-ignore: TODO: Use the CancellationToken. + token: vscode.CancellationToken + ): Thenable { + return new Promise((resolve, reject) => { + const cwd = path.dirname(document.fileName); + let stdout = ''; + let stderr = ''; + + // Use spawn instead of exec to avoid maxBufferExceeded error + const p = cp.spawn(this.binaryPath, [document.fileName], { cwd }); + // TODO: We need to use the CancellationToken. + p.stdout.setEncoding('utf8'); + p.stdout.on('data', (data: any) => (stdout += data)); + p.stderr.on('data', (data: any) => (stderr += data)); + p.on('error', (err: any) => { + if (err && (err).code === 'ENOENT') { + return reject(); + } + }); + p.on('close', (code: number) => { + if (code !== 0) { + return reject(stderr); + } + const fileStart = new vscode.Position(0, 0); + const fileEnd = document.lineAt(document.lineCount - 1).range.end; + const textEdits: vscode.TextEdit[] = [ + new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) + ]; + return resolve(textEdits); + }); + }); + } +} From ad29c6694540d603ab4f8776429fa666ccb648a7 Mon Sep 17 00:00:00 2001 From: Rubens Farias Date: Tue, 5 Apr 2022 13:45:03 -0400 Subject: [PATCH 2/4] Fix formatting Since buf doesn't yet support reading from stdin for formatting, this require some hacky work around of temporary files. --- package-lock.json | 6 ++--- package.json | 19 +++++++++++++- src/formatter.ts | 63 ++++++++++++++++++++++++++++------------------- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2eded2e..b93cb32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-buf", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-buf", - "version": "0.4.0", + "version": "0.5.0", "license": "Apache-2.0", "devDependencies": { "@types/glob": "^7.1.3", @@ -23,7 +23,7 @@ "vscode-test": "^1.4.0" }, "engines": { - "vscode": "^1.51.0" + "vscode": "^1.63.0" } }, "node_modules/@babel/code-frame": { diff --git a/package.json b/package.json index f5586f2..098586a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-buf", "displayName": "Buf", "description": "Visual Studio Code support for Buf", - "version": "0.4.0", + "version": "0.5.0", "icon": "logo.png", "publisher": "bufbuild", "repository": { @@ -23,6 +23,7 @@ "vscode": "^1.63.0" }, "categories": [ + "Formatters", "Linters" ], "keywords": [ @@ -31,6 +32,7 @@ "protobuf", "protocol buffers", "buf", + "bufbuild", "lint" ], "activationEvents": [ @@ -61,6 +63,11 @@ } } }, + "configurationDefaults": { + "[proto]": { + "editor.formatOnSave": true + } + }, "languages": [ { "id": "yaml", @@ -70,6 +77,16 @@ "buf.work", "buf.gen" ] + }, + { + "id": "proto", + "extensions": [ + ".proto" + ], + "aliases": [ + "Protocol Buffers", + "Protobufs" + ] } ] }, diff --git a/src/formatter.ts b/src/formatter.ts index 552bd0f..9e61072 100644 --- a/src/formatter.ts +++ b/src/formatter.ts @@ -1,6 +1,8 @@ import * as cp from "child_process"; +import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; +import { TextEncoder } from "util"; export class Formatter implements vscode.DocumentFormattingEditProvider { readonly binaryPath: string = ''; @@ -30,38 +32,47 @@ export class Formatter implements vscode.DocumentFormattingEditProvider { ); } + private randomId(): string { + return (Math.floor(Math.random() * 100000)).toString() + } + private runFormatter( document: vscode.TextDocument, - // @ts-ignore: TODO: Use the CancellationToken. token: vscode.CancellationToken ): Thenable { return new Promise((resolve, reject) => { - const cwd = path.dirname(document.fileName); - let stdout = ''; - let stderr = ''; + const cwd = path.join(os.tmpdir(), this.randomId()) + vscode.workspace.fs.createDirectory(vscode.Uri.file(cwd)).then(() => { + // Buf format expects a `.proto` file, so add unique id as a prefix. + const backupFile = path.join(cwd, this.randomId() + "-" + path.basename(document.fileName)) + vscode.workspace.fs.writeFile(vscode.Uri.file(backupFile), new TextEncoder().encode(document.getText())).then(() => { + let stdout = ''; + let stderr = ''; - // Use spawn instead of exec to avoid maxBufferExceeded error - const p = cp.spawn(this.binaryPath, [document.fileName], { cwd }); - // TODO: We need to use the CancellationToken. - p.stdout.setEncoding('utf8'); - p.stdout.on('data', (data: any) => (stdout += data)); - p.stderr.on('data', (data: any) => (stderr += data)); - p.on('error', (err: any) => { - if (err && (err).code === 'ENOENT') { - return reject(); - } - }); - p.on('close', (code: number) => { - if (code !== 0) { - return reject(stderr); - } - const fileStart = new vscode.Position(0, 0); - const fileEnd = document.lineAt(document.lineCount - 1).range.end; - const textEdits: vscode.TextEdit[] = [ - new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) - ]; - return resolve(textEdits); + const p = cp.spawn(this.binaryPath, ['format', backupFile], { cwd }); + token.onCancellationRequested(() => !p.killed && p.kill()); + + p.stdout.setEncoding('utf8'); + p.stdout.on('data', (data: any) => (stdout += data)); + p.stderr.on('data', (data: any) => (stderr += data)); + p.on('error', (err: any) => { + if (err && (err).code === 'ENOENT') { + return reject(); + } + }); + p.on('close', (code: number) => { + if (code !== 0) { + return reject(stderr); + } + const fileStart = new vscode.Position(0, 0); + const fileEnd = document.lineAt(document.lineCount - 1).range.end; + const textEdits: vscode.TextEdit[] = [ + new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) + ]; + return resolve(textEdits); + }); + }) }); - }); + }) } } From c0cba34134a789c0bc10f8d56b71fb9ce4ba0d17 Mon Sep 17 00:00:00 2001 From: Rubens Farias Date: Tue, 5 Apr 2022 13:47:39 -0400 Subject: [PATCH 3/4] add semicolons --- src/formatter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/formatter.ts b/src/formatter.ts index 9e61072..74b172a 100644 --- a/src/formatter.ts +++ b/src/formatter.ts @@ -41,10 +41,10 @@ export class Formatter implements vscode.DocumentFormattingEditProvider { token: vscode.CancellationToken ): Thenable { return new Promise((resolve, reject) => { - const cwd = path.join(os.tmpdir(), this.randomId()) + const cwd = path.join(os.tmpdir(), this.randomId()); vscode.workspace.fs.createDirectory(vscode.Uri.file(cwd)).then(() => { // Buf format expects a `.proto` file, so add unique id as a prefix. - const backupFile = path.join(cwd, this.randomId() + "-" + path.basename(document.fileName)) + const backupFile = path.join(cwd, this.randomId() + "-" + path.basename(document.fileName)); vscode.workspace.fs.writeFile(vscode.Uri.file(backupFile), new TextEncoder().encode(document.getText())).then(() => { let stdout = ''; let stderr = ''; @@ -71,8 +71,8 @@ export class Formatter implements vscode.DocumentFormattingEditProvider { ]; return resolve(textEdits); }); - }) + }); }); - }) + }); } } From 5082d2cadba543a82046450142d81363179e8868 Mon Sep 17 00:00:00 2001 From: Rubens Farias Date: Tue, 5 Apr 2022 14:40:48 -0400 Subject: [PATCH 4/4] comments --- package.json | 5 +++-- src/formatter.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 098586a..40e9e3f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "protocol buffers", "buf", "bufbuild", - "lint" + "lint", + "format" ], "activationEvents": [ "workspaceContains:**/*.proto", @@ -85,7 +86,7 @@ ], "aliases": [ "Protocol Buffers", - "Protobufs" + "Protobuf" ] } ] diff --git a/src/formatter.ts b/src/formatter.ts index 74b172a..aac1ae2 100644 --- a/src/formatter.ts +++ b/src/formatter.ts @@ -33,7 +33,7 @@ export class Formatter implements vscode.DocumentFormattingEditProvider { } private randomId(): string { - return (Math.floor(Math.random() * 100000)).toString() + return (Math.floor(Math.random() * 100000000)).toString(); } private runFormatter( @@ -41,7 +41,7 @@ export class Formatter implements vscode.DocumentFormattingEditProvider { token: vscode.CancellationToken ): Thenable { return new Promise((resolve, reject) => { - const cwd = path.join(os.tmpdir(), this.randomId()); + const cwd = path.join(os.tmpdir(), "vscode-buf-" + this.randomId()); vscode.workspace.fs.createDirectory(vscode.Uri.file(cwd)).then(() => { // Buf format expects a `.proto` file, so add unique id as a prefix. const backupFile = path.join(cwd, this.randomId() + "-" + path.basename(document.fileName));