Skip to content

Commit 97834ab

Browse files
committed
Use a child process for debugging instead of a terminal
1 parent 4d3f9c4 commit 97834ab

File tree

2 files changed

+110
-84
lines changed

2 files changed

+110
-84
lines changed

src/debugger.ts

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,88 @@
11
import * as vscode from "vscode";
2-
import { DebugSession, TerminatedEvent } from "@vscode/debugadapter";
3-
import { getWorkspaceFolder } from "./workspace";
4-
import { Configuration } from "./configuration";
5-
import { logToast } from "./logger";
6-
import { isValidExecutable } from "./extension";
7-
8-
function getTerminal(name: string): vscode.Terminal {
9-
let i: number;
10-
for (i = 0; i < vscode.window.terminals.length; i++) {
11-
if (vscode.window.terminals[i].name === name) {
12-
return vscode.window.terminals[i];
13-
}
14-
}
15-
return vscode.window.createTerminal(name);
16-
}
2+
import { DebugSession, ExitedEvent, InitializedEvent, TerminatedEvent } from "@vscode/debugadapter";
3+
import { ExecuteRunpyRun } from "./extension";
4+
import { DebugProtocol } from "@vscode/debugprotocol";
5+
import { logMessage } from "./logger";
6+
import { ChildProcessWithoutNullStreams } from "child_process";
177

188
export class RenpyAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
199
createDebugAdapterDescriptor(session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
20-
return new vscode.DebugAdapterInlineImplementation(new RenpyDebugSession(session.configuration.command, session.configuration.args));
10+
return new vscode.DebugAdapterInlineImplementation(new RenpyDebugSession());
2111
}
2212
}
2313

2414
class RenpyDebugSession extends DebugSession {
25-
private command = "run";
26-
private args?: string[];
15+
childProcess: ChildProcessWithoutNullStreams | null = null;
2716

28-
public constructor(command: string, args?: string[]) {
29-
super();
30-
this.command = command;
31-
if (args) {
32-
this.args = args;
17+
protected override initializeRequest(response: DebugProtocol.InitializeResponse): void {
18+
this.sendEvent(new InitializedEvent());
19+
20+
response.body = { supportTerminateDebuggee: true };
21+
22+
const childProcess = ExecuteRunpyRun();
23+
if (childProcess === null) {
24+
logMessage(vscode.LogLevel.Error, "Ren'Py executable location not configured or is invalid.");
25+
return;
3326
}
27+
this.childProcess = childProcess;
28+
29+
childProcess
30+
.addListener("spawn", () => {
31+
const processEvent: DebugProtocol.ProcessEvent = {
32+
event: "process",
33+
body: {
34+
name: "Ren'Py",
35+
isLocalProcess: true,
36+
startMethod: "launch",
37+
},
38+
seq: 0,
39+
type: "event",
40+
};
41+
if (childProcess.pid !== undefined) {
42+
processEvent.body.systemProcessId = childProcess.pid;
43+
}
44+
this.sendEvent(processEvent);
45+
this.sendResponse(response);
46+
})
47+
.addListener("exit", (code) => {
48+
this.sendEvent(new ExitedEvent(code ?? 1));
49+
this.sendEvent(new TerminatedEvent());
50+
});
51+
childProcess.stdout.on("data", (data) => {
52+
logMessage(vscode.LogLevel.Info, `Ren'Py stdout: ${data}`);
53+
});
54+
childProcess.stderr.on("data", (data) => {
55+
logMessage(vscode.LogLevel.Error, `Ren'Py stderr: ${data}`);
56+
});
3457
}
3558

36-
protected override initializeRequest(): void {
37-
const terminal = getTerminal("Ren'py Debug");
38-
terminal.show();
39-
let program = Configuration.getRenpyExecutablePath();
59+
protected override terminateRequest(): void {
60+
this.terminate();
61+
}
4062

41-
if (!isValidExecutable(program)) {
42-
logToast(vscode.LogLevel.Error, "Ren'Py executable location not configured or is invalid.");
43-
return;
63+
protected override disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
64+
if (args.terminateDebuggee) {
65+
this.terminate();
66+
} else {
67+
this.disconnect();
68+
this.sendEvent(new TerminatedEvent());
4469
}
70+
}
4571

46-
program += " " + getWorkspaceFolder();
47-
if (this.command) {
48-
program += " " + this.command;
72+
private terminate() {
73+
if (this.childProcess === null) {
74+
return;
4975
}
50-
if (this.args) {
51-
program += " " + this.args.join(" ");
76+
this.childProcess.kill();
77+
this.childProcess = null;
78+
}
79+
80+
private disconnect() {
81+
if (this.childProcess === null) {
82+
return;
5283
}
53-
terminal.sendText(program);
54-
this.sendEvent(new TerminatedEvent());
84+
this.childProcess.disconnect();
85+
this.childProcess = null;
5586
}
5687
}
5788

@@ -63,8 +94,6 @@ export class RenpyConfigurationProvider implements vscode.DebugConfigurationProv
6394
config.type = "renpy";
6495
config.request = "launch";
6596
config.name = "Ren'Py: Launch";
66-
config.command = "run";
67-
config.args = [];
6897
}
6998
}
7099
return config;

src/extension.ts

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,12 @@ export async function activate(context: ExtensionContext): Promise<void> {
159159
return;
160160
}
161161

162-
debug.startDebugging(
163-
undefined,
164-
{
165-
type: "renpy",
166-
name: "Run Project",
167-
request: "launch",
168-
program: rpyPath,
169-
},
170-
{ noDebug: true },
171-
);
172-
173162
//call renpy
174163
const result = RunWorkspaceFolder();
175164
if (result) {
176-
logToast(LogLevel.Info, "Ren'Py is running successfully");
165+
logMessage(LogLevel.Info, "Ren'Py is running successfully");
166+
} else {
167+
logToast(LogLevel.Error, "Ren'Py failed to run.");
177168
}
178169
});
179170
context.subscriptions.push(runCommand);
@@ -327,44 +318,50 @@ export function isValidExecutable(renpyExecutableLocation: string): boolean {
327318
return false;
328319
}
329320
return fs.existsSync(renpyExecutableLocation);
321+
1;
330322
}
331323
// Attempts to run renpy executable through console commands.
332-
function RunWorkspaceFolder(): boolean {
324+
export function RunWorkspaceFolder(): boolean {
325+
const childProcess = ExecuteRunpyRun();
326+
if (childProcess === null) {
327+
logToast(LogLevel.Error, "Ren'Py executable location not configured or is invalid.");
328+
return false;
329+
}
330+
childProcess
331+
.on("spawn", () => {
332+
updateStatusBar("$(sync~spin) Running Ren'Py...");
333+
})
334+
.on("error", (error) => {
335+
logMessage(LogLevel.Error, `Ren'Py spawn error: ${error}`);
336+
})
337+
.on("exit", () => {
338+
updateStatusBar(getStatusBarText());
339+
});
340+
childProcess.stdout.on("data", (data) => {
341+
logMessage(LogLevel.Info, `Ren'Py stdout: ${data}`);
342+
});
343+
childProcess.stderr.on("data", (data) => {
344+
logMessage(LogLevel.Error, `Ren'Py stderr: ${data}`);
345+
});
346+
347+
return true;
348+
}
349+
350+
export function ExecuteRunpyRun(): cp.ChildProcessWithoutNullStreams | null {
333351
const rpyPath = Configuration.getRenpyExecutablePath();
334352

335-
if (isValidExecutable(rpyPath)) {
336-
const renpyPath = cleanUpPath(Uri.file(rpyPath).path);
337-
const cwd = renpyPath.substring(0, renpyPath.lastIndexOf("/"));
338-
const workFolder = getWorkspaceFolder();
339-
const args: string[] = [`${workFolder}`, "run"];
340-
if (workFolder.endsWith("/game")) {
341-
try {
342-
updateStatusBar("$(sync~spin) Running Ren'Py...");
343-
const result = cp.spawnSync(rpyPath, args, {
344-
cwd: `${cwd}`,
345-
env: { PATH: process.env.PATH },
346-
});
347-
if (result.error) {
348-
logMessage(LogLevel.Error, `renpy spawn error: ${result.error}`);
349-
return false;
350-
}
351-
if (result.stderr && result.stderr.length > 0) {
352-
logMessage(LogLevel.Error, `renpy spawn stderr: ${result.stderr}`);
353-
return false;
354-
}
355-
} catch (error) {
356-
logMessage(LogLevel.Error, `renpy spawn error: ${error}`);
357-
return false;
358-
} finally {
359-
updateStatusBar(getStatusBarText());
360-
}
361-
return true;
362-
}
363-
return false;
364-
} else {
365-
logMessage(LogLevel.Warning, "config for renpy does not exist");
366-
return false;
353+
if (!isValidExecutable(rpyPath)) {
354+
return null;
367355
}
356+
357+
const renpyPath = cleanUpPath(Uri.file(rpyPath).path);
358+
const cwd = renpyPath.substring(0, renpyPath.lastIndexOf("/"));
359+
const workFolder = getWorkspaceFolder();
360+
const args: string[] = [`${workFolder}`, "run"];
361+
return cp.spawn(rpyPath, args, {
362+
cwd: `${cwd}`,
363+
env: { PATH: process.env.PATH },
364+
});
368365
}
369366

370367
function ExecuteRenpyCompile(): boolean {

0 commit comments

Comments
 (0)