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