Skip to content

Commit

Permalink
Merge pull request #47 from s2quake/fix/completion
Browse files Browse the repository at this point in the history
Improve command completion
  • Loading branch information
s2quake authored Dec 26, 2024
2 parents 45bbb93 + ec0a558 commit d0ab2b5
Show file tree
Hide file tree
Showing 38 changed files with 852 additions and 464 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Commands changes

To be Released.

* Renamed method `GetCompletion` to `GetCompletions`. [[#47]]

[#47]: https://github.com/s2quake/commands/pull/47


7.0.0
-------------
Expand Down
4 changes: 2 additions & 2 deletions example/JSSoft.Commands.Repl/SystemTerminal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ protected override string FormatPrompt(string prompt)
return prompt;
}

protected override string[] GetCompletion(string[] items, string find)
protected override string[] GetCompletions(string[] items, string find)
{
return _commandContext.GetCompletion(items, find);
return _commandContext.GetCompletions(items, find);
}

protected override Task OnExecuteAsync(string command, CancellationToken cancellationToken)
Expand Down
13 changes: 13 additions & 0 deletions src/JSSoft.Commands/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// <copyright file="AssemblyInfo.cs" company="JSSoft">
// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
// Licensed under the MIT License. See LICENSE.md in the project root for license information.
// </copyright>

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("JSSoft.Commands.Tests, PublicKey=002400000480000094000000060200000" +
"0240000525341310004000001000100b95aa034365ba94cb2597445ba4a19b4cbe" +
"54fe4402c3a7c99211bb321127c9c75d7a01523e1770a28886d857f5d9f1623d1d" +
"5ceb772bfabaa1b2037cc470b087d66dd5120758034f8eb84a352c577316a522dd" +
"95fd01bd868040176a3fd8d15ce3e3fbec56e5f833c5ccd1eddfa008f16714501b" +
"fba78ad788095faedcb7dba")]
10 changes: 9 additions & 1 deletion src/JSSoft.Commands/CommandAsyncBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ internal string ExecutionName
protected IProgress<ProgressInfo> Progress
=> _progress ?? throw new InvalidOperationException("The progress is not available.");

public virtual string[] GetCompletions(CommandCompletionContext completionContext) => [];
string[] ICommand.GetCompletions(CommandCompletionContext completionContext)
=> GetCompletions(completionContext);

Task IAsyncExecutable.ExecuteAsync(
CancellationToken cancellationToken, IProgress<ProgressInfo> progress)
Expand All @@ -128,6 +129,13 @@ Task IAsyncExecutable.ExecuteAsync(

string ICommand.GetUsage(bool isDetail) => OnUsagePrint(isDetail);

protected virtual string[] GetCompletions(CommandCompletionContext completionContext)
{
var memberDescriptor = completionContext.MemberDescriptor;
var instance = this;
return memberDescriptor.GetCompletionsInternal(instance);
}

protected abstract Task OnExecuteAsync(CancellationToken cancellationToken);

protected CommandMemberDescriptor GetDescriptor(string propertyName)
Expand Down
7 changes: 6 additions & 1 deletion src/JSSoft.Commands/CommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ internal string ExecutionName
string[] ICommand.GetCompletions(CommandCompletionContext completionContext)
=> GetCompletions(completionContext);

protected virtual string[] GetCompletions(CommandCompletionContext completionContext) => [];
protected virtual string[] GetCompletions(CommandCompletionContext completionContext)
{
var memberDescriptor = completionContext.MemberDescriptor;
var instance = this;
return memberDescriptor.GetCompletionsInternal(instance);
}

protected abstract void OnExecute();

Expand Down
2 changes: 1 addition & 1 deletion src/JSSoft.Commands/CommandCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public CommandCollection(IEnumerable<ICommand> commands)
Add(command);
}

_commandList = new List<ICommand>(commands);
_commandList = [.. commands];
}

public static CommandCollection Empty { get; } = new() { IsLocked = true };
Expand Down
29 changes: 0 additions & 29 deletions src/JSSoft.Commands/CommandCompletionAttribute.cs

This file was deleted.

60 changes: 7 additions & 53 deletions src/JSSoft.Commands/CommandCompletionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,16 @@

namespace JSSoft.Commands;

public sealed class CommandCompletionContext
public sealed class CommandCompletionContext(
ICommand command,
CommandMemberDescriptor memberDescriptor,
IReadOnlyDictionary<string, object?> properties)
{
private CommandCompletionContext(
ICommand command,
CommandMemberDescriptor memberDescriptor,
string find,
IReadOnlyDictionary<string, object?> properties)
{
Command = command;
MemberDescriptor = memberDescriptor;
Find = find;
Properties = properties;
}
public ICommand Command { get; } = command;

public ICommand Command { get; }
public CommandMemberDescriptor MemberDescriptor { get; } = memberDescriptor;

public CommandMemberDescriptor MemberDescriptor { get; }

public string Find { get; }

public IReadOnlyDictionary<string, object?> Properties { get; }
public IReadOnlyDictionary<string, object?> Properties { get; } = properties;

public string MemberName => MemberDescriptor.MemberName;

internal static object? Create(
ICommand command,
ParseContext parseContext,
string find)
{
var properties = new Dictionary<string, object?>();
var parseDescriptorByMemberDescriptor
= parseContext.Items.ToDictionary(item => item.MemberDescriptor);

foreach (var item in parseDescriptorByMemberDescriptor.ToArray())
{
var memberDescriptor = item.Key;
var parseDescriptor = item.Value;
if (parseDescriptor.HasValue is true)
{
properties.Add(memberDescriptor.MemberName, parseDescriptor.Value);
if (memberDescriptor.IsVariables is false)
{
parseDescriptorByMemberDescriptor.Remove(memberDescriptor);
}
}
}

if (find.StartsWith(CommandUtility.Delimiter) is false
&& find.StartsWith(CommandUtility.ShortDelimiter) is false
&& parseDescriptorByMemberDescriptor.Count is not 0)
{
var memberDescriptor = parseDescriptorByMemberDescriptor.Keys.First();
return new CommandCompletionContext(
command, memberDescriptor, find, properties);
}

return null;
}
}
69 changes: 52 additions & 17 deletions src/JSSoft.Commands/CommandContextBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT License. See LICENSE.md in the project root for license information.
// </copyright>

using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -142,8 +143,46 @@ public async Task ExecuteAsync(
OnExecuted(EventArgs.Empty);
}

[Obsolete("Use GetCompletions instead.")]
public string[] GetCompletion(string[] items, string find)
=> GetCompletion(_commandNode, new List<string>(items), find);
{
var completions = GetCompletions(items, find);
if (find == string.Empty)
{
return completions;
}

return [.. completions.Where(item => item.StartsWith(find))];
}

public string[] GetCompletions(string[] items, string find)
{
var completionList = new List<string>(items.Length);
var itemList = new List<string>(items);
var completions = GetCompletions(_commandNode, itemList);
for (var i = 0; i < completions.Length; i++)
{
var completion = completions[i];
if (completion != string.Empty)
{
if (find == string.Empty || completion.StartsWith(find) is true)
{
completionList.Add(completions[i]);
}
}
else
{
Trace.TraceWarning($"The '{i}' completion string is empty. It will be ignored.");
}
}

if (find != string.Empty)
{
completionList.Sort();
}

return [.. completionList];
}

internal static ICommand? GetCommand(ICommand parent, IList<string> argList)
{
Expand Down Expand Up @@ -299,16 +338,14 @@ private static void AttachContext(ICommand command, ICommandContext commandConte
}
}

private string[] GetCompletion(
ICommand parent, IList<string> itemList, string find)
private static string[] GetCompletions(
ICommand parent, IList<string> itemList)
{
if (itemList.Count is 0)
{
var query = from child in parent.Commands
where child.IsEnabled is true
from name in new string[] { child.Name }.Concat(child.Aliases)
where name.StartsWith(find)
orderby name
select name;
return query.ToArray();
}
Expand All @@ -317,15 +354,15 @@ orderby name
var commandName = itemList[0];
if (parent.TryGetCommand(commandName, out var command) is true)
{
if (command.IsEnabled is true && command.Commands.Any() is true)
if (command.IsEnabled is true && command.Commands.Count > 0)
{
itemList.RemoveAt(0);
return GetCompletion(command, itemList, find);
return GetCompletions(command, itemList);
}
else
{
var args = itemList.Skip(1).ToArray();
if (GetCompletion(command, args, find) is string[] completions)
if (GetCompletions(command, args) is string[] completions)
{
return completions;
}
Expand All @@ -336,20 +373,18 @@ orderby name
}
}

private string[] GetCompletion(ICommand command, string[] args, string find)
private static string[] GetCompletions(ICommand command, string[] args)
{
var memberDescriptors = CommandDescriptor.GetMemberDescriptors(command);
var settings = Settings;
var parseContext = new ParseContext(memberDescriptors, settings, args);
var context = CommandCompletionContext.Create(command, parseContext, find);
if (context is CommandCompletionContext completionContext)
var parseContext = ParseContext.Create(memberDescriptors, args);
if (parseContext.Descriptor is { } descriptor)
{
var properties = parseContext.GetProperties();
var memberDescriptor = descriptor.MemberDescriptor;
var completionContext = new CommandCompletionContext(
command, memberDescriptor, properties);
return command.GetCompletions(completionContext);
}
else if (context is string[] completions)
{
return completions;
}

return [];
}
Expand Down
2 changes: 1 addition & 1 deletion src/JSSoft.Commands/CommandDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ where methodInfo.IsBrowsable() is true
methodDescriptorList.Capacity = items.Length + staticMethodDescriptors.Length;
foreach (var item in items)
{
methodDescriptorList.Add(new StandardCommandMethodDescriptor(item));
methodDescriptorList.Add(new CommandMethodDescriptor(item));
}

methodDescriptorList.AddRange(staticMethodDescriptors);
Expand Down
12 changes: 6 additions & 6 deletions src/JSSoft.Commands/CommandInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ private void Invoke(CommandMethodDescriptor methodDescriptor, string[] args)
var instance = Instance;
var memberDescriptors = methodDescriptor.Members;
var settings = Settings;
var parseContext = new ParseContext(memberDescriptors, settings, args);
var parseContext = ParseContext.Create(memberDescriptors, args);
var methodInstance = new CommandMethodInstance(methodDescriptor, instance);
parseContext.SetValue(methodInstance);
parseContext.SetValue(methodInstance, settings);
methodDescriptor.Invoke(instance, methodInstance);
}

Expand All @@ -190,9 +190,9 @@ private Task InvokeAsync(
var instance = Instance;
var memberDescriptors = methodDescriptor.Members;
var settings = Settings;
var parseContext = new ParseContext(memberDescriptors, settings, args);
var parseContext = ParseContext.Create(memberDescriptors, args);
var methodInstance = new CommandMethodInstance(methodDescriptor, instance);
parseContext.SetValue(methodInstance);
parseContext.SetValue(methodInstance, settings);
return methodDescriptor.InvokeAsync(
instance, methodInstance, cancellationToken, progress);
}
Expand All @@ -202,7 +202,7 @@ private void Parse(string[] args)
var instance = Instance;
var memberDescriptors = CommandDescriptor.GetMemberDescriptors(instance);
var settings = Settings;
var parserContext = new ParseContext(memberDescriptors, settings, args);
parserContext.SetValue(instance);
var parserContext = ParseContext.Create(memberDescriptors, args);
parserContext.SetValue(instance, settings);
}
}
27 changes: 10 additions & 17 deletions src/JSSoft.Commands/CommandMemberDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System.Diagnostics;
using System.Threading.Tasks;
using JSSoft.Commands.Exceptions;

namespace JSSoft.Commands;

Expand Down Expand Up @@ -58,18 +59,7 @@ public abstract class CommandMemberDescriptor(
internal void VerifyTrigger(ParseDescriptorCollection parseDescriptors)
=> OnVerifyTrigger(parseDescriptors);

internal string[]? GetCompletionInternal(object instance, string find)
{
if (GetCompletion(instance, find) is { } items)
{
var query = from item in items
where item.StartsWith(find)
select item;
return query.ToArray();
}

return null;
}
internal string[] GetCompletionsInternal(object instance) => GetCompletions(instance);

protected abstract void SetValue(object instance, object? value);

Expand All @@ -79,14 +69,17 @@ protected virtual void OnVerifyTrigger(ParseDescriptorCollection parseDescriptor
{
}

protected virtual string[]? GetCompletion(object instance, string find) => null;
protected virtual string[] GetCompletions(object instance) => [];

protected string[] GetCompletion(
object instance, string find, CommandMemberCompletionAttribute attribute)
protected string[] GetCompletions(
object instance, CommandMemberCompletionAttribute attribute)
{
var declaringType = MemberInfo.DeclaringType;
var methodName = attribute.MethodName;
var methodInfo = attribute.GetMethodInfo(instance.GetType(), methodName);
var obj = methodInfo.DeclaringType == instance.GetType() ? instance : null;
var parameterTypes = Array.Empty<Type>();
var methodInfo = attribute.GetMethodInfo(
MemberInfo.DeclaringType, methodName, parameterTypes);
var obj = declaringType.IsInstanceOfType(instance) ? instance : null;

try
{
Expand Down
Loading

0 comments on commit d0ab2b5

Please sign in to comment.