Innovenergy_trunk/csharp/lib/Utils/AutoComplete.cs

162 lines
5.1 KiB
C#

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static System.ConsoleColor;
using static System.ConsoleKey;
using static System.StringComparison;
namespace InnovEnergy.Lib.Utils;
public static class AutoComplete
{
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public static String? ChooseFrom(this String prompt,
IEnumerable<String> chooseFrom,
String initialInput = "",
Int32 nPopUpLines = 7)
{
if (chooseFrom == null || !chooseFrom.Any())
throw new ArgumentException(nameof(chooseFrom));
var chooseFromSorted = chooseFrom.OrderBy(s => s).ToList();
if (chooseFromSorted.Count == 0 || chooseFromSorted.Any(StringUtils.IsNullOrEmpty))
throw new ArgumentException(nameof(chooseFrom));
var acceptedChars = chooseFromSorted
.SelectMany(c => c.ToLower())
.ToHashSet();
var filtered = chooseFromSorted;
prompt = prompt.TrimEnd() + " ";
var input = initialInput;
var selectedLine = 0;
var defaultBg = Console.BackgroundColor;
var maxLen = chooseFromSorted.Max(n => n.Length);
prompt.Write();
// TODO: remove handler when done
Console.CancelKeyPress += (_, _) =>
{
ClearPopUp();
BackToInput();
};
while (true)
{
filtered = chooseFromSorted.Any(e => e.Contains(input, InvariantCultureIgnoreCase))
? chooseFromSorted.Where(e => e.Contains(input, InvariantCultureIgnoreCase)).ToList()
: filtered;
Debug.Assert(filtered.Count > 0);
var nChoices = Math.Min(nPopUpLines, filtered.Count);
ShowInput(input, prompt);
ShowPopUp(nChoices);
BackToInput();
var keyInfo = Console.ReadKey(true);
var key = keyInfo.Key;
var keyChar = keyInfo.KeyChar;
if (key == UpArrow ) selectedLine = (selectedLine - 1).Modulo(nChoices);
else if (key == DownArrow) selectedLine = (selectedLine + 1).Modulo(nChoices);
else if (key == Escape)
{
ClearPopUp();
BackToInput();
return null;
}
else if (key == Backspace && !input.IsNullOrEmpty())
{
input = input.Substring(0, input.Length - 1);
selectedLine = 0;
}
else if (key is Enter or Tab)
{
var selectedElement = filtered[selectedLine];
ClearPopUp();
BackToBeforeInput();
return selectedElement;
}
else if (acceptedChars.Contains(Char.ToLower(keyChar)))
{
input += keyInfo.KeyChar;
selectedLine = 0;
}
}
void BackToInput()
{
Console.SetCursorPosition(prompt.Length + input.Length, Console.CursorTop - nPopUpLines - 1);
}
void BackToBeforeInput()
{
Console.SetCursorPosition(0, Console.CursorTop - nPopUpLines - 2);
}
void ClearPopUp()
{
foreach (var _ in Enumerable.Range(0, nPopUpLines + 1))
"".PadRight(maxLen + prompt.Length).WriteLine();
}
void ShowPopUp(Int32 nChoices)
{
foreach (var i in Enumerable.Range(0, nPopUpLines))
{
var line = i < nChoices
? filtered[i]
: "";
var hlStart = line.IndexOf(input, InvariantCultureIgnoreCase);
var hlEnd = hlStart + input.Length;
Console.BackgroundColor = i == selectedLine ? DarkGray : defaultBg;
"".PadLeft(prompt.Length).Write(defaultBg, defaultBg);
if (hlStart >= 0)
DrawLineWithHighlight(line, hlStart, hlEnd);
else
DrawLineWithoutHighlight(line); // nothing to highlight
"".PadRight(maxLen - line.Length).WriteLine(defaultBg, defaultBg);
}
Console.BackgroundColor = defaultBg;
void DrawLineWithHighlight(String line, Int32 hlStart, Int32 hlEnd)
{
line.Substring(0, hlStart).Write(Cyan);
line.Substring(hlStart, input.Length).Write(Yellow);
line.Substring(hlEnd).Write(Cyan);
}
void DrawLineWithoutHighlight(String line)
{
line.Write(Cyan);
}
}
}
private static void ShowInput(String inp, String prompt)
{
const String instructions = "type to narrow selection...";
Console.CursorLeft = prompt.Length;
if (inp.IsNullOrEmpty())
instructions.WriteLine(Gray);
else
inp.PadRight(Math.Max(instructions.Length, inp.Length + 1)).WriteLine(Cyan);
}
}