8000 Enable experimental feature to support tab completion on abbreviated cmdlets by SteveL-MSFT · Pull Request #8109 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ private static List<CompletionResult> CompleteCommand(CompletionContext context,
string commandName = context.WordToComplete;
string quote = HandleDoubleAndSingleQuote(ref commandName);

commandName += "*";
List<CompletionResult> commandResults = null;

if (commandName.IndexOfAny(Utils.Separators.DirectoryOrDrive) == -1)
Expand All @@ -100,29 +99,7 @@ private static List<CompletionResult> CompleteCommand(CompletionContext context,
lastAst = context.RelatedAsts.Last();
}

var powershell = context.Helper
.AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))
.AddParameter("All")
.AddParameter("Name", commandName);

if (moduleName != null)
powershell.AddParameter("Module", moduleName);
if (!types.Equals(CommandTypes.All))
powershell.AddParameter("CommandType", types);

Exception exceptionThrown;
var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);

if (commandInfos != null && commandInfos.Count > 1)
{
// OrderBy is using stable sorting
var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer());
commandResults = MakeCommandsUnique(sortedCommandInfos, /* includeModulePrefix: */ false, addAmpersandIfNecessary, quote);
}
else
{
commandResults = MakeCommandsUnique(commandInfos, /* includeModulePrefix: */ false, addAmpersandIfNecessary, quote);
}
commandResults = ExecuteGetCommandCommand(useModulePrefix: false);

if (lastAst != null)
{
Expand Down Expand Up @@ -159,31 +136,74 @@ private static List<CompletionResult> CompleteCommand(CompletionContext context,
moduleName = commandName.Substring(0, indexOfFirstBackslash);
commandName = commandName.Substring(indexOfFirstBackslash + 1);

var powershell = context.Helper
commandResults = ExecuteGetCommandCommand(useModulePrefix: true);
}
}

return commandResults;

List<CompletionResult> ExecuteGetCommandCommand(bool useModulePrefix)
{
var powershell = context.Helper
.AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))
.AddParameter("All")
.AddParameter("Name", commandName + "*");

if (moduleName != null)
{
powershell.AddParameter("Module", moduleName);
}

if (!types.Equals(CommandTypes.All))
{
powershell.AddParameter("CommandType", types);
}

// Exception is ignored, the user simply does not get any completion results if the pipeline fails
Exception exceptionThrown;
var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);

if (commandInfos == null || commandInfos.Count == 0)
{
powershell.Commands.Clear();
powershell
.AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))
.AddParameter("All")
.AddParameter("Name", commandName)
.AddParameter("Module", moduleName);

if (!types.Equals(CommandTypes.All))
powershell.AddParameter("CommandType", types);
.AddParameter("Name", commandName);

Exception exceptionThrown;
var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);
if (ExperimentalFeature.IsEnabled("PSUseAbbreviationExpansion"))
{
powershell.AddParameter("UseAbbreviationExpansion");
}

if (commandInfos != null && commandInfos.Count > 1)
if (moduleName != null)
{
var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer());
commandResults = MakeCommandsUnique(sortedCommandInfos, /* includeModulePrefix: */ true, addAmpersandIfNecessary, quote);
powershell.AddParameter("Module", moduleName);
}
else

if (!types.Equals(CommandTypes.All))
{
commandResults = MakeCommandsUnique(commandInfos, /* includeModulePrefix: */ true, addAmpersandIfNecessary, quote);
powershell.AddParameter("CommandType", types);
}

commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);
}
}

return commandResults;
List<CompletionResult> completionResults = null;

if (commandInfos != null && commandInfos.Count > 1)
{
// OrderBy is using stable sorting
var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer());
completionResults = MakeCommandsUnique(sortedCommandInfos, useModulePrefix, addAmpersandIfNecessary, quote);
}
else
{
completionResults = MakeCommandsUnique(commandInfos, useModulePrefix, addAmpersandIfNecessary, quote);
}

return completionResults;
}
}

private static readonly HashSet<string> s_keywordsToExcludeFromAddingAmpersand
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1493,7 +1493,6 @@ internal IEnumerator<CmdletInfo> GetCmdletInfo(string cmdletName, bool searchAll

// Check the current cmdlet cache then check the top level
// if we aren't already at the top level.

SessionStateScopeEnumerator scopeEnumerator =
new SessionStateScopeEnumerator(Context.EngineSessionState.CurrentScope);

Expand Down
35 changes: 28 additions & 7 deletions src/System.Management.Automation/engine/CommandSearcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation.Internal;
using Dbg = System.Management.Automation.Diagnostics;

namespace System.Management.Automation
Expand Down Expand Up @@ -725,7 +726,7 @@ private CommandInfo GetNextFunction()
{
CommandInfo result = null;

if ((_commandResolutionOptions & SearchResolutionOptions.ResolveFunctionPatterns) != 0)
if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveFunctionPatterns))
{
if (_matchingFunctionEnumerator == null)
{
Expand All @@ -745,13 +746,20 @@ private CommandInfo GetNextFunction()
{
matchingFunction.Add((CommandInfo)functionEntry.Value);
}
else if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.UseAbbreviationExpansion))
{
if (_commandName.Equals(ModuleUtils.AbbreviateName((string)functionEntry.Key), StringComparison.OrdinalIgnoreCase))
{
matchingFunction.Add((CommandInfo)functionEntry.Value);
}
}
}

// Process functions from modules
CommandInfo c = GetFunctionFromModules(_commandName);
if (c != null)
CommandInfo cmdInfo = GetFunctionFromModules(_commandName);
if (cmdInfo != null)
{
matchingFunction.Add(c);
matchingFunction.Add(cmdInfo);
}

_matchingFunctionEnumerator = matchingFunction.GetEnumerator();
Expand Down Expand Up @@ -946,17 +954,18 @@ private CommandInfo GetFunction(string function)
private CmdletInfo GetNextCmdlet()
{
CmdletInfo result = null;
bool useAbbreviationExpansion = _commandResolutionOptions.HasFlag(SearchResolutionOptions.UseAbbreviationExpansion);

if (_matchingCmdlet == null)
{
if ((_commandResolutionOptions & SearchResolutionOptions.CommandNameIsPattern) != 0)
if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.CommandNameIsPattern) || useAbbreviationExpansion)
{
Collection<CmdletInfo> matchingCmdletInfo = new Collection<CmdletInfo>();

PSSnapinQualifiedName PSSnapinQualifiedCommandName =
PSSnapinQualifiedName.GetInstance(_commandName);

if (PSSnapinQualifiedCommandName == null)
if (!useAbbreviationExpansion && PSSnapinQualifiedCommandName == null)
{
return null;
}
Expand Down Expand Up @@ -984,6 +993,13 @@ private CmdletInfo GetNextCmdlet()
matchingCmdletInfo.Add(cmdlet);
}
}
else if (useAbbreviationExpansion)
{
if (_commandName.Equals(ModuleUtils.AbbreviateName(cmdlet.Name), StringComparison.OrdinalIgnoreCase))
{
matchingCmdletInfo.Add(cmdlet);
}
}
}
}

Expand All @@ -992,7 +1008,7 @@ private CmdletInfo GetNextCmdlet()
else
{
_matchingCmdlet = _context.CommandDiscovery.GetCmdletInfo(_commandName,
(_commandResolutionOptions & SearchResolutionOptions.SearchAllScopes) != 0);
_commandResolutionOptions.HasFlag(SearchResolutionOptions.SearchAllScopes));
}
}

Expand Down Expand Up @@ -1588,5 +1604,10 @@ internal enum SearchResolutionOptions

/// <summary>Use fuzzy matching.</summary>
FuzzyMatch = 0x10,

/// <summary>
/// Enable searching for cmdlets/functions by abbreviation expansion.
/// </summary>
UseAbbreviationExpansion = 0x20,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ static ExperimentalFeature()
new ExperimentalFeature(name: "PSImplicitRemotingBatching",
description: "Batch implicit remoting proxy commands to improve performance",
source: EngineSource,
isEnabled: false)
isEnabled: false),
new ExperimentalFeature(name: "PSUseAbbreviationExpansion",
description: "Allow tab completion of cmdlets and functions by abbreviation",
source: EngineSource,
isEnabled: false),
};
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);

Expand Down
30 changes: 22 additions & 8 deletions src/System.Management.Automation/engine/GetCommandCommand.cs
4D8B
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ public PSTypeName[] ParameterType

private List<CommandScore> _commandScores = new List<CommandScore>();

/// <summary>
/// Gets or sets the parameter that determines if return cmdlets based on abbreviation expansion.
/// This means it matches cmdlets where the uppercase characters for the noun match
/// the given characters. i.e., g-sgc would match Get-SomeGreatCmdlet.
/// </summary>
[Experimental("PSUseAbbreviationExpansion", ExperimentAction.Show)]
[Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = "AllCommandSet")]
public SwitchParameter UseAbbreviationExpansion { get; set; }

#endregion Definitions of cmdlet parameters

#region Overrides
Expand Down Expand Up @@ -701,6 +710,16 @@ private void AccumulateMatchingCommands(IEnumerable<string> commandNames)
options = SearchResolutionOptions.SearchAllScopes;
}

if (UseAbbreviationExpansion)
{
options |= SearchResolutionOptions.UseAbbreviationExpansion;
}

if (UseFuzzyMatching)
{
options |= SearchResolutionOptions.FuzzyMatch;
}

if ((this.CommandType & CommandTypes.Alias) != 0)
{
options |= SearchResolutionOptions.ResolveAliasPatterns;
Expand Down Expand Up @@ -728,13 +747,7 @@ private void AccumulateMatchingCommands(IEnumerable<string> commandNames)
moduleName = this.Module[0];
}

bool isPattern = WildcardPattern.ContainsWildcardCharacters(plainCommandName);
if (UseFuzzyMatching)
{
options |= SearchResolutionOptions.FuzzyMatch;
isPattern = true;
}

bool isPattern = WildcardPattern.ContainsWildcardCharacters(plainCommandName) || UseAbbreviationExpansion || UseFuzzyMatching;
if (isPattern)
{
options |= SearchResolutionOptions.CommandNameIsPattern;
Expand Down Expand Up @@ -798,7 +811,8 @@ private void AccumulateMatchingCommands(IEnumerable<string> commandNames)
this.Context,
this.MyInvocation.CommandOrigin,
rediscoverImportedModules: true,
moduleVersionRequired: _isFullyQualifiedModuleSpecified);
moduleVersionRequired: _isFullyQualifiedModuleSpecified,
useAbbreviationExpansion: UseAbbreviationExpansion);
}

foreach (CommandInfo command in commands)
Expand Down
32 changes: 28 additions & 4 deletions src/System.Management.Automation/engine/Modules/ModuleUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Globalization;
using System.IO;
using System.Management.Automation.Runspaces;
using System.Text;
using Dbg = System.Management.Automation.Diagnostics;

namespace System.Management.Automation.Internal
Expand Down Expand Up @@ -411,8 +412,9 @@ internal static IEnumerable<CommandScore> GetFuzzyMatchingCommands(string patter
/// <param name="rediscoverImportedModules">If true, rediscovers imported modules.</param>
/// <param name="moduleVersionRequired">Specific module version to be required.</param>
/// <param name="useFuzzyMatching">Use fuzzy matching.</param>
/// <returns>Returns CommandInfo IEnumerable.</returns>
internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, bool useFuzzyMatching = false)
/// <param name="useAbbreviationExpansion">Use abbreviation expansion for matching.</param>
/// <returns>Returns matching CommandInfo IEnumerable.</returns>
internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, bool useFuzzyMatching = false, bool useAbbreviationExpansion = false)
{
// Otherwise, if it had wildcards, just return the "AvailableCommand"
// type of command info.
Expand Down Expand Up @@ -450,7 +452,8 @@ internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, Exe
foreach (KeyValuePair<string, CommandInfo> entry in psModule.ExportedCommands)
{
if (commandPattern.IsMatch(entry.Value.Name) ||
(useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)))
(useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) ||
(useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(entry.Value.Name), StringComparison.OrdinalIgnoreCase)))
{
CommandInfo current = null;
switch (entry.Value.CommandType)
Expand Down Expand Up @@ -509,7 +512,8 @@ internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, Exe
CommandTypes commandTypes = pair.Value;

if (commandPattern.IsMatch(commandName) ||
(useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(commandName, pattern)))
(useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(commandName, pattern)) ||
(useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(commandName), StringComparison.OrdinalIgnoreCase)))
{
bool shouldExportCommand = true;

Expand Down Expand Up @@ -578,6 +582,26 @@ internal static IEnumerable< 4D8B CommandInfo> GetMatchingCommands(string pattern, Exe
}
}
}

/// <summary>
/// Returns abbreviated version of a command name.
/// </summary>
/// <param name="commandName">Name of the command to transform.</param>
/// <returns>Abbreviated version of the command name.</returns>
internal static string AbbreviateName(string commandName)
{
// Use default size of 6 which represents expected average abbreviation length
StringBuilder abbreviation = new StringBuilder(6);
foreach (char c in commandName)
{
if (char.IsUpper(c) || c == '-')
{
abbreviation.Append(c);
}
}

return abbreviation.ToString();
}
}

internal struct CommandScore
Expand Down
Loading
0