/// <inheritdoc/>
        /// <remarks>
        /// Tries to get the suggestions for the user input from the command history. If that doesn't find
        /// <paramref name="suggestionCount"/> suggestions, it'll fallback to find the suggestion regardless of command history.
        /// </remarks>
        public virtual CommandLineSuggestion GetSuggestion(PredictionContext context, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken)
        {
            Validation.CheckArgument(context, $"{nameof(context)} cannot be null");
            Validation.CheckArgument <ArgumentOutOfRangeException>(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0.");

            var        relatedAsts = context.RelatedAsts;
            CommandAst commandAst  = null;

            for (var i = relatedAsts.Count - 1; i >= 0; --i)
            {
                if (relatedAsts[i] is CommandAst c)
                {
                    commandAst = c;
                    break;
                }
            }

            if (commandAst == null)
            {
                return(null);
            }

            var commandName = commandAst.GetCommandName();

            if (string.IsNullOrWhiteSpace(commandName))
            {
                return(null);
            }

            ParameterSet inputParameterSet = null;

            try
            {
                inputParameterSet = new ParameterSet(commandAst, _azContext);
            }
            catch when(!IsSupportedCommand(commandName))
            {
                // We only ignore the exception when the command name is not supported.
                // We want to collect the telemetry about the exception how common it is for the format we don't support.
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            var rawUserInput               = context.InputAst.ToString();
            var presentCommands            = new Dictionary <string, int>();
            var commandBasedPredictor      = _commandBasedPredictor;
            var commandToRequestPrediction = _commandToRequestPrediction;

            var result = commandBasedPredictor?.Item2?.GetSuggestion(commandName,
                                                                     inputParameterSet,
                                                                     rawUserInput,
                                                                     presentCommands,
                                                                     suggestionCount,
                                                                     maxAllowedCommandDuplicate,
                                                                     cancellationToken);

            if ((result != null) && (result.Count > 0))
            {
                var suggestionSource = SuggestionSource.PreviousCommand;

                if (string.Equals(commandToRequestPrediction, commandBasedPredictor?.Item1, StringComparison.Ordinal))
                {
                    suggestionSource = SuggestionSource.CurrentCommand;
                }

                for (var i = 0; i < result.Count; ++i)
                {
                    result.UpdateSuggestionSource(i, suggestionSource);
                }
            }

            if ((result == null) || (result.Count < suggestionCount))
            {
                var fallbackPredictor        = _fallbackPredictor;
                var suggestionCountToRequest = (result == null) ? suggestionCount : suggestionCount - result.Count;
                var resultsFromFallback      = fallbackPredictor?.GetSuggestion(commandName,
                                                                                inputParameterSet,
                                                                                rawUserInput,
                                                                                presentCommands,
                                                                                suggestionCountToRequest,
                                                                                maxAllowedCommandDuplicate,
                                                                                cancellationToken);

                if ((result == null) && (resultsFromFallback != null))
                {
                    result = resultsFromFallback;

                    for (var i = 0; i < result.Count; ++i)
                    {
                        result.UpdateSuggestionSource(i, SuggestionSource.StaticCommands);
                    }
                }
                else if ((resultsFromFallback != null) && (resultsFromFallback.Count > 0))
                {
                    for (var i = 0; i < resultsFromFallback.Count; ++i)
                    {
                        result.AddSuggestion(resultsFromFallback.PredictiveSuggestions[i], resultsFromFallback.SourceTexts[i], SuggestionSource.StaticCommands);
                    }
                }
            }

            return(result);
        }
Пример #2
0
        /// <summary>
        /// Returns suggestions given the user input.
        /// </summary>
        /// <param name="inputCommandName">The command name extracted from the user input.</param>
        /// <param name="inputParameterSet">The parameter set extracted from the user input.</param>
        /// <param name="rawUserInput">The string format of the command line from user input.</param>
        /// <param name="presentCommands">Commands already present. Contents may be added to this collection.</param>
        /// <param name="suggestionCount">The number of suggestions to return.</param>
        /// <param name="maxAllowedCommandDuplicate">The maximum amount of the same commands in the list of predictions.</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The collections of suggestions.</returns>
        public CommandLineSuggestion GetSuggestion(string inputCommandName,
                                                   ParameterSet inputParameterSet,
                                                   string rawUserInput,
                                                   IDictionary <string, int> presentCommands,
                                                   int suggestionCount,
                                                   int maxAllowedCommandDuplicate,
                                                   CancellationToken cancellationToken)
        {
            Validation.CheckArgument(!string.IsNullOrWhiteSpace(inputCommandName), $"{nameof(inputCommandName)} cannot be null or whitespace.");
            Validation.CheckArgument(inputParameterSet, $"{nameof(inputParameterSet)} cannot be null.");
            Validation.CheckArgument(!string.IsNullOrWhiteSpace(rawUserInput), $"{nameof(rawUserInput)} cannot be null or whitespace.");
            Validation.CheckArgument(presentCommands, $"{nameof(presentCommands)} cannot be null.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0.");

            const int             commandCollectionCapacity = 10;
            CommandLineSuggestion result = new();
            var duplicateResults         = new Dictionary <string, DuplicateResult>(commandCollectionCapacity, StringComparer.OrdinalIgnoreCase);

            var isCommandNameComplete = inputParameterSet.Parameters.Any() || rawUserInput.EndsWith(' ');

            Func <string, bool> commandNameQuery = (command) => command.Equals(inputCommandName, StringComparison.OrdinalIgnoreCase);

            if (!isCommandNameComplete)
            {
                commandNameQuery = (command) => command.StartsWith(inputCommandName, StringComparison.OrdinalIgnoreCase);
            }

            // Try to find the matching command and arrange the parameters in the order of the input.
            //
            // Predictions should be flexible, e.g. if "Command -Name N -Location L" is a possibility,
            // then "Command -Location L -Name N" should also be possible.
            //
            // resultBuilder and usedParams are used to store the information to construct the result.
            // We want to avoid too much heap allocation for the performance purpose.

            const int parameterCollectionCapacity = 10;
            var       resultBuilder = new StringBuilder();
            var       usedParams    = new HashSet <int>(parameterCollectionCapacity);
            var       sourceBuilder = new StringBuilder();

            for (var i = 0; i < _commandLinePredictions.Count && result.Count < suggestionCount; ++i)
            {
                if (commandNameQuery(_commandLinePredictions[i].Name))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    resultBuilder.Clear();
                    resultBuilder.Append(_commandLinePredictions[i].Name);
                    usedParams.Clear();
                    string commandNoun = ParameterValuePredictor.GetAzCommandNoun(_commandLinePredictions[i].Name).ToLower();

                    if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, commandNoun, _commandLinePredictions[i].ParameterSet, usedParams))
                    {
                        PredictRestOfParameters(resultBuilder, commandNoun, _commandLinePredictions[i].ParameterSet.Parameters, usedParams);

                        if (resultBuilder.Length <= rawUserInput.Length)
                        {
                            continue;
                        }

                        var prediction = resultBuilder.ToString();

                        sourceBuilder.Clear();
                        sourceBuilder.Append(_commandLinePredictions[i].Name);

                        foreach (var p in _commandLinePredictions[i].ParameterSet.Parameters)
                        {
                            AppendParameterNameAndValue(sourceBuilder, p.Name, p.Value);
                        }

                        if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name))
                        {
                            result.AddSuggestion(new PredictiveSuggestion(prediction, _commandLinePredictions[i].Description), sourceBuilder.ToString());
                            presentCommands.Add(_commandLinePredictions[i].Name, 1);
                        }
                        else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate)
                        {
                            result.AddSuggestion(new PredictiveSuggestion(prediction, _commandLinePredictions[i].Description), sourceBuilder.ToString());
                            presentCommands[_commandLinePredictions[i].Name] += 1;
                        }
                        else
                        {
                            _ = duplicateResults.TryAdd(prediction, new DuplicateResult(sourceBuilder.ToString(), _commandLinePredictions[i].Description));
                        }
                    }
                }
            }

            var resultCount = result.Count;

            if ((resultCount < suggestionCount) && (duplicateResults.Count > 0))
            {
                foreach (var temp in duplicateResults.Take(suggestionCount - resultCount))
                {
                    result.AddSuggestion(new PredictiveSuggestion(temp.Key, temp.Value.Description), temp.Value.Source);
                }
            }

            return(result);
        }
Пример #3
0
        /// <summary>
        /// Determines if parameter set contains all of the parameters of the input.
        /// </summary>
        /// <param name="builder">StringBuilder that aggregates the prediction text output.</param>
        /// <param name="inputParameters">Parsed ParameterSet from the user input AST.</param>
        /// <param name="commandNoun">Command Noun.</param>
        /// <param name="predictionParameters">Candidate prediction parameter set.</param>
        /// <param name="usedParams">Set of used parameters for set.</param>
        private bool DoesPredictionParameterSetMatchInput(StringBuilder builder, ParameterSet inputParameters, string commandNoun, ParameterSet predictionParameters, HashSet <int> usedParams)
        {
            foreach (var inputParameter in inputParameters.Parameters)
            {
                var matchIndex = FindParameterPositionInSet(inputParameter, predictionParameters, usedParams);
                if (matchIndex == -1)
                {
                    return(false);
                }
                else
                {
                    usedParams.Add(matchIndex);
                    if (inputParameter.Value != null)
                    {
                        AppendParameterNameAndValue(builder, predictionParameters.Parameters[matchIndex].Name, inputParameter.Value);
                    }
                    else
                    {
                        BuildParameterValue(builder, commandNoun, predictionParameters.Parameters[matchIndex]);
                    }
                }
            }

            return(true);
        }
Пример #4
0
        /// <inheritdoc/>
        /// <remarks>
        /// Tries to get the suggestions for the user input from the command history. If that doesn't find
        /// <paramref name="suggestionCount"/> suggestions, it'll fallback to find the suggestion regardless of command history.
        /// </remarks>
        public virtual CommandLineSuggestion GetSuggestion(PredictionContext context, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken)
        {
            Validation.CheckArgument(context, $"{nameof(context)} cannot be null");
            Validation.CheckArgument <ArgumentOutOfRangeException>(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0.");

            var        relatedAsts = context.RelatedAsts;
            CommandAst commandAst  = null;

            for (var i = relatedAsts.Count - 1; i >= 0; --i)
            {
                if (relatedAsts[i] is CommandAst c)
                {
                    commandAst = c;
                    break;
                }
            }

            if (commandAst == null)
            {
                return(null);
            }

            var commandName = commandAst.GetCommandName();

            if (string.IsNullOrWhiteSpace(commandName))
            {
                return(null);
            }

            ParameterSet inputParameterSet = null;

            try
            {
                inputParameterSet = new ParameterSet(commandAst, _azContext);
            }
            catch when(!IsSupportedCommand(commandName))
            {
                // We only ignore the exception when the command name is not supported.
                // We want to collect the telemetry about the exception how common it is for the format we don't support.
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            // We want to show a survey/feedback cmdlet at the end of the suggestion list. We try to find one less
            // suggestions to make room for that cmdlet and avoid too much computation.
            // But if only one suggestion is requested, we don't replace it with the survey cmdlets.
            var suggestionFromPredictorCount = (suggestionCount == 1) ? 1 : (suggestionCount - 1);

            var rawUserInput               = context.InputAst.ToString();
            var presentCommands            = new Dictionary <string, int>();
            var commandBasedPredictor      = _commandBasedPredictor;
            var commandToRequestPrediction = _commandToRequestPrediction;

            var result = commandBasedPredictor?.Item2?.GetSuggestion(commandName,
                                                                     inputParameterSet,
                                                                     rawUserInput,
                                                                     presentCommands,
                                                                     suggestionFromPredictorCount,
                                                                     maxAllowedCommandDuplicate,
                                                                     cancellationToken);

            if ((result != null) && (result.Count > 0))
            {
                var suggestionSource = SuggestionSource.PreviousCommand;

                if (string.Equals(commandToRequestPrediction, commandBasedPredictor?.Item1, StringComparison.Ordinal))
                {
                    suggestionSource = SuggestionSource.CurrentCommand;
                }

                for (var i = 0; i < result.Count; ++i)
                {
                    result.UpdateSuggestionSource(i, suggestionSource);
                }
            }

            if ((result == null) || (result.Count < suggestionFromPredictorCount))
            {
                var fallbackPredictor        = _fallbackPredictor;
                var suggestionCountToRequest = (result == null) ? suggestionFromPredictorCount : suggestionFromPredictorCount - result.Count;
                var resultsFromFallback      = fallbackPredictor?.GetSuggestion(commandName,
                                                                                inputParameterSet,
                                                                                rawUserInput,
                                                                                presentCommands,
                                                                                suggestionCountToRequest,
                                                                                maxAllowedCommandDuplicate,
                                                                                cancellationToken);

                if ((result == null) && (resultsFromFallback != null))
                {
                    result = resultsFromFallback;

                    for (var i = 0; i < result.Count; ++i)
                    {
                        result.UpdateSuggestionSource(i, SuggestionSource.StaticCommands);
                    }
                }
                else if ((resultsFromFallback != null) && (resultsFromFallback.Count > 0))
                {
                    for (var i = 0; i < resultsFromFallback.Count; ++i)
                    {
                        result.AddSuggestion(resultsFromFallback.PredictiveSuggestions[i], resultsFromFallback.SourceTexts[i], SuggestionSource.StaticCommands);
                    }
                }
            }

            if (suggestionCount > 1)
            {
                // Add the survey/feedback cmdlet at the end if the user isn't typing it.
                bool isSurveyCmdletFound = false;

                if (result != null)
                {
                    foreach (var predictiveCommand in result.SourceTexts)
                    {
                        if (string.Equals(predictiveCommand, _surveyCmdlets[_azContext.Cohort].Command, StringComparison.Ordinal))
                        {
                            isSurveyCmdletFound = true;
                            break;
                        }
                    }
                }
                else
                {
                    result = new CommandLineSuggestion();
                }

                if (!isSurveyCmdletFound)
                {
                    var toAddCmdlet      = _surveyCmdlets[_azContext.Cohort].Command;
                    var toAddDescription = _surveyCmdlets[_azContext.Cohort].Description;
                    result.AddSuggestion(new PredictiveSuggestion($"{toAddCmdlet} # {toAddDescription}", toAddCmdlet), toAddCmdlet, SuggestionSource.StaticCommands);
                }
            }

            return(result);
        }
Пример #5
0
        /// <summary>
        /// Iterate over command elements to extract local parameter values.
        ///
        /// Store these values by a key
        /// consisting of the suffix of the command + the parameter name.  There are some exceptions, e.g.
        /// credential, location, where the parameter name itself is the key.
        ///
        /// For example, New-AzResourceGroup -Name Hello -Location 'EastUS' will store into local parameters:
        ///   ResourceGroupName => Hello
        ///   Location => 'EastUS'
        /// </summary>
        /// <param name="command">The command ast element</param>
        /// <remarks>
        /// This doesn't support positional parameter.
        /// </remarks>
        private void ExtractLocalParameters(CommandAst command)
        {
            // Azure PowerShell command is in the form of {Verb}-Az{Noun}, e.g. New-AzResource.
            // We need to extract the noun to construct the parameter name.

            var commandName = command.GetCommandName();
            var commandNoun = ParameterValuePredictor.GetAzCommandNoun(commandName)?.ToLower();

            if (commandNoun == null)
            {
                return;
            }

            Dictionary <string, string> commandNounMap = null;

            _commandParamToResourceMap?.TryGetValue(commandNoun, out commandNounMap);

            bool isParameterUpdated = false;

            var parameterSet = new ParameterSet(command, _azContext);

            for (var i = 0; i < parameterSet.Parameters.Count; ++i)
            {
                var parameterName  = parameterSet.Parameters[i].Name.ToLower();
                var parameterValue = parameterSet.Parameters[i].Value;

                if (string.IsNullOrWhiteSpace(parameterValue) || string.IsNullOrWhiteSpace(parameterName))
                {
                    continue;
                }

                var parameterKey = parameterName;
                var mappedValue  = parameterKey;
                if (commandNounMap?.TryGetValue(parameterName, out mappedValue) == true)
                {
                    parameterKey = mappedValue;
                }

                this._localParameterValues.AddOrUpdate(parameterKey, parameterValue, (k, v) => parameterValue);
                isParameterUpdated = true;
            }

            if (isParameterUpdated)
            {
                _cancellationTokenSource?.Cancel();
                _cancellationTokenSource = new CancellationTokenSource();
                var cancellationToken = _cancellationTokenSource.Token;

                Task.Run(() =>
                {
                    String localParameterValuesJson = JsonSerializer.Serialize <ConcurrentDictionary <string, string> >(_localParameterValues, JsonUtilities.DefaultSerializerOptions);
                    _mutex.WaitOne();
                    cancellationToken.ThrowIfCancellationRequested();
                    try
                    {
                        System.IO.File.WriteAllText(_paramValueHistoryFilePath, localParameterValuesJson);
                    }
                    finally
                    {
                        _mutex.ReleaseMutex();
                    }
                }, cancellationToken);
            }
        }
Пример #6
0
        /// <inheritdoc/>
        /// <remarks>
        /// Tries to get the suggestions for the user input from the command history. If that doesn't find
        /// <paramref name="suggestionCount"/> suggestions, it'll fallback to find the suggestion regardless of command history.
        /// </remarks>
        public CommandLineSuggestion GetSuggestion(Ast input, int suggestionCount, int maxAllowedCommandDuplicate, CancellationToken cancellationToken)
        {
            Validation.CheckArgument(input, $"{nameof(input)} cannot be null");
            Validation.CheckArgument <ArgumentOutOfRangeException>(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0.");

            var commandAst  = input.FindAll(p => p is CommandAst, true).LastOrDefault() as CommandAst;
            var commandName = (commandAst?.CommandElements?.FirstOrDefault() as StringConstantExpressionAst)?.Value;

            if (string.IsNullOrWhiteSpace(commandName))
            {
                return(null);
            }

            var inputParameterSet          = new ParameterSet(commandAst);
            var rawUserInput               = input.Extent.Text;
            var presentCommands            = new Dictionary <string, int>();
            var commandBasedPredictor      = _commandBasedPredictor;
            var commandToRequestPrediction = _commandToRequestPrediction;

            var result = commandBasedPredictor?.Item2?.GetSuggestion(commandName,
                                                                     inputParameterSet,
                                                                     rawUserInput,
                                                                     presentCommands,
                                                                     suggestionCount,
                                                                     maxAllowedCommandDuplicate,
                                                                     cancellationToken);

            if ((result != null) && (result.Count > 0))
            {
                var suggestionSource = SuggestionSource.PreviousCommand;

                if (string.Equals(commandToRequestPrediction, commandBasedPredictor?.Item1, StringComparison.Ordinal))
                {
                    suggestionSource = SuggestionSource.CurrentCommand;
                }

                for (var i = 0; i < result.Count; ++i)
                {
                    result.UpdateSuggestionSource(i, suggestionSource);
                }
            }

            if ((result == null) || (result.Count < suggestionCount))
            {
                var fallbackPredictor        = _fallbackPredictor;
                var suggestionCountToRequest = (result == null) ? suggestionCount : suggestionCount - result.Count;
                var resultsFromFallback      = fallbackPredictor?.GetSuggestion(commandName,
                                                                                inputParameterSet,
                                                                                rawUserInput,
                                                                                presentCommands,
                                                                                suggestionCountToRequest,
                                                                                maxAllowedCommandDuplicate,
                                                                                cancellationToken);

                if ((result == null) && (resultsFromFallback != null))
                {
                    result = resultsFromFallback;

                    for (var i = 0; i < result.Count; ++i)
                    {
                        result.UpdateSuggestionSource(i, SuggestionSource.StaticCommands);
                    }
                }
                else if ((resultsFromFallback != null) && (resultsFromFallback.Count > 0))
                {
                    for (var i = 0; i < resultsFromFallback.Count; ++i)
                    {
                        if (result.SourceTexts.Contains(resultsFromFallback.SourceTexts[i]))
                        {
                            continue;
                        }

                        result.AddSuggestion(resultsFromFallback.PredictiveSuggestions[i], resultsFromFallback.SourceTexts[i], SuggestionSource.StaticCommands);
                    }
                }
            }

            return(result);
        }