|
@@ -5,9 +5,14 @@
|
|
|
* https://code.visualstudio.com/api/extension-guides/debugger-extension
|
|
|
*/
|
|
|
|
|
|
-import { logger, Logger, LoggingDebugSession, InitializedEvent, Thread } from 'vscode-debugadapter'
|
|
|
-import { DebugProtocol } from 'vscode-debugprotocol'
|
|
|
-import { WaitGroup } from '@jpwilliams/waitgroup'
|
|
|
+import {
|
|
|
+ logger, Logger, LoggingDebugSession, InitializedEvent,
|
|
|
+ Thread, Breakpoint
|
|
|
+} from 'vscode-debugadapter';
|
|
|
+import { DebugProtocol } from 'vscode-debugprotocol';
|
|
|
+import { WaitGroup } from '@jpwilliams/waitgroup';
|
|
|
+import { ECALDebugClient } from './ecalDebugClient';
|
|
|
+import * as vscode from 'vscode';
|
|
|
|
|
|
/**
|
|
|
* ECALDebugArguments are the arguments which VSCode can pass to the debug adapter.
|
|
@@ -15,9 +20,9 @@ import { WaitGroup } from '@jpwilliams/waitgroup'
|
|
|
* debug adapter from a lauch configuration ('.vscode/launch.json') in a project folder.
|
|
|
*/
|
|
|
interface ECALDebugArguments extends DebugProtocol.LaunchRequestArguments {
|
|
|
-
|
|
|
+ host: string; // Host of the ECAL debug server
|
|
|
+ port: number; // Port of the ECAL debug server
|
|
|
dir: string; // Root directory for ECAL interpreter
|
|
|
- serverURL: string // URL of the ECAL debug server
|
|
|
executeOnEntry?: boolean; // Flag if the debugged script should be executed when the debug session is started
|
|
|
trace?: boolean; // Flag to enable verbose logging of the adapter protocol
|
|
|
}
|
|
@@ -36,18 +41,33 @@ export class ECALDebugSession extends LoggingDebugSession {
|
|
|
*/
|
|
|
private wgConfig = new WaitGroup();
|
|
|
|
|
|
+ private client: ECALDebugClient;
|
|
|
+
|
|
|
+ private extout : vscode.OutputChannel = vscode.window.createOutputChannel('ECAL Debug Session');
|
|
|
+
|
|
|
+ private config :ECALDebugArguments = {} as ECALDebugArguments;
|
|
|
+
|
|
|
/**
|
|
|
* Create a new debug adapter which is used for one debug session.
|
|
|
*/
|
|
|
public constructor () {
|
|
|
- super('mock-debug.txt')
|
|
|
- console.error('##### constructor')
|
|
|
- // Lines and columns start at 1
|
|
|
- this.setDebuggerLinesStartAt1(true)
|
|
|
- this.setDebuggerColumnsStartAt1(true)
|
|
|
-
|
|
|
- // Increment the config WaitGroup counter for configurationDoneRequest()
|
|
|
- this.wgConfig.add(1)
|
|
|
+ super('mock-debug.txt');
|
|
|
+
|
|
|
+ this.extout.appendLine('Creating Debug Session');
|
|
|
+ this.client = new ECALDebugClient(new LogChannelAdapter(this.extout));
|
|
|
+
|
|
|
+ // Add event handlers
|
|
|
+
|
|
|
+ this.client.on('pauseOnBreakpoint', e => {
|
|
|
+ console.log("event:", e)
|
|
|
+ })
|
|
|
+
|
|
|
+ // Lines and columns start at 1
|
|
|
+ this.setDebuggerLinesStartAt1(true);
|
|
|
+ this.setDebuggerColumnsStartAt1(true);
|
|
|
+
|
|
|
+ // Increment the config WaitGroup counter for configurationDoneRequest()
|
|
|
+ this.wgConfig.add(1);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -55,16 +75,16 @@ export class ECALDebugSession extends LoggingDebugSession {
|
|
|
* interrogates the debug adapter on the features which it provides.
|
|
|
*/
|
|
|
protected initializeRequest (response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
|
|
- console.log('##### initializeRequest:', args)
|
|
|
+ console.log('##### initializeRequest:', args);
|
|
|
|
|
|
- response.body = response.body || {}
|
|
|
+ response.body = response.body || {};
|
|
|
|
|
|
- // The adapter implements the configurationDoneRequest.
|
|
|
- response.body.supportsConfigurationDoneRequest = true
|
|
|
+ // The adapter implements the configurationDoneRequest.
|
|
|
+ response.body.supportsConfigurationDoneRequest = true;
|
|
|
|
|
|
- this.sendResponse(response)
|
|
|
+ this.sendResponse(response);
|
|
|
|
|
|
- this.sendEvent(new InitializedEvent())
|
|
|
+ this.sendEvent(new InitializedEvent());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -72,191 +92,275 @@ export class ECALDebugSession extends LoggingDebugSession {
|
|
|
* finished the initialization of the debug adapter.
|
|
|
*/
|
|
|
protected configurationDoneRequest (response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void {
|
|
|
- console.error('##### configurationDoneRequest')
|
|
|
+ console.log('##### configurationDoneRequest');
|
|
|
|
|
|
- super.configurationDoneRequest(response, args)
|
|
|
- this.wgConfig.done()
|
|
|
+ super.configurationDoneRequest(response, args);
|
|
|
+ this.wgConfig.done();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* The client (e.g. VSCode) asks the debug adapter to start the debuggee communication.
|
|
|
*/
|
|
|
protected async launchRequest (response: DebugProtocol.LaunchResponse, args: ECALDebugArguments) {
|
|
|
- console.error('##### launchRequest:', args)
|
|
|
+ console.log('##### launchRequest:', args);
|
|
|
|
|
|
- // Setup logging either verbose or just on errors
|
|
|
+ this.config = args; // Store the configuration
|
|
|
|
|
|
- logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Error, false)
|
|
|
+ // Setup logging either verbose or just on errors
|
|
|
|
|
|
- await this.wgConfig.wait() // Wait for configuration sequence to finish
|
|
|
+ logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Error, false);
|
|
|
|
|
|
- this.sendResponse(response)
|
|
|
+ await this.wgConfig.wait(); // Wait for configuration sequence to finish
|
|
|
+
|
|
|
+ this.extout.appendLine(`Configuration loaded: ${JSON.stringify(args)}`);
|
|
|
+
|
|
|
+ await this.client.conect(args.host, args.port);
|
|
|
+
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected async setBreakPointsRequest (response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise<void> {
|
|
|
- console.error('##### setBreakPointsRequest:', args)
|
|
|
+ console.error('##### setBreakPointsRequest:', args);
|
|
|
+
|
|
|
+ const breakpoints:DebugProtocol.Breakpoint[] = [];
|
|
|
+
|
|
|
+ if (args.source.path?.indexOf(this.config.dir) === 0) {
|
|
|
+ const source = args.source.path.slice(this.config.dir.length + 1);
|
|
|
+
|
|
|
+ // Clear all breakpoints of the file
|
|
|
+
|
|
|
+ await this.client.clearBreakpoints(source);
|
|
|
+
|
|
|
+ // Set all breakpoints
|
|
|
|
|
|
- response.body = {
|
|
|
- breakpoints: []
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ for (const line of args.lines || []) {
|
|
|
+ await this.client.setBreakpoint(`${source}:${line}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Confirm that the breakpoints have been set
|
|
|
+
|
|
|
+ const status = await this.client.status();
|
|
|
+ if (status) {
|
|
|
+ for (const [k, v] of Object.entries(status.breakpoints)) {
|
|
|
+ if (v) {
|
|
|
+ const line = parseInt(k.split(':')[1]);
|
|
|
+ this.extout.appendLine(`Setting breakpoint for ${args.source.name}: ${line}`);
|
|
|
+ breakpoints.push(new Breakpoint(true, line));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ response.body = {
|
|
|
+ breakpoints
|
|
|
+ };
|
|
|
+
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
- protected threadsRequest (response: DebugProtocol.ThreadsResponse): void {
|
|
|
- console.error('##### threadsRequest')
|
|
|
+ protected async threadsRequest (response: DebugProtocol.ThreadsResponse): Promise<void> {
|
|
|
+ console.log('##### threadsRequest');
|
|
|
+
|
|
|
+ const status = await this.client.status();
|
|
|
+ const threads = [];
|
|
|
|
|
|
- // runtime supports no threads so just return a default thread.
|
|
|
- response.body = {
|
|
|
- threads: [
|
|
|
- new Thread(1, 'thread 1')
|
|
|
- ]
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ if (status) {
|
|
|
+ for (const tid in Object.keys(status.threads)) {
|
|
|
+ threads.push(new Thread(parseInt(tid), `Thread ${tid}`));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ threads.push(new Thread(1, 'Thread 1'));
|
|
|
+ }
|
|
|
+
|
|
|
+ response.body = {
|
|
|
+ threads
|
|
|
+ };
|
|
|
+
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected stackTraceRequest (response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
|
|
- console.error('##### stackTraceRequest:', args)
|
|
|
+ console.error('##### stackTraceRequest:', args);
|
|
|
|
|
|
- response.body = {
|
|
|
- stackFrames: []
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ response.body = {
|
|
|
+ stackFrames: []
|
|
|
+ };
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected scopesRequest (response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
|
|
- console.error('##### scopesRequest:', args)
|
|
|
+ console.error('##### scopesRequest:', args);
|
|
|
|
|
|
- response.body = {
|
|
|
- scopes: []
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ response.body = {
|
|
|
+ scopes: []
|
|
|
+ };
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected async variablesRequest (response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, request?: DebugProtocol.Request) {
|
|
|
- console.error('##### variablesRequest', args, request)
|
|
|
+ console.error('##### variablesRequest', args, request);
|
|
|
|
|
|
- response.body = {
|
|
|
- variables: []
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ response.body = {
|
|
|
+ variables: []
|
|
|
+ };
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected continueRequest (response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
|
|
- console.error('##### continueRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### continueRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected reverseContinueRequest (response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void {
|
|
|
- console.error('##### reverseContinueRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### reverseContinueRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected nextRequest (response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
|
|
- console.error('##### nextRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### nextRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected stepBackRequest (response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
|
|
- console.error('##### stepBackRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### stepBackRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected stepInTargetsRequest (response: DebugProtocol.StepInTargetsResponse, args: DebugProtocol.StepInTargetsArguments) {
|
|
|
- console.error('##### stepInTargetsRequest', args)
|
|
|
- response.body = {
|
|
|
- targets: []
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### stepInTargetsRequest', args);
|
|
|
+ response.body = {
|
|
|
+ targets: []
|
|
|
+ };
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected stepInRequest (response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void {
|
|
|
- console.error('##### stepInRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### stepInRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected stepOutRequest (response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void {
|
|
|
- console.error('##### stepOutRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### stepOutRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected async evaluateRequest (response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): Promise<void> {
|
|
|
- console.error('##### evaluateRequest', args)
|
|
|
+ let result: any;
|
|
|
+
|
|
|
+ try {
|
|
|
+ result = await this.client.sendCommandString(`${args.expression}\r\n`);
|
|
|
+
|
|
|
+ if (typeof (result) !== 'string') {
|
|
|
+ result = JSON.stringify(result, null, ' ');
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ result = String(e);
|
|
|
+ }
|
|
|
|
|
|
- response.body = {
|
|
|
- result: 'evaluate',
|
|
|
- variablesReference: 0
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ response.body = {
|
|
|
+ result,
|
|
|
+ variablesReference: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected dataBreakpointInfoRequest (response: DebugProtocol.DataBreakpointInfoResponse, args: DebugProtocol.DataBreakpointInfoArguments): void {
|
|
|
- console.error('##### dataBreakpointInfoRequest', args)
|
|
|
+ console.error('##### dataBreakpointInfoRequest', args);
|
|
|
|
|
|
- response.body = {
|
|
|
- dataId: null,
|
|
|
- description: 'cannot break on data access',
|
|
|
- accessTypes: undefined,
|
|
|
- canPersist: false
|
|
|
- }
|
|
|
+ response.body = {
|
|
|
+ dataId: null,
|
|
|
+ description: 'cannot break on data access',
|
|
|
+ accessTypes: undefined,
|
|
|
+ canPersist: false
|
|
|
+ };
|
|
|
|
|
|
- this.sendResponse(response)
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected setDataBreakpointsRequest (response: DebugProtocol.SetDataBreakpointsResponse, args: DebugProtocol.SetDataBreakpointsArguments): void {
|
|
|
- console.error('##### setDataBreakpointsRequest', args)
|
|
|
+ console.error('##### setDataBreakpointsRequest', args);
|
|
|
|
|
|
- response.body = {
|
|
|
- breakpoints: []
|
|
|
- }
|
|
|
+ response.body = {
|
|
|
+ breakpoints: []
|
|
|
+ };
|
|
|
|
|
|
- this.sendResponse(response)
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected completionsRequest (response: DebugProtocol.CompletionsResponse, args: DebugProtocol.CompletionsArguments): void {
|
|
|
- console.error('##### completionsRequest', args)
|
|
|
-
|
|
|
- response.body = {
|
|
|
- targets: [
|
|
|
- {
|
|
|
- label: 'item 10',
|
|
|
- sortText: '10'
|
|
|
- },
|
|
|
- {
|
|
|
- label: 'item 1',
|
|
|
- sortText: '01'
|
|
|
- },
|
|
|
- {
|
|
|
- label: 'item 2',
|
|
|
- sortText: '02'
|
|
|
- },
|
|
|
- {
|
|
|
- label: 'array[]',
|
|
|
- selectionStart: 6,
|
|
|
- sortText: '03'
|
|
|
- },
|
|
|
- {
|
|
|
- label: 'func(arg)',
|
|
|
- selectionStart: 5,
|
|
|
- selectionLength: 3,
|
|
|
- sortText: '04'
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### completionsRequest', args);
|
|
|
+
|
|
|
+ response.body = {
|
|
|
+ targets: [
|
|
|
+ {
|
|
|
+ label: 'item 10',
|
|
|
+ sortText: '10'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'item 1',
|
|
|
+ sortText: '01'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'item 2',
|
|
|
+ sortText: '02'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'array[]',
|
|
|
+ selectionStart: 6,
|
|
|
+ sortText: '03'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'func(arg)',
|
|
|
+ selectionStart: 5,
|
|
|
+ selectionLength: 3,
|
|
|
+ sortText: '04'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected cancelRequest (response: DebugProtocol.CancelResponse, args: DebugProtocol.CancelArguments) {
|
|
|
- console.error('##### cancelRequest', args)
|
|
|
- this.sendResponse(response)
|
|
|
+ console.error('##### cancelRequest', args);
|
|
|
+ this.sendResponse(response);
|
|
|
}
|
|
|
|
|
|
protected customRequest (command: string, response: DebugProtocol.Response, args: any) {
|
|
|
- console.error('##### customRequest', args)
|
|
|
+ console.error('##### customRequest', args);
|
|
|
+
|
|
|
+ if (command === 'toggleFormatting') {
|
|
|
+ this.sendResponse(response);
|
|
|
+ } else {
|
|
|
+ super.customRequest(command, response, args);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (command === 'toggleFormatting') {
|
|
|
- this.sendResponse(response)
|
|
|
- } else {
|
|
|
- super.customRequest(command, response, args)
|
|
|
- }
|
|
|
+ public shutdown () {
|
|
|
+ console.log('#### Shutdown');
|
|
|
+ this.client?.shutdown().then(() => {
|
|
|
+ this.extout.appendLine('Debug Session has finished');
|
|
|
+ }).catch(e => {
|
|
|
+ this.extout.appendLine(`Debug Session has finished with an error: ${e}`);
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+class LogChannelAdapter {
|
|
|
+ private out: vscode.OutputChannel
|
|
|
+
|
|
|
+ constructor (out: vscode.OutputChannel) {
|
|
|
+ this.out = out;
|
|
|
+ }
|
|
|
+
|
|
|
+ log (value: string): void {
|
|
|
+ this.out.appendLine(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ error (value: string): void {
|
|
|
+ this.out.appendLine(`Error: ${value}`);
|
|
|
+ setTimeout(() => {
|
|
|
+ this.out.show(true);
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+}
|