ConsoleKeyMapping.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. namespace Terminal.Gui.Drivers;
  2. /// <summary>Helper class to handle mapping between <see cref="KeyCode"/> and <see cref="ConsoleKeyInfo"/>.</summary>
  3. public static class ConsoleKeyMapping
  4. {
  5. /// <summary>
  6. /// Gets a <see cref="ConsoleKeyInfo"/> from a <see cref="KeyCode"/>.
  7. /// </summary>
  8. /// <param name="key">The key code to convert.</param>
  9. /// <returns>A ConsoleKeyInfo representing the key.</returns>
  10. /// <remarks>
  11. /// This method is primarily used for test simulation via <see cref="IKeyConverter{T}.ToKeyInfo"/>.
  12. /// It produces a keyboard-layout-agnostic "best effort" ConsoleKeyInfo suitable for testing.
  13. /// For shifted characters (e.g., Shift+2), the character returned is US keyboard layout (Shift+2 = '@').
  14. /// This is acceptable for test simulation but may not match the user's actual keyboard layout.
  15. /// </remarks>
  16. public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
  17. {
  18. ConsoleModifiers modifiers = MapToConsoleModifiers (key);
  19. KeyCode keyWithoutModifiers = key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
  20. // Map to ConsoleKey enum
  21. (ConsoleKey consoleKey, char keyChar) = MapToConsoleKeyAndChar (keyWithoutModifiers, modifiers);
  22. return new (
  23. keyChar,
  24. consoleKey,
  25. modifiers.HasFlag (ConsoleModifiers.Shift),
  26. modifiers.HasFlag (ConsoleModifiers.Alt),
  27. modifiers.HasFlag (ConsoleModifiers.Control)
  28. );
  29. }
  30. /// <summary>Gets <see cref="ConsoleModifiers"/> from <see cref="bool"/> modifiers.</summary>
  31. /// <param name="shift">The shift key.</param>
  32. /// <param name="alt">The alt key.</param>
  33. /// <param name="control">The control key.</param>
  34. /// <returns>The console modifiers.</returns>
  35. public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control)
  36. {
  37. var modifiers = new ConsoleModifiers ();
  38. if (shift)
  39. {
  40. modifiers |= ConsoleModifiers.Shift;
  41. }
  42. if (alt)
  43. {
  44. modifiers |= ConsoleModifiers.Alt;
  45. }
  46. if (control)
  47. {
  48. modifiers |= ConsoleModifiers.Control;
  49. }
  50. return modifiers;
  51. }
  52. /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
  53. /// <param name="consoleKeyInfo">The console key.</param>
  54. /// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
  55. public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo)
  56. {
  57. KeyCode keyCode;
  58. switch (consoleKeyInfo.Key)
  59. {
  60. case ConsoleKey.Enter:
  61. keyCode = KeyCode.Enter;
  62. break;
  63. case ConsoleKey.Delete:
  64. keyCode = KeyCode.Delete;
  65. break;
  66. case ConsoleKey.UpArrow:
  67. keyCode = KeyCode.CursorUp;
  68. break;
  69. case ConsoleKey.DownArrow:
  70. keyCode = KeyCode.CursorDown;
  71. break;
  72. case ConsoleKey.LeftArrow:
  73. keyCode = KeyCode.CursorLeft;
  74. break;
  75. case ConsoleKey.RightArrow:
  76. keyCode = KeyCode.CursorRight;
  77. break;
  78. case ConsoleKey.PageUp:
  79. keyCode = KeyCode.PageUp;
  80. break;
  81. case ConsoleKey.PageDown:
  82. keyCode = KeyCode.PageDown;
  83. break;
  84. case ConsoleKey.Home:
  85. keyCode = KeyCode.Home;
  86. break;
  87. case ConsoleKey.End:
  88. keyCode = KeyCode.End;
  89. break;
  90. case ConsoleKey.Insert:
  91. keyCode = KeyCode.Insert;
  92. break;
  93. case ConsoleKey.F1:
  94. keyCode = KeyCode.F1;
  95. break;
  96. case ConsoleKey.F2:
  97. keyCode = KeyCode.F2;
  98. break;
  99. case ConsoleKey.F3:
  100. keyCode = KeyCode.F3;
  101. break;
  102. case ConsoleKey.F4:
  103. keyCode = KeyCode.F4;
  104. break;
  105. case ConsoleKey.F5:
  106. keyCode = KeyCode.F5;
  107. break;
  108. case ConsoleKey.F6:
  109. keyCode = KeyCode.F6;
  110. break;
  111. case ConsoleKey.F7:
  112. keyCode = KeyCode.F7;
  113. break;
  114. case ConsoleKey.F8:
  115. keyCode = KeyCode.F8;
  116. break;
  117. case ConsoleKey.F9:
  118. keyCode = KeyCode.F9;
  119. break;
  120. case ConsoleKey.F10:
  121. keyCode = KeyCode.F10;
  122. break;
  123. case ConsoleKey.F11:
  124. keyCode = KeyCode.F11;
  125. break;
  126. case ConsoleKey.F12:
  127. keyCode = KeyCode.F12;
  128. break;
  129. case ConsoleKey.F13:
  130. keyCode = KeyCode.F13;
  131. break;
  132. case ConsoleKey.F14:
  133. keyCode = KeyCode.F14;
  134. break;
  135. case ConsoleKey.F15:
  136. keyCode = KeyCode.F15;
  137. break;
  138. case ConsoleKey.F16:
  139. keyCode = KeyCode.F16;
  140. break;
  141. case ConsoleKey.F17:
  142. keyCode = KeyCode.F17;
  143. break;
  144. case ConsoleKey.F18:
  145. keyCode = KeyCode.F18;
  146. break;
  147. case ConsoleKey.F19:
  148. keyCode = KeyCode.F19;
  149. break;
  150. case ConsoleKey.F20:
  151. keyCode = KeyCode.F20;
  152. break;
  153. case ConsoleKey.F21:
  154. keyCode = KeyCode.F21;
  155. break;
  156. case ConsoleKey.F22:
  157. keyCode = KeyCode.F22;
  158. break;
  159. case ConsoleKey.F23:
  160. keyCode = KeyCode.F23;
  161. break;
  162. case ConsoleKey.F24:
  163. keyCode = KeyCode.F24;
  164. break;
  165. case ConsoleKey.Clear:
  166. keyCode = KeyCode.Clear;
  167. break;
  168. case ConsoleKey.Tab:
  169. keyCode = KeyCode.Tab;
  170. break;
  171. case ConsoleKey.Spacebar:
  172. keyCode = KeyCode.Space;
  173. break;
  174. case ConsoleKey.Backspace:
  175. keyCode = KeyCode.Backspace;
  176. break;
  177. default:
  178. if ((int)consoleKeyInfo.KeyChar is >= 1 and <= 26)
  179. {
  180. keyCode = (KeyCode)(consoleKeyInfo.KeyChar + 64);
  181. }
  182. else
  183. {
  184. keyCode = (KeyCode)consoleKeyInfo.KeyChar;
  185. }
  186. break;
  187. }
  188. keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
  189. return keyCode;
  190. }
  191. /// <summary>Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.</summary>
  192. /// <param name="key">The key code.</param>
  193. /// <returns>The console modifiers.</returns>
  194. public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
  195. {
  196. var modifiers = new ConsoleModifiers ();
  197. // BUGFIX: Only set Shift if ShiftMask is explicitly set.
  198. // KeyCode.A-Z (65-90) represent UNSHIFTED keys, even though their numeric values
  199. // match uppercase ASCII characters. Do NOT check char.IsUpper!
  200. if (key.HasFlag (KeyCode.ShiftMask))
  201. {
  202. modifiers |= ConsoleModifiers.Shift;
  203. }
  204. if (key.HasFlag (KeyCode.AltMask))
  205. {
  206. modifiers |= ConsoleModifiers.Alt;
  207. }
  208. if (key.HasFlag (KeyCode.CtrlMask))
  209. {
  210. modifiers |= ConsoleModifiers.Control;
  211. }
  212. return modifiers;
  213. }
  214. /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
  215. /// <param name="modifiers">The console modifiers.</param>
  216. /// <param name="key">The key code.</param>
  217. /// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
  218. public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key)
  219. {
  220. var keyMod = new KeyCode ();
  221. if ((modifiers & ConsoleModifiers.Shift) != 0)
  222. {
  223. keyMod = KeyCode.ShiftMask;
  224. }
  225. if ((modifiers & ConsoleModifiers.Control) != 0)
  226. {
  227. keyMod |= KeyCode.CtrlMask;
  228. }
  229. if ((modifiers & ConsoleModifiers.Alt) != 0)
  230. {
  231. keyMod |= KeyCode.AltMask;
  232. }
  233. return keyMod != KeyCode.Null ? keyMod | key : key;
  234. }
  235. /// <summary>
  236. /// Maps a KeyCode to its corresponding ConsoleKey and character representation.
  237. /// </summary>
  238. private static (ConsoleKey consoleKey, char keyChar) MapToConsoleKeyAndChar (KeyCode key, ConsoleModifiers modifiers)
  239. {
  240. var keyValue = (uint)key;
  241. // Check if this is a special key (value > MaxCodePoint means it's offset by MaxCodePoint)
  242. if (keyValue > (uint)KeyCode.MaxCodePoint)
  243. {
  244. var specialKey = (ConsoleKey)(keyValue - (uint)KeyCode.MaxCodePoint);
  245. // Special keys don't have printable characters
  246. char specialChar = specialKey switch
  247. {
  248. ConsoleKey.Enter => '\r',
  249. ConsoleKey.Tab => '\t',
  250. ConsoleKey.Escape => '\u001B',
  251. ConsoleKey.Backspace => '\b',
  252. ConsoleKey.Spacebar => ' ',
  253. _ => '\0' // Function keys, arrows, etc. have no character
  254. };
  255. return (specialKey, specialChar);
  256. }
  257. // Handle letter keys (A-Z)
  258. if (keyValue >= (uint)KeyCode.A && keyValue <= (uint)KeyCode.Z)
  259. {
  260. var letterKey = (ConsoleKey)keyValue;
  261. var letterChar = (char)('a' + (keyValue - (uint)KeyCode.A));
  262. if (modifiers.HasFlag (ConsoleModifiers.Shift))
  263. {
  264. letterChar = char.ToUpper (letterChar);
  265. }
  266. return (letterKey, letterChar);
  267. }
  268. // Handle number keys (D0-D9) with US keyboard layout
  269. if (keyValue >= (uint)KeyCode.D0 && keyValue <= (uint)KeyCode.D9)
  270. {
  271. var numberKey = (ConsoleKey)keyValue;
  272. char numberChar;
  273. if (modifiers.HasFlag (ConsoleModifiers.Shift))
  274. {
  275. // US keyboard layout: Shift+0-9 produces )!@#$%^&*(
  276. numberChar = ")!@#$%^&*(" [(int)(keyValue - (uint)KeyCode.D0)];
  277. }
  278. else
  279. {
  280. numberChar = (char)('0' + (keyValue - (uint)KeyCode.D0));
  281. }
  282. return (numberKey, numberChar);
  283. }
  284. // Handle other standard keys
  285. var standardKey = (ConsoleKey)keyValue;
  286. if (Enum.IsDefined (typeof (ConsoleKey), (int)keyValue))
  287. {
  288. char standardChar = standardKey switch
  289. {
  290. ConsoleKey.Enter => '\r',
  291. ConsoleKey.Tab => '\t',
  292. ConsoleKey.Escape => '\u001B',
  293. ConsoleKey.Backspace => '\b',
  294. ConsoleKey.Spacebar => ' ',
  295. ConsoleKey.Clear => '\0',
  296. _ when keyValue <= 0x1F => '\0', // Control characters
  297. _ => (char)keyValue
  298. };
  299. return (standardKey, standardChar);
  300. }
  301. // For printable Unicode characters, return character with ConsoleKey.None
  302. if (keyValue <= 0x10FFFF && !char.IsControl ((char)keyValue))
  303. {
  304. return (ConsoleKey.None, (char)keyValue);
  305. }
  306. // Fallback
  307. return (ConsoleKey.None, (char)keyValue);
  308. }
  309. }