Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial switch to breakpoint APIs #1119

Merged
Prev Previous commit
Next Next commit
misc clean up and fixed action script blocks
  • Loading branch information
TylerLeonhardt committed Feb 3, 2020
commit ce6e88d907e84aa664c7b32288b831298a3ae8f5
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@
//

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Logging;
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
using Microsoft.PowerShell.EditorServices.Utility;

namespace Microsoft.PowerShell.EditorServices.Services
{
Expand Down Expand Up @@ -44,7 +39,7 @@ public BreakpointService(

public async Task<List<Breakpoint>> GetBreakpointsAsync()
{
if (VersionUtils.IsPS7OrGreater)
if (BreakpointApiUtils.SupportsBreakpointApis)
{
return BreakpointApiUtils.GetBreakpoints(
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
Expand All @@ -60,7 +55,7 @@ public async Task<List<Breakpoint>> GetBreakpointsAsync()

public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
{
if (VersionUtils.IsPS7OrGreater)
if (BreakpointApiUtils.SupportsBreakpointApis)
{
foreach (BreakpointDetails breakpointDetails in breakpoints)
{
Expand Down Expand Up @@ -150,7 +145,7 @@ public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string esc

public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpoints(IEnumerable<CommandBreakpointDetails> breakpoints)
{
if (VersionUtils.IsPS7OrGreater)
if (BreakpointApiUtils.SupportsBreakpointApis)
{
foreach (CommandBreakpointDetails commandBreakpointDetails in breakpoints)
{
Expand Down Expand Up @@ -226,7 +221,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
{
try
{
if (VersionUtils.IsPS7OrGreater)
if (BreakpointApiUtils.SupportsBreakpointApis)
{
foreach (Breakpoint breakpoint in BreakpointApiUtils.GetBreakpoints(
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
Expand Down Expand Up @@ -266,7 +261,7 @@ public async Task RemoveAllBreakpointsAsync(string scriptPath = null)

public async Task RemoveBreakpointsAsync(IEnumerable<Breakpoint> breakpoints)
{
if (VersionUtils.IsPS7OrGreater)
if (BreakpointApiUtils.SupportsBreakpointApis)
{
foreach (Breakpoint breakpoint in breakpoints)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Utility;

namespace Microsoft.PowerShell.EditorServices.Services.DebugAdapter

Expand Down Expand Up @@ -115,6 +113,14 @@ static BreakpointApiUtils()

#endregion

#region Public Static Properties

// TODO: Try to compute this more dynamically. If we're launching a script in the PSIC, there are APIs are available in PS 5.1 and up.
// For now, only PS7 or greater gets this feature.
public static bool SupportsBreakpointApis => VersionUtils.IsPS7OrGreater;

#endregion

#region Public Static Methods

public static Breakpoint SetBreakpoint(Debugger debugger, BreakpointDetailsBase breakpoint, int? runspaceId = null)
Expand Down Expand Up @@ -172,7 +178,7 @@ public static ScriptBlock GetBreakpointActionScriptBlock(string condition, strin
// In the HitCount only case, this is simple as we can just use the HitCount
// property on the breakpoint object which is represented by $_.
builder.Insert(0, $"if ($_.HitCount -eq {parsedHitCount}) {{ ")
.Append(" }}");
.Append(" }");
}

Interlocked.Increment(ref breakpointHitCounter);
Expand All @@ -181,7 +187,7 @@ public static ScriptBlock GetBreakpointActionScriptBlock(string condition, strin
$"$global:{s_psesGlobalVariableNamePrefix}BreakHitCounter_{breakpointHitCounter}";
TylerLeonhardt marked this conversation as resolved.
Show resolved Hide resolved

builder.Insert(0, $"if (++{globalHitCountVarName} -eq {parsedHitCount}) {{ ")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is out of scope of this PR, but man I'd love to see these global's removed. We should have a "visible but internal" static API that manages stuff like this I think. Something like:

namespace Whatever.EditorServices.Internal
{
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static class BreakpointStateManager
    {
        [Hidden, EditorBrowsable(EditorBrowsableState.Never)]
        public static bool TestHitCount(string hitCountKeyName, int parsedHitCount);
    }
}

.Append(" }}");
.Append(" }");
}

if (!string.IsNullOrWhiteSpace(condition))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request
string fileExtension = Path.GetExtension(scriptFile?.FilePath ?? "")?.ToLower();
bool isUntitledPath = ScriptFile.IsUntitledPath(request.Source.Path);
TylerLeonhardt marked this conversation as resolved.
Show resolved Hide resolved
if ((!isUntitledPath && fileExtension != ".ps1" && fileExtension != ".psm1") ||
(!VersionUtils.IsPS7OrGreater && isUntitledPath))
(!BreakpointApiUtils.SupportsBreakpointApis && isUntitledPath))
{
_logger.LogWarning(
$"Attempted to set breakpoints on a non-PowerShell file: {request.Source.Path}");
Expand All @@ -189,7 +189,8 @@ public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request
(int)srcBreakpoint.Line,
(int?)srcBreakpoint.Column,
srcBreakpoint.Condition,
srcBreakpoint.HitCondition))
srcBreakpoint.HitCondition,
srcBreakpoint.LogMessage))
.ToArray();

// If this is a "run without debugging (Ctrl+F5)" session ignore requests to set breakpoints.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
using Microsoft.PowerShell.EditorServices.Services.PowerShellContext;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using Microsoft.PowerShell.EditorServices.Utility;
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
using OmniSharp.Extensions.DebugAdapter.Protocol.Requests;
using OmniSharp.Extensions.JsonRpc;
Expand Down Expand Up @@ -100,12 +100,16 @@ private async Task LaunchScriptAsync(string scriptToLaunch)
{
ScriptFile untitledScript = _workspaceService.GetFile(scriptToLaunch);

if (VersionUtils.IsPS7OrGreater)
if (BreakpointApiUtils.SupportsBreakpointApis)
{
// Parse untitled files with their `Untitled:` URI as the file name which will cache the URI & contents within the PowerShell parser.
// By doing this, we light up the ability to debug Untitled files with breakpoints.
// This is only possible via the direct usage of the breakpoint APIs in PowerShell because
// Set-PSBreakpoint validates that paths are actually on the filesystem.
ScriptBlockAst ast = Parser.ParseInput(untitledScript.Contents, untitledScript.DocumentUri, out Token[] tokens, out ParseError[] errors);

// This seems to be the simplest way to invoke a script block (which contains breakpoint information) via the PowerShell API.
PSCommand cmd = new PSCommand().AddScript("& $args[0]").AddArgument(ast.GetScriptBlock());
var cmd = new PSCommand().AddScript("& $args[0]").AddArgument(ast.GetScriptBlock());
TylerLeonhardt marked this conversation as resolved.
Show resolved Hide resolved
await _powerShellContextService
.ExecuteCommandAsync<object>(cmd, sendOutputToHost: true, sendErrorToHost:true)
.ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public async Task<InitializeResponse> Handle(InitializeRequestArguments request,
// Now send the Initialize response to continue setup
return new InitializeResponse
{
SupportsConditionalBreakpoints = true,
SupportsConfigurationDoneRequest = true,
SupportsFunctionBreakpoints = true,
SupportsConditionalBreakpoints = true,
SupportsHitConditionalBreakpoints = true,
SupportsLogPoints = true,
SupportsSetVariable = true
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Logging;
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.PowerShellContext;
using OmniSharp.Extensions.DebugAdapter.Protocol.Events;
Expand Down Expand Up @@ -370,6 +371,10 @@ await _powerShellContextService.ExecuteScriptStringAsync(
.ExecuteScriptStringAsync(debugRunspaceCmd)
.ContinueWith(OnExecutionCompletedAsync);

if (runspaceVersion.Version.Major >= 7)
{
_jsonRpcServer.SendNotification(EventNames.Initialized);
}
return Unit.Value;
}

Expand All @@ -387,33 +392,33 @@ private async Task OnExecutionCompletedAsync(Task executeTask)

_logger.LogTrace("Execution completed, terminating...");

//_debugStateService.ExecutionCompleted = true;

//_debugEventHandlerService.UnregisterEventHandlers();

//if (_debugStateService.IsAttachSession)
//{
// // Pop the sessions
// if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess)
// {
// try
// {
// await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSHostProcess");

// if (_debugStateService.IsRemoteAttach &&
// _powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote)
// {
// await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSSession");
// }
// }
// catch (Exception e)
// {
// _logger.LogException("Caught exception while popping attached process after debugging", e);
// }
// }
//}

//_debugService.IsClientAttached = false;
_debugStateService.ExecutionCompleted = true;

_debugEventHandlerService.UnregisterEventHandlers();

if (_debugStateService.IsAttachSession)
{
// Pop the sessions
if (_powerShellContextService.CurrentRunspace.Context == RunspaceContext.EnteredProcess)
{
try
{
await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSHostProcess");

if (_debugStateService.IsRemoteAttach &&
_powerShellContextService.CurrentRunspace.Location == RunspaceLocation.Remote)
{
await _powerShellContextService.ExecuteScriptStringAsync("Exit-PSSession");
}
}
catch (Exception e)
{
_logger.LogException("Caught exception while popping attached process after debugging", e);
}
}
}

_debugService.IsClientAttached = false;
_jsonRpcServer.SendNotification(EventNames.Terminated);
}
}
Expand Down