Init
This commit is contained in:
+72
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// An IQcSuggestor that caches the created IQcSuggestion objects
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The item type that suggestions are produced from.</typeparam>
|
||||
public abstract class BasicCachedQcSuggestor<TItem> : IQcSuggestor
|
||||
{
|
||||
private readonly Dictionary<TItem, IQcSuggestion> _suggestionCache = new Dictionary<TItem, IQcSuggestion>();
|
||||
|
||||
/// <summary>
|
||||
/// If suggestions can be produced for the provided context.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="options">Options used by the suggestor.</param>
|
||||
/// <returns>If suggestions can be produced.</returns>
|
||||
protected abstract bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an item to a suggestion.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to convert to a suggestion.</param>
|
||||
/// <returns>The converted suggestion.</returns>
|
||||
protected abstract IQcSuggestion ItemToSuggestion(TItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the items for a given context.
|
||||
/// </summary>
|
||||
/// <param name="context">The context to produce items for.</param>
|
||||
/// <param name="options">Options used by the suggestor.</param>
|
||||
/// <returns>The items produced.</returns>
|
||||
protected abstract IEnumerable<TItem> GetItems(SuggestionContext context, SuggestorOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the provided suggestion matches the provided context.
|
||||
/// Override to remove the filtering or add custom filtering.
|
||||
/// </summary>
|
||||
/// <param name="context">The context to test the suggestion against.</param>
|
||||
/// <param name="suggestion">The suggestion to test.</param>
|
||||
/// <param name="options">Options used to test the suggestion.</param>
|
||||
/// <returns>If the suggestion matches the context.</returns>
|
||||
protected virtual bool IsMatch(SuggestionContext context, IQcSuggestion suggestion, SuggestorOptions options)
|
||||
{
|
||||
return SuggestorUtilities.IsCompatible(context.Prompt, suggestion.PrimarySignature, options);
|
||||
}
|
||||
|
||||
public IEnumerable<IQcSuggestion> GetSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
if (!CanProvideSuggestions(context, options))
|
||||
{
|
||||
return Enumerable.Empty<IQcSuggestion>();
|
||||
}
|
||||
|
||||
return GetItems(context, options)
|
||||
.Select(ItemToSuggestionCached)
|
||||
.Where(suggestion => IsMatch(context, suggestion, options));
|
||||
}
|
||||
|
||||
private IQcSuggestion ItemToSuggestionCached(TItem item)
|
||||
{
|
||||
if (_suggestionCache.TryGetValue(item, out IQcSuggestion suggestion))
|
||||
{
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
return _suggestionCache[item] = ItemToSuggestion(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7a2b48636fac184aa202176dea0dab9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// A suggestion that can be auto completed and displayed.
|
||||
/// </summary>
|
||||
public interface IQcSuggestion
|
||||
{
|
||||
/// <summary>
|
||||
/// The full signature of the suggestion for display purposes.
|
||||
/// </summary>
|
||||
string FullSignature { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The primary component of the signature.
|
||||
/// </summary>
|
||||
string PrimarySignature { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The secondary component of the signature.
|
||||
/// </summary>
|
||||
string SecondarySignature { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the provided prompt matches this suggestion
|
||||
/// </summary>
|
||||
/// <param name="prompt">The prompt to check.</param>
|
||||
/// <returns>If the prompt matches the suggestion.</returns>
|
||||
bool MatchesPrompt(string prompt);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the completion value for this a prompt with this suggestion.
|
||||
/// </summary>
|
||||
/// <param name="prompt">The prompt to complete.</param>
|
||||
/// <returns>The completion value.</returns>
|
||||
string GetCompletion(string prompt);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the completion tail, similar to the secondary signature, for a prompt with this suggestion.
|
||||
/// </summary>
|
||||
/// <param name="prompt">The prompt to complete the tail for.</param>
|
||||
/// <returns>The completion tail.</returns>
|
||||
string GetCompletionTail(string prompt);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the inner suggestion context for this suggestion, allowing for further suggestions to be provided.
|
||||
/// </summary>
|
||||
/// <param name="context">The outer suggestion context.</param>
|
||||
/// <returns>The inner suggestion context, null if none can be created.</returns>
|
||||
SuggestionContext? GetInnerSuggestionContext(SuggestionContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0db96f25927d64e46b8effecade0732d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that can remove suggestions produced by the QuantumSuggestor
|
||||
/// </summary>
|
||||
public interface IQcSuggestionFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if a suggestion should be permitted given the provided context.
|
||||
/// </summary>
|
||||
/// <param name="suggestion">The suggestion to query.</param>
|
||||
/// <param name="context">The context for the suggestion.</param>
|
||||
/// <returns>If the suggestion should be permitted.</returns>
|
||||
bool IsSuggestionPermitted(IQcSuggestion suggestion, SuggestionContext context);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe8cbfb6d5020864fae6264d766ffa01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// A suggestor that is loaded by the QuantumSuggestor to suggest IQcSuggestions
|
||||
/// </summary>
|
||||
public interface IQcSuggestor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the suggestions for a given context.
|
||||
/// </summary>
|
||||
/// <param name="context">The context to provide suggestions for.</param>
|
||||
/// <param name="options">Options used by the suggestor.</param>
|
||||
/// <returns>The suggestions produced for the context.</returns>
|
||||
IEnumerable<IQcSuggestion> GetSuggestions(SuggestionContext context, SuggestorOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0aeb09aef885a334a851c9793052aecd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Base type for tags to use for the suggestion system.
|
||||
/// Suggestors may check the context for the presence of different tags.
|
||||
/// </summary>
|
||||
public interface IQcSuggestorTag
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c77697a7053da34b9ef7d45aa2727d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
using QFSW.QC.Comparators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a filtered and sorted list of suggestions for a given context using IQcSuggestors and IQcSuggestionFilter
|
||||
/// </summary>
|
||||
public class QuantumSuggestor
|
||||
{
|
||||
private readonly IQcSuggestor[] _suggestors;
|
||||
private readonly IQcSuggestionFilter[] _suggestionFilters;
|
||||
private readonly List<IQcSuggestion> _suggestionBuffer = new List<IQcSuggestion>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Quantum Suggestor with a custom set of suggestors an suggestion filters.
|
||||
/// </summary>
|
||||
/// <param name="suggestors">The IQcSuggestors to use in this Quantum Suggestor.</param>
|
||||
/// /// <param name="suggestionFilters">The IQcSuggestionFilters to use in this Quantum Suggestor.</param>
|
||||
public QuantumSuggestor(IEnumerable<IQcSuggestor> suggestors, IEnumerable<IQcSuggestionFilter> suggestionFilters)
|
||||
{
|
||||
_suggestors = suggestors.ToArray();
|
||||
_suggestionFilters = suggestionFilters.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Quantum Suggestor with the default injected suggestors and suggestion filters.
|
||||
/// </summary>
|
||||
public QuantumSuggestor() : this(
|
||||
new InjectionLoader<IQcSuggestor>().GetInjectedInstances(),
|
||||
new InjectionLoader<IQcSuggestionFilter>().GetInjectedInstances())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets suggestions for a given context.
|
||||
/// </summary>
|
||||
/// <param name="context">The context to get suggestions for.</param>
|
||||
/// <param name="options">Options for the suggestor.</param>
|
||||
/// <returns>The sorted and filtered suggestions for the provided context.</returns>
|
||||
public IEnumerable<IQcSuggestion> GetSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
PreprocessContext(ref context);
|
||||
|
||||
// Get and filter suggestions
|
||||
IEnumerable<IQcSuggestion> suggestions =
|
||||
_suggestors
|
||||
.SelectMany(x => x.GetSuggestions(context, options))
|
||||
.Where(x => IsSuggestionPermitted(x, context));
|
||||
|
||||
_suggestionBuffer.Clear();
|
||||
_suggestionBuffer.AddRange(suggestions);
|
||||
|
||||
// Sort suggestions
|
||||
AlphanumComparator comparator = new AlphanumComparator();
|
||||
IOrderedEnumerable<IQcSuggestion> sortedSuggestions =
|
||||
_suggestionBuffer
|
||||
.OrderBy(x => x.PrimarySignature.Length)
|
||||
.ThenBy(x => x.PrimarySignature, comparator)
|
||||
.ThenBy(x => x.SecondarySignature.Length)
|
||||
.ThenBy(x => x.SecondarySignature, comparator);
|
||||
|
||||
if (options.Fuzzy)
|
||||
{
|
||||
StringComparison comparisonType = options.CaseSensitive
|
||||
? StringComparison.CurrentCulture
|
||||
: StringComparison.CurrentCultureIgnoreCase;
|
||||
|
||||
sortedSuggestions = sortedSuggestions
|
||||
.OrderBy(x => x.PrimarySignature.IndexOf(context.Prompt, comparisonType));
|
||||
}
|
||||
|
||||
// Return suggestions to user
|
||||
return sortedSuggestions;
|
||||
}
|
||||
|
||||
private void PreprocessContext(ref SuggestionContext context)
|
||||
{
|
||||
// Strip the scope on the provided prompt in the context to improve suggestions
|
||||
// We want to allow incomplete scope reduction on the prompt so that you get suggestions
|
||||
// as if you had finished the scope properly
|
||||
TextProcessing.ReduceScopeOptions options = TextProcessing.ReduceScopeOptions.Default;
|
||||
options.ReduceIncompleteScope = true;
|
||||
|
||||
context.Prompt = context.Prompt.ReduceScope(options);
|
||||
}
|
||||
|
||||
private bool IsSuggestionPermitted(IQcSuggestion suggestion, SuggestionContext context)
|
||||
{
|
||||
// LINQ alternative produces too much garbage
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (IQcSuggestionFilter filter in _suggestionFilters)
|
||||
{
|
||||
if (!filter.IsSuggestionPermitted(suggestion, context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2234ee814efd4f24b9b6d922c0526de0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Raw suggestion of a given value.
|
||||
/// </summary>
|
||||
public class RawSuggestion : IQcSuggestion
|
||||
{
|
||||
private readonly string _value;
|
||||
private readonly bool _singleLiteral;
|
||||
private readonly string _completion;
|
||||
|
||||
public string FullSignature => _value;
|
||||
public string PrimarySignature => _value;
|
||||
public string SecondarySignature => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a suggestion from the provided value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to suggest.</param>
|
||||
/// <param name="singleLiteral">If the value should be treated as a single literal then "" will be used as necessary.</param>
|
||||
public RawSuggestion(string value, bool singleLiteral = false)
|
||||
{
|
||||
_value = value;
|
||||
_singleLiteral = singleLiteral;
|
||||
_completion = _value;
|
||||
|
||||
if (_completion.CanSplitScoped(' ', '"', '"'))
|
||||
{
|
||||
_completion = $"\"{_completion}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesPrompt(string prompt)
|
||||
{
|
||||
if (_singleLiteral)
|
||||
{
|
||||
prompt = prompt.Trim('"');
|
||||
}
|
||||
|
||||
return prompt == _value;
|
||||
}
|
||||
|
||||
public string GetCompletion(string prompt)
|
||||
{
|
||||
return _completion;
|
||||
}
|
||||
|
||||
public string GetCompletionTail(string prompt)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public SuggestionContext? GetInnerSuggestionContext(SuggestionContext context)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77020387dd1961a4781323957a90dac1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// The context to provide suggestions for.
|
||||
/// </summary>
|
||||
public struct SuggestionContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The depth of the suggestion context.
|
||||
/// </summary>
|
||||
public int Depth;
|
||||
|
||||
/// <summary>
|
||||
/// The prompt to generate suggestions for at this depth.
|
||||
/// </summary>
|
||||
public string Prompt;
|
||||
|
||||
/// <summary>
|
||||
/// If any, a specific type to target when producing suggestions.
|
||||
/// </summary>
|
||||
public Type TargetType;
|
||||
|
||||
/// <summary>
|
||||
/// Any tags added to the suggestion context that may be queried by suggestors.
|
||||
/// </summary>
|
||||
public IQcSuggestorTag[] Tags;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified tag exists.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The tag to check.</typeparam>
|
||||
/// <returns>If the tag exists.</returns>
|
||||
public bool HasTag<T>() where T : IQcSuggestorTag
|
||||
{
|
||||
if (Tags == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// foreach loop has better performance
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (IQcSuggestorTag tag in Tags)
|
||||
{
|
||||
if (tag is T)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified tag from the context.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The tag to get from the context.</typeparam>
|
||||
/// <returns>The tag if it could be found, otherwise a KeyNotFoundException exception is thrown.</returns>
|
||||
public T GetTag<T>() where T : IQcSuggestorTag
|
||||
{
|
||||
if (Tags != null)
|
||||
{
|
||||
foreach (IQcSuggestorTag tag in Tags)
|
||||
{
|
||||
if (tag is T foundTag)
|
||||
{
|
||||
return foundTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"No tags of type {typeof(T)} could be found.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all instances of the specified tag from the context.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The tag to get from the context.</typeparam>
|
||||
/// <returns>The tags in the context.</returns>
|
||||
public IEnumerable<T> GetTags<T>() where T : IQcSuggestorTag
|
||||
{
|
||||
if (Tags != null)
|
||||
{
|
||||
foreach (IQcSuggestorTag tag in Tags)
|
||||
{
|
||||
if (tag is T foundTag)
|
||||
{
|
||||
yield return foundTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 319e8165d4db42d488cb24106ce579f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// A managed set of suggestions for a given context.
|
||||
/// </summary>
|
||||
public class SuggestionSet
|
||||
{
|
||||
/// <summary>
|
||||
/// The context this suggestion set was produced for.
|
||||
/// </summary>
|
||||
public SuggestionContext Context;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the current selection in the set.
|
||||
/// </summary>
|
||||
public int SelectionIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The suggestions contained within the set.
|
||||
/// </summary>
|
||||
public readonly List<IQcSuggestion> Suggestions = new List<IQcSuggestion>();
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected suggestion in the set, if any.
|
||||
/// </summary>
|
||||
public IQcSuggestion CurrentSelection =>
|
||||
SelectionIndex >= 0 && SelectionIndex < Suggestions.Count
|
||||
? Suggestions[SelectionIndex]
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c180cb50f5d8024180ce8015ea31999
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,323 @@
|
||||
using QFSW.QC.Pooling;
|
||||
using QFSW.QC.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Managed stack of suggestion sets updated from a user prompt.
|
||||
/// Each suggestion set represents a layer of suggestions, allowing for nested suggestions.
|
||||
/// </summary>
|
||||
public class SuggestionStack
|
||||
{
|
||||
private readonly QuantumSuggestor _suggestor;
|
||||
private readonly List<SuggestionSet> _suggestionSets = new List<SuggestionSet>();
|
||||
private readonly Pool<SuggestionSet> _setPool = new Pool<SuggestionSet>();
|
||||
private readonly StringBuilder _stringBuilder = new StringBuilder();
|
||||
|
||||
/// <summary>
|
||||
/// The topmost valid suggestion set in the stack.
|
||||
/// </summary>
|
||||
public SuggestionSet TopmostSuggestionSet => _suggestionSets.LastOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// The selected suggestion, if any, in the topmost suggestion set.
|
||||
/// </summary>
|
||||
public IQcSuggestion TopmostSuggestion => TopmostSuggestionSet?.CurrentSelection;
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked when a new suggestion set is created.
|
||||
/// </summary>
|
||||
public event Action<SuggestionSet> OnSuggestionSetCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SuggestionStack with a default QuantumSuggestor
|
||||
/// </summary>
|
||||
public SuggestionStack() : this(new QuantumSuggestor())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a SuggestionStack with a user provided QuantumSuggestor
|
||||
/// </summary>
|
||||
/// <param name="suggestor">The QuantumSuggestor to use when creating suggestions for the stack.</param>
|
||||
public SuggestionStack(QuantumSuggestor suggestor)
|
||||
{
|
||||
_suggestor = suggestor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the stack of all suggestions.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
while (PopSet()) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the stack to the new prompt.
|
||||
/// </summary>
|
||||
/// <param name="prompt">The prompt to update the stack with.</param>
|
||||
/// <param name="options">Options to provide to the suggestor.</param>
|
||||
public void UpdateStack(string prompt, SuggestorOptions options)
|
||||
{
|
||||
// Clear if empty
|
||||
if (string.IsNullOrWhiteSpace(prompt))
|
||||
{
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
PropagateContextChanges(prompt);
|
||||
PopInvalidLayers();
|
||||
BuildInitialLayer(prompt, options);
|
||||
BuildNewLayers(options);
|
||||
}
|
||||
|
||||
private SuggestionContext? GetInnerSuggestionContext(SuggestionSet set)
|
||||
{
|
||||
IQcSuggestion currentSuggestion = set.CurrentSelection;
|
||||
SuggestionContext currentContext = set.Context;
|
||||
return currentSuggestion?.GetInnerSuggestionContext(currentContext);
|
||||
}
|
||||
|
||||
private void InvalidateLayersFrom(int index)
|
||||
{
|
||||
PopSets(_suggestionSets.Count - index);
|
||||
}
|
||||
|
||||
private void PropagateContextChanges(string prompt)
|
||||
{
|
||||
if (_suggestionSets.Count == 0)
|
||||
{
|
||||
// Nothing to propagate
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize initial set with new prompt
|
||||
_suggestionSets[0].Context.Prompt = prompt;
|
||||
|
||||
// Propagate context changes
|
||||
for (int i = 0; i < _suggestionSets.Count - 1; i++)
|
||||
{
|
||||
SuggestionSet currentSet = _suggestionSets[i];
|
||||
SuggestionContext? newNextContext = GetInnerSuggestionContext(currentSet);
|
||||
|
||||
if (newNextContext != null)
|
||||
{
|
||||
// Update context for existing layers
|
||||
SuggestionSet nextSet = _suggestionSets[i + 1];
|
||||
nextSet.Context = newNextContext.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Null context means the layer must be invalidated
|
||||
InvalidateLayersFrom(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopInvalidLayers()
|
||||
{
|
||||
for (int i = 0; i < _suggestionSets.Count; i++)
|
||||
{
|
||||
SuggestionSet set = _suggestionSets[i];
|
||||
SuggestionContext context = set.Context;
|
||||
IQcSuggestion suggestion = set.CurrentSelection;
|
||||
|
||||
if (suggestion == null || !suggestion.MatchesPrompt(context.Prompt))
|
||||
{
|
||||
InvalidateLayersFrom(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void BuildInitialLayer(string prompt, SuggestorOptions options)
|
||||
{
|
||||
if (_suggestionSets.Count == 0)
|
||||
{
|
||||
SuggestionContext context = new SuggestionContext
|
||||
{
|
||||
Prompt = prompt,
|
||||
Depth = 0,
|
||||
TargetType = null,
|
||||
};
|
||||
|
||||
CreateLayer(context, options);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildNewLayers(SuggestorOptions options)
|
||||
{
|
||||
// Create new layers if possible
|
||||
if (TopmostSuggestion != null)
|
||||
{
|
||||
SuggestionSet set = TopmostSuggestionSet;
|
||||
SuggestionContext? newNextContext = GetInnerSuggestionContext(set);
|
||||
|
||||
if (newNextContext != null)
|
||||
{
|
||||
// Create new layer, then recursively try to build more
|
||||
if (CreateLayer(newNextContext.Value, options))
|
||||
{
|
||||
BuildNewLayers(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryAutoSelectSuggestion(SuggestionSet set, string prompt)
|
||||
{
|
||||
if (set.CurrentSelection != null)
|
||||
{
|
||||
// Don't try to select something if we already have a selection
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to select the first item if it matches
|
||||
IQcSuggestion candidate = set.Suggestions.FirstOrDefault();
|
||||
if (candidate != null && candidate.MatchesPrompt(prompt))
|
||||
{
|
||||
set.SelectionIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateLayer(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
// Get the suggestions and create the new set
|
||||
IEnumerable<IQcSuggestion> suggestions =
|
||||
_suggestor.GetSuggestions(context, options);
|
||||
|
||||
SuggestionSet set = PushSet();
|
||||
set.Context = context;
|
||||
set.Suggestions.AddRange(suggestions);
|
||||
|
||||
// Remove the new layer if its empty
|
||||
if (set.Suggestions.Count == 0)
|
||||
{
|
||||
PopSet();
|
||||
return false;
|
||||
}
|
||||
|
||||
OnSuggestionSetCreated?.Invoke(set);
|
||||
|
||||
// Try to auto select a suggestion in the set
|
||||
TryAutoSelectSuggestion(set, context.Prompt);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current completion value of the stack.
|
||||
/// </summary>
|
||||
/// <returns>The combined completion value of the stack.</returns>
|
||||
public string GetCompletion()
|
||||
{
|
||||
if (_suggestionSets.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
IEnumerable<IQcSuggestion> suggestionChain =
|
||||
_suggestionSets
|
||||
.Select(x => x.CurrentSelection)
|
||||
.Where(x => x != null);
|
||||
|
||||
SuggestionContext context = _suggestionSets[0].Context;
|
||||
_stringBuilder.Clear();
|
||||
|
||||
foreach (IQcSuggestion suggestion in suggestionChain)
|
||||
{
|
||||
string part = context.Prompt;
|
||||
SuggestionContext? newContext = suggestion.GetInnerSuggestionContext(context);
|
||||
|
||||
if (newContext != null)
|
||||
{
|
||||
_stringBuilder.Append(part, 0, part.Length - newContext.Value.Prompt.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
_stringBuilder.Append(suggestion.GetCompletion(part));
|
||||
}
|
||||
}
|
||||
|
||||
return _stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current completion tail of the stack.
|
||||
/// </summary>
|
||||
/// <returns>The combined completion tail of the stack.</returns>
|
||||
public string GetCompletionTail()
|
||||
{
|
||||
_stringBuilder.Clear();
|
||||
foreach (SuggestionSet set in _suggestionSets.Reversed())
|
||||
{
|
||||
SuggestionContext context = set.Context;
|
||||
_stringBuilder.Append(set.CurrentSelection?.GetCompletionTail(context.Prompt));
|
||||
}
|
||||
|
||||
return _stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the selected suggestion in the topmost set.
|
||||
/// </summary>
|
||||
/// <param name="suggestionIndex">The index of the suggestion in the topmost set to select.</param>
|
||||
/// <returns>If the suggestion could be selected.</returns>
|
||||
public bool SetSuggestionIndex(int suggestionIndex)
|
||||
{
|
||||
if (_suggestionSets.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (suggestionIndex < 0 || suggestionIndex > TopmostSuggestionSet.Suggestions.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TopmostSuggestionSet.SelectionIndex = suggestionIndex;
|
||||
TopmostSuggestionSet.Context.Prompt = TopmostSuggestion.PrimarySignature;
|
||||
return true;
|
||||
}
|
||||
|
||||
private SuggestionSet PushSet()
|
||||
{
|
||||
SuggestionSet set = _setPool.GetObject();
|
||||
set.SelectionIndex = -1;
|
||||
set.Suggestions.Clear();
|
||||
|
||||
_suggestionSets.Add(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
private bool PopSet()
|
||||
{
|
||||
if (_suggestionSets.Count > 0)
|
||||
{
|
||||
int removeIndex = _suggestionSets.Count - 1;
|
||||
SuggestionSet set = _suggestionSets[removeIndex];
|
||||
_suggestionSets.RemoveAt(removeIndex);
|
||||
_setPool.Release(set);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool PopSets(int count)
|
||||
{
|
||||
bool successful = true;
|
||||
while (successful && count-- > 0)
|
||||
{
|
||||
successful &= PopSet();
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6052e7f2cfec30345aa76c4322154c0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Options used by the suggestor when producing suggestions.
|
||||
/// </summary>
|
||||
public struct SuggestorOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// If case of the prompt should be considered when producing suggestions.
|
||||
/// </summary>
|
||||
public bool CaseSensitive;
|
||||
|
||||
/// <summary>
|
||||
/// If fuzzy searching should be used when producing suggestions.
|
||||
/// </summary>
|
||||
public bool Fuzzy;
|
||||
|
||||
/// <summary>
|
||||
/// If overloads of the same suggestion should be collapsed into
|
||||
/// a single suggestion with optional elements where possible.
|
||||
/// </summary>
|
||||
public bool CollapseOverloads;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2780a4abd4711f34faad34e752d4b278
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Base attribute for all IQcSuggestorTag sources.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)]
|
||||
public abstract class SuggestorTagAttribute : Attribute
|
||||
{
|
||||
public abstract IQcSuggestorTag[] GetSuggestorTags();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad821e4afc8f0904cbf3770ef3528fa9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using QFSW.QC.Utilities;
|
||||
|
||||
namespace QFSW.QC
|
||||
{
|
||||
/// <summary>
|
||||
/// Common utilities used by the suggestion system.
|
||||
/// </summary>
|
||||
public static class SuggestorUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if a prompt is compatible with a string suggestion.
|
||||
/// </summary>
|
||||
/// <param name="prompt">The prompt to test.</param>
|
||||
/// <param name="suggestion">The string suggestion to test again.</param>
|
||||
/// <param name="options">The options used by the suggestor.</param>
|
||||
/// <returns>If the prompt is compatible.</returns>
|
||||
public static bool IsCompatible(string prompt, string suggestion, SuggestorOptions options)
|
||||
{
|
||||
if (prompt.Length > suggestion.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.Fuzzy)
|
||||
{
|
||||
return options.CaseSensitive
|
||||
? suggestion.Contains(prompt)
|
||||
: suggestion.ContainsCaseInsensitive(prompt);
|
||||
}
|
||||
|
||||
return suggestion.StartsWith(prompt, !options.CaseSensitive, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a881c697c3a6c3e46a5e20f241472b01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b438b39ea80851c4ebad089e1e61aaa6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class BoolSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
private readonly string[] _values =
|
||||
{
|
||||
"true",
|
||||
"false"
|
||||
};
|
||||
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return context.TargetType == typeof(bool);
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string value)
|
||||
{
|
||||
return new RawSuggestion(value);
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return _values;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d92f72d4245dd241a6011cd64ae3644
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class CommandNameSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return context.HasTag<Tags.CommandNameTag>()
|
||||
&& !string.IsNullOrWhiteSpace(context.Prompt);
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string commandName)
|
||||
{
|
||||
return new RawSuggestion(commandName);
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
string incompleteCommandName =
|
||||
context.Prompt
|
||||
.SplitScopedFirst(' ')
|
||||
.SplitFirst('<');
|
||||
|
||||
return QuantumConsoleProcessor.GetUniqueCommands()
|
||||
.Select(command => command.CommandName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c09d30aec8067b41af36708c7808282
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class CommandSuggestion : IQcSuggestion
|
||||
{
|
||||
private readonly CommandData _command;
|
||||
private readonly string[] _paramNames;
|
||||
private readonly int _numOptionalParams;
|
||||
|
||||
private readonly Dictionary<string, Type[]> _genericSignatureCache = new Dictionary<string, Type[]>();
|
||||
private readonly Dictionary<ParameterInfo, IQcSuggestorTag[]> _parameterTagsCache = new Dictionary<ParameterInfo, IQcSuggestorTag[]>();
|
||||
private readonly StringBuilder _stringBuilder = new StringBuilder();
|
||||
|
||||
private struct ParsedCommandNameInfo
|
||||
{
|
||||
public string RawName;
|
||||
public string CommandName;
|
||||
public string GenericSignature;
|
||||
public string[] GenericArgNames;
|
||||
}
|
||||
|
||||
private ParsedCommandNameInfo _currentCommandNameCache;
|
||||
|
||||
public string FullSignature => _command.CommandSignature;
|
||||
public string PrimarySignature => _command.CommandName;
|
||||
public string SecondarySignature { get; }
|
||||
public CommandData Command => _command;
|
||||
|
||||
public CommandSuggestion(CommandData command, int numOptionalParams = 0)
|
||||
{
|
||||
_command = command;
|
||||
_paramNames = _command.ParameterSignature.Split(' ');
|
||||
|
||||
_numOptionalParams = numOptionalParams;
|
||||
for (int i = _paramNames.Length - _numOptionalParams; i < _paramNames.Length; i++)
|
||||
{
|
||||
_paramNames[i] = $"[{_paramNames[i]}]";
|
||||
}
|
||||
|
||||
SecondarySignature = $"{_command.GenericSignature} {string.Join(" ", _paramNames)}";
|
||||
}
|
||||
|
||||
public bool MatchesPrompt(string prompt)
|
||||
{
|
||||
UpdateCurrentCache(prompt);
|
||||
return _currentCommandNameCache.CommandName == _command.CommandName;
|
||||
}
|
||||
|
||||
public string GetCompletion(string prompt)
|
||||
{
|
||||
return _command.CommandName;
|
||||
}
|
||||
|
||||
public string GetCompletionTail(string prompt)
|
||||
{
|
||||
UpdateCurrentCache(prompt);
|
||||
_stringBuilder.Clear();
|
||||
|
||||
int numParamsInPrompt = prompt
|
||||
.SplitScoped(' ')
|
||||
.Count(x => !string.IsNullOrWhiteSpace(x)) - 1;
|
||||
|
||||
// Can't be less than zero params in the prompt
|
||||
numParamsInPrompt = Mathf.Max(numParamsInPrompt, 0);
|
||||
|
||||
int numParamsToPrint = _command.ParamCount - numParamsInPrompt;
|
||||
|
||||
if (prompt == _currentCommandNameCache.CommandName)
|
||||
{
|
||||
_stringBuilder.Append(_command.GenericSignature);
|
||||
}
|
||||
|
||||
// Print out the params not in the prompt
|
||||
for (int i = 0; i < numParamsToPrint; i++)
|
||||
{
|
||||
// Add a space only if there's no whitespace trail already
|
||||
if (i > 0 || !prompt.EndsWith(" "))
|
||||
{
|
||||
_stringBuilder.Append(' ');
|
||||
}
|
||||
|
||||
int paramIdx = i + numParamsInPrompt;
|
||||
_stringBuilder.Append(_paramNames[paramIdx]);
|
||||
}
|
||||
|
||||
return _stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public SuggestionContext? GetInnerSuggestionContext(SuggestionContext context)
|
||||
{
|
||||
UpdateCurrentCache(context.Prompt);
|
||||
|
||||
// We want to consider this a new prompt if we've hit whitespace and we're not in an incomplete scope
|
||||
bool emptyPromptEnd = context.Prompt.EndsWith(" ") && context.Prompt.GetMaxScopeDepthAtEnd() == 0;
|
||||
string[] promptParts = context.Prompt
|
||||
.SplitScoped(' ')
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToArray();
|
||||
|
||||
int promptArgs = promptParts.Length - 1;
|
||||
if (emptyPromptEnd)
|
||||
{
|
||||
promptArgs++;
|
||||
}
|
||||
|
||||
if (promptArgs <= 0 || promptArgs > _command.ParamCount)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int paramIndex = promptArgs - 1;
|
||||
SuggestionContext newContext = context;
|
||||
newContext.Depth++;
|
||||
newContext.TargetType = GetParameterType(paramIndex);
|
||||
newContext.Tags = GetParameterTags(paramIndex);
|
||||
newContext.Prompt = emptyPromptEnd
|
||||
? string.Empty
|
||||
: promptParts.LastOrDefault();
|
||||
|
||||
return newContext;
|
||||
}
|
||||
|
||||
private void UpdateCurrentCache(string prompt)
|
||||
{
|
||||
string rawName = prompt.SplitScopedFirst(' ');
|
||||
if (rawName != _currentCommandNameCache.RawName)
|
||||
{
|
||||
_currentCommandNameCache = ParseCommandNameInfo(rawName);
|
||||
}
|
||||
}
|
||||
|
||||
private ParsedCommandNameInfo ParseCommandNameInfo(string rawName)
|
||||
{
|
||||
string[] commandNameParts = rawName.Split(new[] { '<' }, 2);
|
||||
|
||||
ParsedCommandNameInfo info = new ParsedCommandNameInfo();
|
||||
info.RawName = rawName;
|
||||
info.CommandName = commandNameParts[0];
|
||||
|
||||
if (_command.IsGeneric)
|
||||
{
|
||||
info.GenericSignature = commandNameParts.Length > 1 ? $"<{commandNameParts[1]}" : "";
|
||||
info.GenericArgNames = info.GenericSignature
|
||||
.ReduceScope('<', '>')
|
||||
.SplitScoped(',');
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private Type[] ParseGenericTypes(ParsedCommandNameInfo commandNameInfo)
|
||||
{
|
||||
return commandNameInfo
|
||||
.GenericArgNames
|
||||
.Select(QuantumParser.ParseType)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private Type[] GetParameterTypes(ParsedCommandNameInfo commandNameInfo)
|
||||
{
|
||||
// Return normal types if not generic
|
||||
if (!_command.IsGeneric)
|
||||
{
|
||||
return _command.ParamTypes;
|
||||
}
|
||||
|
||||
// Return cached if available
|
||||
if (_genericSignatureCache.TryGetValue(commandNameInfo.GenericSignature, out Type[] paramTypes))
|
||||
{
|
||||
return paramTypes;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Build types from generic types
|
||||
Type[] genericTypes = ParseGenericTypes(_currentCommandNameCache);
|
||||
paramTypes = _command.MakeGenericArguments(genericTypes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Use normal types if unable to process generics
|
||||
paramTypes = _command.ParamTypes;
|
||||
}
|
||||
|
||||
return _genericSignatureCache[commandNameInfo.GenericSignature] = paramTypes;
|
||||
}
|
||||
|
||||
private Type GetParameterType(int paramIndex)
|
||||
{
|
||||
Type[] paramTypes = GetParameterTypes(_currentCommandNameCache);
|
||||
return paramTypes[paramIndex];
|
||||
}
|
||||
|
||||
private IQcSuggestorTag[] GetParameterTags(int paramIndex)
|
||||
{
|
||||
ParameterInfo parameter = _command.MethodParamData[paramIndex];
|
||||
if (_parameterTagsCache.TryGetValue(parameter, out IQcSuggestorTag[] tags))
|
||||
{
|
||||
return tags;
|
||||
}
|
||||
|
||||
return _parameterTagsCache[parameter] =
|
||||
parameter
|
||||
.GetCustomAttributes<SuggestorTagAttribute>()
|
||||
.SelectMany(x => x.GetSuggestorTags())
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a094f36512b47b429cba82b4de046c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QFSW.QC.Utilities;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public struct CollapsedCommand
|
||||
{
|
||||
public CommandData Command;
|
||||
public int NumOptionalParams;
|
||||
|
||||
public CollapsedCommand(CommandData command)
|
||||
{
|
||||
Command = command;
|
||||
NumOptionalParams = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandSuggestor : BasicCachedQcSuggestor<CollapsedCommand>
|
||||
{
|
||||
private readonly Dictionary<string, List<CommandData>> _commandGroups = new Dictionary<string, List<CommandData>>();
|
||||
private readonly Stack<CollapsedCommand> _commandCollector = new Stack<CollapsedCommand>();
|
||||
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return context.Depth == 0;
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(CollapsedCommand collapsedCommand)
|
||||
{
|
||||
return new CommandSuggestion(collapsedCommand.Command, collapsedCommand.NumOptionalParams);
|
||||
}
|
||||
|
||||
protected override IEnumerable<CollapsedCommand> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
string incompleteCommandName =
|
||||
context.Prompt
|
||||
.SplitScopedFirst(' ')
|
||||
.SplitFirst('<');
|
||||
|
||||
IEnumerable<CommandData> commands = GetCommands(incompleteCommandName, options);
|
||||
return options.CollapseOverloads
|
||||
? CollapseCommands(commands)
|
||||
: commands.Select(x => new CollapsedCommand(x));
|
||||
}
|
||||
|
||||
public IEnumerable<CommandData> GetCommands(string incompleteCommandName, SuggestorOptions options)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(incompleteCommandName))
|
||||
{
|
||||
return Enumerable.Empty<CommandData>();
|
||||
}
|
||||
|
||||
return QuantumConsoleProcessor.GetAllCommands()
|
||||
.Where(command => SuggestorUtilities.IsCompatible(incompleteCommandName, command.CommandName, options));
|
||||
}
|
||||
|
||||
protected override bool IsMatch(SuggestionContext context, IQcSuggestion suggestion, SuggestorOptions options)
|
||||
{
|
||||
// Perform filtering in GetCommands
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<CollapsedCommand> CollapseCommands(IEnumerable<CommandData> commands)
|
||||
{
|
||||
// Reset the command groups but keep lists around for better memory performance
|
||||
foreach (List<CommandData> commandGroup in _commandGroups.Values)
|
||||
{
|
||||
commandGroup.Clear();
|
||||
}
|
||||
|
||||
// Allocate commands to their groups
|
||||
foreach (CommandData command in commands)
|
||||
{
|
||||
if (!_commandGroups.TryGetValue(command.CommandName, out List<CommandData> commandGroup))
|
||||
{
|
||||
commandGroup = new List<CommandData>();
|
||||
_commandGroups[command.CommandName] = commandGroup;
|
||||
}
|
||||
|
||||
commandGroup.Add(command);
|
||||
}
|
||||
|
||||
// For each group, iterate over commands from least to most parameters
|
||||
// If the new candidate is the same as the previous candidate + 1 new parameter
|
||||
// Then absorb the previous command as an optional argument, otherwise keep both
|
||||
foreach (List<CommandData> commandGroup in _commandGroups.Values)
|
||||
{
|
||||
commandGroup.InsertionSortBy(x => x.ParamCount);
|
||||
_commandCollector.Clear();
|
||||
|
||||
foreach (CommandData command in commandGroup)
|
||||
{
|
||||
CollapsedCommand newCandidate = new CollapsedCommand(command);
|
||||
if (_commandCollector.Count > 0)
|
||||
{
|
||||
CollapsedCommand prevCandidate = _commandCollector.Peek();
|
||||
CommandData newCommand = newCandidate.Command;
|
||||
CommandData prevCommand = prevCandidate.Command;
|
||||
|
||||
if (newCommand.ParamCount == prevCommand.ParamCount + 1)
|
||||
{
|
||||
if (newCommand.ParameterSignature.StartsWith(prevCommand.ParameterSignature))
|
||||
{
|
||||
_commandCollector.Pop();
|
||||
newCandidate.NumOptionalParams += 1 + prevCandidate.NumOptionalParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_commandCollector.Push(newCandidate);
|
||||
}
|
||||
|
||||
foreach (CollapsedCommand collapsedCommand in _commandCollector)
|
||||
{
|
||||
yield return collapsedCommand;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a81fd548b8f63a409ff49dd984e0847
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using QFSW.QC.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class ComponentSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
Type targetType = context.TargetType;
|
||||
return targetType != null
|
||||
&& targetType.IsDerivedTypeOf(typeof(Component))
|
||||
&& !targetType.IsGenericParameter;
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string name)
|
||||
{
|
||||
return new RawSuggestion(name, true);
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return Object.FindObjectsOfType(context.TargetType)
|
||||
.Select(cmp => (Component) cmp)
|
||||
.Select(cmp => cmp.gameObject.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9268967e70c604946bad8e88174c2db6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class EnumSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
private readonly Dictionary<Type, string[]> _enumCaseCache = new Dictionary<Type, string[]>();
|
||||
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
Type targetType = context.TargetType;
|
||||
return targetType != null
|
||||
&& targetType.IsEnum;
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string item)
|
||||
{
|
||||
return new RawSuggestion(item);
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return GetEnumCases(context.TargetType);
|
||||
}
|
||||
|
||||
private string[] GetEnumCases(Type enumType)
|
||||
{
|
||||
if (_enumCaseCache.TryGetValue(enumType, out string[] cachedEnumCases))
|
||||
{
|
||||
return cachedEnumCases;
|
||||
}
|
||||
|
||||
string[] enumCases =
|
||||
enumType.GetEnumNames()
|
||||
.Select(x => x.ToString())
|
||||
.ToArray();
|
||||
|
||||
return _enumCaseCache[enumType] = enumCases;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4db269b19a4ec5f48b6e97ac1ef7c183
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class GameObjectSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return context.TargetType == typeof(GameObject);
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string name)
|
||||
{
|
||||
return new RawSuggestion(name, true);
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return Object.FindObjectsOfType<GameObject>()
|
||||
.Select(obj => obj.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b03355cf0721c774fb78eb92d958d535
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces the available suggestions for the suggestion system.
|
||||
/// </summary>
|
||||
public class InlineSuggestor : IQcSuggestor
|
||||
{
|
||||
public IEnumerable<IQcSuggestion> GetSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
foreach (Tags.InlineSuggestionsTag t in context.GetTags<Tags.InlineSuggestionsTag>())
|
||||
{
|
||||
foreach (string s in t.Suggestions)
|
||||
{
|
||||
yield return new RawSuggestion(s, singleLiteral: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fed3d8fbe2fe15b4680532bcd9ac9d78
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class MacroSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return context.Prompt.StartsWith("#");
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string macro)
|
||||
{
|
||||
return new RawSuggestion($"#{macro}");
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return QuantumMacros.GetMacros()
|
||||
.Select(x => x.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76d43cae401d00140b4e5c5fd5bd17ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "QFSW.QC.Suggestors",
|
||||
"references": [
|
||||
"QFSW.QC"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 786923d329e69bf40b3d855b3e56d0cc
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using QFSW.QC.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC.Suggestors
|
||||
{
|
||||
public class SceneNameSuggestor : BasicCachedQcSuggestor<string>
|
||||
{
|
||||
protected override bool CanProvideSuggestions(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
return context.HasTag<Tags.SceneNameTag>();
|
||||
}
|
||||
|
||||
protected override IQcSuggestion ItemToSuggestion(string sceneName)
|
||||
{
|
||||
return new RawSuggestion(sceneName, true);
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetItems(SuggestionContext context, SuggestorOptions options)
|
||||
{
|
||||
if (context.GetTag<Tags.SceneNameTag>().LoadedOnly)
|
||||
{
|
||||
return SceneUtilities.GetLoadedScenes()
|
||||
.Select(x => x.name);
|
||||
}
|
||||
|
||||
return SceneUtilities.GetAllSceneNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9473b0454f2e70d4d80abe5e83c818dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc2f0197bd226cd40bc028edbefb49c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace QFSW.QC.Suggestors.Tags
|
||||
{
|
||||
public struct CommandNameTag : IQcSuggestorTag
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that command name values should be suggested for the parameter.
|
||||
/// </summary>
|
||||
public sealed class CommandNameAttribute : SuggestorTagAttribute
|
||||
{
|
||||
private readonly IQcSuggestorTag[] _tags = { new CommandNameTag() };
|
||||
|
||||
public override IQcSuggestorTag[] GetSuggestorTags()
|
||||
{
|
||||
return _tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93d4e2dcc4c88c547a4f39af4a2a41e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QFSW.QC.Suggestors.Tags
|
||||
{
|
||||
/// <summary>
|
||||
/// Object that holds all the inlined suggestions provided
|
||||
/// to <see cref="SuggestionsAttribute"/>.
|
||||
/// </summary>
|
||||
public struct InlineSuggestionsTag : IQcSuggestorTag
|
||||
{
|
||||
public readonly IEnumerable<string> Suggestions;
|
||||
|
||||
public InlineSuggestionsTag(IEnumerable<string> suggestions)
|
||||
{
|
||||
Suggestions = suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides suggestions for the parameter.
|
||||
/// </summary>
|
||||
public sealed class SuggestionsAttribute : SuggestorTagAttribute
|
||||
{
|
||||
private readonly IQcSuggestorTag[] _tags;
|
||||
|
||||
/// <param name="suggestions">String-convertible suggestions.</param>
|
||||
public SuggestionsAttribute(params object[] suggestions)
|
||||
{
|
||||
InlineSuggestionsTag tag = new InlineSuggestionsTag(
|
||||
suggestions.Select(o => o.ToString()));
|
||||
_tags = new IQcSuggestorTag[] { tag };
|
||||
}
|
||||
|
||||
public override IQcSuggestorTag[] GetSuggestorTags() => _tags;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a01c376fe68c3c3498d2b9e431470c5c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace QFSW.QC.Suggestors.Tags
|
||||
{
|
||||
public struct SceneNameTag : IQcSuggestorTag
|
||||
{
|
||||
public bool LoadedOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that scene name values should be suggested for the parameter.
|
||||
/// </summary>
|
||||
public sealed class SceneNameAttribute : SuggestorTagAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, only loaded scenes will be suggested.
|
||||
/// </summary>
|
||||
public bool LoadedOnly
|
||||
{
|
||||
get => _tag.LoadedOnly;
|
||||
set => _tag.LoadedOnly = value;
|
||||
}
|
||||
|
||||
private SceneNameTag _tag;
|
||||
|
||||
public override IQcSuggestorTag[] GetSuggestorTags()
|
||||
{
|
||||
return new IQcSuggestorTag[] { _tag };
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ba203ccdff5c994eb35ca0c491659cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user