From b5076b09aa65d09a594d5ffc7c7cb2415fcae50b Mon Sep 17 00:00:00 2001 From: David Wilson Date: Fri, 24 Mar 2017 16:57:31 -0700 Subject: [PATCH] Report column number in stack frame when debugger stops This change causes the column number of the debugger's currently stopped position to be passed through with the top stack frame. This allows VS Code's debugger UI to show an indicator that points to where the debugger stopped when stepping through code. Resolve PowerShell/vscode-powershell#616 --- .../DebugAdapter/StackFrame.cs | 10 ++++++-- .../DebugAdapter/StoppedEvent.cs | 4 ---- .../Server/DebugAdapter.cs | 2 -- .../Debugging/DebugService.cs | 24 +++++++++++++++++++ .../Debugging/StackFrameDetails.cs | 20 ++++++++++++---- .../DebugAdapterTests.cs | 16 +++++++++++-- 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs index 910472c86..d9ba5e8c4 100644 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs +++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/StackFrame.cs @@ -15,8 +15,12 @@ public class StackFrame public int Line { get; set; } + public int? EndLine { get; set; } + public int Column { get; set; } + public int? EndColumn { get; set; } + // /** An identifier for the stack frame. */ //id: number; ///** The name of the stack frame, typically a method name */ @@ -38,8 +42,10 @@ public static StackFrame Create( { Id = id, Name = stackFrame.FunctionName, - Line = stackFrame.LineNumber, - Column = stackFrame.ColumnNumber, + Line = stackFrame.StartLineNumber, + EndLine = stackFrame.EndLineNumber > 0 ? (int?)stackFrame.EndLineNumber : null, + Column = stackFrame.StartColumnNumber, + EndColumn = stackFrame.EndColumnNumber > 0 ? (int?)stackFrame.EndColumnNumber : null, Source = new Source { Path = stackFrame.ScriptPath diff --git a/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs b/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs index b0850ddb6..8d88a1e5a 100644 --- a/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs +++ b/src/PowerShellEditorServices.Protocol/DebugAdapter/StoppedEvent.cs @@ -28,10 +28,6 @@ public class StoppedEventBody public Source Source { get; set; } - public int Line { get; set; } - - public int Column { get; set; } - /// /// Gets or sets additional information such as an error message. /// diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index 29142a83f..909ea977c 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -857,8 +857,6 @@ await this.SendEvent( { Path = e.ScriptPath, }, - Line = e.LineNumber, - Column = e.ColumnNumber, ThreadId = 1, Reason = debuggerStoppedReason }); diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 20ac0f04c..81d042eea 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Debugging/DebugService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.PowerShell.EditorServices.Debugging; @@ -41,6 +42,7 @@ public class DebugService private VariableContainerDetails globalScopeVariables; private VariableContainerDetails scriptScopeVariables; private StackFrameDetails[] stackFrameDetails; + private PropertyInfo invocationTypeScriptPositionProperty; private static int breakpointHitCounter = 0; @@ -81,6 +83,12 @@ public DebugService( this.powerShellContext.BreakpointUpdated += this.OnBreakpointUpdated; this.remoteFileManager = remoteFileManager; + + this.invocationTypeScriptPositionProperty = + typeof(InvocationInfo) + .GetProperty( + "ScriptPosition", + BindingFlags.NonPublic | BindingFlags.Instance); } #endregion @@ -1100,6 +1108,22 @@ await this.remoteFileManager.FetchRemoteFile( this.powerShellContext.CurrentRunspace); } + if (this.stackFrameDetails.Length > 0) + { + // Augment the top stack frame with details from the stop event + IScriptExtent scriptExtent = + this.invocationTypeScriptPositionProperty + .GetValue(e.InvocationInfo) as IScriptExtent; + + if (scriptExtent != null) + { + this.stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber; + this.stackFrameDetails[0].EndLineNumber = scriptExtent.EndLineNumber; + this.stackFrameDetails[0].StartColumnNumber = scriptExtent.StartColumnNumber; + this.stackFrameDetails[0].EndColumnNumber = scriptExtent.EndColumnNumber; + } + } + // Notify the host that the debugger is stopped this.DebuggerStopped?.Invoke( sender, diff --git a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs index 8b8a07eed..df8ffecd4 100644 --- a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs +++ b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs @@ -35,15 +35,25 @@ public class StackFrameDetails /// public string FunctionName { get; private set; } + /// + /// Gets the start line number of the script where the stack frame occurred. + /// + public int StartLineNumber { get; internal set; } + /// /// Gets the line number of the script where the stack frame occurred. /// - public int LineNumber { get; private set; } + public int EndLineNumber { get; internal set; } + + /// + /// Gets the start column number of the line where the stack frame occurred. + /// + public int StartColumnNumber { get; internal set; } /// - /// Gets the column number of the line where the stack frame occurred. + /// Gets the end column number of the line where the stack frame occurred. /// - public int ColumnNumber { get; private set; } + public int EndColumnNumber { get; internal set; } /// /// Gets or sets the VariableContainerDetails that contains the auto variables. @@ -82,8 +92,8 @@ static internal StackFrameDetails Create( { ScriptPath = (callStackFrameObject.Properties["ScriptName"].Value as string) ?? NoFileScriptPath, FunctionName = callStackFrameObject.Properties["FunctionName"].Value as string, - LineNumber = (int)(callStackFrameObject.Properties["ScriptLineNumber"].Value ?? 0), - ColumnNumber = 0, // Column number isn't given in PowerShell stack frames + StartLineNumber = (int)(callStackFrameObject.Properties["ScriptLineNumber"].Value ?? 0), + StartColumnNumber = 0, // Column number isn't given in PowerShell stack frames AutoVariables = autoVariables, LocalVariables = localVariables }; diff --git a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs index bc2f45504..2d5fc905a 100644 --- a/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs +++ b/test/PowerShellEditorServices.Test.Host/DebugAdapterTests.cs @@ -85,13 +85,25 @@ await this.SendRequest( // Wait for a couple breakpoints StoppedEventBody stoppedDetails = await breakEventTask; Assert.Equal(DebugScriptPath, stoppedDetails.Source.Path); - Assert.Equal(5, stoppedDetails.Line); + + var stackTraceResponse = + await this.SendRequest( + StackTraceRequest.Type, + new StackTraceRequestArguments()); + + Assert.Equal(5, stackTraceResponse.StackFrames[0].Line); breakEventTask = this.WaitForEvent(StoppedEvent.Type); await this.SendRequest(ContinueRequest.Type, new object()); stoppedDetails = await breakEventTask; Assert.Equal(DebugScriptPath, stoppedDetails.Source.Path); - Assert.Equal(7, stoppedDetails.Line); + + stackTraceResponse = + await this.SendRequest( + StackTraceRequest.Type, + new StackTraceRequestArguments()); + + Assert.Equal(7, stackTraceResponse.StackFrames[0].Line); // Abort script execution await this.SendRequest(DisconnectRequest.Type, new object());