636 lines
19 KiB
JavaScript
636 lines
19 KiB
JavaScript
const {
|
|
LoggingDebugSession,
|
|
InitializedEvent,
|
|
TerminatedEvent,
|
|
OutputEvent,
|
|
StoppedEvent,
|
|
ThreadEvent,
|
|
Thread,
|
|
Scope,
|
|
Variable
|
|
} = require('vscode-debugadapter');
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
class MiniDebugSession extends LoggingDebugSession {
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
// Redirigir console.log a la Debug Console
|
|
//console.log = (...args) => { this.sendEvent(new OutputEvent(args.join(" ") + "\n")); };
|
|
console.log("Debug Adapter iniciado");
|
|
this._stackFrames = [];
|
|
this.setDebuggerLinesStartAt1(true);
|
|
this.setDebuggerColumnsStartAt1(true);
|
|
this._pendingLocals = null;
|
|
this._pendingUpvalues = null;
|
|
this._pendingGlobals = null;
|
|
this._refTable = new Map(); // ref → { luaRef }
|
|
this._nextRef = 1000; // IDs únicos para tablas
|
|
this._pendingEval = null;
|
|
this.programStarted = false;
|
|
this._nextVarRef = 100;
|
|
this._varRefMap = new Map();
|
|
this._exceptionFilters = new Set(["uncaught"]);
|
|
}
|
|
|
|
initializeRequest(response, args) {
|
|
console.log("[ADAPTER] initializeRequest");
|
|
this.sendEvent(new InitializedEvent());
|
|
|
|
response.body = {
|
|
supportsConfigurationDoneRequest: true,
|
|
supportsEvaluateForHovers: true,
|
|
supportsStepBack: false,
|
|
supportsSetVariable: true,
|
|
supportsRestartFrame: false,
|
|
supportsGotoTargetsRequest: false,
|
|
supportsStepInTargetsRequest: false,
|
|
supportsCompletionsRequest: false,
|
|
supportsRestartRequest: false,
|
|
supportsExceptionInfoRequest: false,
|
|
supportsDelayedStackTraceLoading: false,
|
|
supportsLoadedSourcesRequest: false,
|
|
supportsLogPoints: true,
|
|
supportsTerminateRequest: true,
|
|
supportsTerminateDebuggee: true,
|
|
supportsFunctionBreakpoints: false,
|
|
supportsConditionalBreakpoints: true,
|
|
supportsHitConditionalBreakpoints: true,
|
|
supportsConfigurationDoneRequest: true,
|
|
supportsSetExpression: false,
|
|
supportsClipboardContext: false,
|
|
supportsValueFormattingOptions: false,
|
|
supportsExceptionOptions: false,
|
|
supportsExceptionFilterOptions: true,
|
|
supportsSingleThreadExecutionRequests: true
|
|
};
|
|
response.body.exceptionBreakpointFilters = [
|
|
{ filter: "all", label: "Break on all exceptions", default: false },
|
|
{ filter: "uncaught", label: "Break on uncaught exceptions", default: true }
|
|
];
|
|
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
pendingStackTrace(frames) {
|
|
this._stackFrames = frames;
|
|
}
|
|
|
|
handleLine(line) {
|
|
console.log("[ADAPTER] RAW LINE:", JSON.stringify(line));
|
|
|
|
if (!line.startsWith("@@DEBUG@@")) {
|
|
console.log("[ADAPTER] Ignorando línea no-debug");
|
|
return;
|
|
}
|
|
|
|
const jsonStr = line.substring(9);
|
|
console.log("[ADAPTER] JSON A PARSEAR:", jsonStr);
|
|
|
|
let json;
|
|
try {
|
|
json = JSON.parse(jsonStr);
|
|
} catch (e) {
|
|
console.log("[ADAPTER] ERROR PARSEANDO JSON:", e.message);
|
|
return;
|
|
}
|
|
|
|
console.log("[ADAPTER] JSON OK:", json);
|
|
console.log("[ADAPTER] TYPE:", json.type);
|
|
|
|
if (json.type === "luaError") {
|
|
const e = new OutputEvent(json.message + "\n", "stderr");
|
|
e.body.source = { path: json.file };
|
|
e.body.line = json.line;
|
|
this.sendEvent(e);
|
|
return;
|
|
}
|
|
|
|
if (json.type === "break") {
|
|
this._lastStackFrames = null;
|
|
this._lastTotalFrames = 0;
|
|
|
|
this.currentFile = json.file;
|
|
this.currentLine = json.line;
|
|
|
|
this.sendEvent(new StoppedEvent("breakpoint", 1));
|
|
return;
|
|
}
|
|
if (json.type === "step") {
|
|
this._lastStackFrames = null;
|
|
this._lastTotalFrames = 0;
|
|
|
|
this.sendEvent(new StoppedEvent("step", 1));
|
|
return;
|
|
}
|
|
|
|
if (json.type === "pause") {
|
|
this._lastStackFrames = null;
|
|
this._lastTotalFrames = 0;
|
|
|
|
this.sendEvent(new StoppedEvent("pause", 1));
|
|
return;
|
|
}
|
|
|
|
if (json.type === "stackTrace") {
|
|
this._lastStackFrames = json.payload.stackFrames;
|
|
this._lastTotalFrames = json.payload.totalFrames;
|
|
|
|
if (this._pendingStackTraceResponse) {
|
|
const resp = this._pendingStackTraceResponse;
|
|
this._pendingStackTraceResponse = null;
|
|
|
|
resp.body = {
|
|
stackFrames: this._lastStackFrames,
|
|
totalFrames: this._lastTotalFrames
|
|
};
|
|
|
|
this.sendResponse(resp);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (json.type === "variables") {
|
|
console.log("[ADAPTER] VARIABLES PAYLOAD:", JSON.stringify(json.payload));
|
|
|
|
// 1. Convertimos las variables del motor a formato DAP
|
|
const vars = json.payload.variables.map(v => {
|
|
if (v.type === "table" && v.ref > 0) {
|
|
const id = this._nextRef++;
|
|
this._refTable.set(id, v.ref);
|
|
return {
|
|
name: v.name,
|
|
value: v.value,
|
|
type: v.type,
|
|
variablesReference: id
|
|
};
|
|
}
|
|
|
|
return {
|
|
name: v.name,
|
|
value: v.value,
|
|
type: v.type,
|
|
variablesReference: 0
|
|
};
|
|
});
|
|
|
|
// 2. Seleccionamos el pendingResponse correcto según el kind
|
|
let response = null;
|
|
|
|
if (json.payload.kind === "locals") {
|
|
response = this._pendingLocals;
|
|
this._pendingLocals = null;
|
|
|
|
} else if (json.payload.kind === "upvalues") {
|
|
response = this._pendingUpvalues;
|
|
this._pendingUpvalues = null;
|
|
|
|
} else if (json.payload.kind === "globals") {
|
|
response = this._pendingGlobals;
|
|
this._pendingGlobals = null;
|
|
|
|
} else if (json.payload.kind === "expand") {
|
|
response = this._pendingExpand;
|
|
this._pendingExpand = null;
|
|
|
|
} else {
|
|
console.log("[ADAPTER] VARIABLES: kind desconocido:", json.payload.kind);
|
|
return;
|
|
}
|
|
|
|
if (!response) {
|
|
console.log("[ADAPTER] VARIABLES: no pending response para kind", json.payload.kind);
|
|
return;
|
|
}
|
|
|
|
// 3. Enviamos la respuesta a VSCode
|
|
response.body = { variables: vars };
|
|
this.sendResponse(response);
|
|
return;
|
|
}
|
|
|
|
if (json.type === "eval") {
|
|
const r = this._pendingEval;
|
|
this._pendingEval = null;
|
|
|
|
const v = json.payload.result;
|
|
|
|
let variablesReference = 0;
|
|
|
|
if (v.type === "table" && v.ref > 0) {
|
|
const id = this._nextRef++;
|
|
this._refTable.set(id, v.ref);
|
|
variablesReference = id;
|
|
}
|
|
|
|
r.body = {
|
|
result: v.value ?? "",
|
|
variablesReference
|
|
};
|
|
|
|
this.sendResponse(r);
|
|
return;
|
|
}
|
|
|
|
if (json.type === "setVariable") {
|
|
if (this._pendingSetVariable) {
|
|
//const newValue = String(msg.payload.value);
|
|
this._pendingSetVariable.body = {
|
|
value: json.payload.value
|
|
};
|
|
this.sendResponse(this._pendingSetVariable);
|
|
this._pendingSetVariable = null;
|
|
}
|
|
}
|
|
|
|
if (json.type === "log") {
|
|
this.sendEvent(new OutputEvent(json.payload.text + "\n"));
|
|
return;
|
|
}
|
|
|
|
if (json.type === "stopped" && json.payload.reason === "exception") {
|
|
this.sendEvent(new StoppedEvent("exception", 1, json.payload.text));
|
|
return;
|
|
}
|
|
|
|
// salida normal
|
|
this.sendEvent(new OutputEvent(line + "\n", "stdout"));
|
|
}
|
|
|
|
launchRequest(response, args) {
|
|
console.log("[ADAPTER] launchRequest");
|
|
|
|
const program = args.program;
|
|
const cwd = args.cwd || process.cwd();
|
|
|
|
this.process = spawn(program, [], {
|
|
cwd: cwd,
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
});
|
|
|
|
this.programStarted = true;
|
|
|
|
this.stdoutBuffer = "";
|
|
|
|
this.process.stdout.on('data', data => {
|
|
this.stdoutBuffer += data.toString();
|
|
|
|
let idx;
|
|
while ((idx = this.stdoutBuffer.indexOf("\n")) >= 0) {
|
|
const line = this.stdoutBuffer.slice(0, idx).trim();
|
|
this.stdoutBuffer = this.stdoutBuffer.slice(idx + 1);
|
|
|
|
this.handleLine(line);
|
|
}
|
|
});
|
|
|
|
this.process.stderr.on('data', data => {
|
|
this.sendEvent(new OutputEvent(data.toString(), "stderr"));
|
|
});
|
|
|
|
this.process.on('exit', () => {
|
|
this.sendEvent(new TerminatedEvent());
|
|
});
|
|
|
|
// 3. Ahora que stdout ya está conectado, enviar breakpoints
|
|
if (this.breakpoints) {
|
|
for (const path in this.breakpoints) {
|
|
//const lines = this.breakpoints[path];
|
|
|
|
this.sendDebugCommand({
|
|
cmd: "setBreakpoints",
|
|
file: path,
|
|
breakpoints: this.breakpoints[path] //lines
|
|
});
|
|
}
|
|
}
|
|
|
|
this.sendDebugCommand({
|
|
cmd: "setExceptionFilters",
|
|
filters: [...this._exceptionFilters]
|
|
});
|
|
|
|
this.sendEvent(new ThreadEvent("started", 1));
|
|
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
sendDebugCommand(obj) {
|
|
if (!this.process || !this.process.stdin) return;
|
|
const msg = "@@DEBUGCMD@@" + JSON.stringify(obj) + "\n";
|
|
console.log("ENVIANDO CMD:", msg);
|
|
this.process.stdin.write(msg);
|
|
}
|
|
|
|
nextRequest(response, args) {
|
|
this.sendDebugCommand({ cmd: "stepOver" });
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
stepInRequest(response, args) {
|
|
this.sendDebugCommand({ cmd: "stepInto" });
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
stepOutRequest(response, args) {
|
|
this.sendDebugCommand({ cmd: "stepOut" });
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
setBreakPointsRequest(response, args) {
|
|
const path = args.source.path;
|
|
const clientBreakpoints = args.breakpoints || [];
|
|
|
|
const lines = clientBreakpoints.map(bp => bp.line);
|
|
|
|
if (!this.breakpoints)
|
|
this.breakpoints = {};
|
|
|
|
//this.breakpoints[path] = lines;
|
|
this.breakpoints[path] = clientBreakpoints.map(bp => ({
|
|
line: bp.line,
|
|
condition: bp.condition || null,
|
|
logMessage: bp.logMessage || null,
|
|
hitCondition: bp.hitCondition || null
|
|
}));
|
|
|
|
// Si el programa YA está corriendo → enviar breakpoints al motor
|
|
if (this.programStarted) {
|
|
this.sendDebugCommand({
|
|
cmd: "setBreakpoints",
|
|
file: path,
|
|
breakpoints: this.breakpoints[path]
|
|
});
|
|
}
|
|
|
|
response.body = {
|
|
breakpoints: lines.map(line => ({
|
|
verified: true,
|
|
line: line
|
|
}))
|
|
};
|
|
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
continueRequest(response, args) {
|
|
const msg = {
|
|
cmd: "continue"
|
|
};
|
|
|
|
this.sendDebugCommand(msg);
|
|
//this.process.stdin.write(
|
|
// "@@DEBUGCMD@@" + JSON.stringify(msg) + "\n"
|
|
//);
|
|
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
pauseRequest(response, args) {
|
|
this.sendDebugCommand({ cmd: "pause" });
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
disconnectRequest(response, args) {
|
|
console.log("[ADAPTER] disconnectRequest");
|
|
|
|
if (this.process) {
|
|
try {
|
|
// 1. Cerrar stdin para desbloquear lecturas
|
|
this.process.stdin.end();
|
|
} catch (e) {}
|
|
|
|
try {
|
|
// 2. Intento de kill suave
|
|
this.process.kill();
|
|
} catch (e) {}
|
|
|
|
try {
|
|
// 3. Kill duro por si acaso
|
|
this.process.kill('SIGKILL');
|
|
} catch (e) {}
|
|
}
|
|
|
|
// 4. Notificar a VSCode
|
|
this.sendEvent(new TerminatedEvent());
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
terminateRequest(response, args) {
|
|
console.log("[ADAPTER] terminateRequest");
|
|
|
|
if (this.process) {
|
|
try { this.process.stdin.end(); } catch (e) {}
|
|
try { this.process.kill(); } catch (e) {}
|
|
try { this.process.kill('SIGKILL'); } catch (e) {}
|
|
}
|
|
|
|
this.sendEvent(new TerminatedEvent());
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
threadsRequest(response) {
|
|
// VSCode exige al menos un thread
|
|
response.body = {
|
|
threads: [
|
|
new Thread(1, "Main Thread")
|
|
]
|
|
};
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
stackTraceRequest(response, args) {
|
|
console.log("[ADAPTER] stackTraceRequest");
|
|
|
|
if (!this._lastStackFrames) {
|
|
// Pedimos el stack trace al motor
|
|
this.sendDebugCommand({ cmd: "stackTrace" });
|
|
// Guardar el response para enviarlo más tarde
|
|
this._pendingStackTraceResponse = response;
|
|
return;
|
|
}
|
|
|
|
// Si ya tenemos frames, responder inmediatamente
|
|
response.body = {
|
|
stackFrames: this._lastStackFrames,
|
|
totalFrames: this._lastTotalFrames
|
|
};
|
|
|
|
this.sendResponse(response);
|
|
// Esperamos la respuesta del motor
|
|
/* const check = () => {
|
|
if (this._stackFrames && this._stackFrames.length > 0) {
|
|
const frames = this._stackFrames;
|
|
this._stackFrames = [];
|
|
|
|
response.body = {
|
|
stackFrames: frames.map((f, i) => ({
|
|
id: i,
|
|
name: f.name,
|
|
source: { path: f.file },
|
|
line: f.line,
|
|
column: 1
|
|
})),
|
|
totalFrames: frames.length
|
|
};
|
|
this._lastFrames = frames;
|
|
|
|
this.sendResponse(response);
|
|
} else {
|
|
setTimeout(check, 5);
|
|
}
|
|
};
|
|
|
|
check();*/
|
|
}
|
|
|
|
makeVarRef(type, frameId) {
|
|
const ref = this._nextVarRef++;
|
|
this._varRefMap.set(ref, { type, frameId });
|
|
return ref;
|
|
}
|
|
|
|
// Aún no tiene en cuenta el frame
|
|
scopesRequest(response, args) {
|
|
console.log("[ADAPTER] scopesRequest, frameId =", args.frameId);
|
|
const frameId = args.frameId;
|
|
|
|
response.body = {
|
|
scopes: [
|
|
new Scope("Locals", this.makeVarRef("locals", frameId), false),
|
|
new Scope("Upvalues", this.makeVarRef("upvalues", frameId), false),
|
|
new Scope("Globals", this.makeVarRef("globals", frameId), true) ]
|
|
};
|
|
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
variablesRequest(response, args) {
|
|
console.log("[ADAPTER] variablesRequest ref =", args.variablesReference);
|
|
const info = this._varRefMap.get(args.variablesReference);
|
|
|
|
if (info) {
|
|
const { type, frameId } = info;
|
|
|
|
if (type === "locals") {
|
|
this._pendingLocals = response;
|
|
this.sendDebugCommand({ cmd: "locals", frame: frameId });
|
|
return;
|
|
}
|
|
|
|
if (type === "upvalues") {
|
|
this._pendingUpvalues = response;
|
|
this.sendDebugCommand({ cmd: "upvalues", frame: frameId });
|
|
return;
|
|
}
|
|
|
|
if (type === "globals") {
|
|
this._pendingGlobals = response;
|
|
this.sendDebugCommand({ cmd: "globals" });
|
|
return;
|
|
}
|
|
}
|
|
// Expansión de tablas
|
|
if (args.variablesReference >= 1000) {
|
|
const luaRef = this._refTable.get(args.variablesReference);
|
|
|
|
if (!luaRef) {
|
|
console.log("[ADAPTER] No existe luaRef para", args.variablesReference);
|
|
response.body = { variables: [] };
|
|
this.sendResponse(response);
|
|
return;
|
|
}
|
|
|
|
this._pendingExpand = response;
|
|
this.sendDebugCommand({ cmd: "expand", ref: luaRef });
|
|
return;
|
|
}
|
|
|
|
// de momento, resto vacío
|
|
response.body = { variables: [] };
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
evaluateRequest(response, args) {
|
|
const expr = args.expression;
|
|
const frameId = args.frameId ?? 0;
|
|
|
|
// Detectar asignación: contiene "=" y no empieza por "=="
|
|
const isAssign = expr.includes("=") && !expr.includes("==");
|
|
|
|
this._pendingEval = response;
|
|
|
|
if (isAssign) {
|
|
this.sendDebugCommand({
|
|
cmd: "evalAssign",
|
|
frame: frameId,
|
|
expr: expr
|
|
});
|
|
} else {
|
|
this.sendDebugCommand({
|
|
cmd: "eval",
|
|
expr: expr,
|
|
frame: frameId
|
|
});
|
|
}
|
|
}
|
|
|
|
setVariableRequest(response, args) {
|
|
const { variablesReference, name, value } = args;
|
|
|
|
// Caso 1: locals/upvalues/globals
|
|
const info = this._varRefMap.get(variablesReference);
|
|
if (info) {
|
|
this._pendingSetVariable = response;
|
|
this.sendDebugCommand({
|
|
cmd: "setVariable",
|
|
frame: info.frameId,
|
|
scope: info.type,
|
|
name: name,
|
|
value: value
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Caso 2: miembros de tabla
|
|
if (variablesReference >= 1000) {
|
|
const luaRef = this._refTable.get(variablesReference);
|
|
|
|
this._pendingSetVariable = response;
|
|
this.sendDebugCommand({
|
|
cmd: "setTableField",
|
|
ref: luaRef,
|
|
key: name,
|
|
value: value
|
|
});
|
|
return;
|
|
}
|
|
|
|
// fallback
|
|
response.body = { value: value };
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
setExceptionBreakPointsRequest(response, args) {
|
|
// Si VSCode envía filtros explícitos, los usamos
|
|
if (args.filters && args.filters.length > 0) {
|
|
this._exceptionFilters = new Set(args.filters);
|
|
}
|
|
|
|
// Si VSCode envía [], NO tocamos los filtros por defecto
|
|
|
|
response.body = {};
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
configurationDoneRequest(response, args) {
|
|
console.log("[ADAPTER] configurationDoneRequest");
|
|
this.sendResponse(response);
|
|
}
|
|
|
|
}
|
|
|
|
new MiniDebugSession().start(process.stdin, process.stdout);
|