@@ -4921,13 +4921,22 @@ private static SortedSet<string> BuildSpecialVariablesCache()
4921
4921
4922
4922
#region Comments
4923
4923
4924
- // Complete the history entries
4925
- internal static List < CompletionResult > CompleteComment ( CompletionContext context )
4924
+ internal static List < CompletionResult > CompleteComment ( CompletionContext context , ref int replacementIndex , ref int replacementLength )
4926
4925
{
4927
- List < CompletionResult > results = new List < CompletionResult > ( ) ;
4926
+ // Complete #requires statements
4927
+ if ( context . WordToComplete . StartsWith ( "#requires " , StringComparison . OrdinalIgnoreCase ) )
4928
+ {
4929
+ return CompleteRequires ( context , ref replacementIndex , ref replacementLength ) ;
4930
+ }
4928
4931
4932
+ var results = new List < CompletionResult > ( ) ;
4933
+
4934
+ // Complete the history entries
4929
4935
Match matchResult = Regex . Match ( context . WordToComplete , @"^#([\w\-]*)$" ) ;
4930
- if ( ! matchResult . Success ) { return results ; }
4936
+ if ( ! matchResult . Success )
4937
+ {
4938
+ return results ;
4939
+ }
4931
4940
4932
4941
string wordToComplete = matchResult . Groups [ 1 ] . Value ;
4933
4942
Collection < PSObject > psobjs ;
@@ -4988,6 +4997,197 @@ internal static List<CompletionResult> CompleteComment(CompletionContext context
4988
4997
return results ;
4989
4998
}
4990
4999
5000
+ private static List < CompletionResult > CompleteRequires ( CompletionContext context , ref int replacementIndex , ref int replacementLength )
5001
+ {
5002
+ var results = new List < CompletionResult > ( ) ;
5003
+
5004
+ int cursorIndex = context . CursorPosition . ColumnNumber - 1 ;
5005
+ string lineToCursor = context . CursorPosition . Line . Substring ( 0 , cursorIndex ) ;
5006
+
5007
+ // RunAsAdministrator must be the last parameter in a Requires statement so no completion if the cursor is after the parameter.
5008
+ if ( lineToCursor . Contains ( " -RunAsAdministrator" , StringComparison . OrdinalIgnoreCase ) )
5009
+ {
5010
+ return results ;
5011
+ }
5012
+
5013
+ // Regex to find parameter like " -Parameter1" or " -"
5014
+ MatchCollection hashtableKeyMatches = Regex . Matches ( lineToCursor , @"\s+-([A-Za-z]+|$)" ) ;
5015
+ if ( hashtableKeyMatches . Count == 0 )
5016
+ {
5017
+ return results ;
5018
+ }
5019
+
5020
+ Group currentParameterMatch = hashtableKeyMatches [ ^ 1 ] . Groups [ 1 ] ;
5021
+
5022
+ // Complete the parameter if the cursor is at a parameter
5023
+ if ( currentParameterMatch . Index + currentParameterMatch . Length == cursorIndex )
5024
+ {
5025
+ string currentParameterPrefix = currentParameterMatch . Value ;
5026
+
5027
+ replacementIndex = context . CursorPosition . Offset - currentParameterPrefix . Length ;
5028
+ replacementLength = currentParameterPrefix . Length ;
5029
+
5030
+ // Produce completions for all parameters that begin with the prefix we've found,
5031
+ // but which haven't already been specified in the line we need to complete
5032
+ foreach ( KeyValuePair < string , string > parameter in s_requiresParameters )
5033
+ {
5034
+ if ( parameter . Key . StartsWith ( currentParameterPrefix , StringComparison . OrdinalIgnoreCase )
5035
+ && ! context . CursorPosition . Line . Contains ( $ " -{ parameter . Key } ", StringComparison . OrdinalIgnoreCase ) )
5036
+ {
5037
+ results . Add ( new CompletionResult ( parameter . Key , parameter . Key , CompletionResultType . ParameterName , parameter . Value ) ) ;
5038
+ }
5039
+ }
5040
+
5041
+ return results ;
5042
+ }
5043
+
5044
+ // Regex to find parameter values (any text that appears after various delimiters)
5045
+ hashtableKeyMatches = Regex . Matches ( lineToCursor , @"(\s+|,|;|{|\""|'|=)(\w+|$)" ) ;
5046
+ string currentValue ;
5047
+ if ( hashtableKeyMatches . Count == 0 )
5048
+ {
5049
+ currentValue = string . Empty ;
5050
+ }
5051
+ else
5052
+ {
5053
+ currentValue = hashtableKeyMatches [ ^ 1 ] . Groups [ 2 ] . Value ;
5054
+ }
5055
+
5056
+ replacementIndex = context . CursorPosition . Offset - currentValue . Length ;
5057
+ replacementLength = currentValue . Length ;
5058
+
5059
+ // Complete PSEdition parameter values
5060
+ if ( currentParameterMatch . Value . Equals ( "PSEdition" , StringComparison . OrdinalIgnoreCase ) )
5061
+ {
5062
+ foreach ( KeyValuePair < string , string > psEditionEntry in s_requiresPSEditions )
5063
+ {
5064
+ if ( psEditionEntry . Key . StartsWith ( currentValue , StringComparison . OrdinalIgnoreCase ) )
5065
+ {
5066
+ results . Add ( new CompletionResult ( psEditionEntry . Key , psEditionEntry . Key , CompletionResultType . ParameterValue , psEditionEntry . Value ) ) ;
5067
+ }
5068
+ }
5069
+
5070
+ return results ;
5071
+ }
5072
+
5073
+ // Complete Modules module specification values
5074
+ if ( currentParameterMatch . Value . Equals ( "Modules" , StringComparison . OrdinalIgnoreCase ) )
5075
+ {
5076
+ int hashtableStart = lineToCursor . LastIndexOf ( "@{" ) ;
5077
+ int hashtableEnd = lineToCursor . LastIndexOf ( '}' ) ;
5078
+
5079
+ bool insideHashtable = hashtableStart != - 1 && ( hashtableEnd == - 1 || hashtableEnd < hashtableStart ) ;
5080
+
5081
+ // If not inside a hashtable, try to complete a module simple name
5082
+ if ( ! insideHashtable )
5083
+ {
5084
+ context . WordToComplete = currentValue ;
5085
+ return CompleteModuleName ( context , true ) ;
5086
+ }
5087
+
5088
+ string hashtableString = lineToCursor . Substring ( hashtableStart ) ;
5089
+
5090
+ // Regex to find hashtable keys with or without quotes
5091
+ hashtableKeyMatches = Regex . Matches ( hashtableString , @"(@{|;)\s*(?:'|\""|\w*)\w*" ) ;
5092
+
5093
+ // Build the list of keys we might want to complete, based on what's already been provided
5094
+ var moduleSpecKeysToComplete = new HashSet < string > ( s_requiresModuleSpecKeys . Keys ) ;
5095
+ bool sawModuleNameLast = false ;
5096
+ foreach ( Match existingHashtableKeyMatch in hashtableKeyMatches )
5097
+ {
5098
+ string existingHashtableKey = existingHashtableKeyMatch . Value . TrimStart ( s_hashtableKeyPrefixes ) ;
5099
+
5100
+ if ( string . IsNullOrEmpty ( existingHashtableKey ) )
5101
+ {
5102
+ continue ;
5103
+ }
5104
+
5105
+ // Remove the existing key we just saw
5106
+ moduleSpecKeysToComplete . Remove ( existingHashtableKey ) ;
5107
+
5108
+ // We need to remember later if we saw "ModuleName" as the last hashtable key, for completions
5109
+ if ( sawModuleNameLast = existingHashtableKey . Equals ( "ModuleName" , StringComparison . OrdinalIgnoreCase ) )
5110
+ {
5111
+ continue ;
5112
+ }
5113
+
5114
+ // "RequiredVersion" is mutually exclusive with "ModuleVersion" and "MaximumVersion"
5115
+ if ( existingHashtableKey . Equals ( "ModuleVersion" , StringComparison . OrdinalIgnoreCase )
5116
+ || existingHashtableKey . Equals ( "MaximumVersion" , StringComparison . OrdinalIgnoreCase ) )
5117
+ {
5118
+ moduleSpecKeysToComplete . Remove ( "RequiredVersion" ) ;
5119
+ continue ;
5120
+ }
5121
+
5122
+ if ( existingHashtableKey . Equals ( "RequiredVersion" , StringComparison . OrdinalIgnoreCase ) )
5123
+ {
5124
+ moduleSpecKeysToComplete . Remove ( "ModuleVersion" ) ;
5125
+ moduleSpecKeysToComplete . Remove ( "MaximumVersion" ) ;
5126
+ continue ;
5127
+ }
5128
+ }
5129
+
5130
+ Group lastHashtableKeyPrefixGroup = hashtableKeyMatches [ ^ 1 ] . Groups [ 0 ] ;
5131
+
5132
+ // If we're not completing a key for the hashtable, try to complete module names, but nothing else
5133
+ bool completingHashtableKey = lastHashtableKeyPrefixGroup . Index + lastHashtableKeyPrefixGroup . Length == hashtableString . Length ;
5134
+ if ( ! completingHashtableKey )
5135
+ {
5136
+ if ( sawModuleNameLast )
5137
+ {
5138
+ context . WordToComplete = currentValue ;
5139
+ return CompleteModuleName ( context , true ) ;
5140
+ }
5141
+
5142
+ return results ;
5143
+ }
5144
+
5145
+ // Now try to complete hashtable keys
5146
+ foreach ( string moduleSpecKey in moduleSpecKeysToComplete )
5147
+ {
5148
+ if ( moduleSpecKey . StartsWith ( currentValue , StringComparison . OrdinalIgnoreCase ) )
5149
+ {
5150
+ results . Add ( new CompletionResult ( moduleSpecKey , moduleSpecKey , CompletionResultType . ParameterValue , s_requiresModuleSpecKeys [ moduleSpecKey ] ) ) ;
5151
+ }
5152
+ }
5153
+ }
5154
+
5155
+ return results ;
5156
+ }
5157
+
5158
+ private static readonly IReadOnlyDictionary < string , string > s_requiresParameters = new SortedList < string , string > ( StringComparer . OrdinalIgnoreCase )
5159
+ {
5160
+ { "Modules" , "Specifies PowerShell modules that the script requires." } ,
5161
+ { "PSEdition" , "Specifies a PowerShell edition that the script requires." } ,
5162
+ { "RunAsAdministrator" , "Specifies that PowerShell must be running as administrator on Windows." } ,
5163
+ { "Version" , "Specifies the minimum version of PowerShell that the script requires." } ,
5164
+ } ;
5165
+
5166
+ private static readonly IReadOnlyDictionary < string , string > s_requiresPSEditions = new SortedList < string , string > ( StringComparer . OrdinalIgnoreCase )
5167
+ {
5168
+ { "Core" , "Specifies that the script requires PowerShell Core to run." } ,
5169
+ { "Desktop" , "Specifies that the script requires Windows PowerShell to run." } ,
5170
+ } ;
5171
+
5172
+ private static readonly IReadOnlyDictionary < string , string > s_requiresModuleSpecKeys = new SortedList < string , string > ( StringComparer . OrdinalIgnoreCase )
5173
+ {
5174
+ { "ModuleName" , "Required. Specifies the module name." } ,
5175
+ { "GUID" , "Optional. Specifies the GUID of the module." } ,
5176
+ { "ModuleVersion" , "Specifies a minimum acceptable version of the module." } ,
5177
+ { "RequiredVersion" , "Specifies an exact, required version of the module." } ,
5178
+ { "MaximumVersion" , "Specifies the maximum acceptable version of the module." } ,
5179
+ } ;
5180
+
5181
+ private static readonly char [ ] s_hashtableKeyPrefixes = new [ ]
5182
+ {
5183
+ '@' ,
5184
+ '{' ,
5185
+ ';' ,
5186
+ '"' ,
5187
+ '\' ' ,
5188
+ ' ' ,
5189
+ } ;
5190
+
4991
5191
#endregion Comments
4992
5192
4993
5193
#region Members
0 commit comments