/// <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.Count > 0) || 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); 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.GetCommandNoun(_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) { // We don't add anything to to the raw user input. So skip this. continue; } var prediction = resultBuilder.ToString(); if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name)) { result.AddSuggestion(new PredictiveSuggestion(prediction, _commandLinePredictions[i].Description), _commandLinePredictions[i].SourceText); presentCommands.Add(_commandLinePredictions[i].Name, 1); } else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate) { result.AddSuggestion(new PredictiveSuggestion(prediction, _commandLinePredictions[i].Description), _commandLinePredictions[i].SourceText); presentCommands[_commandLinePredictions[i].Name] += 1; } else { _ = duplicateResults.TryAdd(prediction, new DuplicateResult(_commandLinePredictions[i].SourceText, _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); }
/// <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.GetCommandNoun(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); } }