Skip to content

Commit ef47274

Browse files
hook up bp sync services
1 parent 5e7a614 commit ef47274

File tree

12 files changed

+271
-12
lines changed

12 files changed

+271
-12
lines changed

src/PowerShellEditorServices/Server/PsesLanguageServer.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public async Task StartAsync()
8787
.AddLanguageProtocolLogging()
8888
.SetMinimumLevel(_minimumLogLevel))
8989
// TODO: Consider replacing all WithHandler with AddSingleton
90+
.WithHandler<ClientBreakpointHandler>()
9091
.WithHandler<PsesWorkspaceSymbolsHandler>()
9192
.WithHandler<PsesTextDocumentHandler>()
9293
.WithHandler<GetVersionHandler>()
@@ -151,9 +152,12 @@ public async Task StartAsync()
151152
LoadProfiles = initializationOptions?.GetValue("enableProfileLoading")?.Value<bool>() ?? true,
152153
// TODO: Consider deprecating the setting which sets this and
153154
// instead use WorkspacePath exclusively.
154-
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath
155+
InitialWorkingDirectory = initializationOptions?.GetValue("initialWorkingDirectory")?.Value<string>() ?? workspaceService.WorkspacePath,
156+
157+
SupportsBreakpointSync = initializationOptions?.GetValue("supportsBreakpointSync")?.Value<bool>() ?? false,
155158
};
156159

160+
languageServer.Services.GetService<BreakpointSyncService>().IsSupported = hostStartOptions.SupportsBreakpointSync;
157161
_psesHost = languageServer.Services.GetService<PsesInternalHost>();
158162
return _psesHost.TryStartAsync(hostStartOptions, cancellationToken);
159163
});

src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public static IServiceCollection AddPsesLanguageServices(
3636
.AddSingleton<TemplateService>()
3737
.AddSingleton<EditorOperationsService>()
3838
.AddSingleton<RemoteFileManagerService>()
39+
.AddSingleton<BreakpointService>()
40+
.AddSingleton<DebugStateService>()
41+
.AddSingleton<BreakpointSyncService>()
3942
.AddSingleton((provider) =>
4043
{
4144
ExtensionService extensionService = new(
@@ -61,18 +64,22 @@ public static IServiceCollection AddPsesDebugServices(
6164
PsesDebugServer psesDebugServer)
6265
{
6366
PsesInternalHost internalHost = languageServiceProvider.GetService<PsesInternalHost>();
67+
DebugStateService debugStateService = languageServiceProvider.GetService<DebugStateService>();
68+
BreakpointService breakpointService = languageServiceProvider.GetService<BreakpointService>();
69+
BreakpointSyncService breakpointSyncService = languageServiceProvider.GetService<BreakpointSyncService>();
6470

6571
return collection
6672
.AddSingleton(internalHost)
6773
.AddSingleton<IRunspaceContext>(internalHost)
6874
.AddSingleton<IPowerShellDebugContext>(internalHost.DebugContext)
75+
.AddSingleton(breakpointSyncService)
6976
.AddSingleton(languageServiceProvider.GetService<IInternalPowerShellExecutionService>())
7077
.AddSingleton(languageServiceProvider.GetService<WorkspaceService>())
7178
.AddSingleton(languageServiceProvider.GetService<RemoteFileManagerService>())
7279
.AddSingleton(psesDebugServer)
7380
.AddSingleton<DebugService>()
74-
.AddSingleton<BreakpointService>()
75-
.AddSingleton<DebugStateService>()
81+
.AddSingleton(breakpointService)
82+
.AddSingleton(debugStateService)
7683
.AddSingleton<DebugEventHandlerService>();
7784
}
7885
}

src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ internal class BreakpointService
2222
private readonly IInternalPowerShellExecutionService _executionService;
2323
private readonly PsesInternalHost _editorServicesHost;
2424
private readonly DebugStateService _debugStateService;
25+
private readonly BreakpointSyncService _breakpointSyncService;
2526

2627
// TODO: This needs to be managed per nested session
2728
internal readonly Dictionary<string, HashSet<Breakpoint>> BreakpointsPerFile = new();
@@ -32,12 +33,14 @@ public BreakpointService(
3233
ILoggerFactory factory,
3334
IInternalPowerShellExecutionService executionService,
3435
PsesInternalHost editorServicesHost,
35-
DebugStateService debugStateService)
36+
DebugStateService debugStateService,
37+
BreakpointSyncService breakpointSyncService)
3638
{
3739
_logger = factory.CreateLogger<BreakpointService>();
3840
_executionService = executionService;
3941
_editorServicesHost = editorServicesHost;
4042
_debugStateService = debugStateService;
43+
_breakpointSyncService = breakpointSyncService;
4144
}
4245

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

6265
public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
6366
{
67+
if (_breakpointSyncService.IsSupported)
68+
{
69+
// Since we're syncing breakpoints outside of debug configurations, if we can't find
70+
// an existing breakpoint then mark it as unverified.
71+
foreach (BreakpointDetails details in breakpoints)
72+
{
73+
if (_breakpointSyncService.TryGetBreakpointByServerId(details.Id, out SyncedBreakpoint syncedBreakpoint))
74+
{
75+
continue;
76+
}
77+
78+
details.Verified = false;
79+
}
80+
81+
return breakpoints.ToArray();
82+
}
83+
6484
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))
6585
{
6686
foreach (BreakpointDetails breakpointDetails in breakpoints)
@@ -229,6 +249,13 @@ public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpointsAs
229249
/// </summary>
230250
public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
231251
{
252+
// Only need to remove all breakpoints if we're not able to sync outside of debug
253+
// sessions.
254+
if (_breakpointSyncService.IsSupported)
255+
{
256+
return;
257+
}
258+
232259
try
233260
{
234261
if (BreakpointApiUtils.SupportsBreakpointApis(_editorServicesHost.CurrentRunspace))

src/PowerShellEditorServices/Services/DebugAdapter/DebugStateService.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,20 @@ internal class DebugStateService
4040
internal int ReleaseSetBreakpointHandle() => _setBreakpointInProgressHandle.Release();
4141

4242
internal Task WaitForSetBreakpointHandleAsync() => _setBreakpointInProgressHandle.WaitAsync();
43+
44+
internal void Reset()
45+
{
46+
NoDebug = false;
47+
Arguments = null;
48+
IsRemoteAttach = false;
49+
RunspaceId = null;
50+
IsAttachSession = false;
51+
WaitingForAttach = false;
52+
ScriptToLaunch = null;
53+
ExecutionCompleted = false;
54+
IsInteractiveDebugSession = false;
55+
IsUsingTempIntegratedConsole = false;
56+
ServerStarted = null;
57+
}
4358
}
4459
}

src/PowerShellEditorServices/Services/DebugAdapter/Debugging/BreakpointApiUtils.cs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,18 @@ internal static class BreakpointApiUtils
2424

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

27+
private static readonly Lazy<Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>> s_setVariableBreakpointLazy;
28+
2729
private static readonly Lazy<Func<Debugger, int?, List<Breakpoint>>> s_getBreakpointsLazy;
2830

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

33+
private static readonly Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>> s_disableBreakpointLazy;
34+
35+
private static readonly Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>> s_enableBreakpointLazy;
36+
37+
private static readonly Lazy<Action<Debugger, IEnumerable<Breakpoint>, int?>> s_setBreakpointsLazy;
38+
3139
private static readonly Version s_minimumBreakpointApiPowerShellVersion = new(7, 0, 0, 0);
3240

3341
private static int breakpointHitCounter;
@@ -67,6 +75,17 @@ static BreakpointApiUtils()
6775
setCommandBreakpointMethod);
6876
});
6977

78+
s_setVariableBreakpointLazy = new Lazy<Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>>(() =>
79+
{
80+
Type[] setVariableBreakpointParameters = new[] { typeof(string), typeof(VariableAccessMode), typeof(ScriptBlock), typeof(string), typeof(int?) };
81+
MethodInfo setVariableBreakpointMethod = typeof(Debugger).GetMethod("SetVariableBreakpoint", setVariableBreakpointParameters);
82+
83+
return (Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>)Delegate.CreateDelegate(
84+
typeof(Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint>),
85+
firstArgument: null,
86+
setVariableBreakpointMethod);
87+
});
88+
7089
s_getBreakpointsLazy = new Lazy<Func<Debugger, int?, List<Breakpoint>>>(() =>
7190
{
7291
Type[] getBreakpointsParameters = new[] { typeof(int?) };
@@ -88,19 +107,60 @@ static BreakpointApiUtils()
88107
firstArgument: null,
89108
removeBreakpointMethod);
90109
});
110+
111+
s_disableBreakpointLazy = new Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>>(() =>
112+
{
113+
Type[] disableBreakpointParameters = new[] { typeof(Breakpoint), typeof(int?) };
114+
MethodInfo disableBreakpointMethod = typeof(Debugger).GetMethod("DisableBreakpoint", disableBreakpointParameters);
115+
116+
return (Func<Debugger, Breakpoint, int?, Breakpoint>)Delegate.CreateDelegate(
117+
typeof(Func<Debugger, Breakpoint, int?, Breakpoint>),
118+
firstArgument: null,
119+
disableBreakpointMethod);
120+
});
121+
122+
s_enableBreakpointLazy = new Lazy<Func<Debugger, Breakpoint, int?, Breakpoint>>(() =>
123+
{
124+
Type[] enableBreakpointParameters = new[] { typeof(Breakpoint), typeof(int?) };
125+
MethodInfo enableBreakpointMethod = typeof(Debugger).GetMethod("EnableBreakpoint", enableBreakpointParameters);
126+
127+
return (Func<Debugger, Breakpoint, int?, Breakpoint>)Delegate.CreateDelegate(
128+
typeof(Func<Debugger, Breakpoint, int?, Breakpoint>),
129+
firstArgument: null,
130+
enableBreakpointMethod);
131+
});
132+
133+
s_setBreakpointsLazy = new Lazy<Action<Debugger, IEnumerable<Breakpoint>, int?>>(() =>
134+
{
135+
Type[] setBreakpointsParameters = new[] { typeof(IEnumerable<Breakpoint>), typeof(int?) };
136+
MethodInfo setBreakpointsMethod = typeof(Debugger).GetMethod("SetBreakpoints", setBreakpointsParameters);
137+
138+
return (Action<Debugger, IEnumerable<Breakpoint>, int?>)Delegate.CreateDelegate(
139+
typeof(Action<Debugger, IEnumerable<Breakpoint>, int?>),
140+
firstArgument: null,
141+
setBreakpointsMethod);
142+
});
91143
}
92144

93145
#endregion
94146

95147
#region Private Static Properties
96148

97-
private static Func<Debugger, string, int, int, ScriptBlock, int?, LineBreakpoint> SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value;
149+
internal static Func<Debugger, string, int, int, ScriptBlock, int?, LineBreakpoint> SetLineBreakpointDelegate => s_setLineBreakpointLazy.Value;
150+
151+
internal static Func<Debugger, string, ScriptBlock, string, int?, CommandBreakpoint> SetCommandBreakpointDelegate => s_setCommandBreakpointLazy.Value;
152+
153+
internal static Func<Debugger, string, VariableAccessMode, ScriptBlock, string, int?, VariableBreakpoint> SetVariableBreakpointDelegate => s_setVariableBreakpointLazy.Value;
154+
155+
internal static Func<Debugger, int?, List<Breakpoint>> GetBreakpointsDelegate => s_getBreakpointsLazy.Value;
156+
157+
internal static Func<Debugger, Breakpoint, int?, bool> RemoveBreakpointDelegate => s_removeBreakpointLazy.Value;
98158

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

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

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

105165
#endregion
106166

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/BreakpointHandlers.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,46 @@ internal class BreakpointHandlers : ISetFunctionBreakpointsHandler, ISetBreakpoi
3131
private readonly DebugStateService _debugStateService;
3232
private readonly WorkspaceService _workspaceService;
3333
private readonly IRunspaceContext _runspaceContext;
34+
private readonly BreakpointSyncService _breakpointSyncService;
3435

3536
public BreakpointHandlers(
3637
ILoggerFactory loggerFactory,
3738
DebugService debugService,
3839
DebugStateService debugStateService,
3940
WorkspaceService workspaceService,
40-
IRunspaceContext runspaceContext)
41+
IRunspaceContext runspaceContext,
42+
BreakpointSyncService breakpointSyncService)
4143
{
4244
_logger = loggerFactory.CreateLogger<BreakpointHandlers>();
4345
_debugService = debugService;
4446
_debugStateService = debugStateService;
4547
_workspaceService = workspaceService;
4648
_runspaceContext = runspaceContext;
49+
_breakpointSyncService = breakpointSyncService;
4750
}
4851

4952
public async Task<SetBreakpointsResponse> Handle(SetBreakpointsArguments request, CancellationToken cancellationToken)
5053
{
54+
if (_breakpointSyncService.IsSupported)
55+
{
56+
// TODO: Find some way to actually verify these. Unfortunately the sync event comes
57+
// through *after* the `SetBreakpoints` event so we can't just look at what synced
58+
// breakpoints were successfully set.
59+
return new SetBreakpointsResponse()
60+
{
61+
Breakpoints = new Container<Breakpoint>(
62+
request.Breakpoints
63+
.Select(sbp => new Breakpoint()
64+
{
65+
Line = sbp.Line,
66+
Column = sbp.Column,
67+
Verified = true,
68+
Source = request.Source,
69+
Id = 0,
70+
})),
71+
};
72+
}
73+
5174
if (!_workspaceService.TryGetFile(request.Source.Path, out ScriptFile scriptFile))
5275
{
5376
string message = _debugStateService.NoDebug ? string.Empty : "Source file could not be accessed, breakpoint not set.";
@@ -125,6 +148,21 @@ await _debugService.SetLineBreakpointsAsync(
125148

126149
public async Task<SetFunctionBreakpointsResponse> Handle(SetFunctionBreakpointsArguments request, CancellationToken cancellationToken)
127150
{
151+
if (_breakpointSyncService.IsSupported)
152+
{
153+
return new SetFunctionBreakpointsResponse()
154+
{
155+
Breakpoints = new Container<Breakpoint>(
156+
_breakpointSyncService.GetSyncedBreakpoints()
157+
.Where(sbp => sbp.Client.FunctionName is not null and not "")
158+
.Select(sbp => new Breakpoint()
159+
{
160+
Verified = true,
161+
Message = sbp.Client.FunctionName,
162+
})),
163+
};
164+
}
165+
128166
CommandBreakpointDetails[] breakpointDetails = request.Breakpoints
129167
.Select((funcBreakpoint) => CommandBreakpointDetails.Create(
130168
funcBreakpoint.Name,

src/PowerShellEditorServices/Services/DebugAdapter/Handlers/LaunchAndAttachHandler.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Management.Automation;
88
using System.Management.Automation.Remoting;
9+
using System.Management.Automation.Runspaces;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using Microsoft.Extensions.Logging;
@@ -90,6 +91,7 @@ internal class LaunchAndAttachHandler : ILaunchHandler<PsesLaunchRequestArgument
9091
private static readonly Version s_minVersionForCustomPipeName = new(6, 2);
9192
private readonly ILogger<LaunchAndAttachHandler> _logger;
9293
private readonly BreakpointService _breakpointService;
94+
private readonly BreakpointSyncService _breakpointSyncService;
9395
private readonly DebugService _debugService;
9496
private readonly IRunspaceContext _runspaceContext;
9597
private readonly IInternalPowerShellExecutionService _executionService;
@@ -102,6 +104,7 @@ public LaunchAndAttachHandler(
102104
ILoggerFactory factory,
103105
IDebugAdapterServerFacade debugAdapterServer,
104106
BreakpointService breakpointService,
107+
BreakpointSyncService breakpointSyncService,
105108
DebugEventHandlerService debugEventHandlerService,
106109
DebugService debugService,
107110
IRunspaceContext runspaceContext,
@@ -112,6 +115,7 @@ public LaunchAndAttachHandler(
112115
_logger = factory.CreateLogger<LaunchAndAttachHandler>();
113116
_debugAdapterServer = debugAdapterServer;
114117
_breakpointService = breakpointService;
118+
_breakpointSyncService = breakpointSyncService;
115119
_debugEventHandlerService = debugEventHandlerService;
116120
_debugService = debugService;
117121
_runspaceContext = runspaceContext;
@@ -237,6 +241,12 @@ public async Task<AttachResponse> Handle(PsesAttachRequestArguments request, Can
237241

238242
private async Task<AttachResponse> HandleImpl(PsesAttachRequestArguments request, CancellationToken cancellationToken)
239243
{
244+
cancellationToken.Register(() =>
245+
{
246+
if (Runspace.DefaultRunspace != null)
247+
{
248+
}
249+
});
240250
// The debugger has officially started. We use this to later check if we should stop it.
241251
((PsesInternalHost)_executionService).DebugContext.IsActive = true;
242252

@@ -431,7 +441,15 @@ await _executionService.ExecutePSCommandAsync(
431441
}
432442

433443
// Clear any existing breakpoints before proceeding
434-
await _breakpointService.RemoveAllBreakpointsAsync().ConfigureAwait(continueOnCapturedContext: false);
444+
if (_breakpointSyncService.IsSupported)
445+
{
446+
_breakpointSyncService.SyncServerAfterAttach();
447+
}
448+
else
449+
{
450+
await _breakpointService.RemoveAllBreakpointsAsync()
451+
.ConfigureAwait(continueOnCapturedContext: false);
452+
}
435453

436454
_debugStateService.WaitingForAttach = true;
437455
Task nonAwaitedTask = _executionService
@@ -515,6 +533,7 @@ await _executionService.ExecutePSCommandAsync(
515533

516534
_debugService.IsClientAttached = false;
517535
_debugAdapterServer.SendNotification(EventNames.Terminated);
536+
_debugStateService.Reset();
518537
}
519538
}
520539
}

src/PowerShellEditorServices/Services/PowerShell/Host/HostStartOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ internal struct HostStartOptions
88
public bool LoadProfiles { get; set; }
99

1010
public string InitialWorkingDirectory { get; set; }
11+
12+
public bool SupportsBreakpointSync { get; set; }
1113
}
1214
}

0 commit comments

Comments
 (0)