Skip to content

Commit aa00910

Browse files
Initial switch to breakpoint APIs (#1119)
* Initial switch to breakpoint APIs * codacy * codacy2 * address keith feedback * misc other code * move to runspaceId * fallback get-psbreakpoint * remove notification that caused issues * misc clean up and fixed action script blocks * codacy * get rid of extra GetBreakpointActionScriptBlock * new new breakpoint apis * misc feedback * make things internal for codacy * patrick comments
1 parent 40380f6 commit aa00910

15 files changed

+776
-404
lines changed

Diff for: src/PowerShellEditorServices/Server/PsesServiceCollectionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public static IServiceCollection AddPsesDebugServices(
6464
.AddSingleton(languageServiceProvider.GetService<RemoteFileManagerService>())
6565
.AddSingleton<PsesDebugServer>(psesDebugServer)
6666
.AddSingleton<DebugService>()
67+
.AddSingleton<BreakpointService>()
6768
.AddSingleton<DebugStateService>(new DebugStateService
6869
{
6970
OwnsEditorSession = useTempSession
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Management.Automation;
10+
using System.Threading.Tasks;
11+
using Microsoft.Extensions.Logging;
12+
using Microsoft.PowerShell.EditorServices.Logging;
13+
using Microsoft.PowerShell.EditorServices.Services.DebugAdapter;
14+
15+
namespace Microsoft.PowerShell.EditorServices.Services
16+
{
17+
internal class BreakpointService
18+
{
19+
private readonly ILogger<BreakpointService> _logger;
20+
private readonly PowerShellContextService _powerShellContextService;
21+
private readonly DebugStateService _debugStateService;
22+
23+
// TODO: This needs to be managed per nested session
24+
internal readonly Dictionary<string, HashSet<Breakpoint>> BreakpointsPerFile =
25+
new Dictionary<string, HashSet<Breakpoint>>();
26+
27+
internal readonly HashSet<Breakpoint> CommandBreakpoints =
28+
new HashSet<Breakpoint>();
29+
30+
public BreakpointService(
31+
ILoggerFactory factory,
32+
PowerShellContextService powerShellContextService,
33+
DebugStateService debugStateService)
34+
{
35+
_logger = factory.CreateLogger<BreakpointService>();
36+
_powerShellContextService = powerShellContextService;
37+
_debugStateService = debugStateService;
38+
}
39+
40+
public async Task<List<Breakpoint>> GetBreakpointsAsync()
41+
{
42+
if (BreakpointApiUtils.SupportsBreakpointApis)
43+
{
44+
return BreakpointApiUtils.GetBreakpoints(
45+
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
46+
_debugStateService.RunspaceId);
47+
}
48+
49+
// Legacy behavior
50+
PSCommand psCommand = new PSCommand();
51+
psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");
52+
IEnumerable<Breakpoint> breakpoints = await _powerShellContextService.ExecuteCommandAsync<Breakpoint>(psCommand);
53+
return breakpoints.ToList();
54+
}
55+
56+
public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
57+
{
58+
if (BreakpointApiUtils.SupportsBreakpointApis)
59+
{
60+
foreach (BreakpointDetails breakpointDetails in breakpoints)
61+
{
62+
try
63+
{
64+
BreakpointApiUtils.SetBreakpoint(_powerShellContextService.CurrentRunspace.Runspace.Debugger, breakpointDetails, _debugStateService.RunspaceId);
65+
}
66+
catch(InvalidOperationException e)
67+
{
68+
breakpointDetails.Message = e.Message;
69+
breakpointDetails.Verified = false;
70+
}
71+
}
72+
73+
return breakpoints;
74+
}
75+
76+
// Legacy behavior
77+
PSCommand psCommand = null;
78+
List<BreakpointDetails> configuredBreakpoints = new List<BreakpointDetails>();
79+
foreach (BreakpointDetails breakpoint in breakpoints)
80+
{
81+
ScriptBlock actionScriptBlock = null;
82+
83+
// Check if this is a "conditional" line breakpoint.
84+
if (!string.IsNullOrWhiteSpace(breakpoint.Condition) ||
85+
!string.IsNullOrWhiteSpace(breakpoint.HitCondition) ||
86+
!string.IsNullOrWhiteSpace(breakpoint.LogMessage))
87+
{
88+
actionScriptBlock = BreakpointApiUtils.GetBreakpointActionScriptBlock(
89+
breakpoint.Condition,
90+
breakpoint.HitCondition,
91+
breakpoint.LogMessage,
92+
out string errorMessage);
93+
94+
if (!string.IsNullOrEmpty(errorMessage))
95+
{
96+
breakpoint.Verified = false;
97+
breakpoint.Message = errorMessage;
98+
configuredBreakpoints.Add(breakpoint);
99+
continue;
100+
}
101+
}
102+
103+
// On first iteration psCommand will be null, every subsequent
104+
// iteration will need to start a new statement.
105+
if (psCommand == null)
106+
{
107+
psCommand = new PSCommand();
108+
}
109+
else
110+
{
111+
psCommand.AddStatement();
112+
}
113+
114+
psCommand
115+
.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint")
116+
.AddParameter("Script", escapedScriptPath)
117+
.AddParameter("Line", breakpoint.LineNumber);
118+
119+
// Check if the user has specified the column number for the breakpoint.
120+
if (breakpoint.ColumnNumber.HasValue && breakpoint.ColumnNumber.Value > 0)
121+
{
122+
// It bums me out that PowerShell will silently ignore a breakpoint
123+
// where either the line or the column is invalid. I'd rather have an
124+
// error or warning message I could relay back to the client.
125+
psCommand.AddParameter("Column", breakpoint.ColumnNumber.Value);
126+
}
127+
128+
if (actionScriptBlock != null)
129+
{
130+
psCommand.AddParameter("Action", actionScriptBlock);
131+
}
132+
}
133+
134+
// If no PSCommand was created then there are no breakpoints to set.
135+
if (psCommand != null)
136+
{
137+
IEnumerable<Breakpoint> setBreakpoints =
138+
await _powerShellContextService.ExecuteCommandAsync<Breakpoint>(psCommand);
139+
configuredBreakpoints.AddRange(
140+
setBreakpoints.Select(BreakpointDetails.Create));
141+
}
142+
143+
return configuredBreakpoints;
144+
}
145+
146+
public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpoints(IEnumerable<CommandBreakpointDetails> breakpoints)
147+
{
148+
if (BreakpointApiUtils.SupportsBreakpointApis)
149+
{
150+
foreach (CommandBreakpointDetails commandBreakpointDetails in breakpoints)
151+
{
152+
try
153+
{
154+
BreakpointApiUtils.SetBreakpoint(_powerShellContextService.CurrentRunspace.Runspace.Debugger, commandBreakpointDetails, _debugStateService.RunspaceId);
155+
}
156+
catch(InvalidOperationException e)
157+
{
158+
commandBreakpointDetails.Message = e.Message;
159+
commandBreakpointDetails.Verified = false;
160+
}
161+
}
162+
163+
return breakpoints;
164+
}
165+
166+
// Legacy behavior
167+
PSCommand psCommand = null;
168+
List<CommandBreakpointDetails> configuredBreakpoints = new List<CommandBreakpointDetails>();
169+
foreach (CommandBreakpointDetails breakpoint in breakpoints)
170+
{
171+
// On first iteration psCommand will be null, every subsequent
172+
// iteration will need to start a new statement.
173+
if (psCommand == null)
174+
{
175+
psCommand = new PSCommand();
176+
}
177+
else
178+
{
179+
psCommand.AddStatement();
180+
}
181+
182+
psCommand
183+
.AddCommand(@"Microsoft.PowerShell.Utility\Set-PSBreakpoint")
184+
.AddParameter("Command", breakpoint.Name);
185+
186+
// Check if this is a "conditional" line breakpoint.
187+
if (!string.IsNullOrWhiteSpace(breakpoint.Condition) ||
188+
!string.IsNullOrWhiteSpace(breakpoint.HitCondition))
189+
{
190+
ScriptBlock actionScriptBlock =
191+
BreakpointApiUtils.GetBreakpointActionScriptBlock(
192+
breakpoint.Condition,
193+
breakpoint.HitCondition,
194+
logMessage: null,
195+
out string errorMessage);
196+
197+
// If there was a problem with the condition string,
198+
// move onto the next breakpoint.
199+
if (!string.IsNullOrEmpty(errorMessage))
200+
{
201+
breakpoint.Verified = false;
202+
breakpoint.Message = errorMessage;
203+
configuredBreakpoints.Add(breakpoint);
204+
continue;
205+
}
206+
207+
psCommand.AddParameter("Action", actionScriptBlock);
208+
}
209+
}
210+
211+
// If no PSCommand was created then there are no breakpoints to set.
212+
if (psCommand != null)
213+
{
214+
IEnumerable<Breakpoint> setBreakpoints =
215+
await _powerShellContextService.ExecuteCommandAsync<Breakpoint>(psCommand);
216+
configuredBreakpoints.AddRange(
217+
setBreakpoints.Select(CommandBreakpointDetails.Create));
218+
}
219+
220+
return configuredBreakpoints;
221+
}
222+
223+
/// <summary>
224+
/// Clears all breakpoints in the current session.
225+
/// </summary>
226+
public async Task RemoveAllBreakpointsAsync(string scriptPath = null)
227+
{
228+
try
229+
{
230+
if (BreakpointApiUtils.SupportsBreakpointApis)
231+
{
232+
foreach (Breakpoint breakpoint in BreakpointApiUtils.GetBreakpoints(
233+
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
234+
_debugStateService.RunspaceId))
235+
{
236+
if (scriptPath == null || scriptPath == breakpoint.Script)
237+
{
238+
BreakpointApiUtils.RemoveBreakpoint(
239+
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
240+
breakpoint,
241+
_debugStateService.RunspaceId);
242+
}
243+
}
244+
245+
return;
246+
}
247+
248+
// Legacy behavior
249+
250+
PSCommand psCommand = new PSCommand();
251+
psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Get-PSBreakpoint");
252+
253+
if (!string.IsNullOrEmpty(scriptPath))
254+
{
255+
psCommand.AddParameter("Script", scriptPath);
256+
}
257+
258+
psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint");
259+
260+
await _powerShellContextService.ExecuteCommandAsync<object>(psCommand).ConfigureAwait(false);
261+
}
262+
catch (Exception e)
263+
{
264+
_logger.LogException("Caught exception while clearing breakpoints from session", e);
265+
}
266+
}
267+
268+
public async Task RemoveBreakpointsAsync(IEnumerable<Breakpoint> breakpoints)
269+
{
270+
if (BreakpointApiUtils.SupportsBreakpointApis)
271+
{
272+
foreach (Breakpoint breakpoint in breakpoints)
273+
{
274+
BreakpointApiUtils.RemoveBreakpoint(
275+
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
276+
breakpoint,
277+
_debugStateService.RunspaceId);
278+
279+
switch (breakpoint)
280+
{
281+
case CommandBreakpoint commandBreakpoint:
282+
CommandBreakpoints.Remove(commandBreakpoint);
283+
break;
284+
case LineBreakpoint lineBreakpoint:
285+
if (BreakpointsPerFile.TryGetValue(lineBreakpoint.Script, out HashSet<Breakpoint> bps))
286+
{
287+
bps.Remove(lineBreakpoint);
288+
}
289+
break;
290+
default:
291+
throw new ArgumentException("Unsupported breakpoint type.");
292+
}
293+
}
294+
295+
return;
296+
}
297+
298+
// Legacy behavior
299+
var breakpointIds = breakpoints.Select(b => b.Id).ToArray();
300+
if(breakpointIds.Length > 0)
301+
{
302+
PSCommand psCommand = new PSCommand();
303+
psCommand.AddCommand(@"Microsoft.PowerShell.Utility\Remove-PSBreakpoint");
304+
psCommand.AddParameter("Id", breakpoints.Select(b => b.Id).ToArray());
305+
306+
await _powerShellContextService.ExecuteCommandAsync<object>(psCommand);
307+
}
308+
}
309+
310+
311+
}
312+
}

0 commit comments

Comments
 (0)