5

I am accepting a user input file path right now:

Console.WriteLine("Input file path");
string path = Console.ReadLine();

try
{
    data = System.IO.File.ReadAllBytes(path);
}
catch
{
    Console.WriteLine("Invalid file path entered");
    System.Console.ReadKey();
    return 1;
}

But if the user enters the path incorrectly, they'll have to enter the entire thing again. I realize that currently my app will just exit when the user enters something wrong and I could ask again, but I would still like to make this a bit easier for the user.

Instead, I would like to have the Windows command line functionality of autocomplete for a path when a user tabs while entering it. For example, if I open cmd and type cd C:\win and hit TAB, cmd will find C:\Windows.

Is it possible to add that ability to a console app for user input?

Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
hwustrack
  • 67
  • 3
  • 8
  • You could do it, but you'd have to basically reimplement `Console.ReadLine()` using `ReadKey()` etc. – Blorgbeard Aug 19 '16 at 01:47
  • This is a lot of work to make it pretty & usable. See my suggestion on [Code Review](http://codereview.stackexchange.com/a/139203/59161) – t3chb0t Aug 20 '16 at 06:59

4 Answers4

7

At first I thought clearing a specific console line was not feasible, but a quick search showed me that nothing is impossible.

So I created a new console application and started thinking of how I'd get something like that to work. Below is the "first working draft" - I'm about to [heavily] refactor it on my own and then put the resulting code up on Code Review, but this should be good enough to get you started.

The program makes the Tab key autocomplete the current input using an array of strings as data, matching the first item it finds; you'll have to tweak it a bit if you want something smarter (like having the current folder's child paths as data, and/or iterating through matches at each consecutive press of the Tab key):

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)
        {
            var currentInput = builder.ToString();
            if (input.Key == ConsoleKey.Tab)
            {
                var match = data.FirstOrDefault(item => item != currentInput && item.StartsWith(currentInput, true, CultureInfo.InvariantCulture));
                if (string.IsNullOrEmpty(match))
                {
                    input = Console.ReadKey(intercept: true);
                    continue;
                }

                ClearCurrentLine();
                builder.Clear();

                Console.Write(match);
                builder.Append(match);
            }
            else
            {
                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);
                }
            }

            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);
    }
}

Thanks for this question, that was fun!

Community
  • 1
  • 1
Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
  • Note: a roughly refactored version of this code is up for review [here](http://codereview.stackexchange.com/q/139172/23788) if the reader wants to help improve this code. – Mathieu Guindon Aug 19 '16 at 18:32
1

I'm adding to Mathieu Guindon's excellent answer in case someone else has this use case.

If you want to have a prompt inviting the user to make their choice, and want to be able to keep the prompt and just cycle through the options replacing the name of each one, all you have to do is change the ClearCurrentLine method so that it looks like this:

private static void ClearCurrentLine(int cursorLeft)
{
    var currentLine = Console.CursorTop;
    Console.SetCursorPosition(cursorLeft, Console.CursorTop);
    Console.Write(new string(' ', Console.WindowWidth - cursorLeft));
    Console.SetCursorPosition(cursorLeft, currentLine);
}

Then change the caller to pass in the appropriate value for cursorLeft. Dumb example:

var prompt = "Make your choice: ";
Console.Write(prompt);

// ...

ClearCurrentLine(prompt.Length)

This way 'Make your choice: ' won't be deleted by the method.

You could just print the prompt again, but this feels a bit cleaner.

s.m.
  • 7,895
  • 2
  • 38
  • 46
0

You should be able to achieve it. Although it is not straight forward and there may be no ready-made libraries, you can use something like below:

 var userInputString = "";
    while (true){

    var char = Console.Read();
    // append to userInputString
    // search your directory and suggest the path using a combination of SetCursorPosition and Console.Write and bring the cursor back to user's current typing position. Hint: store Console.CursorLeft in another variable before you use SetCursorPosition so that you can set the cursor position back. 
    // If user presses <TAB> to accept the suggestion or of the file path that user keyed in exists "break" the loop.
    // So the loop never exits until the user either keys in correct path or accepts the suggestion
    }

Some links that may help you:

C# Console - set cursor position to the last visible line

https://msdn.microsoft.com/en-us/library/system.console.setcursorposition(v=vs.110).aspx

Community
  • 1
  • 1
Raghu
  • 699
  • 7
  • 14
  • May be useful: http://stackoverflow.com/questions/29201697/hide-replace-when-typing-a-password-c – Raghu Aug 19 '16 at 02:03
0

Although this question is 7 years old now, here is my update:

Cautionary tale: I would advise against coding this yourself. I tried to do it, and the rabbit hole kept getting deeper and deeper... until you feel like you are re-implementing all the Console functionalities that already existed in the first place. To me the straw that broke the camel's back was trying to make things work with input lines that were longer than Console.BufferWidth (= multi-line).

BUT thankfully other people smarter than me have already coded this thing properly:


ReadLine library

Usage with tab autocomplete:

class AutoCompletionHandler : IAutoCompleteHandler
{
    public char[] Separators { get; set; } = new char[] { ' ' };

    public string[] GetSuggestions(string text, int index)
    {
        var suggestions = new string[] { "clone", "stash" };
        return suggestions;
    }
}

ReadLine.AutoCompletionHandler = new AutoCompletionHandler();
var input = ReadLine.Read();

I found the snippet above in a Medium post written by the author of the library: https://medium.com/@tonerdo/a-better-keyboard-input-experience-for-net-console-apps-73a24f09cd0e

Xavier Peña
  • 7,399
  • 9
  • 57
  • 99