import * as vscode from 'vscode' ; import { BazelConfiguration , BzlConfiguration , StarlarkDebuggerConfiguration , StarlarkDebuggerSettings , } from './configuration' ; import { CommandName } from './constants' ; import { Settings } from './settings' ; import { LaunchableComponent , LaunchArgs , Status , StatusError } from './status' ; import { SocketDebugClient , LogLevel } from 'node-debugprotocol-client' ; import { isDefined } from 'vscode-common/out/types' ; export class StarlarkDebugger extends LaunchableComponent < StarlarkDebuggerConfiguration > implements vscode . DebugAdapterDescriptorFactory , vscode . DebugConfigurationProvider { constructor ( public readonly settings , private readonly bazelSettings , public readonly bzlSettings , private readonly workspaceFolder ) { super ( 'SDB' , settings , CommandName . LaunchDebugAdapter , 'starlark-debug-adapter' ) ; this . disposables . push ( vscode . debug . registerDebugAdapterDescriptorFactory ( 'starlark' , this ) ) ; this . disposables . push ( vscode . debug . registerDebugConfigurationProvider ( 'starlark' , this ) ) ; vscode . commands . registerCommand ( CommandName . AskForDebugTargetLabel , this . handleCommandAskForDebugTargetLabel , this . disposables ) ; } async handleCommandAskForDebugTargetLabel ( ) { return vscode . window . showInputBox ( { placeHolder : 'Please enter the label of bazel build target for the debug session' , value : '//:your_build_target_here' } ) ; } param command param label returns async invoke ( command , label ) { const debugSettings = await this . settings . get ( ) ; let folder = undefined ; if ( this . workspaceFolder ) { folder = vscode . workspace . getWorkspaceFolder ( this . workspaceFolder ) ; } return vscode . debug . startDebugging ( folder , { type : 'starlark' , name : 'Launch to a Starlark Debug Session for ' + label , request : 'launch' , targetLabel : label , debugServerHost : debugSettings . debugAdapterHost , debugServerPort : debugSettings . debugAdapterPort } ) ; } @override
      async shouldLaunch ( e ) { if ( ! this . workspaceFolder ) { vscode . window . showWarningMessage ( 'Cannot launch debug adapter in folder without a workspace.' ) ; return false ; } const cfg = await this . settings . get ( ) ; return cfg . autoLaunch ; } @override
      async launchInternal ( ) { const cfg = await this . settings . get ( ) ; const client = new SocketDebugClient ( { port : cfg . debugAdapterPort , host : cfg . debugAdapterHost , logLevel : LogLevel . On , loggerName : 'Starlark Debug Adapter Client' } ) ; try { await client . connectAdapter ( ) ; } catch ( e ) { if ( isTCPConnectionError ( e ) && e . code === 'ECONNREFUSED' ) { throw new StatusError ( 'Debug Adapter is not running' , Status . STOPPED ) ; } else { throw e ; } } finally { client . disconnectAdapter ( ) ; } } async getLaunchArgs ( ) { const cfg = await this . settings . get ( ) ; const args = [ cfg . debugAdapterExecutable , ... cfg . debugAdapterCommand , `--address=${ cfg . debugAdapterHost }:${ cfg . debugAdapterPort }` , `--debug_working_directory=${ this . workspaceFolder ! . fsPath }` , ] ; if ( cfg . debugServerHost ) { args . push ( `--debug_server_host=${ cfg . debugServerHost }` ) ; } if ( cfg . debugServerPort ) { args . push ( `--debug_server_port=${ cfg . debugServerPort }` ) ; } return { command : args . map ( a => a . replace ( '${workspaceFolder}' , this . workspaceFolder ! . fsPath ) ) , showSuccessfulLaunchTerminal : false , showFailedLaunchTerminal : true , } ; } handleLaunchFailed ( launchArgs , terminal ) { super . handleLaunchFailed ( launchArgs , terminal ) ; } async createDebugAdapterDescriptor ( session , executable ) { return this . createDebugAdapterServerDescriptor ( session , executable ) ; } async createDebugAdapterServerDescriptor ( session , executable ) { const cfg = await this . settings . get ( ) ; return new vscode . DebugAdapterServer ( cfg . debugAdapterPort , cfg . debugAdapterHost ) ; } /**
     * Massage a debug configuration just before a debug session is being
     * launched, e.g. add all missing attributes to the debug configuration.
     */ async resolveDebugConfiguration ( folder , config , token ? ) { return config ; } param folder param debugConfiguration param token return async resolveDebugConfigurationWithSubstitutedVariables ( folder , config , token ? ) { if ( config . request !== 'launch' ) { return config ; } if ( this . status !== Status . READY && this . status !== Status . DISABLED ) { await this . handleCommandLaunch ( ) ; this . restart ( ) ; } if ( ! config . targetLabel ) { config . targetLabel = await this . handleCommandAskForDebugTargetLabel ( ) ; } if ( ! config . targetLabel ) { vscode . window . showInformationMessage ( 'A label for the "bazel build" command is required.  Please add it to your launch configuration.' ) ; return ; } const bazelSettings = await this . bazelSettings . get ( ) ; const flags = bazelSettings . starlarkDebugFlags || [ ] ; const extraFlags = config . extraBazelFlags || [ ] ; await vscode . commands . executeCommand ( CommandName . Invoke , [ 'build' , config . targetLabel , ... flags , ... extraFlags ] . filter ( arg => isDefined ( arg ) ) ) ; return config ; } } interface TCPConnectionError { code : string ; errno : number ; syscall : string ; address : string ; port : number ; } export function isTCPConnectionError ( e ) { return ( e as TCPConnectionError ) . code !== undefined ; }