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

Extract code into separate rpc server #27

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
162 changes: 64 additions & 98 deletions src/commandRunner.ts
Original file line number Diff line number Diff line change
@@ -1,117 +1,83 @@
import { Minimatch } from "minimatch";
import * as vscode from "vscode";

import { getCommunicationDirPath } from "./paths";
import { any } from "./regex";
import { Request } from "./types";
import { Io } from "./io";
import { RpcServer } from "./rpcServer";
import type { Payload } from "./types";

export default class CommandRunner {
private allowRegex!: RegExp;
private denyRegex!: RegExp | null;
private backgroundWindowProtection!: boolean;

constructor(private io: Io) {
this.reloadConfiguration = this.reloadConfiguration.bind(this);
this.runCommand = this.runCommand.bind(this);

this.reloadConfiguration();
vscode.workspace.onDidChangeConfiguration(this.reloadConfiguration);
}

reloadConfiguration() {
const allowList = vscode.workspace
.getConfiguration("command-server")
.get<string[]>("allowList")!;

this.allowRegex = any(
...allowList.map((glob) => new Minimatch(glob).makeRe())
);

const denyList = vscode.workspace
.getConfiguration("command-server")
.get<string[]>("denyList")!;
private allowRegex!: RegExp;
private denyRegex!: RegExp | null;
private backgroundWindowProtection!: boolean;
private rpc: RpcServer<Payload>;

constructor() {
this.reloadConfiguration = this.reloadConfiguration.bind(this);
this.runCommand = this.runCommand.bind(this);
this.executeRequest = this.executeRequest.bind(this);

this.rpc = new RpcServer<Payload>(
getCommunicationDirPath(),
this.executeRequest
);

this.reloadConfiguration();
vscode.workspace.onDidChangeConfiguration(this.reloadConfiguration);
}

this.denyRegex =
denyList.length === 0
? null
: any(...denyList.map((glob) => new Minimatch(glob).makeRe()));
reloadConfiguration() {
const allowList = vscode.workspace
.getConfiguration("command-server")
.get<string[]>("allowList")!;

this.backgroundWindowProtection = vscode.workspace
.getConfiguration("command-server")
.get<boolean>("backgroundWindowProtection")!;
}
this.allowRegex = any(
...allowList.map((glob) => new Minimatch(glob).makeRe())
);

/**
* Reads a command from the request file and executes it. Writes information
* about command execution to the result of the command to the response file,
* If requested, will wait for command to finish, and can also write command
* output to response file. See also documentation for Request / Response
* types.
*/
async runCommand() {
await this.io.prepareResponse();
const denyList = vscode.workspace
.getConfiguration("command-server")
.get<string[]>("denyList")!;

let request: Request;
this.denyRegex =
denyList.length === 0
? null
: any(...denyList.map((glob) => new Minimatch(glob).makeRe()));

try {
request = await this.io.readRequest();
} catch (err) {
await this.io.closeResponse();
throw err;
this.backgroundWindowProtection = vscode.workspace
.getConfiguration("command-server")
.get<boolean>("backgroundWindowProtection")!;
}

const { commandId, args, uuid, returnCommandOutput, waitForFinish } =
request;

const warnings = [];

let commandPromise: Thenable<unknown> | undefined;
/**
* Reads a command from the request file and executes it. Writes information
* about command execution to the result of the command to the response file,
* If requested, will wait for command to finish, and can also write command
* output to response file. See also documentation for Request / Response
* types.
*/
runCommand(): Promise<void> {
return this.rpc.executeRequest();
}

try {
if (!vscode.window.state.focused) {
if (this.backgroundWindowProtection) {
throw new Error("This editor is not active");
} else {
warnings.push("This editor is not active");
private async executeRequest({ commandId, args }: Payload) {
if (!vscode.window.state.focused) {
if (this.backgroundWindowProtection) {
throw new Error("This editor is not active");
} else {
// TODO: How should we handle this?
// warnings.push("This editor is not active");
console.warn("This editor is not active");
}
}
}

if (!commandId.match(this.allowRegex)) {
throw new Error("Command not in allowList");
}

if (this.denyRegex != null && commandId.match(this.denyRegex)) {
throw new Error("Command in denyList");
}

commandPromise = vscode.commands.executeCommand(commandId, ...args);

let commandReturnValue = null;

if (returnCommandOutput) {
commandReturnValue = await commandPromise;
} else if (waitForFinish) {
await commandPromise;
}

await this.io.writeResponse({
error: null,
uuid,
returnValue: commandReturnValue,
warnings,
});
} catch (err) {
await this.io.writeResponse({
error: (err as Error).message,
uuid,
warnings,
});
}
if (!commandId.match(this.allowRegex)) {
throw new Error("Command not in allowList");
}

await this.io.closeResponse();
if (this.denyRegex != null && commandId.match(this.denyRegex)) {
throw new Error("Command in denyList");
}

if (commandPromise != null) {
await commandPromise;
return vscode.commands.executeCommand(commandId, ...args);
}
}
}
7 changes: 0 additions & 7 deletions src/constants.ts

This file was deleted.

75 changes: 37 additions & 38 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,48 @@
import * as vscode from "vscode";

import { NativeIo } from "./nativeIo";
import CommandRunner from "./commandRunner";
import { getCommunicationDirPath } from "./paths";
import { initializeCommunicationDir } from "./rpcServer";
import { getInboundSignal } from "./signal";
import { FocusedElementType } from "./types";

export async function activate(context: vscode.ExtensionContext) {
const io = new NativeIo();
await io.initialize();

const commandRunner = new CommandRunner(io);
let focusedElementType: FocusedElementType | undefined;
initializeCommunicationDir(getCommunicationDirPath());
const commandRunner = new CommandRunner();
let focusedElementType: FocusedElementType | undefined;

context.subscriptions.push(
vscode.commands.registerCommand(
"command-server.runCommand",
async (focusedElementType_: FocusedElementType) => {
focusedElementType = focusedElementType_;
await commandRunner.runCommand();
focusedElementType = undefined;
}
),
vscode.commands.registerCommand(
"command-server.getFocusedElementType",
() => focusedElementType
)
);
context.subscriptions.push(
vscode.commands.registerCommand(
"command-server.runCommand",
async (focusedElementType_: FocusedElementType) => {
focusedElementType = focusedElementType_;
await commandRunner.runCommand();
focusedElementType = undefined;
}
),
vscode.commands.registerCommand(
"command-server.getFocusedElementType",
() => focusedElementType
)
);

return {
/**
* The type of the focused element in vscode at the moment of the command being executed.
*/
getFocusedElementType: async () => focusedElementType,
return {
/**
* The type of the focused element in vscode at the moment of the command being executed.
*/
getFocusedElementType: async () => focusedElementType,

/**
* These signals can be used as a form of IPC to indicate that an event has
* occurred.
*/
signals: {
/**
* This signal is emitted by the voice engine to indicate that a phrase has
* just begun execution.
*/
prePhrase: io.getInboundSignal("prePhrase"),
},
};
/**
* These signals can be used as a form of IPC to indicate that an event has
* occurred.
*/
signals: {
/**
* This signal is emitted by the voice engine to indicate that a phrase has
* just begun execution.
*/
prePhrase: getInboundSignal("prePhrase"),
},
};
}

// this method is called when your extension is deactivated
Expand Down
29 changes: 0 additions & 29 deletions src/io.ts

This file was deleted.

Loading