From 7bdd38a9a3451d0cc22ef8cb0a8512469c9edecf Mon Sep 17 00:00:00 2001 From: Darien Pardinas Diaz Date: Wed, 27 Nov 2024 13:51:48 -0500 Subject: [PATCH] Added external formatter registration --- CHANGELOG.md | 5 +++ README.md | 4 ++ package.json | 5 ++- src/services/formatter.ts | 63 ++++++++++++++++++++++++++++++++ src/services/requestProcessor.ts | 21 +++++++++-- 5 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/services/formatter.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da80a2..bc427fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## [0.0.14] + +- Added `custom.registerExternalFormatter` to support external formatters that send code snippets to a HTTP endpoint. +- Added more flexibility to `custom.goToFileLineCharacter` to handle file paths that include row and column number separated with colon `:` (e.g. `readme.txt:42:7`) + ## [0.0.13] - Bug Fix: VSCode folder to port hashing algorithm was not working correctly. diff --git a/README.md b/README.md index f3098e2..c64859b 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ curl http://localhost:37100 -d '{"command":"custom.runInTerminal", "args": ["pwd # Kill all terminals curl http://localhost:37100 -d '{"command":"workbench.action.terminal.killAll"}' # or curl http://localhost:37100/?command=workbench.action.terminal.killAll + +# Register an external formatter endpoint available at http://localhost:12345 that accepts POST requests and formats C++ and Python code +curl http://localhost:37100 -d '{"command":"custom.registerExternalFormatter", "args":["http://localhost:12345", ["cpp", "python"], "POST"]}' ``` All requests are expected to be in a JSON HTTP request body in the form: @@ -110,6 +113,7 @@ As the extension progresses, I plan to add more _special_ commands (i.e. command - `custom.showInformationMessage`, `custom.showWarningMessage` and `custom.showErrorMessage`: show message dialogs to the user and let them click on a button - `custom.listInstalledExtensions`: get the list of installed extension IDs - `custom.getExtensionInfo`: get details of an installed extension by passing the extension ID +- `custom.registerExternalFormatter`: registers an external formatter via a HTTP endpoint. The HTTP endpoint will receive a JSON body with the following properties`{"file": "", "snippet": "", "language": ""}` and it should return in the body the formatted code snippet (or the original if the code can't be formatted). ## To implement in the near future: - Add the ability to set a breakpoint at the specified file/line combination diff --git a/package.json b/package.json index 8204f29..1a8b509 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "This extension allows you to remotely control Visual Studio Code via a REST endpoint, taking automation to the next level.", "publisher": "dpar39", "license": "MIT", - "version": "0.0.13", + "version": "0.0.14", "engines": { "vscode": "^1.55.0" }, @@ -30,7 +30,8 @@ "url": "https://github.com/dpar39/vscode-rest-control" }, "categories": [ - "Other" + "Other", + "Formatters" ], "activationEvents": [ "*" diff --git a/src/services/formatter.ts b/src/services/formatter.ts new file mode 100644 index 0000000..b481c14 --- /dev/null +++ b/src/services/formatter.ts @@ -0,0 +1,63 @@ +import * as vscode from "vscode"; +import * as http from "http"; +import * as https from "https"; + +let formatterRegistration: vscode.Disposable; +export async function registerExternalFormatter( + formatterEndpoint: string, + languages: string[], + httpMethod: string +) { + languages = languages || (await vscode.languages.getLanguages()); + httpMethod = httpMethod || "POST"; + if (formatterRegistration) { + formatterRegistration.dispose(); + } + const url = new URL(formatterEndpoint); + formatterRegistration = vscode.languages.registerDocumentFormattingEditProvider(languages, { + provideDocumentFormattingEdits( + doc: vscode.TextDocument + ): vscode.ProviderResult { + const payload = JSON.stringify({ + file: doc.fileName, + language: doc.languageId, + snippet: doc.getText(), + }); + const options = { + hostname: url.hostname, + port: url.port, + path: url.pathname, + method: httpMethod, + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(payload), + }, + }; + const range = new vscode.Range( + doc.lineAt(0).range.start, + doc.lineAt(doc.lineCount - 1).range.end + ); + return new Promise((accept, reject) => { + const httpModule = url.protocol.startsWith("https") ? https : http; + const req = httpModule.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + accept([vscode.TextEdit.replace(range, data)]); + }); + res.on("error", (err) => { + accept([]); // no edits + vscode.window.showErrorMessage( + `Failed to format document with custom formatter: ${err}`, + "OK" + ); + }); + }); + req.write(payload); + req.end(); + }); + }, + }); +} diff --git a/src/services/requestProcessor.ts b/src/services/requestProcessor.ts index 34fd70e..9af1b03 100644 --- a/src/services/requestProcessor.ts +++ b/src/services/requestProcessor.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import * as path from "path"; import { quickPick } from "./quickPick"; +import { registerExternalFormatter } from "./formatter"; function createObject(arg: any): any { if (typeof arg === "object" && arg.hasOwnProperty("__type__")) { @@ -83,7 +84,7 @@ export async function processRemoteControlRequest(command: string, args: any[]): } if (command === "custom.workspaceFolders") { - return vscode.workspace.workspaceFolders?.map(ws => { + return vscode.workspace.workspaceFolders?.map((ws) => { return { name: ws.name, index: ws.index, @@ -110,10 +111,18 @@ export async function processRemoteControlRequest(command: string, args: any[]): if (command === "custom.showInputBox") { return await vscode.window.showInputBox(args[0]); - } + } if (command === "custom.goToFileLineCharacter") { - const filePath = args[0]; + let filePath = args[0]; + let row = args[1]; + let col = args[2]; + const match = /([^:]+)(:\d+)?(:\d+)?/.exec(filePath); + if (match) { + filePath = match[1]; + row = row || (match[2] ? parseInt(match[2].substring(1)) - 1 : 0); + col = col || (match[3] ? parseInt(match[3].substring(1)) - 1 : 0); + } let uri = null; if (!path.isAbsolute(filePath)) { let candidates = await vscode.workspace.findFiles(args[0]); @@ -126,13 +135,17 @@ export async function processRemoteControlRequest(command: string, args: any[]): if (uri === null) { throw new Error(`Unable to locate file: ${filePath}`); } - const position = new vscode.Position(args[1] || 0, args[2] || 0); + const position = new vscode.Position(row, col); const location = new vscode.Location(uri, position); return await vscode.commands.executeCommand("editor.action.goToLocations", uri, position, [ location, ]); } + if (command === "custom.registerExternalFormatter") { + return await registerExternalFormatter(args[0], args[1], args[2]); + } + // try to run an arbitrary command with the arguments provided as is return await vscode.commands.executeCommand(command, ...args); }