Skip to content

Commit

Permalink
hook up bp sync services
Browse files Browse the repository at this point in the history
  • Loading branch information
SeeminglyScience committed Jul 8, 2022
1 parent 5e7a614 commit ef47274
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 12 deletions.
6 changes: 5 additions & 1 deletion src/PowerShellEditorServices/Server/PsesLanguageServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public async Task StartAsync()
.AddLanguageProtocolLogging()
.SetMinimumLevel(_minimumLogLevel))
// TODO: Consider replacing all WithHandler with AddSingleton
.WithHandler<ClientBreakpointHandler>()
.WithHandler<PsesWorkspaceSymbolsHandler>()
.WithHandler<PsesTextDocumentHandler>()
.WithHandler<GetVersionHandler>()
Expand Down Expand Up @@ -151,9 +152,12 @@ public async Task StartAsync()
LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value<bool>() ?? true,
// TODO: Consider deprecating the setting which sets this and
// instead use WorkspacePath exclusively.
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath,

SupportsBreakpointSync = initializationOptions?.GetValue("supportsBreakpointSync")?.Value<bool>() ?? false,
};

languageServer.Services.GetService<BreakpointSyncService>().IsSupported = hostStartOptions.SupportsBreakpointSync;
_psesHost = languageServer.Services.GetService<PsesInternalHost>();
return _psesHost.TryStartAsync(hostStartOptions, cancellationToken);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public static IServiceCollection AddPsesLanguageServices(
.AddSingleton<TemplateService>()
.AddSingleton<EditorOperationsService>()
.AddSingleton<RemoteFileManagerService>()
.AddSingleton<BreakpointService>()
.AddSingleton<DebugStateService>()
.AddSingleton<BreakpointSyncService>()
.AddSingleton((provider) =>
{
ExtensionService extensionService = new(
Expand All @@ -61,18 +64,22 @@ public static IServiceCollection AddPsesDebugServices(
PsesDebugServer psesDebugServer)
{
PsesInternalHost internalHost = languageServiceProvider.GetService<PsesInternalHost>();
DebugStateService debugStateService = languageServiceProvider.GetService<DebugStateService>();
BreakpointService breakpointService = languageServiceProvider.GetService<BreakpointService>();
BreakpointSyncService breakpointSyncService = languageServiceProvider.GetService<BreakpointSyncService>();

return collection
.AddSingleton(internalHost)
.AddSingleton<IRunspaceContext>(internalHost)
.AddSingleton<IPowerShellDebugContext>(internalHost.DebugContext)
.AddSingleton(breakpointSyncService)
.AddSingleton(languageServiceProvider.GetService<IInternalPowerShellExecutionService>())
.AddSingleton(languageServiceProvider.GetService<WorkspaceService>())
.AddSingleton(languageServiceProvider.GetService<RemoteFileManagerService>())
.AddSingleton(psesDebugServer)
.AddSingleton<DebugService>()
.AddSingleton<BreakpointService>()
.AddSingleton<DebugStateService>()
.AddSingleton(breakpointService)
.AddSingleton(debugStateService)
.AddSingleton<DebugEventHandlerService>();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal class BreakpointService
private readonly IInternalPowerShellExecutionService _executionService;
private readonly PsesInternalHost _editorServicesHost;
private readonly DebugStateService _debugStateService;
private readonly BreakpointSyncService _breakpointSyncService;

// TODO: This needs to be managed per nested session
internal readonly Dictionary<string, HashSet<Breakpoint>> BreakpointsPerFile = new();
Expand All @@ -32,12 +33,14 @@ public BreakpointService(
ILoggerFactory factory,
IInternalPowerShellExecutionService executionService,
PsesInternalHost editorServicesHost,
DebugStateService debugStateService)
DebugStateService debugStateService,
BreakpointSyncService breakpointSyncService)
{
_logger = factory.CreateLogger<BreakpointService>();
_executionService = executionService;
_editorServicesHost = editorServicesHost;
_debugStateService = debugStateService;
_breakpointSyncService = breakpointSyncService;
}

public async Task<List<Breakpoint>> GetBreakpointsAsync()
Expand All @@ -61,6 +64,23 @@ public async Task<List<Breakpoint>> GetBreakpointsAsync()

public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
{
if (_breakpointSyncService.IsSupported)
{
// Since we're syncing breakpoints outside of debug configurations, if we can't find
// an existing breakpoint then mark it as unverified.
foreach (BreakpointDetails details in breakpoints)
{
if (_breakpointSyncService.TryGetBreakpointByServerId(details.Id, out SyncedBreakpoint syncedBreakpoint))
{
continue;
}

details.Verified = false;
}

return breakpoints.ToArray();
}

if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
{
foreach (BreakpointDetails breakpointDetails in breakpoints)
Expand Down Expand Up @@ -229,6 +249,13 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAs
/// </summary>
public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
{
// Only need to remove all breakpoints if we're not able to sync outside of debug
// sessions.
if (_breakpointSyncService.IsSupported)
{
return;
}

try
{
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,20 @@ internal class DebugStateService
internal int ReleaseSetBreakpointHandle() => _setBreakpointInProgressHandle.Release();

internal Task WaitForSetBreakpointHandleAsync() => _setBreakpointInProgressHandle.WaitAsync();

internal void Reset()
{
NoDebug = false;
Arguments = null;
IsRemoteAttach = false;
RunspaceId = null;
IsAttachSession = false;
WaitingForAttach = false;
ScriptToLaunch = null;
ExecutionCompleted = false;
IsInteractiveDebugSession = false;
IsUsingTempIntegratedConsole = false;
ServerStarted = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@ internal static class BreakpointApiUtils

private static readonly Lazy<Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint>> s_setCommandBreakpointLazy;

private static readonly Lazy<Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>> s_setVariableBreakpointLazy;

private static readonly Lazy<Func<Debugger, int?, List<Breakpoint>>> s_getBreakpointsLazy;

private static readonly Lazy<Func<Debugger, Breakpoint, int?, bool>> s_removeBreakpointLazy;

private static readonly Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>> s_disableBreakpointLazy;

private static readonly Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>> s_enableBreakpointLazy;

private static readonly Lazy<Action<Debugger, IEnumerable<Breakpoint>, int?>> s_setBreakpointsLazy;

private static readonly Version s_minimumBreakpointApiPowerShellVersion = new(7, 0, 0, 0);

private static int breakpointHitCounter;
Expand Down Expand Up @@ -67,6 +75,17 @@ static BreakpointApiUtils()
setCommandBreakpointMethod);
});

s_setVariableBreakpointLazy = new Lazy<Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>>(() =>
{
Type[] setVariableBreakpointParameters = new[] { typeof(string), typeof(VariableAccessMode), typeof(ScriptBlock), typeof(string), typeof(int?) };
MethodInfo setVariableBreakpointMethod = typeof(Debugger).GetMethod("SetVariableBreakpoint", setVariableBreakpointParameters);

return (Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>)Delegate.CreateDelegate(
typeof(Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>),
firstArgument: null,
setVariableBreakpointMethod);
});

s_getBreakpointsLazy = new Lazy<Func<Debugger, int?, List<Breakpoint>>>(() =>
{
Type[] getBreakpointsParameters = new[] { typeof(int?) };
Expand All @@ -88,19 +107,60 @@ static BreakpointApiUtils()
firstArgument: null,
removeBreakpointMethod);
});

s_disableBreakpointLazy = new Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>>(() =>
{
Type[] disableBreakpointParameters = new[] { typeof(Breakpoint), typeof(int?) };
MethodInfo disableBreakpointMethod = typeof(Debugger).GetMethod("DisableBreakpoint", disableBreakpointParameters);

return (Func<Debugger, Breakpoint, int?, Breakpoint>)Delegate.CreateDelegate(
typeof(Func<Debugger, Breakpoint, int?, Breakpoint>),
firstArgument: null,
disableBreakpointMethod);
});

s_enableBreakpointLazy = new Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>>(() =>
{
Type[] enableBreakpointParameters = new[] { typeof(Breakpoint), typeof(int?) };
MethodInfo enableBreakpointMethod = typeof(Debugger).GetMethod("EnableBreakpoint", enableBreakpointParameters);

return (Func<Debugger, Breakpoint, int?, Breakpoint>)Delegate.CreateDelegate(
typeof(Func<Debugger, Breakpoint, int?, Breakpoint>),
firstArgument: null,
enableBreakpointMethod);
});

s_setBreakpointsLazy = new Lazy<Action<Debugger, IEnumerable<Breakpoint>, int?>>(() =>
{
Type[] setBreakpointsParameters = new[] { typeof(IEnumerable<Breakpoint>), typeof(int?) };
MethodInfo setBreakpointsMethod = typeof(Debugger).GetMethod("SetBreakpoints", setBreakpointsParameters);

return (Action<Debugger, IEnumerable<Breakpoint>, int?>)Delegate.CreateDelegate(
typeof(Action<Debugger, IEnumerable<Breakpoint>, int?>),
firstArgument: null,
setBreakpointsMethod);
});
}

#endregion

#region Private Static Properties

private static Func<Debugger, string, int, int, ScriptBlock, int?, LineBreakpoint> SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value;
internal static Func<Debugger, string, int, int, ScriptBlock, int?, LineBreakpoint> SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value;

internal static Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint> SetCommandBreakpointDelegate => s_setCommandBreakpointLazy.Value;

internal static Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint> SetVariableBreakpointDelegate => s_setVariableBreakpointLazy.Value;

internal static Func<Debugger, int?, List<Breakpoint>> GetBreakpointsDelegate => s_getBreakpointsLazy.Value;

internal static Func<Debugger, Breakpoint, int?, bool> RemoveBreakpointDelegate => s_removeBreakpointLazy.Value;

private static Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint> SetCommandBreakpointDelegate => s_setCommandBreakpointLazy.Value;
internal static Func<Debugger, Breakpoint, int?, Breakpoint> DisableBreakpointDelegate => s_disableBreakpointLazy.Value;

private static Func<Debugger, int?, List<Breakpoint>> GetBreakpointsDelegate => s_getBreakpointsLazy.Value;
internal static Func<Debugger, Breakpoint, int?, Breakpoint> EnableBreakpointDelegate => s_enableBreakpointLazy.Value;

private static Func<Debugger, Breakpoint, int?, bool> RemoveBreakpointDelegate => s_removeBreakpointLazy.Value;
internal static Action<Debugger, IEnumerable<Breakpoint>, int?> SetBreakpointsDelegate => s_setBreakpointsLazy.Value;

#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,46 @@ internal class BreakpointHandlers : ISetFunctionBreakpointsHandler, ISetBreakpoi
private readonly DebugStateService _debugStateService;
private readonly WorkspaceService _workspaceService;
private readonly IRunspaceContext _runspaceContext;
private readonly BreakpointSyncService _breakpointSyncService;

public BreakpointHandlers(
ILoggerFactory loggerFactory,
DebugService debugService,
DebugStateService debugStateService,
WorkspaceService workspaceService,
IRunspaceContext runspaceContext)
IRunspaceContext runspaceContext,
BreakpointSyncService breakpointSyncService)
{
_logger = loggerFactory.CreateLogger<BreakpointHandlers>();
_debugService = debugService;
_debugStateService = debugStateService;
_workspaceService = workspaceService;
_runspaceContext = runspaceContext;
_breakpointSyncService = breakpointSyncService;
}

public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request, CancellationToken cancellationToken)
{
if (_breakpointSyncService.IsSupported)
{
// TODO: Find some way to actually verify these. Unfortunately the sync event comes
// through *after* the `SetBreakpoints` event so we can't just look at what synced
// breakpoints were successfully set.
return new SetBreakpointsResponse()
{
Breakpoints = new Container<Breakpoint>(
request.Breakpoints
.Select(sbp => new Breakpoint()
{
Line = sbp.Line,
Column = sbp.Column,
Verified = true,
Source = request.Source,
Id = 0,
})),
};
}

if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile))
{
string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set.";
Expand Down Expand Up @@ -125,6 +148,21 @@ await _debugService.SetLineBreakpointsAsync(

public async Task<SetFunctionBreakpointsResponse> Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken)
{
if (_breakpointSyncService.IsSupported)
{
return new SetFunctionBreakpointsResponse()
{
Breakpoints = new Container<Breakpoint>(
_breakpointSyncService.GetSyncedBreakpoints()
.Where(sbp => sbp.Client.FunctionName is not null and not "")
.Select(sbp => new Breakpoint()
{
Verified = true,
Message = sbp.Client.FunctionName,
})),
};
}

CommandBreakpointDetails[] breakpointDetails = request.Breakpoints
.Select((funcBreakpoint) => CommandBreakpointDetails.Create(
funcBreakpoint.Name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Remoting;
using System.Management.Automation.Runspaces;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -90,6 +91,7 @@ internal class LaunchAndAttachHandler : ILaunchHandler<PsesLaunchRequestArgument
private static readonly Version s_minVersionForCustomPipeName = new(6, 2);
private readonly ILogger<LaunchAndAttachHandler> _logger;
private readonly BreakpointService _breakpointService;
private readonly BreakpointSyncService _breakpointSyncService;
private readonly DebugService _debugService;
private readonly IRunspaceContext _runspaceContext;
private readonly IInternalPowerShellExecutionService _executionService;
Expand All @@ -102,6 +104,7 @@ public LaunchAndAttachHandler(
ILoggerFactory factory,
IDebugAdapterServerFacade debugAdapterServer,
BreakpointService breakpointService,
BreakpointSyncService breakpointSyncService,
DebugEventHandlerService debugEventHandlerService,
DebugService debugService,
IRunspaceContext runspaceContext,
Expand All @@ -112,6 +115,7 @@ public LaunchAndAttachHandler(
_logger = factory.CreateLogger<LaunchAndAttachHandler>();
_debugAdapterServer = debugAdapterServer;
_breakpointService = breakpointService;
_breakpointSyncService = breakpointSyncService;
_debugEventHandlerService = debugEventHandlerService;
_debugService = debugService;
_runspaceContext = runspaceContext;
Expand Down Expand Up @@ -237,6 +241,12 @@ public async Task<AttachResponse> Handle(PsesAttachRequestArguments request, Can

private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request, CancellationToken cancellationToken)
{
cancellationToken.Register(() =>
{
if (Runspace.DefaultRunspace != null)
{
}
});
// The debugger has officially started. We use this to later check if we should stop it.
((PsesInternalHost)_executionService).DebugContext.IsActive = true;

Expand Down Expand Up @@ -431,7 +441,15 @@ await _executionService.ExecutePSCommandAsync(
}

// Clear any existing breakpoints before proceeding
await _breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false);
if (_breakpointSyncService.IsSupported)
{
_breakpointSyncService.SyncServerAfterAttach();
}
else
{
await _breakpointService.RemoveAllBreakpointsAsync()
.ConfigureAwait(continueOnCapturedContext: false);
}

_debugStateService.WaitingForAttach = true;
Task nonAwaitedTask = _executionService
Expand Down Expand Up @@ -515,6 +533,7 @@ await _executionService.ExecutePSCommandAsync(

_debugService.IsClientAttached = false;
_debugAdapterServer.SendNotification(EventNames.Terminated);
_debugStateService.Reset();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ internal struct HostStartOptions
public bool LoadProfiles { get; set; }

public string InitialWorkingDirectory { get; set; }

public bool SupportsBreakpointSync { get; set; }
}
}
Loading

0 comments on commit ef47274

Please sign in to comment.