diff --git a/src/System.Management.Automation/engine/CommandPathSearch.cs b/src/System.Management.Automation/engine/CommandPathSearch.cs
index a5b8a232b2..92a533e6ec 100644
--- a/src/System.Management.Automation/engine/CommandPathSearch.cs
+++ b/src/System.Management.Automation/engine/CommandPathSearch.cs
@@ -37,17 +37,17 @@ namespace System.Management.Automation
///
/// The patterns to search for in the paths.
///
- ///
- /// Use likely relevant search.
+ ///
+ /// The fuzzy matcher to use for fuzzy searching.
///
internal CommandPathSearch(
string commandName,
LookupPathCollection lookupPaths,
ExecutionContext context,
Collection? acceptableCommandNames,
- bool useFuzzyMatch)
+ FuzzyMatcher? fuzzyMatcher)
{
- _useFuzzyMatch = useFuzzyMatch;
+ _fuzzyMatcher = fuzzyMatcher;
string[] commandPatterns;
if (acceptableCommandNames != null)
{
@@ -434,13 +434,13 @@ namespace System.Management.Automation
// to forcefully use null if pattern is "."
if (pattern.Length != 1 || pattern[0] != '.')
{
- if (_useFuzzyMatch)
+ if (_fuzzyMatcher is not null)
{
var files = new List();
var matchingFiles = Directory.EnumerateFiles(directory);
foreach (string file in matchingFiles)
{
- if (FuzzyMatcher.IsFuzzyMatch(Path.GetFileName(file), pattern))
+ if (_fuzzyMatcher.IsFuzzyMatch(Path.GetFileName(file), pattern))
{
files.Add(file);
}
@@ -589,7 +589,7 @@ namespace System.Management.Automation
private readonly string[] _orderedPathExt;
private readonly Collection? _acceptableCommandNames;
- private readonly bool _useFuzzyMatch = false;
+ private readonly FuzzyMatcher? _fuzzyMatcher;
#endregion private members
}
diff --git a/src/System.Management.Automation/engine/CommandSearcher.cs b/src/System.Management.Automation/engine/CommandSearcher.cs
index cf798ddabb..3b1490cb3a 100644
--- a/src/System.Management.Automation/engine/CommandSearcher.cs
+++ b/src/System.Management.Automation/engine/CommandSearcher.cs
@@ -24,29 +24,20 @@ namespace System.Management.Automation
/// Constructs a command searching enumerator that resolves the location
/// to a command using a standard algorithm.
///
- ///
- /// The name of the command to look for.
- ///
- ///
- /// Determines which types of commands glob resolution of the name will take place on.
- ///
- ///
- /// The types of commands to look for.
- ///
- ///
- /// The execution context for this engine instance...
- ///
- ///
- /// If is null.
- ///
- ///
- /// If is null or empty.
- ///
+ /// The name of the command to look for.
+ /// Determines which types of commands glob resolution of the name will take place on.
+ /// The types of commands to look for.
+ /// The execution context for this engine instance.
+ /// The fuzzy matcher to use for fuzzy searching.
+ ///
+ /// If is null.
+ /// If is null or empty.
internal CommandSearcher(
string commandName,
SearchResolutionOptions options,
CommandTypes commandTypes,
- ExecutionContext context)
+ ExecutionContext context,
+ FuzzyMatcher? fuzzyMatcher = null)
{
Diagnostics.Assert(context != null, "caller to verify context is not null");
Diagnostics.Assert(!string.IsNullOrEmpty(commandName), "caller to verify commandName is valid");
@@ -55,6 +46,7 @@ namespace System.Management.Automation
_context = context;
_commandResolutionOptions = options;
_commandTypes = commandTypes;
+ _fuzzyMatcher = fuzzyMatcher;
// Initialize the enumerators
this.Reset();
@@ -705,8 +697,7 @@ namespace System.Management.Automation
foreach (KeyValuePair aliasEntry in _context.EngineSessionState.GetAliasTable())
{
if (aliasMatcher.IsMatch(aliasEntry.Key) ||
- (_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) &&
- FuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName)))
+ (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName)))
{
matchingAliases.Add(aliasEntry.Value);
}
@@ -785,8 +776,7 @@ namespace System.Management.Automation
foreach ((string functionName, FunctionInfo functionInfo) in _context.EngineSessionState.GetFunctionTable())
{
if (functionMatcher.IsMatch(functionName) ||
- (_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) &&
- FuzzyMatcher.IsFuzzyMatch(functionName, _commandName)))
+ (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(functionName, _commandName)))
{
matchingFunction.Add(functionInfo);
}
@@ -1018,10 +1008,8 @@ namespace System.Management.Automation
{
foreach (CmdletInfo cmdlet in cmdletList)
{
- if (cmdletMatcher != null &&
- cmdletMatcher.IsMatch(cmdlet.Name) ||
- (_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) &&
- FuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName)))
+ if ((cmdletMatcher is not null && cmdletMatcher.IsMatch(cmdlet.Name)) ||
+ (_fuzzyMatcher is not null && _fuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName)))
{
if (string.IsNullOrEmpty(moduleName) || moduleName.Equals(cmdlet.ModuleName, StringComparison.OrdinalIgnoreCase))
{
@@ -1496,6 +1484,11 @@ namespace System.Management.Automation
///
private readonly ExecutionContext _context;
+ ///
+ /// The fuzzy matcher to use for fuzzy searching.
+ ///
+ private readonly FuzzyMatcher? _fuzzyMatcher;
+
///
/// A routine to initialize the path searcher...
///
@@ -1528,7 +1521,7 @@ namespace System.Management.Automation
_context.CommandDiscovery.GetLookupDirectoryPaths(),
_context,
acceptableCommandNames: null,
- useFuzzyMatch: _commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch));
+ _fuzzyMatcher);
}
else
{
@@ -1544,7 +1537,7 @@ namespace System.Management.Automation
_context.CommandDiscovery.GetLookupDirectoryPaths(),
_context,
ConstructSearchPatternsFromName(_commandName, commandDiscovery: true),
- useFuzzyMatch: false);
+ fuzzyMatcher: null);
}
else if (_canDoPathLookupResult == CanDoPathLookupResult.PathIsRooted)
{
@@ -1568,7 +1561,7 @@ namespace System.Management.Automation
directoryCollection,
_context,
ConstructSearchPatternsFromName(fileName, commandDiscovery: true),
- useFuzzyMatch: false);
+ fuzzyMatcher: null);
}
else
{
@@ -1608,7 +1601,7 @@ namespace System.Management.Automation
directoryCollection,
_context,
ConstructSearchPatternsFromName(fileName, commandDiscovery: true),
- useFuzzyMatch: false);
+ fuzzyMatcher: null);
}
else
{
@@ -1727,17 +1720,14 @@ namespace System.Management.Automation
CommandNameIsPattern = 0x04,
SearchAllScopes = 0x08,
- /// Use fuzzy matching.
- FuzzyMatch = 0x10,
-
///
/// Enable searching for cmdlets/functions by abbreviation expansion.
///
- UseAbbreviationExpansion = 0x20,
+ UseAbbreviationExpansion = 0x10,
///
/// Enable resolving wildcard in paths.
///
- ResolveLiteralThenPathPatterns = 0x40
+ ResolveLiteralThenPathPatterns = 0x20
}
}
diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs
index 7beb1f3259..b211744965 100644
--- a/src/System.Management.Automation/engine/GetCommandCommand.cs
+++ b/src/System.Management.Automation/engine/GetCommandCommand.cs
@@ -345,7 +345,8 @@ namespace Microsoft.PowerShell.Commands
[Parameter(ParameterSetName = "AllCommandSet")]
public uint FuzzyMinimumDistance { get; set; } = 5;
- private List _commandScores = new List();
+ private FuzzyMatcher _fuzzyMatcher;
+ private List _commandScores;
///
/// Gets or sets the parameter that determines if return cmdlets based on abbreviation expansion.
@@ -367,7 +368,11 @@ namespace Microsoft.PowerShell.Commands
#if LEGACYTELEMETRY
_timer.Start();
#endif
- base.BeginProcessing();
+ if (UseFuzzyMatching)
+ {
+ _fuzzyMatcher = new FuzzyMatcher(FuzzyMinimumDistance);
+ _commandScores = new List();
+ }
if (ShowCommandInfo.IsPresent && Syntax.IsPresent)
{
@@ -503,14 +508,11 @@ namespace Microsoft.PowerShell.Commands
private void OutputResultsHelper(IEnumerable results)
{
- CommandOrigin origin = this.MyInvocation.CommandOrigin;
+ CommandOrigin origin = MyInvocation.CommandOrigin;
if (UseFuzzyMatching)
{
- _commandScores = _commandScores
- .Where(x => x.Score <= FuzzyMinimumDistance)
- .OrderBy(static x => x.Score)
- .ToList();
+ _commandScores = _commandScores.OrderBy(static x => x.Score).ToList();
results = _commandScores.Select(static x => x.Command);
}
@@ -784,11 +786,6 @@ namespace Microsoft.PowerShell.Commands
options |= SearchResolutionOptions.UseAbbreviationExpansion;
}
- if (UseFuzzyMatching)
- {
- options |= SearchResolutionOptions.FuzzyMatch;
- }
-
if ((this.CommandType & CommandTypes.Alias) != 0)
{
options |= SearchResolutionOptions.ResolveAliasPatterns;
@@ -861,24 +858,25 @@ namespace Microsoft.PowerShell.Commands
IEnumerable commands;
if (UseFuzzyMatching)
{
- foreach (var commandScore in System.Management.Automation.Internal.ModuleUtils.GetFuzzyMatchingCommands(
+ foreach (var commandScore in ModuleUtils.GetFuzzyMatchingCommands(
plainCommandName,
- this.Context,
- this.MyInvocation.CommandOrigin,
+ Context,
+ MyInvocation.CommandOrigin,
+ _fuzzyMatcher,
rediscoverImportedModules: true,
moduleVersionRequired: _isFullyQualifiedModuleSpecified))
{
_commandScores.Add(commandScore);
}
- commands = _commandScores.Select(static x => x.Command).ToList();
+ commands = _commandScores.Select(static x => x.Command);
}
else
{
- commands = System.Management.Automation.Internal.ModuleUtils.GetMatchingCommands(
+ commands = ModuleUtils.GetMatchingCommands(
plainCommandName,
- this.Context,
- this.MyInvocation.CommandOrigin,
+ Context,
+ MyInvocation.CommandOrigin,
rediscoverImportedModules: true,
moduleVersionRequired: _isFullyQualifiedModuleSpecified,
useAbbreviationExpansion: UseAbbreviationExpansion);
@@ -939,12 +937,12 @@ namespace Microsoft.PowerShell.Commands
private bool FindCommandForName(SearchResolutionOptions options, string commandName, bool isPattern, bool emitErrors, ref int currentCount, out bool isDuplicate)
{
- CommandSearcher searcher =
- new CommandSearcher(
- commandName,
- options,
- this.CommandType,
- this.Context);
+ var searcher = new CommandSearcher(
+ commandName,
+ options,
+ CommandType,
+ Context,
+ _fuzzyMatcher);
bool resultFound = false;
isDuplicate = false;
@@ -1032,8 +1030,10 @@ namespace Microsoft.PowerShell.Commands
if (UseFuzzyMatching)
{
- int score = FuzzyMatcher.GetDamerauLevenshteinDistance(current.Name, commandName);
- _commandScores.Add(new CommandScore(current, score));
+ if (_fuzzyMatcher.IsFuzzyMatch(current.Name, commandName, out int score))
+ {
+ _commandScores.Add(new CommandScore(current, score));
+ }
}
_accumulatedResults.Add(current);
diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs
index 8dab2283fc..a69b89744a 100644
--- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs
+++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs
@@ -385,15 +385,15 @@ namespace System.Management.Automation.Internal
/// Command pattern.
/// Execution context.
/// Command origin.
+ /// Fuzzy matcher to use.
/// If true, rediscovers imported modules.
/// Specific module version to be required.
/// IEnumerable tuple containing the CommandInfo and the match score.
- internal static IEnumerable GetFuzzyMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false)
+ internal static IEnumerable GetFuzzyMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, FuzzyMatcher fuzzyMatcher, bool rediscoverImportedModules = false, bool moduleVersionRequired = false)
{
- foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, useFuzzyMatching: true))
+ foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, fuzzyMatcher: fuzzyMatcher))
{
- int score = FuzzyMatcher.GetDamerauLevenshteinDistance(command.Name, pattern);
- if (score <= FuzzyMatcher.MinimumDistance)
+ if (fuzzyMatcher.IsFuzzyMatch(command.Name, pattern, out int score))
{
yield return new CommandScore(command, score);
}
@@ -408,10 +408,10 @@ namespace System.Management.Automation.Internal
/// Command origin.
/// If true, rediscovers imported modules.
/// Specific module version to be required.
- /// Use fuzzy matching.
+ /// Fuzzy matcher for fuzzy searching.
/// Use abbreviation expansion for matching.
/// Returns matching CommandInfo IEnumerable.
- internal static IEnumerable GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, bool useFuzzyMatching = false, bool useAbbreviationExpansion = false)
+ internal static IEnumerable GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, FuzzyMatcher fuzzyMatcher = null, bool useAbbreviationExpansion = false)
{
// Otherwise, if it had wildcards, just return the "AvailableCommand"
// type of command info.
@@ -449,7 +449,7 @@ namespace System.Management.Automation.Internal
foreach (KeyValuePair entry in psModule.ExportedCommands)
{
if (commandPattern.IsMatch(entry.Value.Name) ||
- (useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) ||
+ (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) ||
(useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(entry.Value.Name), StringComparison.OrdinalIgnoreCase)))
{
CommandInfo current = null;
@@ -509,7 +509,7 @@ namespace System.Management.Automation.Internal
CommandTypes commandTypes = pair.Value;
if (commandPattern.IsMatch(commandName) ||
- (useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(commandName, pattern)) ||
+ (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(commandName, pattern)) ||
(useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(commandName), StringComparison.OrdinalIgnoreCase)))
{
bool shouldExportCommand = true;
diff --git a/src/System.Management.Automation/utils/FuzzyMatch.cs b/src/System.Management.Automation/utils/FuzzyMatch.cs
index 5b21022a6e..c4e542a3ca 100644
--- a/src/System.Management.Automation/utils/FuzzyMatch.cs
+++ b/src/System.Management.Automation/utils/FuzzyMatch.cs
@@ -1,23 +1,38 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Collections.Generic;
using System.Globalization;
namespace System.Management.Automation
{
- internal static class FuzzyMatcher
+ internal class FuzzyMatcher
{
- public const int MinimumDistance = 5;
+ internal readonly uint MinimumDistance;
+
+ internal FuzzyMatcher(uint minimumDistance)
+ {
+ MinimumDistance = minimumDistance;
+ }
///
/// Determine if the two strings are considered similar.
///
- /// The first string to compare.
- /// The second string to compare.
- /// True if the two strings have a distance <= MinimumDistance.
- public static bool IsFuzzyMatch(string string1, string string2)
+ internal bool IsFuzzyMatch(string candidate, string pattern)
{
- return GetDamerauLevenshteinDistance(string1, string2) <= MinimumDistance;
+ return IsFuzzyMatch(candidate, pattern, out _);
+ }
+
+ ///
+ /// Determine if the two strings are considered similar, and return the similarity score.
+ ///
+ /// The candidate string to be compared.
+ /// The pattern string to be compared with.
+ /// True if the two strings have a distance <= MinimumDistance.
+ internal bool IsFuzzyMatch(string candidate, string pattern, out int score)
+ {
+ score = GetDamerauLevenshteinDistance(candidate, pattern);
+ return score <= MinimumDistance;
}
///
@@ -27,7 +42,7 @@ namespace System.Management.Automation
/// The first string to compare.
/// The second string to compare.
/// The distance value where the lower the value the shorter the distance between the two strings representing a closer match.
- public static int GetDamerauLevenshteinDistance(string string1, string string2)
+ internal static int GetDamerauLevenshteinDistance(string string1, string string2)
{
string1 = string1.ToUpper(CultureInfo.CurrentCulture);
string2 = string2.ToUpper(CultureInfo.CurrentCulture);
@@ -36,8 +51,15 @@ namespace System.Management.Automation
int[,] matrix = new int[bounds.Height, bounds.Width];
- for (int height = 0; height < bounds.Height; height++) { matrix[height, 0] = height; }
- for (int width = 0; width < bounds.Width; width++) { matrix[0, width] = width; }
+ for (int height = 0; height < bounds.Height; height++)
+ {
+ matrix[height, 0] = height;
+ }
+
+ for (int width = 0; width < bounds.Width; width++)
+ {
+ matrix[0, width] = width;
+ }
for (int height = 1; height < bounds.Height; height++)
{