@@ -234,26 +234,28 @@ namespace Discord.Commands | |||||
} | } | ||||
//Execution | //Execution | ||||
public SearchResult Search(ICommandContext context, int argPos) | |||||
=> Search(context, context.Message.Content.Substring(argPos)); | |||||
public SearchResult Search(ICommandContext context, string input) | |||||
public SearchResult Search(ICommandContext context, int argPos, int maxDifferences = 5) | |||||
=> Search(context, context.Message.Content.Substring(argPos), maxDifferences); | |||||
public SearchResult Search(ICommandContext context, string input, int maxDifferences = 5) | |||||
{ | { | ||||
string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); | string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); | ||||
var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); | var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); | ||||
if (matches.Length > 0) | if (matches.Length > 0) | ||||
return SearchResult.FromSuccess(input, matches); | return SearchResult.FromSuccess(input, matches); | ||||
else if (maxDifferences > 0) | |||||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command.", _map.GetPartialMatches(searchInput, maxDifferences).ToImmutableArray()); | |||||
else | else | ||||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | ||||
} | } | ||||
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); | |||||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception, int maxDifferences = 5) | |||||
=> ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling, maxDifferences); | |||||
public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception, int maxDifferences = 5) | |||||
{ | { | ||||
dependencyMap = dependencyMap ?? DependencyMap.Empty; | dependencyMap = dependencyMap ?? DependencyMap.Empty; | ||||
var searchResult = Search(context, input); | |||||
var searchResult = Search(context, input, maxDifferences); | |||||
if (!searchResult.IsSuccess) | if (!searchResult.IsSuccess) | ||||
return searchResult; | return searchResult; | ||||
@@ -1,4 +1,4 @@ | |||||
using System.Collections.Generic; | |||||
using System.Collections.Generic; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
@@ -29,5 +29,10 @@ namespace Discord.Commands | |||||
{ | { | ||||
return _root.GetCommands(_service, text, 0, text != ""); | return _root.GetCommands(_service, text, 0, text != ""); | ||||
} | } | ||||
public IEnumerable<CommandMatch> GetPartialMatches(string text, int maxDifferences) | |||||
{ | |||||
return _root.GetPartialMatches(_service, text, maxDifferences, 0, text != ""); | |||||
} | |||||
} | } | ||||
} | } |
@@ -49,6 +49,7 @@ namespace Discord.Commands | |||||
} | } | ||||
} | } | ||||
} | } | ||||
public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) | public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) | ||||
{ | { | ||||
int nextSegment = NextSegment(text, index, service._separatorChar); | int nextSegment = NextSegment(text, index, service._separatorChar); | ||||
@@ -113,6 +114,52 @@ namespace Discord.Commands | |||||
} | } | ||||
} | } | ||||
internal IEnumerable<CommandMatch> GetPartialMatches(CommandService service, string text, int maxDifference, int index, bool visitChildren = true) | |||||
{ | |||||
var commands = _commands; | |||||
for (int i = 0; i < commands.Length; i++) | |||||
yield return new CommandMatch(_commands[i], _name); | |||||
if (visitChildren) | |||||
{ | |||||
string name; | |||||
CommandMapNode nextNode; | |||||
//Search for next segment | |||||
int nextSegment = NextSegment(text, index, service._separatorChar); | |||||
if (nextSegment == -1) | |||||
name = text.Substring(index); | |||||
else | |||||
name = text.Substring(index, nextSegment - index); | |||||
foreach (var key in _nodes.Keys) | |||||
{ | |||||
if (LevenshteinDistance(name, key) < maxDifference) | |||||
{ | |||||
if (_nodes.TryGetValue(key, out nextNode)) | |||||
foreach (var cmd in nextNode.GetPartialMatches(service, nextSegment == -1 ? "" : text, maxDifference, nextSegment + 1, true)) | |||||
yield return cmd; | |||||
} | |||||
} | |||||
//Check if this is the last command segment before args | |||||
nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); | |||||
if (nextSegment != -1) | |||||
{ | |||||
name = text.Substring(index, nextSegment - index); | |||||
foreach (var key in _nodes.Keys) | |||||
{ | |||||
if (LevenshteinDistance(name, key) < maxDifference) | |||||
{ | |||||
if (_nodes.TryGetValue(key, out nextNode)) | |||||
foreach (var cmd in nextNode.GetPartialMatches(service, nextSegment == -1 ? "" : text, maxDifference, nextSegment + 1, false)) | |||||
yield return cmd; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private static int NextSegment(string text, int startIndex, char separator) | private static int NextSegment(string text, int startIndex, char separator) | ||||
{ | { | ||||
return text.IndexOf(separator, startIndex); | return text.IndexOf(separator, startIndex); | ||||
@@ -131,5 +178,51 @@ namespace Discord.Commands | |||||
} | } | ||||
return (lowest != int.MaxValue) ? lowest : -1; | return (lowest != int.MaxValue) ? lowest : -1; | ||||
} | } | ||||
private static int LevenshteinDistance(string source, string target) | |||||
{ | |||||
var sourceLength = source.Length; | |||||
var targetLength = target.Length; | |||||
if (sourceLength == 0) | |||||
return targetLength; | |||||
if (targetLength == 0) | |||||
return sourceLength; | |||||
var matrix = new int[sourceLength + 1, targetLength + 1]; | |||||
for (int row = 0; row <= sourceLength; matrix[row, 0] = row++) | |||||
{ } | |||||
for (int col = 0; col <= targetLength; matrix[0, col] = col++) | |||||
{ } | |||||
for (int i = 1; i <= sourceLength; i++) | |||||
{ | |||||
char sourceChr = source[i - 1]; | |||||
for (int j = 1; j <= targetLength; j++) | |||||
{ | |||||
char targetChr = target[j - 1]; | |||||
int cost = sourceChr == targetChr ? 0 : 1; | |||||
int above = matrix[i - 1, j] + 1; | |||||
int left = matrix[i, j - 1] + 1; | |||||
int diagonal = matrix[i - 1, j - 1] + cost; | |||||
int minimum = int.MaxValue; | |||||
if (above < left) | |||||
minimum = above; | |||||
else | |||||
minimum = left; | |||||
if (diagonal < minimum) | |||||
minimum = diagonal; | |||||
matrix[i, j] = minimum; | |||||
} | |||||
} | |||||
return matrix[sourceLength, targetLength]; | |||||
} | |||||
} | } | ||||
} | } |
@@ -24,8 +24,8 @@ namespace Discord.Commands | |||||
public static SearchResult FromSuccess(string text, IReadOnlyList<CommandMatch> commands) | public static SearchResult FromSuccess(string text, IReadOnlyList<CommandMatch> commands) | ||||
=> new SearchResult(text, commands, null, null); | => new SearchResult(text, commands, null, null); | ||||
public static SearchResult FromError(CommandError error, string reason) | |||||
=> new SearchResult(null, null, error, reason); | |||||
public static SearchResult FromError(CommandError error, string reason, IReadOnlyList<CommandMatch> suggestions = null) | |||||
=> new SearchResult(null, suggestions, error, reason); | |||||
public static SearchResult FromError(IResult result) | public static SearchResult FromError(IResult result) | ||||
=> new SearchResult(null, null, result.Error, result.ErrorReason); | => new SearchResult(null, null, result.Error, result.ErrorReason); | ||||