Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
CommandCompletion.cs565 linesDownload Raw Back to CommandCompletion
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics.CodeAnalysis;8using System.Management.Automation.Language;9using System.Management.Automation.Runspaces;10#if LEGACYTELEMETRY11using System.Diagnostics;12using Microsoft.PowerShell.Telemetry.Internal;13#endif14 15namespace System.Management.Automation16{17    /// <summary>18    /// Provides a set of possible completions for given input.19    /// </summary>20    public class CommandCompletion21    {22        /// <summary>23        /// Construct the result CompleteInput or TabExpansion2.24        /// </summary>25        public CommandCompletion(Collection<CompletionResult> matches, int currentMatchIndex, int replacementIndex, int replacementLength)26        {27            this.CompletionMatches = matches;28            this.CurrentMatchIndex = currentMatchIndex;29            this.ReplacementIndex = replacementIndex;30            this.ReplacementLength = replacementLength;31        }32 33        #region Fields and Properties34 35        /// <summary>36        /// Current index in <see cref="CompletionMatches"/>.37        /// </summary>38        public int CurrentMatchIndex { get; set; }39 40        /// <summary>41        /// Returns the starting replacement index from the original input.42        /// </summary>43        public int ReplacementIndex { get; set; }44 45        /// <summary>46        /// Returns the length of the text to replace from the original input.47        /// </summary>48        public int ReplacementLength { get; set; }49 50        /// <summary>51        /// Gets all the completion results.52        /// </summary>53        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]54        public Collection<CompletionResult> CompletionMatches { get; set; }55 56        internal static readonly IList<CompletionResult> EmptyCompletionResult = Array.Empty<CompletionResult>();57 58        private static readonly CommandCompletion s_emptyCommandCompletion = new CommandCompletion(59            new Collection<CompletionResult>(EmptyCompletionResult), -1, -1, -1);60 61        #endregion Fields and Properties62 63        #region public methods64 65        /// <summary>66        /// </summary>67        /// <param name="input"></param>68        /// <param name="cursorIndex"></param>69        /// <returns></returns>70        public static Tuple<Ast, Token[], IScriptPosition> MapStringInputToParsedInput(string input, int cursorIndex)71        {72            if (cursorIndex > input.Length)73            {74                throw PSTraceSource.NewArgumentException(nameof(cursorIndex));75            }76 77            Token[] tokens;78            ParseError[] errors;79            var ast = Parser.ParseInput(input, out tokens, out errors);80 81            IScriptPosition cursorPosition =82                ((InternalScriptPosition)ast.Extent.StartScriptPosition).CloneWithNewOffset(cursorIndex);83            return Tuple.Create<Ast, Token[], IScriptPosition>(ast, tokens, cursorPosition);84        }85 86        /// <summary>87        /// </summary>88        /// <param name="input">The input to complete.</param>89        /// <param name="cursorIndex">The index of the cursor in the input.</param>90        /// <param name="options">Optional options to configure how completion is performed.</param>91        /// <returns></returns>92        public static CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options)93        {94            if (input == null || input.Length == 0)95            {96                return s_emptyCommandCompletion;97            }98 99            var parsedInput = MapStringInputToParsedInput(input, cursorIndex);100            return CompleteInputImpl(parsedInput.Item1, parsedInput.Item2, parsedInput.Item3, options);101        }102 103        /// <summary>104        /// </summary>105        /// <param name="ast">Ast for pre-parsed input.</param>106        /// <param name="tokens">Tokens for pre-parsed input.</param>107        /// <param name="positionOfCursor"></param>108        /// <param name="options">Optional options to configure how completion is performed.</param>109        /// <returns></returns>110        public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options)111        {112            if (ast == null)113            {114                throw PSTraceSource.NewArgumentNullException(nameof(ast));115            }116 117            if (tokens == null)118            {119                throw PSTraceSource.NewArgumentNullException(nameof(tokens));120            }121 122            if (positionOfCursor == null)123            {124                throw PSTraceSource.NewArgumentNullException(nameof(positionOfCursor));125            }126 127            if (ast.Extent.Text.Length == 0)128            {129                return s_emptyCommandCompletion;130            }131 132            return CompleteInputImpl(ast, tokens, positionOfCursor, options);133        }134 135        /// <summary>136        /// Invokes the script function TabExpansion2.137        /// </summary>138        /// <param name="input">The input script to complete.</param>139        /// <param name="cursorIndex">The offset in <paramref name="input"/> where completion is requested.</param>140        /// <param name="options">Optional parameter that specifies configurable options for completion.</param>141        /// <param name="powershell">The powershell to use to invoke the script function TabExpansion2.</param>142        /// <returns>A collection of completions with the replacement start and length.</returns>143        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "powershell")]144        public static CommandCompletion CompleteInput(string input, int cursorIndex, Hashtable options, PowerShell powershell)145        {146            if (input == null || input.Length == 0)147            {148                return s_emptyCommandCompletion;149            }150 151            if (cursorIndex > input.Length)152            {153                throw PSTraceSource.NewArgumentException(nameof(cursorIndex));154            }155 156            if (powershell == null)157            {158                throw PSTraceSource.NewArgumentNullException(nameof(powershell));159            }160 161            // If we are in a debugger stop, let the debugger do the command completion.162            var debugger = powershell.Runspace?.Debugger;163            if ((debugger != null) && debugger.InBreakpoint)164            {165                return CompleteInputInDebugger(input, cursorIndex, options, debugger);166            }167 168            var remoteRunspace = powershell.Runspace as RemoteRunspace;169            if (remoteRunspace != null)170            {171                // If the runspace is not available to run commands then exit here because nested commands are not172                // supported on remote runspaces.173                if (powershell.IsNested || (remoteRunspace.RunspaceAvailability != RunspaceAvailability.Available))174                {175                    return s_emptyCommandCompletion;176                }177 178                // If it's in the nested prompt, the powershell instance is created by "PowerShell.Create(RunspaceMode.CurrentRunspace);".179                // In this case, the powershell._runspace is null but if we try to access the property "Runspace", it will create a new180                // local runspace - the default local runspace will be abandoned. So we check the powershell.IsChild first to make sure181                // not to access the property "Runspace" in this case - powershell.isChild will be set to true only in this case.182                if (!powershell.IsChild)183                {184                    CheckScriptCallOnRemoteRunspace(remoteRunspace);185 186                    // TabExpansion2 script is not available prior to PSv3.187                    if (remoteRunspace.GetCapabilities().Equals(Runspaces.RunspaceCapability.Default))188                    {189                        return s_emptyCommandCompletion;190                    }191                }192            }193 194            return CallScriptWithStringParameterSet(input, cursorIndex, options, powershell);195        }196 197        /// <summary>198        /// Invokes the script function TabExpansion2.199        /// </summary>200        /// <param name="ast">The ast for pre-parsed input.</param>201        /// <param name="tokens"></param>202        /// <param name="cursorPosition"></param>203        /// <param name="options">Optional options to configure how completion is performed.</param>204        /// <param name="powershell">The powershell to use to invoke the script function TabExpansion2.</param>205        /// <returns></returns>206        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "powershell")]207        public static CommandCompletion CompleteInput(Ast ast, Token[] tokens, IScriptPosition cursorPosition, Hashtable options, PowerShell powershell)208        {209            if (ast == null)210            {211                throw PSTraceSource.NewArgumentNullException(nameof(ast));212            }213 214            if (tokens == null)215            {216                throw PSTraceSource.NewArgumentNullException(nameof(tokens));217            }218 219            if (cursorPosition == null)220            {221                throw PSTraceSource.NewArgumentNullException(nameof(cursorPosition));222            }223 224            if (powershell == null)225            {226                throw PSTraceSource.NewArgumentNullException(nameof(powershell));227            }228 229            if (ast.Extent.Text.Length == 0)230            {231                return s_emptyCommandCompletion;232            }233 234            // If we are in a debugger stop, let the debugger do the command completion.235            var debugger = powershell.Runspace?.Debugger;236            if ((debugger != null) && debugger.InBreakpoint)237            {238                return CompleteInputInDebugger(ast, tokens, cursorPosition, options, debugger);239            }240 241            var remoteRunspace = powershell.Runspace as RemoteRunspace;242            if (remoteRunspace != null)243            {244                // If the runspace is not available to run commands then exit here because nested commands are not245                // supported on remote runspaces.246                if (powershell.IsNested || (remoteRunspace.RunspaceAvailability != RunspaceAvailability.Available))247                {248                    return s_emptyCommandCompletion;249                }250 251                if (!powershell.IsChild)252                {253                    CheckScriptCallOnRemoteRunspace(remoteRunspace);254 255                    // TabExpansion2 script is not available prior to PSv3.256                    if (remoteRunspace.GetCapabilities().Equals(Runspaces.RunspaceCapability.Default))257                    {258                        return s_emptyCommandCompletion;259                    }260 261                    // When calling the TabExpansion2 script, the input should be the whole script text262                    string input = ast.Extent.Text;263                    int cursorIndex = ((InternalScriptPosition)cursorPosition).Offset;264                    return CallScriptWithStringParameterSet(input, cursorIndex, options, powershell);265                }266            }267 268            return CallScriptWithAstParameterSet(ast, tokens, cursorPosition, options, powershell);269        }270 271        /// <summary>272        /// Get the next result, moving forward or backward.  Supports wraparound, so if there are any results at all,273        /// this method will never fail and never return null.274        /// </summary>275        /// <param name="forward">True if we should move forward through the list, false if backwards.</param>276        /// <returns>The next completion result, or null if no results.</returns>277        public CompletionResult GetNextResult(bool forward)278        {279            CompletionResult result = null;280            var count = CompletionMatches.Count;281            if (count > 0)282            {283                CurrentMatchIndex += forward ? 1 : -1;284                if (CurrentMatchIndex >= count)285                {286                    CurrentMatchIndex = 0;287                }288                else if (CurrentMatchIndex < 0)289                {290                    CurrentMatchIndex = count - 1;291                }292 293                result = CompletionMatches[CurrentMatchIndex];294            }295 296            return result;297        }298 299        #endregion public methods300 301        #region Internal methods302 303        /// <summary>304        /// Command completion while in debug break mode.305        /// </summary>306        /// <param name="input">The input script to complete.</param>307        /// <param name="cursorIndex">The offset in <paramref name="input"/> where completion is requested.</param>308        /// <param name="options">Optional parameter that specifies configurable options for completion.</param>309        /// <param name="debugger">Current debugger.</param>310        /// <returns>A collection of completions with the replacement start and length.</returns>311        internal static CommandCompletion CompleteInputInDebugger(string input, int cursorIndex, Hashtable options, Debugger debugger)312        {313            if (input == null)314            {315                return s_emptyCommandCompletion;316            }317 318            if (cursorIndex > input.Length)319            {320                throw PSTraceSource.NewArgumentException(nameof(cursorIndex));321            }322 323            if (debugger == null)324            {325                throw PSTraceSource.NewArgumentNullException(nameof(debugger));326            }327 328            Command cmd = new Command("TabExpansion2");329            cmd.Parameters.Add("InputScript", input);330            cmd.Parameters.Add("CursorColumn", cursorIndex);331            cmd.Parameters.Add("Options", options);332 333            return ProcessCompleteInputCommand(cmd, debugger);334        }335 336        /// <summary>337        /// Command completion while in debug break mode.338        /// </summary>339        /// <param name="ast">The ast for pre-parsed input.</param>340        /// <param name="tokens"></param>341        /// <param name="cursorPosition"></param>342        /// <param name="options">Optional options to configure how completion is performed.</param>343        /// <param name="debugger">Current debugger.</param>344        /// <returns>Command completion.</returns>345        internal static CommandCompletion CompleteInputInDebugger(Ast ast, Token[] tokens, IScriptPosition cursorPosition, Hashtable options, Debugger debugger)346        {347            if (ast == null)348            {349                throw PSTraceSource.NewArgumentNullException(nameof(ast));350            }351 352            if (tokens == null)353            {354                throw PSTraceSource.NewArgumentNullException(nameof(tokens));355            }356 357            if (cursorPosition == null)358            {359                throw PSTraceSource.NewArgumentNullException(nameof(cursorPosition));360            }361 362            if (debugger == null)363            {364                throw PSTraceSource.NewArgumentNullException(nameof(debugger));365            }366 367            // For remote debugging just pass string input.368            if ((debugger is RemoteDebugger) || debugger.IsPushed)369            {370                string input = ast.Extent.Text;371                int cursorIndex = ((InternalScriptPosition)cursorPosition).Offset;372 373                return CompleteInputInDebugger(input, cursorIndex, options, debugger);374            }375 376            Command cmd = new Command("TabExpansion2");377            cmd.Parameters.Add("Ast", ast);378            cmd.Parameters.Add("Tokens", tokens);379            cmd.Parameters.Add("PositionOfCursor", cursorPosition);380            cmd.Parameters.Add("Options", options);381 382            return ProcessCompleteInputCommand(cmd, debugger);383        }384 385        private static CommandCompletion ProcessCompleteInputCommand(386            Command cmd,387            Debugger debugger)388        {389            PSCommand command = new PSCommand(cmd);390            PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();391 392            debugger.ProcessCommand(command, output);393 394            if (output.Count == 1)395            {396                var commandCompletion = output[0].BaseObject as CommandCompletion;397                if (commandCompletion != null)398                {399                    return commandCompletion;400                }401            }402 403            return s_emptyCommandCompletion;404        }405 406        #endregion407 408        #region private methods409 410        private static void CheckScriptCallOnRemoteRunspace(RemoteRunspace remoteRunspace)411        {412            var remoteRunspaceInternal = remoteRunspace.RunspacePool.RemoteRunspacePoolInternal;413            if (remoteRunspaceInternal != null)414            {415                var transportManager = remoteRunspaceInternal.DataStructureHandler.TransportManager;416                if (transportManager != null && transportManager.TypeTable == null)417                {418                    // The remote runspace was created without a TypeTable instance.419                    // The tab completion results cannot be deserialized if the TypeTable is not available420                    throw PSTraceSource.NewInvalidOperationException(TabCompletionStrings.CannotDeserializeTabCompletionResult);421                }422            }423        }424 425        private static CommandCompletion CallScriptWithStringParameterSet(string input, int cursorIndex, Hashtable options, PowerShell powershell)426        {427            try428            {429                powershell.Commands.Clear();430                powershell.AddCommand("TabExpansion2")431                    .AddArgument(input)432                    .AddArgument(cursorIndex)433                    .AddArgument(options);434                var results = powershell.Invoke();435                if (results == null)436                {437                    return s_emptyCommandCompletion;438                }439 440                if (results.Count == 1)441                {442                    var result = PSObject.Base(results[0]);443                    var commandCompletion = result as CommandCompletion;444                    if (commandCompletion != null)445                    {446                        return commandCompletion;447                    }448                }449            }450            catch (Exception)451            {452            }453            finally454            {455                powershell.Commands.Clear();456            }457 458            return s_emptyCommandCompletion;459        }460 461        private static CommandCompletion CallScriptWithAstParameterSet(Ast ast, Token[] tokens, IScriptPosition cursorPosition, Hashtable options, PowerShell powershell)462        {463            try464            {465                powershell.Commands.Clear();466                powershell.AddCommand("TabExpansion2")467                    .AddArgument(ast)468                    .AddArgument(tokens)469                    .AddArgument(cursorPosition)470                    .AddArgument(options);471                var results = powershell.Invoke();472                if (results == null)473                {474                    return s_emptyCommandCompletion;475                }476 477                if (results.Count == 1)478                {479                    var result = PSObject.Base(results[0]);480                    var commandCompletion = result as CommandCompletion;481                    if (commandCompletion != null)482                    {483                        return commandCompletion;484                    }485                }486            }487            catch (Exception)488            {489            }490            finally491            {492                powershell.Commands.Clear();493            }494 495            return s_emptyCommandCompletion;496        }497 498        // This is the start of the real implementation of autocomplete/intellisense/tab completion499        private static CommandCompletion CompleteInputImpl(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options)500        {501#if LEGACYTELEMETRY502            // We could start collecting telemetry at a later date.503            // We will leave the #if to remind us that we did this once.504            var sw = new Stopwatch();505            sw.Start();506#endif507            using (var powershell = PowerShell.Create(RunspaceMode.CurrentRunspace))508            {509                var context = LocalPipeline.GetExecutionContextFromTLS();510 511                int replacementIndex = -1;512                int replacementLength = -1;513                List<CompletionResult> results = null;514 515                {516                    // If we were invoked from TabExpansion2, we want to "remove" TabExpansion2 and anything it calls517                    // from our results.  We do this by faking out the session so that TabExpansion2 isn't anywhere to be found.518                    SessionStateScope scopeToRestore;519                    if (context.CurrentCommandProcessor is not null520                        && context.CurrentCommandProcessor.Command.CommandInfo.Name.Equals("TabExpansion2", StringComparison.OrdinalIgnoreCase)521                        && context.CurrentCommandProcessor.UseLocalScope522                        && context.EngineSessionState.CurrentScope.Parent is not null)523                    {524                        scopeToRestore = context.EngineSessionState.CurrentScope;525                        context.EngineSessionState.CurrentScope = scopeToRestore.Parent;526                    }527                    else528                    {529                        scopeToRestore = null;530                    }531 532                    try533                    {534                        var completionAnalysis = new CompletionAnalysis(ast, tokens, positionOfCursor, options);535                        results = completionAnalysis.GetResults(powershell, out replacementIndex, out replacementLength);536                    }537                    finally538                    {539                        if (scopeToRestore != null)540                        {541                            context.EngineSessionState.CurrentScope = scopeToRestore;542                        }543                    }544                }545 546                var completionResults = results ?? EmptyCompletionResult;547 548#if LEGACYTELEMETRY549                // no telemetry here. We don't capture tab completion performance.550                sw.Stop();551                TelemetryAPI.ReportTabCompletionTelemetry(sw.ElapsedMilliseconds, completionResults.Count,552                    completionResults.Count > 0 ? completionResults[0].ResultType : CompletionResultType.Text);553#endif554                return new CommandCompletion(555                    new Collection<CompletionResult>(completionResults),556                    -1,557                    replacementIndex,558                    replacementLength);559            }560        }561 562        #endregion private methods563    }564}565