diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts index 0dc9901b43..d152e544b1 100644 --- a/src/debugAdapter/goDebug.ts +++ b/src/debugAdapter/goDebug.ts @@ -150,6 +150,7 @@ interface DebugThread { pc: number; goroutineID: number; function?: DebugFunction; + ReturnValues: DebugVariable[]; } interface StacktraceOut { @@ -1596,30 +1597,42 @@ export class GoDebugSession extends LoggingDebugSession { protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void { log('EvaluateRequest'); - this.evaluateRequestImpl(args).then( - (out) => { - const variable = this.delve.isApiV1 ? out : (out).Variable; - // #2326: Set the fully qualified name for variable mapping - variable.fullyQualifiedName = variable.name; - response.body = this.convertDebugVariableToProtocolVariable(variable); + const re = new RegExp(/\w+(?=\(.*\))/, 'g'); + if (re.test(args.expression)) { + this.evaluateCallImpl(args).then((out) => { + const state = this.delve.isApiV1 ? out : (out).State; + response.body = this.convertDebugVariableToProtocolVariable(state.currentThread.ReturnValues[0]); this.sendResponse(response); - log('EvaluateResponse'); - }, - (err) => { - let dest: ErrorDestination; - // No need to repeatedly show the error pop-up when expressions - // are continiously reevaluated in the Watch panel, which - // already displays errors. - if (args.context === 'watch') { - dest = null; - } else { - dest = ErrorDestination.User; + log('EvaluateCallResponse'); + }, (err) => { + this.sendErrorResponse(response, 2009, 'Unable to complete call: "{e}"', { e: err.toString() }); + }); + } else { + this.evaluateRequestImpl(args).then( + (out) => { + const variable = this.delve.isApiV1 ? out : (out).Variable; + // #2326: Set the fully qualified name for variable mapping + variable.fullyQualifiedName = variable.name; + response.body = this.convertDebugVariableToProtocolVariable(variable); + this.sendResponse(response); + log('EvaluateResponse'); + }, + (err) => { + let dest: ErrorDestination; + // No need to repeatedly show the error pop-up when expressions + // are continiously reevaluated in the Watch panel, which + // already displays errors. + if (args.context === 'watch') { + dest = null; + } else { + dest = ErrorDestination.User; + } + this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', { + e: err.toString() + }, dest); } - this.sendErrorResponse(response, 2009, 'Unable to eval expression: "{e}"', { - e: err.toString() - }, dest); - } - ); + ); + } } protected setVariableRequest( @@ -2108,6 +2121,54 @@ export class GoDebugSession extends LoggingDebugSession { return this.delve.callPromise('Command', [{ name: 'continue' }]).then(callback, errorCallback); } + private evaluateCallImpl(args: DebugProtocol.EvaluateArguments): Thenable { + // default to the topmost stack frame of the current goroutine + let goroutineId = -1; + let frameId = 0; + // args.frameId won't be specified when evaluating global vars + if (args.frameId) { + [goroutineId, frameId] = this.stackFrameHandles.get(args.frameId); + } + const scope = { + goroutineID: goroutineId, + frame: frameId + }; + const evalSymbolArgs = this.delve.isApiV1 + ? { + symbol: args.expression, + scope + } + : { + Expr: args.expression, + Scope: scope, + Cfg: this.delve.loadConfig, + Unsafe: true + }; + const returnValue = this.delve + .callPromise('Command', [ + { + name: 'call', + returnInfoLoadConfig: this.delve.loadConfig, + expr: evalSymbolArgs.Expr, + unsafe: false, + goroutineID: scope.goroutineID + } + ]) + .then( + (val) => val, + (err) => { + logError( + 'Failed to call function: ', + JSON.stringify(evalSymbolArgs.Expr, null, ' '), + '\n\rCall error:', + err.toString() + ); + return Promise.reject(err); + } + ); + return returnValue; + } + private evaluateRequestImpl(args: DebugProtocol.EvaluateArguments): Thenable { // default to the topmost stack frame of the current goroutine let goroutineId = -1;