/// <summary> /// Finds the next command for auto completion /// </summary> /// <param name="partialCommand">The partial command being completed</param> /// <param name="lastResult">The last result returned. Null to get the first match</param> /// <param name="registries">The list of registries to search for matches</param> /// <returns>The next command matching the partial string</returns> public static string FindNextCommand(ReadOnlySpan <char> partialCommand, ReadOnlySpan <char> lastResult, IEnumerable <TerminalRegistry> registries) { //**************************************** ReadOnlySpan <char> CommandText, PartialText; ReadOnlySpan <char> Prefix = default; ReadOnlySpan <char> InstanceName = default; int CharIndex; TerminalTypeSet? TypeSet = null; TerminalTypeInstance?TypeInstance; var PartialMatches = new List <string>(); //**************************************** if (partialCommand.StartsWith("help ".AsSpan(), StringComparison.InvariantCultureIgnoreCase)) { partialCommand = partialCommand.Slice(5); Prefix = "Help ".AsSpan(); } //**************************************** // Find the first word (split on a space) CharIndex = partialCommand.IndexOf(' '); // If there's a space, we're parsing an Instance Type and optional Instance Name, with a partial Command/Variable if (CharIndex != -1) { CommandText = partialCommand.Slice(0, CharIndex); PartialText = partialCommand.Slice(CharIndex + 1); CharIndex = CommandText.IndexOf('.'); // Split into Type and Name if necessary if (CharIndex != -1) { InstanceName = CommandText.Slice(CharIndex + 1); CommandText = CommandText.Slice(0, CharIndex); } foreach (var Registry in registries) { if (Registry.TryGetTypeSet(CommandText, out TypeSet)) { break; } } // If the instance type doesn't match, return the partial command as is if (TypeSet == null) { return(Prefix.Concat(partialCommand)); } if (InstanceName == null) { TypeInstance = TypeSet.Default; CommandText = TypeSet.TypeName.AsSpan(); } else { if (!TypeSet.TryGetNamedInstance(InstanceName, out TypeInstance)) { return(Prefix.Concat(partialCommand)); } CommandText = $"{TypeSet.TypeName}.{TypeInstance.Name}".AsSpan(); } // If the instance doesn't exist, return as is if (TypeInstance == null) { return(Prefix.Concat(partialCommand)); } // Add matching commands foreach (var MyCommand in TypeInstance.Type.Commands) { if (MyCommand.Name.StartsWith(PartialText, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(CommandText.Concat(" ", MyCommand.Name)); } } // Add matching variables foreach (var MyVariable in TypeInstance.Type.Variables) { if (MyVariable.Name.StartsWith(PartialText, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(CommandText.Concat(" ", MyVariable.Name)); } } } else { CharIndex = partialCommand.IndexOf('.'); // If there's a dot, we're parsing an Instance Type, with a partial Instance Name if (CharIndex != -1) { CommandText = partialCommand.Slice(0, CharIndex); PartialText = partialCommand.Slice(CharIndex + 1); foreach (var MyRegistry in registries) { if (MyRegistry.TryGetTypeSet(CommandText, out TypeSet)) { break; } } // If the instance type doesn't match, return the partial command as is if (TypeSet == null) { return(Prefix.Concat(partialCommand)); } foreach (var MyInstanceName in TypeSet.Instances) { if (MyInstanceName.StartsWith(PartialText, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(string.Format("{0}.{1}", TypeSet.TypeName, MyInstanceName)); } } } else { // No dot, we're parsing a partial Command/Variable/Instance Type foreach (var Registry in registries) { // Add matching commands foreach (var Command in Registry.Commands) { if (Command.Name.StartsWith(partialCommand, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(Command.Name); } } // Add matching variables (with an equals sign, so they can't be the same as commands) foreach (var Variable in Registry.Variables) { if (Variable.Name.StartsWith(partialCommand, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(Variable.Name); } } foreach (var Instance in Registry.DefaultInstances) { foreach (var Command in Instance.Type.Commands) { if (Command.Name.StartsWith(partialCommand, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(Command.Name); } } // Add matching variables (with an equals sign, so they can't be the same as commands) foreach (var Variable in Instance.Type.Variables) { if (Variable.Name.StartsWith(partialCommand, StringComparison.InvariantCultureIgnoreCase)) { PartialMatches.Add(Variable.Name); } } } // Add matching type sets foreach (var Type in Registry.TypeSets) { // Only add ones that have an instance if (Type.TypeName.StartsWith(partialCommand, StringComparison.InvariantCultureIgnoreCase) && Type.HasInstance) { PartialMatches.Add(Type.TypeName); } } } } } //**************************************** // Any results? if (PartialMatches.Count == 0) { return(""); } // Sort them, so we can pick the next matching result PartialMatches.Sort(); if (lastResult != null) { // Find one greater than our last match (user has requested the next one) foreach (var NextCommand in PartialMatches) { if (NextCommand.AsSpan().CompareTo(lastResult, StringComparison.OrdinalIgnoreCase) > 0) { return(Prefix.Concat(NextCommand)); } } // Nothing greater, go back to the start } return(Prefix.Concat(PartialMatches[0])); }