Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.0.14 - Added external formatter registration #11

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}