Skip to content

Commit

Permalink
Added external formatter registration
Browse files Browse the repository at this point in the history
  • Loading branch information
dpar39 committed Nov 27, 2024
1 parent a1f2bdd commit 9f13cdf
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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": "<document file path>", "snippet": "<content to be formatted>", "language": "<language id of the current file>"}` 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
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -30,7 +30,8 @@
"url": "https://github.com/dpar39/vscode-rest-control"
},
"categories": [
"Other"
"Other",
"Formatters"
],
"activationEvents": [
"*"
Expand Down
63 changes: 63 additions & 0 deletions src/services/formatter.ts
Original file line number Diff line number Diff line change
@@ -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<vscode.TextEdit[]> {
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",

Check warning on line 32 in src/services/formatter.ts

View workflow job for this annotation

GitHub Actions / Build, test and release

Object Literal Property name `Content-Type` must match one of the following formats: camelCase
"Content-Length": Buffer.byteLength(payload),

Check warning on line 33 in src/services/formatter.ts

View workflow job for this annotation

GitHub Actions / Build, test and release

Object Literal Property name `Content-Length` must match one of the following formats: camelCase
},
};
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();
});
},
});
}
21 changes: 17 additions & 4 deletions src/services/requestProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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__")) {
Expand Down Expand Up @@ -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,
Expand All @@ -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]);
Expand All @@ -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);
}

0 comments on commit 9f13cdf

Please sign in to comment.