namespace Terminal.Gui.Drivers;
/// Helper class to handle mapping between and .
public static class ConsoleKeyMapping
{
///
/// Gets a from a .
///
/// The key code to convert.
/// A ConsoleKeyInfo representing the key.
///
/// This method is primarily used for test simulation via .
/// It produces a keyboard-layout-agnostic "best effort" ConsoleKeyInfo suitable for testing.
/// For shifted characters (e.g., Shift+2), the character returned is US keyboard layout (Shift+2 = '@').
/// This is acceptable for test simulation but may not match the user's actual keyboard layout.
///
public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
{
ConsoleModifiers modifiers = MapToConsoleModifiers (key);
KeyCode keyWithoutModifiers = key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
// Map to ConsoleKey enum
(ConsoleKey consoleKey, char keyChar) = MapToConsoleKeyAndChar (keyWithoutModifiers, modifiers);
return new (
keyChar,
consoleKey,
modifiers.HasFlag (ConsoleModifiers.Shift),
modifiers.HasFlag (ConsoleModifiers.Alt),
modifiers.HasFlag (ConsoleModifiers.Control)
);
}
/// Gets from modifiers.
/// The shift key.
/// The alt key.
/// The control key.
/// The console modifiers.
public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control)
{
var modifiers = new ConsoleModifiers ();
if (shift)
{
modifiers |= ConsoleModifiers.Shift;
}
if (alt)
{
modifiers |= ConsoleModifiers.Alt;
}
if (control)
{
modifiers |= ConsoleModifiers.Control;
}
return modifiers;
}
/// Maps a to a .
/// The console key.
/// The or the .
public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo)
{
KeyCode keyCode;
switch (consoleKeyInfo.Key)
{
case ConsoleKey.Enter:
keyCode = KeyCode.Enter;
break;
case ConsoleKey.Delete:
keyCode = KeyCode.Delete;
break;
case ConsoleKey.UpArrow:
keyCode = KeyCode.CursorUp;
break;
case ConsoleKey.DownArrow:
keyCode = KeyCode.CursorDown;
break;
case ConsoleKey.LeftArrow:
keyCode = KeyCode.CursorLeft;
break;
case ConsoleKey.RightArrow:
keyCode = KeyCode.CursorRight;
break;
case ConsoleKey.PageUp:
keyCode = KeyCode.PageUp;
break;
case ConsoleKey.PageDown:
keyCode = KeyCode.PageDown;
break;
case ConsoleKey.Home:
keyCode = KeyCode.Home;
break;
case ConsoleKey.End:
keyCode = KeyCode.End;
break;
case ConsoleKey.Insert:
keyCode = KeyCode.Insert;
break;
case ConsoleKey.F1:
keyCode = KeyCode.F1;
break;
case ConsoleKey.F2:
keyCode = KeyCode.F2;
break;
case ConsoleKey.F3:
keyCode = KeyCode.F3;
break;
case ConsoleKey.F4:
keyCode = KeyCode.F4;
break;
case ConsoleKey.F5:
keyCode = KeyCode.F5;
break;
case ConsoleKey.F6:
keyCode = KeyCode.F6;
break;
case ConsoleKey.F7:
keyCode = KeyCode.F7;
break;
case ConsoleKey.F8:
keyCode = KeyCode.F8;
break;
case ConsoleKey.F9:
keyCode = KeyCode.F9;
break;
case ConsoleKey.F10:
keyCode = KeyCode.F10;
break;
case ConsoleKey.F11:
keyCode = KeyCode.F11;
break;
case ConsoleKey.F12:
keyCode = KeyCode.F12;
break;
case ConsoleKey.F13:
keyCode = KeyCode.F13;
break;
case ConsoleKey.F14:
keyCode = KeyCode.F14;
break;
case ConsoleKey.F15:
keyCode = KeyCode.F15;
break;
case ConsoleKey.F16:
keyCode = KeyCode.F16;
break;
case ConsoleKey.F17:
keyCode = KeyCode.F17;
break;
case ConsoleKey.F18:
keyCode = KeyCode.F18;
break;
case ConsoleKey.F19:
keyCode = KeyCode.F19;
break;
case ConsoleKey.F20:
keyCode = KeyCode.F20;
break;
case ConsoleKey.F21:
keyCode = KeyCode.F21;
break;
case ConsoleKey.F22:
keyCode = KeyCode.F22;
break;
case ConsoleKey.F23:
keyCode = KeyCode.F23;
break;
case ConsoleKey.F24:
keyCode = KeyCode.F24;
break;
case ConsoleKey.Clear:
keyCode = KeyCode.Clear;
break;
case ConsoleKey.Tab:
keyCode = KeyCode.Tab;
break;
case ConsoleKey.Spacebar:
keyCode = KeyCode.Space;
break;
case ConsoleKey.Backspace:
keyCode = KeyCode.Backspace;
break;
default:
if ((int)consoleKeyInfo.KeyChar is >= 1 and <= 26)
{
keyCode = (KeyCode)(consoleKeyInfo.KeyChar + 64);
}
else
{
keyCode = (KeyCode)consoleKeyInfo.KeyChar;
}
break;
}
keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
return keyCode;
}
/// Map existing modifiers to .
/// The key code.
/// The console modifiers.
public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
{
var modifiers = new ConsoleModifiers ();
// BUGFIX: Only set Shift if ShiftMask is explicitly set.
// KeyCode.A-Z (65-90) represent UNSHIFTED keys, even though their numeric values
// match uppercase ASCII characters. Do NOT check char.IsUpper!
if (key.HasFlag (KeyCode.ShiftMask))
{
modifiers |= ConsoleModifiers.Shift;
}
if (key.HasFlag (KeyCode.AltMask))
{
modifiers |= ConsoleModifiers.Alt;
}
if (key.HasFlag (KeyCode.CtrlMask))
{
modifiers |= ConsoleModifiers.Control;
}
return modifiers;
}
/// Maps a to a .
/// The console modifiers.
/// The key code.
/// The with or the
public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key)
{
var keyMod = new KeyCode ();
if ((modifiers & ConsoleModifiers.Shift) != 0)
{
keyMod = KeyCode.ShiftMask;
}
if ((modifiers & ConsoleModifiers.Control) != 0)
{
keyMod |= KeyCode.CtrlMask;
}
if ((modifiers & ConsoleModifiers.Alt) != 0)
{
keyMod |= KeyCode.AltMask;
}
return keyMod != KeyCode.Null ? keyMod | key : key;
}
///
/// Maps a KeyCode to its corresponding ConsoleKey and character representation.
///
private static (ConsoleKey consoleKey, char keyChar) MapToConsoleKeyAndChar (KeyCode key, ConsoleModifiers modifiers)
{
var keyValue = (uint)key;
// Check if this is a special key (value > MaxCodePoint means it's offset by MaxCodePoint)
if (keyValue > (uint)KeyCode.MaxCodePoint)
{
var specialKey = (ConsoleKey)(keyValue - (uint)KeyCode.MaxCodePoint);
// Special keys don't have printable characters
char specialChar = specialKey switch
{
ConsoleKey.Enter => '\r',
ConsoleKey.Tab => '\t',
ConsoleKey.Escape => '\u001B',
ConsoleKey.Backspace => '\b',
ConsoleKey.Spacebar => ' ',
_ => '\0' // Function keys, arrows, etc. have no character
};
return (specialKey, specialChar);
}
// Handle letter keys (A-Z)
if (keyValue >= (uint)KeyCode.A && keyValue <= (uint)KeyCode.Z)
{
var letterKey = (ConsoleKey)keyValue;
var letterChar = (char)('a' + (keyValue - (uint)KeyCode.A));
if (modifiers.HasFlag (ConsoleModifiers.Shift))
{
letterChar = char.ToUpper (letterChar);
}
return (letterKey, letterChar);
}
// Handle number keys (D0-D9) with US keyboard layout
if (keyValue >= (uint)KeyCode.D0 && keyValue <= (uint)KeyCode.D9)
{
var numberKey = (ConsoleKey)keyValue;
char numberChar;
if (modifiers.HasFlag (ConsoleModifiers.Shift))
{
// US keyboard layout: Shift+0-9 produces )!@#$%^&*(
numberChar = ")!@#$%^&*(" [(int)(keyValue - (uint)KeyCode.D0)];
}
else
{
numberChar = (char)('0' + (keyValue - (uint)KeyCode.D0));
}
return (numberKey, numberChar);
}
// Handle other standard keys
var standardKey = (ConsoleKey)keyValue;
if (Enum.IsDefined (typeof (ConsoleKey), (int)keyValue))
{
char standardChar = standardKey switch
{
ConsoleKey.Enter => '\r',
ConsoleKey.Tab => '\t',
ConsoleKey.Escape => '\u001B',
ConsoleKey.Backspace => '\b',
ConsoleKey.Spacebar => ' ',
ConsoleKey.Clear => '\0',
_ when keyValue <= 0x1F => '\0', // Control characters
_ => (char)keyValue
};
return (standardKey, standardChar);
}
// For printable Unicode characters, return character with ConsoleKey.None
if (keyValue <= 0x10FFFF && !char.IsControl ((char)keyValue))
{
return (ConsoleKey.None, (char)keyValue);
}
// Fallback
return (ConsoleKey.None, (char)keyValue);
}
}