Skip to content

Commit dfc6fe0

Browse files
misc other code
1 parent 041f10d commit dfc6fe0

File tree

3 files changed

+136
-53
lines changed

3 files changed

+136
-53
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/BreakpointService.cs

+53-24
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,53 @@ public BreakpointService(
3131
_powerShellContextService = powerShellContextService;
3232
}
3333

34-
public async Task<BreakpointDetails[]> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
34+
public async Task<IEnumerable<BreakpointDetails>> SetBreakpointsAsync(string escapedScriptPath, IEnumerable<BreakpointDetails> breakpoints)
3535
{
3636
if (VersionUtils.IsPS7OrGreater)
3737
{
38-
return BreakpointApiUtils.SetBreakpoints(
39-
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
40-
breakpoints)
41-
.Select(BreakpointDetails.Create).ToArray();
38+
foreach (BreakpointDetails breakpointDetails in breakpoints)
39+
{
40+
try
41+
{
42+
BreakpointApiUtils.SetBreakpoint(_powerShellContextService.CurrentRunspace.Runspace.Debugger, breakpointDetails);
43+
44+
}
45+
catch(InvalidOperationException e)
46+
{
47+
breakpointDetails.Message = e.Message;
48+
breakpointDetails.Verified = false;
49+
}
50+
}
51+
52+
return breakpoints;
4253
}
4354

4455
// Legacy behavior
4556
PSCommand psCommand = null;
4657
List<BreakpointDetails> configuredBreakpoints = new List<BreakpointDetails>();
4758
foreach (BreakpointDetails breakpoint in breakpoints)
4859
{
60+
ScriptBlock actionScriptBlock = null;
61+
62+
// Check if this is a "conditional" line breakpoint.
63+
if (!string.IsNullOrWhiteSpace(breakpoint.Condition) ||
64+
!string.IsNullOrWhiteSpace(breakpoint.HitCondition) ||
65+
!string.IsNullOrWhiteSpace(breakpoint.LogMessage))
66+
{
67+
try
68+
{
69+
actionScriptBlock = BreakpointApiUtils.GetBreakpointActionScriptBlock(
70+
breakpoint.Condition,
71+
breakpoint.HitCondition,
72+
breakpoint.LogMessage);
73+
}
74+
catch (InvalidOperationException e)
75+
{
76+
breakpoint.Verified = false;
77+
breakpoint.Message = e.Message;
78+
}
79+
}
80+
4981
// On first iteration psCommand will be null, every subsequent
5082
// iteration will need to start a new statement.
5183
if (psCommand == null)
@@ -71,21 +103,8 @@ public async Task<BreakpointDetails[]> SetBreakpointsAsync(string escapedScriptP
71103
psCommand.AddParameter("Column", breakpoint.ColumnNumber.Value);
72104
}
73105

74-
// Check if this is a "conditional" line breakpoint.
75-
if (!string.IsNullOrWhiteSpace(breakpoint.Condition) ||
76-
!string.IsNullOrWhiteSpace(breakpoint.HitCondition))
106+
if (actionScriptBlock != null)
77107
{
78-
ScriptBlock actionScriptBlock =
79-
BreakpointApiUtils.GetBreakpointActionScriptBlock(breakpoint);
80-
81-
// If there was a problem with the condition string,
82-
// move onto the next breakpoint.
83-
if (actionScriptBlock == null)
84-
{
85-
configuredBreakpoints.Add(breakpoint);
86-
continue;
87-
}
88-
89108
psCommand.AddParameter("Action", actionScriptBlock);
90109
}
91110
}
@@ -99,17 +118,27 @@ public async Task<BreakpointDetails[]> SetBreakpointsAsync(string escapedScriptP
99118
setBreakpoints.Select(BreakpointDetails.Create));
100119
}
101120

102-
return configuredBreakpoints.ToArray();
121+
return configuredBreakpoints;
103122
}
104123

105124
public async Task<IEnumerable<CommandBreakpointDetails>> SetCommandBreakpoints(IEnumerable<CommandBreakpointDetails> breakpoints)
106125
{
107126
if (VersionUtils.IsPS7OrGreater)
108127
{
109-
return BreakpointApiUtils.SetBreakpoints(
110-
_powerShellContextService.CurrentRunspace.Runspace.Debugger,
111-
breakpoints)
112-
.Select(CommandBreakpointDetails.Create);
128+
foreach (CommandBreakpointDetails commandBreakpointDetails in breakpoints)
129+
{
130+
try
131+
{
132+
BreakpointApiUtils.SetBreakpoint(_powerShellContextService.CurrentRunspace.Runspace.Debugger, commandBreakpointDetails);
133+
}
134+
catch(InvalidOperationException e)
135+
{
136+
commandBreakpointDetails.Message = e.Message;
137+
commandBreakpointDetails.Verified = false;
138+
}
139+
}
140+
141+
return breakpoints;
113142
}
114143

115144
// Legacy behavior

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

+77-27
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Management.Automation;
1111
using System.Management.Automation.Language;
1212
using System.Reflection;
13+
using System.Text;
1314
using System.Threading;
1415
using Microsoft.Extensions.Logging;
1516

@@ -102,39 +103,29 @@ static BreakpointApiUtils()
102103

103104
#region Public Static Methods
104105

105-
public static IEnumerable<Breakpoint> SetBreakpoints(Debugger debugger, IEnumerable<BreakpointDetailsBase> breakpoints)
106+
public static Breakpoint SetBreakpoint(Debugger debugger, BreakpointDetailsBase breakpoint)
106107
{
107-
var psBreakpoints = new List<Breakpoint>(breakpoints.Count());
108-
109-
foreach (BreakpointDetailsBase breakpoint in breakpoints)
108+
ScriptBlock actionScriptBlock = null;
109+
string logMessage = breakpoint is BreakpointDetails bd ? bd.LogMessage : null;
110+
// Check if this is a "conditional" line breakpoint.
111+
if (!string.IsNullOrWhiteSpace(breakpoint.Condition) ||
112+
!string.IsNullOrWhiteSpace(breakpoint.HitCondition) ||
113+
!string.IsNullOrWhiteSpace(logMessage))
110114
{
111-
ScriptBlock actionScriptBlock = null;
112-
// Check if this is a "conditional" line breakpoint.
113-
if (!string.IsNullOrWhiteSpace(breakpoint.Condition) ||
114-
!string.IsNullOrWhiteSpace(breakpoint.HitCondition))
115-
{
116-
actionScriptBlock = GetBreakpointActionScriptBlock(breakpoint);
117-
}
118-
119-
Breakpoint psBreakpoint;
120-
switch (breakpoint)
121-
{
122-
case BreakpointDetails lineBreakpoint:
123-
psBreakpoint = SetLineBreakpointDelegate(debugger, lineBreakpoint.Source, lineBreakpoint.LineNumber, lineBreakpoint.ColumnNumber ?? 0, actionScriptBlock);
124-
break;
115+
actionScriptBlock = GetBreakpointActionScriptBlock(breakpoint.Condition, breakpoint.HitCondition, logMessage);
116+
}
125117

126-
case CommandBreakpointDetails commandBreakpoint:
127-
psBreakpoint = SetCommandBreakpointDelegate(debugger, commandBreakpoint.Name, null, null);
128-
break;
118+
switch (breakpoint)
119+
{
120+
case BreakpointDetails lineBreakpoint:
121+
return SetLineBreakpointDelegate(debugger, lineBreakpoint.Source, lineBreakpoint.LineNumber, lineBreakpoint.ColumnNumber ?? 0, actionScriptBlock);
129122

130-
default:
131-
throw new NotImplementedException("Other breakpoints not supported yet");
132-
}
123+
case CommandBreakpointDetails commandBreakpoint:
124+
return SetCommandBreakpointDelegate(debugger, commandBreakpoint.Name, null, null);
133125

134-
psBreakpoints.Add(psBreakpoint);
126+
default:
127+
throw new NotImplementedException("Other breakpoints not supported yet");
135128
}
136-
137-
return psBreakpoints;
138129
}
139130

140131
public static List<Breakpoint> GetBreakpoints(Debugger debugger)
@@ -147,6 +138,65 @@ public static bool RemoveBreakpoint(Debugger debugger, Breakpoint breakpoint)
147138
return RemoveBreakpointDelegate(debugger, breakpoint);
148139
}
149140

141+
public static ScriptBlock GetBreakpointActionScriptBlock(string condition, string hitCondition, string logMessage)
142+
{
143+
StringBuilder builder = new StringBuilder(
144+
string.IsNullOrEmpty(logMessage)
145+
? "break"
146+
: $"Microsoft.PowerShell.Utility\\Write-Host '{logMessage}'");
147+
148+
// If HitCondition specified, parse and verify it.
149+
if (!(string.IsNullOrWhiteSpace(hitCondition)))
150+
{
151+
if (!int.TryParse(hitCondition, out int parsedHitCount))
152+
{
153+
throw new InvalidOperationException("Hit Count was not a valid integer.");
154+
}
155+
156+
if(string.IsNullOrWhiteSpace(condition))
157+
{
158+
// In the HitCount only case, this is simple as we can just use the HitCount
159+
// property on the breakpoint object which is represented by $_.
160+
builder.Insert(0, $"if ($_.HitCount -eq {parsedHitCount}) {{ ")
161+
.Append(" }}");
162+
}
163+
164+
Interlocked.Increment(ref breakpointHitCounter);
165+
166+
string globalHitCountVarName =
167+
$"$global:{s_psesGlobalVariableNamePrefix}BreakHitCounter_{breakpointHitCounter}";
168+
169+
builder.Insert(0, $"if (++{globalHitCountVarName} -eq {parsedHitCount}) {{ ")
170+
.Append(" }}");
171+
}
172+
173+
if (!string.IsNullOrWhiteSpace(condition))
174+
{
175+
ScriptBlock parsed = ScriptBlock.Create(condition);
176+
177+
// Check for simple, common errors that ScriptBlock parsing will not catch
178+
// e.g. $i == 3 and $i > 3
179+
if (!ValidateBreakpointConditionAst(parsed.Ast, out string message))
180+
{
181+
throw new InvalidOperationException(message);
182+
}
183+
184+
// Check for "advanced" condition syntax i.e. if the user has specified
185+
// a "break" or "continue" statement anywhere in their scriptblock,
186+
// pass their scriptblock through to the Action parameter as-is.
187+
if (parsed.Ast.Find(ast =>
188+
(ast is BreakStatementAst || ast is ContinueStatementAst), true) != null)
189+
{
190+
return parsed;
191+
}
192+
193+
builder.Insert(0, $"if ({condition}) {{ ")
194+
.Append(" }}");
195+
}
196+
197+
return ScriptBlock.Create(builder.ToString());
198+
}
199+
150200
/// <summary>
151201
/// Inspects the condition, putting in the appropriate scriptblock template
152202
/// "if (expression) { break }". If errors are found in the condition, the

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class BreakpointDetails : BreakpointDetailsBase
3636
/// </summary>
3737
public int? ColumnNumber { get; private set; }
3838

39+
public string LogMessage { get; private set; }
40+
3941
private BreakpointDetails()
4042
{
4143
}
@@ -55,7 +57,8 @@ public static BreakpointDetails Create(
5557
int line,
5658
int? column = null,
5759
string condition = null,
58-
string hitCondition = null)
60+
string hitCondition = null,
61+
string logMessage = null)
5962
{
6063
Validate.IsNotNull("source", source);
6164

@@ -66,7 +69,8 @@ public static BreakpointDetails Create(
6669
LineNumber = line,
6770
ColumnNumber = column,
6871
Condition = condition,
69-
HitCondition = hitCondition
72+
HitCondition = hitCondition,
73+
LogMessage = logMessage
7074
};
7175
}
7276

0 commit comments

Comments
 (0)