Yesterday I stumbled upon a Stack Overflow question that asked if it was possible to implement Tab-triggered auto-completion in a console application.
I thought that was an interesting idea, so I went ahead and wrote a small program that does just that, given a hard-coded string[]
- I might eventually refactor it into its own class and reuse it in my own projects if I ever actually need auto-completion in a console app, but before I do that I'd like some feedback on the way it's implemented, given static
aside, the logic itself is pretty much the way I'll have it regardless of whether it's in a dedicated class as part of something much bigger, or right there in a console application that does nothing but verify that the code works.
Pressing Tab when there's no input will change the current line to say Bar
; because there's a word in data
that starts with Bar
, pressing Tab again will autocomplete to Barbec
; another Tab will make it Barbecue
, and then any subsequent Tab will have no effect, because nothing in the data
starts with Barbecue
- but then you could Backspace until the input is Ba
, type a t to make it Bat
, and when you press Tab then it autocompletes to Batman
.
In other words, it all works exactly as it should. But does it look right?
class Program
{
static void Main(string[] args)
{
var data = new[]
{
"Bar",
"Barbec",
"Barbecue",
"Batman",
};
var builder = new StringBuilder();
var input = Console.ReadKey(intercept:true);
while (input.Key != ConsoleKey.Enter)
{
if (input.Key == ConsoleKey.Tab)
{
HandleTabInput(builder, data);
}
else
{
HandleKeyInput(builder, data, input);
}
input = Console.ReadKey(intercept:true);
}
Console.Write(input.KeyChar);
}
/// <remarks>
/// https://stackoverflow.com/a/8946847/1188513
/// </remarks>>
private static void ClearCurrentLine()
{
var currentLine = Console.CursorTop;
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write(new string(' ', Console.WindowWidth));
Console.SetCursorPosition(0, currentLine);
}
private static void HandleTabInput(StringBuilder builder, IEnumerable<string> data)
{
var currentInput = builder.ToString();
var match = data.FirstOrDefault(item => item != currentInput && item.StartsWith(currentInput, true, CultureInfo.InvariantCulture));
if (string.IsNullOrEmpty(match))
{
return;
}
ClearCurrentLine();
builder.Clear();
Console.Write(match);
builder.Append(match);
}
private static void HandleKeyInput(StringBuilder builder, IEnumerable<string> data, ConsoleKeyInfo input)
{
var currentInput = builder.ToString();
if (input.Key == ConsoleKey.Backspace && currentInput.Length > 0)
{
builder.Remove(builder.Length - 1, 1);
ClearCurrentLine();
currentInput = currentInput.Remove(currentInput.Length - 1);
Console.Write(currentInput);
}
else
{
var key = input.KeyChar;
builder.Append(key);
Console.Write(key);
}
}
}