ecalDebugAdapter.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /**
  2. * Debug Adapter for VS Code to support the ECAL debugger.
  3. *
  4. * See the debugger extension guide:
  5. * https://code.visualstudio.com/api/extension-guides/debugger-extension
  6. */
  7. import {
  8. logger, Logger, LoggingDebugSession, InitializedEvent,
  9. Thread, Breakpoint
  10. } from 'vscode-debugadapter';
  11. import { DebugProtocol } from 'vscode-debugprotocol';
  12. import { WaitGroup } from '@jpwilliams/waitgroup';
  13. import { ECALDebugClient } from './ecalDebugClient';
  14. import * as vscode from 'vscode';
  15. /**
  16. * ECALDebugArguments are the arguments which VSCode can pass to the debug adapter.
  17. * This defines the parameter which a VSCode instance using the ECAL extention can pass to the
  18. * debug adapter from a lauch configuration ('.vscode/launch.json') in a project folder.
  19. */
  20. interface ECALDebugArguments extends DebugProtocol.LaunchRequestArguments {
  21. host: string; // Host of the ECAL debug server
  22. port: number; // Port of the ECAL debug server
  23. dir: string; // Root directory for ECAL interpreter
  24. executeOnEntry?: boolean; // Flag if the debugged script should be executed when the debug session is started
  25. trace?: boolean; // Flag to enable verbose logging of the adapter protocol
  26. }
  27. /**
  28. * Debug adapter implementation.
  29. *
  30. * Uses: https://github.com/microsoft/vscode-debugadapter-node
  31. *
  32. * See the Debug Adapter Protocol (DAP) documentation:
  33. * https://microsoft.github.io/debug-adapter-protocol/overview#How_it_works
  34. */
  35. export class ECALDebugSession extends LoggingDebugSession {
  36. /**
  37. * WaitGroup to wait the finish of the configuration sequence
  38. */
  39. private wgConfig = new WaitGroup();
  40. private client: ECALDebugClient;
  41. private extout : vscode.OutputChannel = vscode.window.createOutputChannel('ECAL Debug Session');
  42. private config :ECALDebugArguments = {} as ECALDebugArguments;
  43. /**
  44. * Create a new debug adapter which is used for one debug session.
  45. */
  46. public constructor () {
  47. super('mock-debug.txt');
  48. this.extout.appendLine('Creating Debug Session');
  49. this.client = new ECALDebugClient(new LogChannelAdapter(this.extout));
  50. // Add event handlers
  51. this.client.on('pauseOnBreakpoint', e => {
  52. console.log("event:", e)
  53. })
  54. // Lines and columns start at 1
  55. this.setDebuggerLinesStartAt1(true);
  56. this.setDebuggerColumnsStartAt1(true);
  57. // Increment the config WaitGroup counter for configurationDoneRequest()
  58. this.wgConfig.add(1);
  59. }
  60. /**
  61. * Called as the first step in the DAP. The client (e.g. VSCode)
  62. * interrogates the debug adapter on the features which it provides.
  63. */
  64. protected initializeRequest (response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
  65. console.log('##### initializeRequest:', args);
  66. response.body = response.body || {};
  67. // The adapter implements the configurationDoneRequest.
  68. response.body.supportsConfigurationDoneRequest = true;
  69. this.sendResponse(response);
  70. this.sendEvent(new InitializedEvent());
  71. }
  72. /**
  73. * Called as part of the "configuration Done" step in the DAP. The client (e.g. VSCode) has
  74. * finished the initialization of the debug adapter.
  75. */
  76. protected configurationDoneRequest (response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void {
  77. console.log('##### configurationDoneRequest');
  78. super.configurationDoneRequest(response, args);
  79. this.wgConfig.done();
  80. }
  81. /**
  82. * The client (e.g. VSCode) asks the debug adapter to start the debuggee communication.
  83. */
  84. protected async launchRequest (response: DebugProtocol.LaunchResponse, args: ECALDebugArguments) {
  85. console.log('##### launchRequest:', args);
  86. this.config = args; // Store the configuration
  87. // Setup logging either verbose or just on errors
  88. logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Error, false);
  89. await this.wgConfig.wait(); // Wait for configuration sequence to finish
  90. this.extout.appendLine(`Configuration loaded: ${JSON.stringify(args)}`);
  91. await this.client.conect(args.host, args.port);
  92. this.sendResponse(response);
  93. }
  94. protected async setBreakPointsRequest (response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise<void> {
  95. console.error('##### setBreakPointsRequest:', args);
  96. const breakpoints:DebugProtocol.Breakpoint[] = [];
  97. if (args.source.path?.indexOf(this.config.dir) === 0) {
  98. const source = args.source.path.slice(this.config.dir.length + 1);
  99. // Clear all breakpoints of the file
  100. await this.client.clearBreakpoints(source);
  101. // Set all breakpoints
  102. for (const line of args.lines || []) {
  103. await this.client.setBreakpoint(`${source}:${line}`);
  104. }
  105. // Confirm that the breakpoints have been set
  106. const status = await this.client.status();
  107. if (status) {
  108. for (const [k, v] of Object.entries(status.breakpoints)) {
  109. if (v) {
  110. const line = parseInt(k.split(':')[1]);
  111. this.extout.appendLine(`Setting breakpoint for ${args.source.name}: ${line}`);
  112. breakpoints.push(new Breakpoint(true, line));
  113. }
  114. }
  115. }
  116. }
  117. response.body = {
  118. breakpoints
  119. };
  120. this.sendResponse(response);
  121. }
  122. protected async threadsRequest (response: DebugProtocol.ThreadsResponse): Promise<void> {
  123. console.log('##### threadsRequest');
  124. const status = await this.client.status();
  125. const threads = [];
  126. if (status) {
  127. for (const tid in Object.keys(status.threads)) {
  128. threads.push(new Thread(parseInt(tid), `Thread ${tid}`));
  129. }
  130. } else {
  131. threads.push(new Thread(1, 'Thread 1'));
  132. }
  133. response.body = {
  134. threads
  135. };
  136. this.sendResponse(response);
  137. }
  138. protected stackTraceRequest (response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
  139. console.error('##### stackTraceRequest:', args);
  140. response.body = {
  141. stackFrames: []
  142. };
  143. this.sendResponse(response);
  144. }
  145. protected scopesRequest (response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
  146. console.error('##### scopesRequest:', args);
  147. response.body = {
  148. scopes: []
  149. };
  150. this.sendResponse(response);
  151. }
  152. protected async variablesRequest (response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, request?: DebugProtocol.Request) {
  153. console.error('##### variablesRequest', args, request);
  154. response.body = {
  155. variables: []
  156. };
  157. this.sendResponse(response);
  158. }
  159. protected continueRequest (response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
  160. console.error('##### continueRequest', args);
  161. this.sendResponse(response);
  162. }
  163. protected reverseContinueRequest (response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments): void {
  164. console.error('##### reverseContinueRequest', args);
  165. this.sendResponse(response);
  166. }
  167. protected nextRequest (response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
  168. console.error('##### nextRequest', args);
  169. this.sendResponse(response);
  170. }
  171. protected stepBackRequest (response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
  172. console.error('##### stepBackRequest', args);
  173. this.sendResponse(response);
  174. }
  175. protected stepInTargetsRequest (response: DebugProtocol.StepInTargetsResponse, args: DebugProtocol.StepInTargetsArguments) {
  176. console.error('##### stepInTargetsRequest', args);
  177. response.body = {
  178. targets: []
  179. };
  180. this.sendResponse(response);
  181. }
  182. protected stepInRequest (response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void {
  183. console.error('##### stepInRequest', args);
  184. this.sendResponse(response);
  185. }
  186. protected stepOutRequest (response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void {
  187. console.error('##### stepOutRequest', args);
  188. this.sendResponse(response);
  189. }
  190. protected async evaluateRequest (response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): Promise<void> {
  191. let result: any;
  192. try {
  193. result = await this.client.sendCommandString(`${args.expression}\r\n`);
  194. if (typeof (result) !== 'string') {
  195. result = JSON.stringify(result, null, ' ');
  196. }
  197. } catch (e) {
  198. result = String(e);
  199. }
  200. response.body = {
  201. result,
  202. variablesReference: 0
  203. };
  204. this.sendResponse(response);
  205. }
  206. protected dataBreakpointInfoRequest (response: DebugProtocol.DataBreakpointInfoResponse, args: DebugProtocol.DataBreakpointInfoArguments): void {
  207. console.error('##### dataBreakpointInfoRequest', args);
  208. response.body = {
  209. dataId: null,
  210. description: 'cannot break on data access',
  211. accessTypes: undefined,
  212. canPersist: false
  213. };
  214. this.sendResponse(response);
  215. }
  216. protected setDataBreakpointsRequest (response: DebugProtocol.SetDataBreakpointsResponse, args: DebugProtocol.SetDataBreakpointsArguments): void {
  217. console.error('##### setDataBreakpointsRequest', args);
  218. response.body = {
  219. breakpoints: []
  220. };
  221. this.sendResponse(response);
  222. }
  223. protected completionsRequest (response: DebugProtocol.CompletionsResponse, args: DebugProtocol.CompletionsArguments): void {
  224. console.error('##### completionsRequest', args);
  225. response.body = {
  226. targets: [
  227. {
  228. label: 'item 10',
  229. sortText: '10'
  230. },
  231. {
  232. label: 'item 1',
  233. sortText: '01'
  234. },
  235. {
  236. label: 'item 2',
  237. sortText: '02'
  238. },
  239. {
  240. label: 'array[]',
  241. selectionStart: 6,
  242. sortText: '03'
  243. },
  244. {
  245. label: 'func(arg)',
  246. selectionStart: 5,
  247. selectionLength: 3,
  248. sortText: '04'
  249. }
  250. ]
  251. };
  252. this.sendResponse(response);
  253. }
  254. protected cancelRequest (response: DebugProtocol.CancelResponse, args: DebugProtocol.CancelArguments) {
  255. console.error('##### cancelRequest', args);
  256. this.sendResponse(response);
  257. }
  258. protected customRequest (command: string, response: DebugProtocol.Response, args: any) {
  259. console.error('##### customRequest', args);
  260. if (command === 'toggleFormatting') {
  261. this.sendResponse(response);
  262. } else {
  263. super.customRequest(command, response, args);
  264. }
  265. }
  266. public shutdown () {
  267. console.log('#### Shutdown');
  268. this.client?.shutdown().then(() => {
  269. this.extout.appendLine('Debug Session has finished');
  270. }).catch(e => {
  271. this.extout.appendLine(`Debug Session has finished with an error: ${e}`);
  272. });
  273. }
  274. }
  275. class LogChannelAdapter {
  276. private out: vscode.OutputChannel
  277. constructor (out: vscode.OutputChannel) {
  278. this.out = out;
  279. }
  280. log (value: string): void {
  281. this.out.appendLine(value);
  282. }
  283. error (value: string): void {
  284. this.out.appendLine(`Error: ${value}`);
  285. setTimeout(() => {
  286. this.out.show(true);
  287. }, 500);
  288. }
  289. }