|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +import * as vscode from "vscode"; |
| 4 | +import * as cp from 'child_process'; |
| 5 | + |
| 6 | +import { IFeature } from "../feature"; |
| 7 | +import { ILogger } from "../logging"; |
| 8 | +import { CommandEnvironmentHelper } from "../helpers/commandHelper"; |
| 9 | +import { IConnectionConfiguration } from '../interfaces'; |
| 10 | +import { ISettings } from '../settings'; |
| 11 | + |
| 12 | +// Socket vs Exec DebugAdapter types |
| 13 | +// https://github.com/Microsoft/vscode/blob/2808feeaf6b24feaaa6ba49fb91ea165c4d5fb06/src/vs/workbench/parts/debug/node/debugger.ts#L58-L61 |
| 14 | +// |
| 15 | +// DebugAdapterExecutable uses stdin/stdout |
| 16 | +// https://github.com/Microsoft/vscode/blob/2808feeaf6b24feaaa6ba49fb91ea165c4d5fb06/src/vs/workbench/parts/debug/node/debugAdapter.ts#L305 |
| 17 | +// |
| 18 | +// DebugAdapterServer uses tcp |
| 19 | +// https://github.com/Microsoft/vscode/blob/2808feeaf6b24feaaa6ba49fb91ea165c4d5fb06/src/vs/workbench/parts/debug/node/debugAdapter.ts#L256 |
| 20 | + |
| 21 | +export class DebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable { |
| 22 | + readonly Context: vscode.ExtensionContext; |
| 23 | + readonly Settings: ISettings; |
| 24 | + readonly Config: IConnectionConfiguration; |
| 25 | + readonly Logger: ILogger; |
| 26 | + |
| 27 | + public ChildProcesses: cp.ChildProcess[] = []; |
| 28 | + |
| 29 | + constructor( |
| 30 | + context: vscode.ExtensionContext, |
| 31 | + settings: ISettings, |
| 32 | + config: IConnectionConfiguration, |
| 33 | + logger: ILogger |
| 34 | + ) { |
| 35 | + this.Context = context; |
| 36 | + this.Settings = settings; |
| 37 | + this.Config = config; |
| 38 | + this.Logger = logger; |
| 39 | + } |
| 40 | + |
| 41 | + public createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable): vscode.ProviderResult<vscode.DebugAdapterDescriptor> { |
| 42 | + // Right now we don't care about session as we only have one type of adapter, which is launch. When |
| 43 | + // we add the ability to attach to a debugger remotely we'll need to switch scenarios based on `session` |
| 44 | + let thisFactory = this; |
| 45 | + |
| 46 | + return new Promise<vscode.DebugAdapterDescriptor>( function(resolve, reject) { |
| 47 | + let debugServer = CommandEnvironmentHelper.getDebugServerRubyEnvFromConfiguration( |
| 48 | + thisFactory.Context.asAbsolutePath(thisFactory.Config.debugServerPath), |
| 49 | + thisFactory.Settings, |
| 50 | + thisFactory.Config, |
| 51 | + ); |
| 52 | + |
| 53 | + let spawn_options: cp.SpawnOptions = {}; |
| 54 | + spawn_options.env = debugServer.options.env; |
| 55 | + spawn_options.stdio = 'pipe'; |
| 56 | + if (process.platform !== 'win32') { spawn_options.shell = true; } |
| 57 | + |
| 58 | + thisFactory.Logger.verbose("Starting the Debug Server with " + debugServer.command + " " + debugServer.args.join(" ")); |
| 59 | + let debugServerProc = cp.spawn(debugServer.command, debugServer.args, spawn_options); |
| 60 | + thisFactory.ChildProcesses.push(debugServerProc); |
| 61 | + |
| 62 | + let debugSessionRunning: boolean = false; |
| 63 | + debugServerProc.stdout.on('data', (data) => { |
| 64 | + thisFactory.Logger.debug("Debug Server STDOUT: " + data.toString()); |
| 65 | + // If the debug client isn't already running and it's sent the trigger text, start up a client |
| 66 | + if ( !debugSessionRunning && (data.toString().match("DEBUG SERVER RUNNING") !== null) ) { |
| 67 | + debugSessionRunning = true; |
| 68 | + |
| 69 | + var p = data.toString().match(/DEBUG SERVER RUNNING (.*):(\d+)/); |
| 70 | + if (p === null) { |
| 71 | + reject("Debug Server started but unable to parse hostname and port"); |
| 72 | + } else { |
| 73 | + thisFactory.Logger.debug("Starting Debug Client connection to " + p[1] + ":" + p[2]); |
| 74 | + resolve(new vscode.DebugAdapterServer(Number(p[2]), p[1])); |
| 75 | + } |
| 76 | + } |
| 77 | + }); |
| 78 | + debugServerProc.on('error', (data) => { |
| 79 | + thisFactory.Logger.error("Debug Srver errored with " + data); |
| 80 | + reject("Spawning Debug Server failed with " + data); |
| 81 | + }); |
| 82 | + debugServerProc.on('close', (exitCode) => { |
| 83 | + thisFactory.Logger.verbose("Debug Server exited with exitcode " + exitCode); |
| 84 | + }); |
| 85 | + }); |
| 86 | + } |
| 87 | + |
| 88 | + public dispose(): any { |
| 89 | + this.ChildProcesses.forEach( (item) => { |
| 90 | + item.kill('SIGHUP'); |
| 91 | + }); |
| 92 | + this.ChildProcesses = []; |
| 93 | + return undefined; |
| 94 | + } |
| 95 | +} |
| 96 | + |
| 97 | +export class DebuggingFeature implements IFeature { |
| 98 | + private factory: DebugAdapterDescriptorFactory; |
| 99 | + |
| 100 | + constructor( |
| 101 | + debugType: string, |
| 102 | + settings: ISettings, |
| 103 | + config: IConnectionConfiguration, |
| 104 | + context: vscode.ExtensionContext, |
| 105 | + logger: ILogger |
| 106 | + ) { |
| 107 | + this.factory = new DebugAdapterDescriptorFactory(context, settings, config, logger); |
| 108 | + |
| 109 | + logger.debug("Registered DebugAdapterDescriptorFactory for " + debugType); |
| 110 | + context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory(debugType, this.factory)); |
| 111 | + } |
| 112 | + |
| 113 | + public dispose(): any { |
| 114 | + if (this.factory !== null) { |
| 115 | + this.factory.dispose(); |
| 116 | + this.factory = null; |
| 117 | + } |
| 118 | + } |
| 119 | +} |
0 commit comments