Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
CompletionHelpers.cs323 linesDownload Raw Back to CommandCompletion
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Buffers;5using System.Collections.Generic;6using System.Management.Automation.Language;7 8namespace System.Management.Automation9{10    /// <summary>11    /// Shared helper class for common completion helper methods.12    /// </summary>13    internal static class CompletionHelpers14    {15        private static readonly SearchValues<char> s_defaultCharsToCheck = SearchValues.Create("$`");16 17        private const string SingleQuote = "'";18        private const string DoubleQuote = "\"";19 20        /// <summary>21        /// Get matching completions from word to complete.22        /// This makes it easier to handle different variations of completions with consideration of quotes.23        /// </summary>24        /// <param name="wordToComplete">The word to complete.</param>25        /// <param name="possibleCompletionValues">The possible completion values to iterate.</param>26        /// <param name="displayInfoMapper">The optional completion display info mapper delegate for tool tip and list item text.</param>27        /// <param name="resultType">The optional completion result type. Default is Text.</param>28        /// <param name="matchStrategy">The optional match strategy delegate.</param>29        /// <returns>List of matching completion results.</returns>30        internal static IEnumerable<CompletionResult> GetMatchingResults(31            string wordToComplete,32            IEnumerable<string> possibleCompletionValues,33            CompletionDisplayInfoMapper displayInfoMapper = null,34            CompletionResultType resultType = CompletionResultType.Text,35            MatchStrategy matchStrategy = null)36        {37            displayInfoMapper ??= DefaultDisplayInfoMapper;38            matchStrategy ??= DefaultMatch;39 40            string quote = HandleDoubleAndSingleQuote(ref wordToComplete);41            if (quote != SingleQuote)42            {43                wordToComplete = NormalizeToExpandableString(wordToComplete);44            }45 46            foreach (string value in possibleCompletionValues)47            {48                if (matchStrategy(value, wordToComplete))49                {50                    string completionText = QuoteCompletionText(value, quote);51 52                    (string toolTip, string listItemText) = displayInfoMapper(value);53 54                    yield return new CompletionResult(completionText, listItemText, resultType, toolTip);55                }56            }57        }58 59        /// <summary>60        /// Provides the display information for a completion result.61        /// This delegate is used to map a string value to its corresponding display information.62        /// </summary>63        /// <param name="value">The input value to be mapped</param>64        /// <returns>Completion display info containing tool tip and list item text.</returns>65        internal delegate (string ToolTip, string ListItemText) CompletionDisplayInfoMapper(string value);66 67        /// <summary>68        /// Provides the default display information for a completion result.69        /// Defaults to using the input value for both the tool tip and list item text.70        /// </summary>71        /// <returns>Completion display info containing tool tip and list item text.</returns>72        internal static readonly CompletionDisplayInfoMapper DefaultDisplayInfoMapper = value73            => (value, value);74 75        /// <summary>76        /// Normalizes the input string to an expandable string format for PowerShell.77        /// </summary>78        /// <param name="value">The input string to be normalized.</param>79        /// <returns>The normalized string with special characters replaced by their PowerShell escape sequences.</returns>80        /// <remarks>81        /// This method replaces special characters in the input string with their PowerShell equivalent escape sequences:82        /// <list type="bullet">83        ///     <item><description>Replaces "\r" (carriage return) with "`r".</description></item>84        ///     <item><description>Replaces "\n" (newline) with "`n".</description></item>85        ///     <item><description>Replaces "\t" (tab) with "`t".</description></item>86        ///     <item><description>Replaces "\0" (null) with "`0".</description></item>87        ///     <item><description>Replaces "\a" (bell) with "`a".</description></item>88        ///     <item><description>Replaces "\b" (backspace) with "`b".</description></item>89        ///     <item><description>Replaces "\u001b" (escape character) with "`e".</description></item>90        ///     <item><description>Replaces "\f" (form feed) with "`f".</description></item>91        ///     <item><description>Replaces "\v" (vertical tab) with "`v".</description></item>92        /// </list>93        /// </remarks>94        internal static string NormalizeToExpandableString(string value)95            => value96                .Replace("\r", "`r")97                .Replace("\n", "`n")98                .Replace("\t", "`t")99                .Replace("\0", "`0")100                .Replace("\a", "`a")101                .Replace("\b", "`b")102                .Replace("\u001b", "`e")103                .Replace("\f", "`f")104                .Replace("\v", "`v");105 106        /// <summary>107        /// Defines a strategy for determining if a value matches a word or pattern.108        /// </summary>109        /// <param name="value">The input string to check for a match.</param>110        /// <param name="wordToComplete">The word or pattern to match against.</param>111        /// <returns>112        /// <c>true</c> if the value matches the specified word or pattern; otherwise, <c>false</c>.113        /// </returns>114        internal delegate bool MatchStrategy(string value, string wordToComplete);115 116        /// <summary>117        /// Determines if the given value matches the specified word using a literal, case-insensitive prefix match.118        /// </summary>119        /// <returns>120        /// <c>true</c> if the value starts with the word (case-insensitively); otherwise, <c>false</c>.121        /// </returns>122        internal static readonly MatchStrategy LiteralMatchOrdinalIgnoreCase = (value, wordToComplete)123            => value.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase);124 125        /// <summary>126        /// Determines if the given value matches the specified word using wildcard pattern matching.127        /// </summary>128        /// <returns>129        /// <c>true</c> if the value matches the word as a wildcard pattern; otherwise, <c>false</c>.130        /// </returns>131        /// <remarks>132        /// Wildcard pattern matching allows for flexible matching, where wilcards can represent133        /// multiple characters in the input. This strategy is case-insensitive.134        /// </remarks>135        internal static readonly MatchStrategy WildcardPatternMatchIgnoreCase = (value, wordToComplete)136            => WildcardPattern137                .Get(wordToComplete + "*", WildcardOptions.IgnoreCase)138                .IsMatch(value);139 140        /// <summary>141        /// Determines if the given value matches the specified word considering wildcard characters literally.142        /// </summary>143        /// <returns>144        /// <c>true</c> if the value matches either the literal normalized word or the wildcard pattern with escaping;145        /// otherwise, <c>false</c>.146        /// </returns>147        /// <remarks>148        /// This strategy first attempts a literal prefix match for performance and, if unsuccessful, escapes the word to complete to149        /// handle any problematic wildcard characters before performing a wildcard match.150        /// </remarks>151        internal static readonly MatchStrategy WildcardPatternEscapeMatch = (value, wordToComplete)152            => LiteralMatchOrdinalIgnoreCase(value, wordToComplete) ||153               WildcardPatternMatchIgnoreCase(value, WildcardPattern.Escape(wordToComplete));154 155        /// <summary>156        /// Determines if the given value matches the specified word taking into account wildcard characters.157        /// </summary>158        /// <returns>159        /// <c>true</c> if the value matches either the literal normalized word or the wildcard pattern; otherwise, <c>false</c>.160        /// </returns>161        /// <remarks>162        /// This strategy attempts a literal match first for performance and, if unsuccessful, evaluates the word against a wildcard pattern.163        /// </remarks>164        internal static readonly MatchStrategy DefaultMatch = (value, wordToComplete)165            => LiteralMatchOrdinalIgnoreCase(value, wordToComplete) ||166               WildcardPatternMatchIgnoreCase(value, wordToComplete);167 168        /// <summary>169        /// Removes wrapping quotes from a string and returns the quote used, if present.170        /// </summary>171        /// <param name="wordToComplete">172        /// The string to process, potentially surrounded by single or double quotes.173        /// This parameter is updated in-place to exclude the removed quotes.174        /// </param>175        /// <returns>176        /// The type of quote detected (single or double), or an empty string if no quote is found.177        /// </returns>178        /// <remarks>179        /// This method checks for single or double quotes at the start and end of the string.180        /// If wrapping quotes are detected and match, both are removed; otherwise, only the front quote is removed.181        /// The string is updated in-place, and only matching front-and-back quotes are stripped.182        /// If no quotes are detected or the input is empty, the original string remains unchanged.183        /// </remarks>184        internal static string HandleDoubleAndSingleQuote(ref string wordToComplete)185        {186            if (string.IsNullOrEmpty(wordToComplete))187            {188                return string.Empty;189            }190 191            char frontQuote = wordToComplete[0];192            bool hasFrontSingleQuote = frontQuote.IsSingleQuote();193            bool hasFrontDoubleQuote = frontQuote.IsDoubleQuote();194 195            if (!hasFrontSingleQuote && !hasFrontDoubleQuote)196            {197                return string.Empty;198            }199 200            string quoteInUse = hasFrontSingleQuote ? SingleQuote : DoubleQuote;201 202            int length = wordToComplete.Length;203            if (length == 1)204            {205                wordToComplete = string.Empty;206                return quoteInUse;207            }208 209            char backQuote = wordToComplete[length - 1];210            bool hasBackSingleQuote = backQuote.IsSingleQuote();211            bool hasBackDoubleQuote = backQuote.IsDoubleQuote();212 213            bool hasBothFrontAndBackQuotes =214                (hasFrontSingleQuote && hasBackSingleQuote) || (hasFrontDoubleQuote && hasBackDoubleQuote);215 216            if (hasBothFrontAndBackQuotes)217            {218                wordToComplete = wordToComplete.Substring(1, length - 2);219                return quoteInUse;220            }221 222            bool hasFrontQuoteAndNoBackQuote =223                (hasFrontSingleQuote || hasFrontDoubleQuote) && !hasBackSingleQuote && !hasBackDoubleQuote;224 225            if (hasFrontQuoteAndNoBackQuote)226            {227                wordToComplete = wordToComplete.Substring(1);228                return quoteInUse;229            }230 231            return string.Empty;232        }233 234        /// <summary>235        /// Determines whether the specified completion string requires quotes.236        /// Quoting is required if:237        /// <list type="bullet">238        ///   <item><description>There are parsing errors in the input string.</description></item>239        ///   <item><description>The parsed token count is not exactly two (the input token + EOF).</description></item>240        ///   <item><description>The first token is a string or a PowerShell keyword containing special characters.</description></item>241        ///   <item><description>The first token is a semi colon or comma token.</description></item>242        /// </list>243        /// </summary>244        /// <param name="completion">The input string to analyze for quoting requirements.</param>245        /// <returns><c>true</c> if the string requires quotes, <c>false</c> otherwise.</returns>246        internal static bool CompletionRequiresQuotes(string completion)247        {248            Parser.ParseInput(completion, out Token[] tokens, out ParseError[] errors);249 250            bool isExpectedTokenCount = tokens.Length == 2;251 252            bool requireQuote = errors.Length > 0 || !isExpectedTokenCount;253 254            Token firstToken = tokens[0];255            bool isStringToken = firstToken is StringToken;256            bool isKeywordToken = (firstToken.TokenFlags & TokenFlags.Keyword) != 0;257            bool isSemiToken = firstToken.Kind == TokenKind.Semi;258            bool isCommaToken = firstToken.Kind == TokenKind.Comma;259 260            if ((!requireQuote && isStringToken) || (isExpectedTokenCount && isKeywordToken))261            {262                requireQuote = ContainsCharsToCheck(firstToken.Text);263            }264 265            else if (isExpectedTokenCount && (isSemiToken || isCommaToken))266            {267                requireQuote = true;268            }269 270            return requireQuote;271        }272 273        /// <summary>274        /// Determines whether the given text contains an escaped newline string.275        /// </summary>276        /// <param name="text">The input string to check for escaped newlines.</param>277        /// <returns>278        /// <c>true</c> if the text contains the escaped Unix-style newline string ("`n") or279        /// the Windows-style newline string ("`r`n"); otherwise, <c>false</c>.280        /// </returns>281        private static bool ContainsEscapedNewlineString(string text)282            => text.Contains("`n", StringComparison.Ordinal);283 284        private static bool ContainsCharsToCheck(ReadOnlySpan<char> text)285            => text.ContainsAny(s_defaultCharsToCheck);286 287        /// <summary>288        /// Quotes a given completion text.289        /// </summary>290        /// <param name="completionText">291        /// The text to be quoted.292        /// </param>293        /// <param name="quote">294        /// The quote character to use for enclosing the text. Defaults to a single quote if not provided.295        /// </param>296        /// <returns>297        /// The quoted <paramref name="completionText"/>.298        /// </returns>299        internal static string QuoteCompletionText(string completionText, string quote)300        {301            // Escaped newlines e.g. `r`n need be surrounded with double quotes302            if (ContainsEscapedNewlineString(completionText))303            {304                return DoubleQuote + completionText + DoubleQuote;305            }306 307            if (!CompletionRequiresQuotes(completionText))308            {309                return quote + completionText + quote;310            }311 312            string quoteInUse = string.IsNullOrEmpty(quote) ? SingleQuote : quote;313 314            if (quoteInUse == SingleQuote)315            {316                completionText = CodeGeneration.EscapeSingleQuotedStringContent(completionText);317            }318 319            return quoteInUse + completionText + quoteInUse;320        }321    }322}323