debug_session.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import {
  2. Breakpoint,
  3. InitializedEvent,
  4. LoggingDebugSession,
  5. Source,
  6. TerminatedEvent,
  7. Thread,
  8. } from "@vscode/debugadapter";
  9. import { DebugProtocol } from "@vscode/debugprotocol";
  10. import { Subject } from "await-notify";
  11. import * as fs from "node:fs";
  12. import { createLogger } from "../../utils";
  13. import { GodotDebugData } from "../debug_runtime";
  14. import { AttachRequestArguments, LaunchRequestArguments } from "../debugger";
  15. import { SceneTreeProvider } from "../scene_tree_provider";
  16. import { ServerController } from "./server_controller";
  17. import { VariablesManager } from "./variables/variables_manager";
  18. const log = createLogger("debugger.session", { output: "Godot Debugger" });
  19. export class GodotDebugSession extends LoggingDebugSession {
  20. public controller = new ServerController(this);
  21. public debug_data = new GodotDebugData(this);
  22. public sceneTree: SceneTreeProvider;
  23. private exception = false;
  24. private configuration_done: Subject = new Subject();
  25. private mode: "launch" | "attach" | "" = "";
  26. public variables_manager: VariablesManager;
  27. public constructor() {
  28. super();
  29. this.setDebuggerLinesStartAt1(false);
  30. this.setDebuggerColumnsStartAt1(false);
  31. }
  32. public dispose() {
  33. this.controller.stop();
  34. }
  35. protected initializeRequest(
  36. response: DebugProtocol.InitializeResponse,
  37. args: DebugProtocol.InitializeRequestArguments,
  38. ) {
  39. log.info("initializeRequest", args);
  40. response.body = response.body || {};
  41. response.body.supportsConfigurationDoneRequest = true;
  42. response.body.supportsTerminateRequest = true;
  43. response.body.supportsEvaluateForHovers = false;
  44. response.body.supportsStepBack = false;
  45. response.body.supportsGotoTargetsRequest = false;
  46. response.body.supportsCancelRequest = false;
  47. response.body.supportsCompletionsRequest = false;
  48. response.body.supportsFunctionBreakpoints = false;
  49. response.body.supportsDataBreakpoints = false;
  50. response.body.supportsBreakpointLocationsRequest = false;
  51. response.body.supportsConditionalBreakpoints = false;
  52. response.body.supportsHitConditionalBreakpoints = false;
  53. response.body.supportsLogPoints = false;
  54. response.body.supportsModulesRequest = false;
  55. response.body.supportsReadMemoryRequest = false;
  56. response.body.supportsRestartFrame = false;
  57. response.body.supportsRestartRequest = false;
  58. response.body.supportsSetExpression = false;
  59. response.body.supportsStepInTargetsRequest = false;
  60. response.body.supportsTerminateThreadsRequest = false;
  61. this.sendResponse(response);
  62. this.sendEvent(new InitializedEvent());
  63. }
  64. protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
  65. log.info("launchRequest", args);
  66. await this.configuration_done.wait(1000);
  67. this.mode = "launch";
  68. this.debug_data.projectPath = args.project;
  69. this.exception = false;
  70. await this.controller.launch(args);
  71. this.sendResponse(response);
  72. }
  73. protected async attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) {
  74. log.info("attachRequest", args);
  75. await this.configuration_done.wait(1000);
  76. this.mode = "attach";
  77. this.exception = false;
  78. await this.controller.attach(args);
  79. this.sendResponse(response);
  80. }
  81. public configurationDoneRequest(
  82. response: DebugProtocol.ConfigurationDoneResponse,
  83. args: DebugProtocol.ConfigurationDoneArguments,
  84. ) {
  85. log.info("configurationDoneRequest", args);
  86. this.configuration_done.notify();
  87. this.sendResponse(response);
  88. }
  89. protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments) {
  90. log.info("continueRequest", args);
  91. if (!this.exception) {
  92. response.body = { allThreadsContinued: true };
  93. this.controller.continue();
  94. this.sendResponse(response);
  95. }
  96. }
  97. protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments) {
  98. log.info("nextRequest", args);
  99. if (!this.exception) {
  100. this.controller.next();
  101. this.sendResponse(response);
  102. }
  103. }
  104. protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments) {
  105. log.info("pauseRequest", args);
  106. if (!this.exception) {
  107. this.controller.break();
  108. this.sendResponse(response);
  109. }
  110. }
  111. protected setBreakPointsRequest(
  112. response: DebugProtocol.SetBreakpointsResponse,
  113. args: DebugProtocol.SetBreakpointsArguments,
  114. ) {
  115. log.info("setBreakPointsRequest", args);
  116. const path = (args.source.path as string).replace(/\\/g, "/");
  117. const client_lines = args.lines || [];
  118. if (fs.existsSync(path)) {
  119. let bps = this.debug_data.get_breakpoints(path);
  120. const bp_lines = bps.map((bp) => bp.line);
  121. for (const bp of bps) {
  122. if (client_lines.indexOf(bp.line) === -1) {
  123. this.debug_data.remove_breakpoint(path, bp.line);
  124. }
  125. }
  126. for (const l of client_lines) {
  127. if (bp_lines.indexOf(l) === -1) {
  128. const bp = args.breakpoints.find((bp_at_line) => bp_at_line.line === l);
  129. if (!bp.condition) {
  130. this.debug_data.set_breakpoint(path, l);
  131. }
  132. }
  133. }
  134. bps = this.debug_data.get_breakpoints(path);
  135. // Sort to ensure breakpoints aren't out-of-order, which would confuse VS Code.
  136. bps.sort((a, b) => (a.line < b.line ? -1 : 1));
  137. response.body = {
  138. breakpoints: bps.map((bp) => {
  139. return new Breakpoint(true, bp.line, 1, new Source(bp.file.split("/").reverse()[0], bp.file));
  140. }),
  141. };
  142. this.sendResponse(response);
  143. }
  144. }
  145. protected stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments) {
  146. log.info("stepInRequest", args);
  147. if (!this.exception) {
  148. this.controller.step();
  149. this.sendResponse(response);
  150. }
  151. }
  152. protected stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments) {
  153. log.info("stepOutRequest", args);
  154. if (!this.exception) {
  155. this.controller.step_out();
  156. this.sendResponse(response);
  157. }
  158. }
  159. protected terminateRequest(response: DebugProtocol.TerminateResponse, args: DebugProtocol.TerminateArguments) {
  160. log.info("terminateRequest", args);
  161. if (this.mode === "launch") {
  162. this.controller.stop();
  163. this.sendEvent(new TerminatedEvent());
  164. }
  165. this.sendResponse(response);
  166. }
  167. protected threadsRequest(response: DebugProtocol.ThreadsResponse) {
  168. log.info("threadsRequest");
  169. response.body = { threads: [new Thread(0, "thread_1")] };
  170. log.info("threadsRequest response", response);
  171. this.sendResponse(response);
  172. }
  173. protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments) {
  174. log.info("stackTraceRequest", args);
  175. if (this.debug_data.last_frame) {
  176. response.body = {
  177. totalFrames: this.debug_data.last_frames.length,
  178. stackFrames: this.debug_data.last_frames.map((sf) => {
  179. return {
  180. id: sf.id,
  181. name: sf.function,
  182. line: sf.line,
  183. column: 1,
  184. source: new Source(sf.file, `${this.debug_data.projectPath}/${sf.file.replace("res://", "")}`),
  185. };
  186. }),
  187. };
  188. }
  189. log.info("stackTraceRequest response", response);
  190. this.sendResponse(response);
  191. }
  192. protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments) {
  193. log.info("scopesRequest", args);
  194. // this.variables_manager.variablesFrameId = args.frameId;
  195. // TODO: create scopes dynamically for a given frame
  196. const vscode_scope_ids = this.variables_manager.get_or_create_frame_scopes(args.frameId);
  197. const scopes_with_references = [
  198. {name: "Locals", variablesReference: vscode_scope_ids.Locals, expensive: false},
  199. {name: "Members", variablesReference: vscode_scope_ids.Members, expensive: false},
  200. {name: "Globals", variablesReference: vscode_scope_ids.Globals, expensive: false},
  201. ];
  202. response.body = {
  203. scopes: scopes_with_references
  204. // scopes: [
  205. // { name: "Locals", variablesReference: 1, expensive: false },
  206. // { name: "Members", variablesReference: 2, expensive: false },
  207. // { name: "Globals", variablesReference: 3, expensive: false },
  208. // ],
  209. };
  210. log.info("scopesRequest response", response);
  211. this.sendResponse(response);
  212. }
  213. protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments) {
  214. log.info("variablesRequest", args);
  215. try {
  216. const variables = await this.variables_manager.get_vscode_object(args.variablesReference);
  217. response.body = {
  218. variables: variables,
  219. };
  220. } catch (error) {
  221. log.error("variablesRequest", error);
  222. response.success = false;
  223. response.message = error.toString();
  224. }
  225. log.info("variablesRequest response", response);
  226. this.sendResponse(response);
  227. }
  228. protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments) {
  229. log.info("evaluateRequest", args);
  230. try {
  231. const parsed_variable = await this.variables_manager.get_vscode_variable_by_name(args.expression, args.frameId);
  232. response.body = {
  233. result: parsed_variable.value,
  234. variablesReference: parsed_variable.variablesReference
  235. };
  236. } catch (error) {
  237. response.success = false;
  238. response.message = error.toString();
  239. response.body = {
  240. result: "null",
  241. variablesReference: 0,
  242. };
  243. }
  244. log.info("evaluateRequest response", response);
  245. this.sendResponse(response);
  246. }
  247. public set_exception(exception: boolean) {
  248. this.exception = true;
  249. }
  250. }