如何覆盖 'tab' 键行为以在控制台中完成输入?
How to overwrite 'tab' key behaviour to complete input in console?
我正在使用一个示例计算器项目,用户可以在其中输入他需要在控制台中执行的选项(操作类型)。目前我正在匹配用户提供的完整输入,以检查他输入的选项是否存在于我的选项列表中,然后执行操作。
目前选项列表如下(将进一步扩展更多新选项):
List<string> options = new List<string>()
{
"add",
"divide",
"multiply",
"subtract",
"modulus"
};
用于指定操作的用户输入:
op add
op divide
'op'这里是一个特定的输入开始,指定further关键字将是一个操作选项。
目前我正在阅读整行,然后拆分输入并确定输入和选项的语法是否符合我的需要。
我在想是否可以包含当用户按下 tab 键时自动完成选项输入的行为。 (就像智能感知或 windows 'cd' 命令所发生的情况一样)。当前按 Tab 键会在输入行中添加空格。
例如:
op mul --> press 'tab' --> op multiply
我正在尝试使用 Console.ReadKey()
读取密钥,看看是否有任何可能,但到目前为止运气不好。
string input = Console.ReadLine(); //Currently fetching input string using this
//var key = Console.ReadKey(); //Not sure what to do here
//split inpput further and check if it matches correct syntax and option entered
您不能使用 ReadLine
执行此操作,因为在用户按下 ENTER
之前它会一直读取。相反,你应该只使用 ReadKey
,像这样:
string opName = "";
while(true)
{
var key = Console.ReadKey();
if (key.Key == ConsoleKey.Enter)
break;
if (key.Key == ConsoleKey.Tab)
{
if (opName.StartsWith("op"))
{
var opString = opName.Split(" ")[1];
var opName = "op" + //get operation that starts with opString
break;
}
}
else
{
opName += key.KeyChar;
}
}
var operationName = var opString = opName.Split(" ")[1];
这样做比您想象的要复杂得多,因为您必须有效地编写自己的 Console.ReadLine()
版本,它必须处理光标移动、退格、删除等操作。当您按 TAB 键扩展关键字时,它还必须处理扩展控制台中的当前文本行。
但是,如果您可以使用只允许退格键的简化版本,您可以编写类似这样的代码(这只是帮助您入门的示例代码;正确的解决方案需要正确的错误处理):
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main()
{
var lookup = new Dictionary<string, string> // Expandable keywords
{
["mul"] = "multiply ", // Note trailing spaces. You may not want those.
["div"] = "divide " ,
["sub"] = "subtract ",
["mod"] = "modulus "
// etc
};
while (true)
{
string result = ReadLineWithKeywordExpansion(lookup);
Console.WriteLine(result);
}
}
public static string ReadLineWithKeywordExpansion(Dictionary<string, string> lookup)
{
var sb = new StringBuilder();
string blank = new string(' ', Console.WindowWidth - 1);
while (true)
{
var k = Console.ReadKey();
if (k.Key == ConsoleKey.Enter)
{
Console.WriteLine();
return sb.ToString();
}
else if (k.Key == ConsoleKey.Backspace)
{
if (sb.Length > 0)
--sb.Length;
}
else if (k.Key == ConsoleKey.Tab)
{
if (lookup.TryGetValue(lastChars(sb, 3), out var rep))
{
sb.Length -= 3;
sb.Append(rep);
}
}
else if (k.KeyChar != '[=10=]') // Ignore special keys.
{
sb.Append(k.KeyChar);
}
Console.Write("\r" + blank);
Console.Write("\r" + sb.ToString());
}
}
/// <summary>Returns the last 'n' chars of a StringBuilder. </summary>
static string lastChars(StringBuilder sb, int n)
{
n = Math.Min(n, sb.Length);
char[] chars = new char[n];
for (int i = 0; i < n; ++i)
chars[i] = sb[i + sb.Length - n];
return new string(chars);
}
}
}
请注意,为简洁起见,我已将关键字长度硬编码为 3。您可能需要更复杂的查找,但适用上述一般原则。
这是对 Matthew Watson 作品的回馈。
此版本应将光标保持在调用时所在的位置,并且不会擦除光标开始处的整行。
它还会查找当前输入中的最后一个 space,并将其右侧的所有内容用作查找的“候选”:
static void Main(string[] args)
{
List<string> commands = new List<string>()
{
"add",
"divide",
"multiply",
"subtract",
"modulus"
};
Console.Write("Command: ");
var cmd = ReadLineWithKeywordExpansion(commands);
Console.Write("Press Enter to Quit");
Console.ReadLine();
}
public static string ReadLineWithKeywordExpansion(List<string> commands)
{
int top = Console.CursorTop;
int left = Console.CursorLeft;
var sb = new StringBuilder();
while (true)
{
var k = Console.ReadKey(true);
if (k.Key == ConsoleKey.Enter)
{
Console.WriteLine();
return sb.ToString();
}
else if (k.Key == ConsoleKey.Backspace)
{
if (sb.Length > 0)
{
--sb.Length;
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString() + " ");
Console.SetCursorPosition(left + sb.Length, top);
}
}
else if (k.Key == ConsoleKey.Tab)
{
int index = 0;
string candidate = sb.ToString();
for(int i=(sb.Length-1); i>=0; i--)
{
if (sb[i]==' ')
{
index = i+1;
candidate = sb.ToString().Substring(index);
break;
}
}
if (candidate != "")
{
string command = commands.FirstOrDefault(cmd => cmd.StartsWith(candidate));
if (command != null)
{
sb.Length = index;
sb.Append(command);
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString());
}
}
}
else if (k.KeyChar != '[=10=]') // Ignore special keys.
{
sb.Append(k.KeyChar);
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString());
}
}
}
我正在使用一个示例计算器项目,用户可以在其中输入他需要在控制台中执行的选项(操作类型)。目前我正在匹配用户提供的完整输入,以检查他输入的选项是否存在于我的选项列表中,然后执行操作。 目前选项列表如下(将进一步扩展更多新选项):
List<string> options = new List<string>()
{
"add",
"divide",
"multiply",
"subtract",
"modulus"
};
用于指定操作的用户输入:
op add
op divide
'op'这里是一个特定的输入开始,指定further关键字将是一个操作选项。
目前我正在阅读整行,然后拆分输入并确定输入和选项的语法是否符合我的需要。
我在想是否可以包含当用户按下 tab 键时自动完成选项输入的行为。 (就像智能感知或 windows 'cd' 命令所发生的情况一样)。当前按 Tab 键会在输入行中添加空格。
例如:
op mul --> press 'tab' --> op multiply
我正在尝试使用 Console.ReadKey()
读取密钥,看看是否有任何可能,但到目前为止运气不好。
string input = Console.ReadLine(); //Currently fetching input string using this
//var key = Console.ReadKey(); //Not sure what to do here
//split inpput further and check if it matches correct syntax and option entered
您不能使用 ReadLine
执行此操作,因为在用户按下 ENTER
之前它会一直读取。相反,你应该只使用 ReadKey
,像这样:
string opName = "";
while(true)
{
var key = Console.ReadKey();
if (key.Key == ConsoleKey.Enter)
break;
if (key.Key == ConsoleKey.Tab)
{
if (opName.StartsWith("op"))
{
var opString = opName.Split(" ")[1];
var opName = "op" + //get operation that starts with opString
break;
}
}
else
{
opName += key.KeyChar;
}
}
var operationName = var opString = opName.Split(" ")[1];
这样做比您想象的要复杂得多,因为您必须有效地编写自己的 Console.ReadLine()
版本,它必须处理光标移动、退格、删除等操作。当您按 TAB 键扩展关键字时,它还必须处理扩展控制台中的当前文本行。
但是,如果您可以使用只允许退格键的简化版本,您可以编写类似这样的代码(这只是帮助您入门的示例代码;正确的解决方案需要正确的错误处理):
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main()
{
var lookup = new Dictionary<string, string> // Expandable keywords
{
["mul"] = "multiply ", // Note trailing spaces. You may not want those.
["div"] = "divide " ,
["sub"] = "subtract ",
["mod"] = "modulus "
// etc
};
while (true)
{
string result = ReadLineWithKeywordExpansion(lookup);
Console.WriteLine(result);
}
}
public static string ReadLineWithKeywordExpansion(Dictionary<string, string> lookup)
{
var sb = new StringBuilder();
string blank = new string(' ', Console.WindowWidth - 1);
while (true)
{
var k = Console.ReadKey();
if (k.Key == ConsoleKey.Enter)
{
Console.WriteLine();
return sb.ToString();
}
else if (k.Key == ConsoleKey.Backspace)
{
if (sb.Length > 0)
--sb.Length;
}
else if (k.Key == ConsoleKey.Tab)
{
if (lookup.TryGetValue(lastChars(sb, 3), out var rep))
{
sb.Length -= 3;
sb.Append(rep);
}
}
else if (k.KeyChar != '[=10=]') // Ignore special keys.
{
sb.Append(k.KeyChar);
}
Console.Write("\r" + blank);
Console.Write("\r" + sb.ToString());
}
}
/// <summary>Returns the last 'n' chars of a StringBuilder. </summary>
static string lastChars(StringBuilder sb, int n)
{
n = Math.Min(n, sb.Length);
char[] chars = new char[n];
for (int i = 0; i < n; ++i)
chars[i] = sb[i + sb.Length - n];
return new string(chars);
}
}
}
请注意,为简洁起见,我已将关键字长度硬编码为 3。您可能需要更复杂的查找,但适用上述一般原则。
这是对 Matthew Watson 作品的回馈。
此版本应将光标保持在调用时所在的位置,并且不会擦除光标开始处的整行。
它还会查找当前输入中的最后一个 space,并将其右侧的所有内容用作查找的“候选”:
static void Main(string[] args)
{
List<string> commands = new List<string>()
{
"add",
"divide",
"multiply",
"subtract",
"modulus"
};
Console.Write("Command: ");
var cmd = ReadLineWithKeywordExpansion(commands);
Console.Write("Press Enter to Quit");
Console.ReadLine();
}
public static string ReadLineWithKeywordExpansion(List<string> commands)
{
int top = Console.CursorTop;
int left = Console.CursorLeft;
var sb = new StringBuilder();
while (true)
{
var k = Console.ReadKey(true);
if (k.Key == ConsoleKey.Enter)
{
Console.WriteLine();
return sb.ToString();
}
else if (k.Key == ConsoleKey.Backspace)
{
if (sb.Length > 0)
{
--sb.Length;
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString() + " ");
Console.SetCursorPosition(left + sb.Length, top);
}
}
else if (k.Key == ConsoleKey.Tab)
{
int index = 0;
string candidate = sb.ToString();
for(int i=(sb.Length-1); i>=0; i--)
{
if (sb[i]==' ')
{
index = i+1;
candidate = sb.ToString().Substring(index);
break;
}
}
if (candidate != "")
{
string command = commands.FirstOrDefault(cmd => cmd.StartsWith(candidate));
if (command != null)
{
sb.Length = index;
sb.Append(command);
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString());
}
}
}
else if (k.KeyChar != '[=10=]') // Ignore special keys.
{
sb.Append(k.KeyChar);
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString());
}
}
}