162 lines
5.1 KiB
C#
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);
|
|
}
|
|
|
|
|
|
} |