Skip to content

Develop Use a child process for debugging instead of a terminal #478 #479

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

Merged
merged 2 commits into from
Apr 26, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ out
dist
*.vsix
dist/*
/examples/**/*.rpyc
/examples/errors.txt
/examples/log.txt
107 changes: 68 additions & 39 deletions src/debugger.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,88 @@
import * as vscode from "vscode";
import { DebugSession, TerminatedEvent } from "@vscode/debugadapter";
import { getWorkspaceFolder } from "./workspace";
import { Configuration } from "./configuration";
import { logToast } from "./logger";
import { isValidExecutable } from "./extension";

function getTerminal(name: string): vscode.Terminal {
let i: number;
for (i = 0; i < vscode.window.terminals.length; i++) {
if (vscode.window.terminals[i].name === name) {
return vscode.window.terminals[i];
}
}
return vscode.window.createTerminal(name);
}
import { DebugSession, ExitedEvent, InitializedEvent, TerminatedEvent } from "@vscode/debugadapter";
import { ExecuteRunpyRun } from "./extension";
import { DebugProtocol } from "@vscode/debugprotocol";
import { logMessage } from "./logger";
import { ChildProcessWithoutNullStreams } from "child_process";

export class RenpyAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
createDebugAdapterDescriptor(session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
return new vscode.DebugAdapterInlineImplementation(new RenpyDebugSession(session.configuration.command, session.configuration.args));
return new vscode.DebugAdapterInlineImplementation(new RenpyDebugSession());
}
}

class RenpyDebugSession extends DebugSession {
private command = "run";
private args?: string[];
childProcess: ChildProcessWithoutNullStreams | null = null;

public constructor(command: string, args?: string[]) {
super();
this.command = command;
if (args) {
this.args = args;
protected override initializeRequest(response: DebugProtocol.InitializeResponse): void {
this.sendEvent(new InitializedEvent());

response.body = { supportTerminateDebuggee: true };

const childProcess = ExecuteRunpyRun();
if (childProcess === null) {
logMessage(vscode.LogLevel.Error, "Ren'Py executable location not configured or is invalid.");
return;
}
this.childProcess = childProcess;

childProcess
.addListener("spawn", () => {
const processEvent: DebugProtocol.ProcessEvent = {
event: "process",
body: {
name: "Ren'Py",
isLocalProcess: true,
startMethod: "launch",
},
seq: 0,
type: "event",
};
if (childProcess.pid !== undefined) {
processEvent.body.systemProcessId = childProcess.pid;
}
this.sendEvent(processEvent);
this.sendResponse(response);
})
.addListener("exit", (code) => {
this.sendEvent(new ExitedEvent(code ?? 1));
this.sendEvent(new TerminatedEvent());
});
childProcess.stdout.on("data", (data) => {
logMessage(vscode.LogLevel.Info, `Ren'Py stdout: ${data}`);
});
childProcess.stderr.on("data", (data) => {
logMessage(vscode.LogLevel.Error, `Ren'Py stderr: ${data}`);
});
}

protected override initializeRequest(): void {
const terminal = getTerminal("Ren'py Debug");
terminal.show();
let program = Configuration.getRenpyExecutablePath();
protected override terminateRequest(): void {
this.terminate();
}

if (!isValidExecutable(program)) {
logToast(vscode.LogLevel.Error, "Ren'Py executable location not configured or is invalid.");
return;
protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
if (args.terminateDebuggee) {
this.terminate();
} else {
this.disconnect();
this.sendEvent(new TerminatedEvent());
}
}

program += " " + getWorkspaceFolder();
if (this.command) {
program += " " + this.command;
private terminate() {
if (this.childProcess === null) {
return;
}
if (this.args) {
program += " " + this.args.join(" ");
this.childProcess.kill();
this.childProcess = null;
}

private disconnect() {
if (this.childProcess === null) {
return;
}
terminal.sendText(program);
this.sendEvent(new TerminatedEvent());
this.childProcess.disconnect();
this.childProcess = null;
}
}

Expand All @@ -63,8 +94,6 @@ export class RenpyConfigurationProvider implements vscode.DebugConfigurationProv
config.type = "renpy";
config.request = "launch";
config.name = "Ren'Py: Launch";
config.command = "run";
config.args = [];
}
}
return config;
Expand Down
87 changes: 42 additions & 45 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,12 @@ export async function activate(context: ExtensionContext): Promise<void> {
return;
}

debug.startDebugging(
undefined,
{
type: "renpy",
name: "Run Project",
request: "launch",
program: rpyPath,
},
{ noDebug: true },
);

//call renpy
const result = RunWorkspaceFolder();
if (result) {
logToast(LogLevel.Info, "Ren'Py is running successfully");
logMessage(LogLevel.Info, "Ren'Py is running successfully");
} else {
logToast(LogLevel.Error, "Ren'Py failed to run.");
}
});
context.subscriptions.push(runCommand);
Expand Down Expand Up @@ -327,44 +318,50 @@ export function isValidExecutable(renpyExecutableLocation: string): boolean {
return false;
}
return fs.existsSync(renpyExecutableLocation);
1;
}
// Attempts to run renpy executable through console commands.
function RunWorkspaceFolder(): boolean {
export function RunWorkspaceFolder(): boolean {
const childProcess = ExecuteRunpyRun();
if (childProcess === null) {
logToast(LogLevel.Error, "Ren'Py executable location not configured or is invalid.");
return false;
}
childProcess
.on("spawn", () => {
updateStatusBar("$(sync~spin) Running Ren'Py...");
})
.on("error", (error) => {
logMessage(LogLevel.Error, `Ren'Py spawn error: ${error}`);
})
.on("exit", () => {
updateStatusBar(getStatusBarText());
});
childProcess.stdout.on("data", (data) => {
logMessage(LogLevel.Info, `Ren'Py stdout: ${data}`);
});
childProcess.stderr.on("data", (data) => {
logMessage(LogLevel.Error, `Ren'Py stderr: ${data}`);
});

return true;
}

export function ExecuteRunpyRun(): cp.ChildProcessWithoutNullStreams | null {
const rpyPath = Configuration.getRenpyExecutablePath();

if (isValidExecutable(rpyPath)) {
const renpyPath = cleanUpPath(Uri.file(rpyPath).path);
const cwd = renpyPath.substring(0, renpyPath.lastIndexOf("/"));
const workFolder = getWorkspaceFolder();
const args: string[] = [`${workFolder}`, "run"];
if (workFolder.endsWith("/game")) {
try {
updateStatusBar("$(sync~spin) Running Ren'Py...");
const result = cp.spawnSync(rpyPath, args, {
cwd: `${cwd}`,
env: { PATH: process.env.PATH },
});
if (result.error) {
logMessage(LogLevel.Error, `renpy spawn error: ${result.error}`);
return false;
}
if (result.stderr && result.stderr.length > 0) {
logMessage(LogLevel.Error, `renpy spawn stderr: ${result.stderr}`);
return false;
}
} catch (error) {
logMessage(LogLevel.Error, `renpy spawn error: ${error}`);
return false;
} finally {
updateStatusBar(getStatusBarText());
}
return true;
}
return false;
} else {
logMessage(LogLevel.Warning, "config for renpy does not exist");
return false;
if (!isValidExecutable(rpyPath)) {
return null;
}

const renpyPath = cleanUpPath(Uri.file(rpyPath).path);
const cwd = renpyPath.substring(0, renpyPath.lastIndexOf("/"));
const workFolder = getWorkspaceFolder();
const args: string[] = [`${workFolder}`, "run"];
return cp.spawn(rpyPath, args, {
cwd: `${cwd}`,
env: { PATH: process.env.PATH },
});
}

function ExecuteRenpyCompile(): boolean {
Expand Down
Loading