@@ -35,6 +35,8 @@ public class DebugService
35
35
private VariableContainerDetails scriptScopeVariables ;
36
36
private StackFrameDetails [ ] stackFrameDetails ;
37
37
38
+ private static int breakpointHitCounter = 0 ;
39
+
38
40
#endregion
39
41
40
42
#region Constructors
@@ -102,7 +104,8 @@ public async Task<BreakpointDetails[]> SetLineBreakpoints(
102
104
}
103
105
104
106
// Check if this is a "conditional" line breakpoint.
105
- if ( breakpoint . Condition != null )
107
+ if ( ! String . IsNullOrWhiteSpace ( breakpoint . Condition ) ||
108
+ ! String . IsNullOrWhiteSpace ( breakpoint . HitCondition ) )
106
109
{
107
110
ScriptBlock actionScriptBlock =
108
111
GetBreakpointActionScriptBlock ( breakpoint ) ;
@@ -157,7 +160,8 @@ public async Task<CommandBreakpointDetails[]> SetCommandBreakpoints(
157
160
psCommand . AddParameter ( "Command" , breakpoint . Name ) ;
158
161
159
162
// Check if this is a "conditional" command breakpoint.
160
- if ( breakpoint . Condition != null )
163
+ if ( ! String . IsNullOrWhiteSpace ( breakpoint . Condition ) ||
164
+ ! String . IsNullOrWhiteSpace ( breakpoint . HitCondition ) )
161
165
{
162
166
ScriptBlock actionScriptBlock = GetBreakpointActionScriptBlock ( breakpoint ) ;
163
167
@@ -719,32 +723,85 @@ private ScriptBlock GetBreakpointActionScriptBlock(
719
723
{
720
724
try
721
725
{
722
- ScriptBlock actionScriptBlock = ScriptBlock . Create ( breakpoint . Condition ) ;
726
+ ScriptBlock actionScriptBlock ;
727
+ int ? hitCount = null ;
723
728
724
- // Check for simple, common errors that ScriptBlock parsing will not catch
725
- // e.g. $i == 3 and $i > 3
726
- string message ;
727
- if ( ! ValidateBreakpointConditionAst ( actionScriptBlock . Ast , out message ) )
729
+ // If HitCondition specified, parse and verify it.
730
+ if ( ! ( String . IsNullOrWhiteSpace ( breakpoint . HitCondition ) ) )
728
731
{
729
- breakpoint . Verified = false ;
730
- breakpoint . Message = message ;
731
- return null ;
732
+ int parsedHitCount ;
733
+
734
+ if ( Int32 . TryParse ( breakpoint . HitCondition , out parsedHitCount ) )
735
+ {
736
+ hitCount = parsedHitCount ;
737
+ }
738
+ else
739
+ {
740
+ breakpoint . Verified = false ;
741
+ breakpoint . Message = $ "The specified HitCount '{ breakpoint . HitCondition } ' is not valid. " +
742
+ "The HitCount must be an integer number." ;
743
+ return null ;
744
+ }
732
745
}
733
746
734
- // Check for "advanced" condition syntax i.e. if the user has specified
735
- // a "break" or "continue" statement anywhere in their scriptblock,
736
- // pass their scriptblock through to the Action parameter as-is.
737
- Ast breakOrContinueStatementAst =
738
- actionScriptBlock . Ast . Find (
739
- ast => ( ast is BreakStatementAst || ast is ContinueStatementAst ) , true ) ;
740
-
741
- // If this isn't advanced syntax then the conditions string should be a simple
742
- // expression that needs to be wrapped in a "if" test that conditionally executes
743
- // a break statement.
744
- if ( breakOrContinueStatementAst == null )
747
+ // Create an Action scriptblock based on condition and/or hit count passed in.
748
+ if ( hitCount . HasValue && String . IsNullOrWhiteSpace ( breakpoint . Condition ) )
749
+ {
750
+ // In the HitCount only case, this is simple as we can just use the HitCount
751
+ // property on the breakpoint object which is represented by $_.
752
+ string action = $ "if ($_.HitCount -eq { hitCount } ) {{ break }}";
753
+ actionScriptBlock = ScriptBlock . Create ( action ) ;
754
+ }
755
+ else if ( ! String . IsNullOrWhiteSpace ( breakpoint . Condition ) )
756
+ {
757
+ // Must be either condition only OR condition and hit count.
758
+ actionScriptBlock = ScriptBlock . Create ( breakpoint . Condition ) ;
759
+
760
+ // Check for simple, common errors that ScriptBlock parsing will not catch
761
+ // e.g. $i == 3 and $i > 3
762
+ string message ;
763
+ if ( ! ValidateBreakpointConditionAst ( actionScriptBlock . Ast , out message ) )
764
+ {
765
+ breakpoint . Verified = false ;
766
+ breakpoint . Message = message ;
767
+ return null ;
768
+ }
769
+
770
+ // Check for "advanced" condition syntax i.e. if the user has specified
771
+ // a "break" or "continue" statement anywhere in their scriptblock,
772
+ // pass their scriptblock through to the Action parameter as-is.
773
+ Ast breakOrContinueStatementAst =
774
+ actionScriptBlock . Ast . Find (
775
+ ast => ( ast is BreakStatementAst || ast is ContinueStatementAst ) , true ) ;
776
+
777
+ // If this isn't advanced syntax then the conditions string should be a simple
778
+ // expression that needs to be wrapped in a "if" test that conditionally executes
779
+ // a break statement.
780
+ if ( breakOrContinueStatementAst == null )
781
+ {
782
+ string wrappedCondition ;
783
+
784
+ if ( hitCount . HasValue )
785
+ {
786
+ string globalHitCountVarName =
787
+ $ "$global:__psEditorServices_BreakHitCounter_{ breakpointHitCounter ++ } ";
788
+
789
+ wrappedCondition =
790
+ $ "if ({ breakpoint . Condition } ) {{ if (++{ globalHitCountVarName } -eq { hitCount } ) {{ break }} }}";
791
+ }
792
+ else
793
+ {
794
+ wrappedCondition = $ "if ({ breakpoint . Condition } ) {{ break }}";
795
+ }
796
+
797
+ actionScriptBlock = ScriptBlock . Create ( wrappedCondition ) ;
798
+ }
799
+ }
800
+ else
745
801
{
746
- string wrappedCondition = $ "if ({ breakpoint . Condition } ) {{ break }}";
747
- actionScriptBlock = ScriptBlock . Create ( wrappedCondition ) ;
802
+ // Shouldn't get here unless someone called this with no condition and no hit count.
803
+ actionScriptBlock = ScriptBlock . Create ( "break" ) ;
804
+ Logger . Write ( LogLevel . Warning , "No condition and no hit count specified by caller." ) ;
748
805
}
749
806
750
807
return actionScriptBlock ;
0 commit comments