Key.cs 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Globalization;
  3. namespace Terminal.Gui.Input;
  4. /// <summary>
  5. /// Provides an abstraction for common keyboard operations and state. Used for processing keyboard input and
  6. /// raising keyboard events.
  7. /// </summary>
  8. /// <remarks>
  9. /// <para>
  10. /// This class provides a high-level abstraction with helper methods and properties for common keyboard
  11. /// operations. Use this class instead of the <see cref="Drivers.KeyCode"/> enumeration for keyboard input
  12. /// whenever possible.
  13. /// </para>
  14. /// <para></para>
  15. /// <para>
  16. /// The default value for <see cref="Key"/> is <see cref="KeyCode.Null"/> and can be tested using
  17. /// <see cref="Key.Empty"/>.
  18. /// </para>
  19. /// <para>
  20. /// <list type="table">
  21. /// <listheader>
  22. /// <term>Concept</term><description>Definition</description>
  23. /// </listheader>
  24. /// <item>
  25. /// <term>Testing Shift State</term>
  26. /// <description>
  27. /// The <c>Is</c> properties (<see cref="IsShift"/>,<see cref="IsCtrl"/>, <see cref="IsAlt"/>)
  28. /// test for shift state; whether the key press was modified by a shift key.
  29. /// </description>
  30. /// </item>
  31. /// <item>
  32. /// <term>Adding Shift State</term>
  33. /// <description>
  34. /// The <c>With</c> properties (<see cref="WithShift"/>,<see cref="WithCtrl"/>,
  35. /// <see cref="WithAlt"/>) return a copy of the Key with the shift modifier applied. This is useful for
  36. /// specifying a key that requires a shift modifier (e.g.
  37. /// <c>var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;</c>.
  38. /// </description>
  39. /// </item>
  40. /// <item>
  41. /// <term>Removing Shift State</term>
  42. /// <description>
  43. /// The <c>No</c> properties (<see cref="NoShift"/>,<see cref="NoCtrl"/>, <see cref="NoAlt"/>)
  44. /// return a copy of the Key with the shift modifier removed. This is useful for specifying a key that
  45. /// does not require a shift modifier (e.g. <c>var ControlDelete = ControlAltDelete.NoCtrl;</c>.
  46. /// </description>
  47. /// </item>
  48. /// <item>
  49. /// <term>Encoding of A..Z</term>
  50. /// <description>
  51. /// Lowercase alpha keys are encoded (in <see cref="Key.KeyCode"/>) as values between 65 and
  52. /// 90 corresponding to the un-shifted A to Z keys on a keyboard. Properties are provided for these
  53. /// (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the encoded values are the same
  54. /// as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted
  55. /// characters.
  56. /// </description>
  57. /// </item>
  58. /// <item>
  59. /// <term>Persistence as strings</term>
  60. /// <description>
  61. /// Keys are persisted as <c>"[Modifiers]+[Key]</c>. For example
  62. /// <c>new Key(Key.Delete).WithAlt.WithDel</c> is persisted as <c>"Ctrl+Alt+Delete"</c>. See
  63. /// <see cref="ToString()"/> and <see cref="TryParse(string, out Key)"/> for more
  64. /// information.
  65. /// </description>
  66. /// </item>
  67. /// </list>
  68. /// </para>
  69. /// </remarks>
  70. public class Key : EventArgs, IEquatable<Key>
  71. {
  72. /// <summary>Constructs a new <see cref="Key"/></summary>
  73. public Key () : this (KeyCode.Null) { }
  74. /// <summary>Constructs a new <see cref="Key"/> from the provided Key value</summary>
  75. /// <param name="k">The key</param>
  76. public Key (KeyCode k) { KeyCode = k; }
  77. /// <summary>
  78. /// Copy constructor.
  79. /// </summary>
  80. /// <param name="key">The Key to copy</param>
  81. public Key (Key key)
  82. {
  83. KeyCode = key.KeyCode;
  84. Handled = key.Handled;
  85. }
  86. /// <summary>Constructs a new <see cref="Key"/> from a char.</summary>
  87. /// <remarks>
  88. /// <para>
  89. /// The key codes for the A..Z keys are encoded as values between 65 and 90 (<see cref="KeyCode.A"/> through
  90. /// <see cref="KeyCode.Z"/>). While these are the same as the ASCII values for uppercase characters, they represent
  91. /// *keys*, not characters. Therefore, this constructor will store 'A'..'Z' as <see cref="KeyCode.A"/>..
  92. /// <see cref="KeyCode.Z"/> with the <see cref="KeyCode.ShiftMask"/> set and will store `a`..`z` as
  93. /// <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/>.
  94. /// </para>
  95. /// </remarks>
  96. /// <param name="ch"></param>
  97. public Key (char ch)
  98. {
  99. switch (ch)
  100. {
  101. case >= 'A' and <= 'Z':
  102. // Upper case A..Z mean "Shift-char" so we need to add Shift
  103. KeyCode = (KeyCode)ch | KeyCode.ShiftMask;
  104. break;
  105. case >= 'a' and <= 'z':
  106. // Lower case a..z mean no shift, so we need to store as Key.A...Key.Z
  107. KeyCode = (KeyCode)(ch - 32);
  108. return;
  109. default:
  110. KeyCode = (KeyCode)ch;
  111. break;
  112. }
  113. }
  114. /// <summary>
  115. /// Constructs a new Key from a string describing the key. See
  116. /// <see cref="TryParse(string, out Key)"/> for information on the format of the string.
  117. /// </summary>
  118. /// <param name="str">The string describing the key.</param>
  119. public Key (string str)
  120. {
  121. bool result = TryParse (str, out Key key);
  122. if (!result)
  123. {
  124. throw new ArgumentException (@$"Invalid key string: {str}", nameof (str));
  125. }
  126. KeyCode = key.KeyCode;
  127. }
  128. /// <summary>
  129. /// Constructs a new Key from an integer describing the key.
  130. /// It parses the integer as Key by calling the constructor with a char or calls the constructor with a
  131. /// KeyCode.
  132. /// </summary>
  133. /// <remarks>
  134. /// Don't rely on <paramref name="value"/> passed from <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/> because
  135. /// would not return the expected keys from 'a' to 'z'.
  136. /// </remarks>
  137. /// <param name="value">The integer describing the key.</param>
  138. /// <exception cref="ArgumentOutOfRangeException"></exception>
  139. /// <exception cref="ArgumentException"></exception>
  140. public Key (int value)
  141. {
  142. if (value < 0 || value > RuneExtensions.MaxUnicodeCodePoint)
  143. {
  144. throw new ArgumentOutOfRangeException (@$"Invalid key value: {value}", nameof (value));
  145. }
  146. if (char.IsSurrogate ((char)value))
  147. {
  148. throw new ArgumentException (@$"Surrogate key not allowed: {value}", nameof (value));
  149. }
  150. Key key;
  151. if (((Rune)value).IsBmp)
  152. {
  153. key = new ((char)value);
  154. }
  155. else
  156. {
  157. key = new ((KeyCode)value);
  158. }
  159. KeyCode = key.KeyCode;
  160. }
  161. /// <summary>
  162. /// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
  163. /// Useful for determining if a key represents is a printable character.
  164. /// </summary>
  165. /// <remarks>
  166. /// <para>Keys with Ctrl or Alt modifiers will return <see langword="default"/>.</para>
  167. /// <para>
  168. /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
  169. /// <see cref="KeyCode.ShiftMask"/> is set.
  170. /// </para>
  171. /// <para>
  172. /// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be
  173. /// <see langword="default"/>.
  174. /// </para>
  175. /// </remarks>
  176. public Rune AsRune => ToRune (KeyCode);
  177. /// <summary>
  178. /// Indicates if the current Key event has already been processed and the driver should stop notifying any other
  179. /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside
  180. /// the
  181. /// subscriber method.
  182. /// </summary>
  183. public bool Handled { get; set; }
  184. /// <summary>Gets a value indicating whether the Alt key was pressed (real or synthesized)</summary>
  185. /// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
  186. public bool IsAlt => (KeyCode & KeyCode.AltMask) != 0;
  187. /// <summary>Gets a value indicating whether the Ctrl key was pressed.</summary>
  188. /// <value><see langword="true"/> if is ctrl; otherwise, <see langword="false"/>.</value>
  189. public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
  190. /// <summary>
  191. /// Gets a value indicating whether the key represents a key in the range of <see cref="KeyCode.A"/> to
  192. /// <see cref="KeyCode.Z"/>, regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is
  193. /// based on these keys which are special cased.
  194. /// </summary>
  195. /// <remarks>
  196. /// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90
  197. /// corresponding to the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g.
  198. /// <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the values are the same as the ASCII values for
  199. /// uppercase characters, these enum values represent *lowercase*, un-shifted characters.
  200. /// </remarks>
  201. public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
  202. /// <summary>Gets a value indicating whether the Shift key was pressed.</summary>
  203. /// <value><see langword="true"/> if is shift; otherwise, <see langword="false"/>.</value>
  204. public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0;
  205. /// <summary>
  206. /// Indicates whether the <see cref="Key"/> is valid or not. Invalid keys are <see cref="Key.Empty"/>, and keys
  207. /// with only shift modifiers.
  208. /// </summary>
  209. public bool IsValid => this != Empty && NoAlt.NoShift.NoCtrl != Empty;
  210. private readonly KeyCode _keyCode;
  211. /// <summary>The encoded key value.</summary>
  212. /// <para>
  213. /// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Drivers.KeyCode"/>) as values between 65 and 90
  214. /// corresponding to the un-shifted A to Z keys on a keyboard. Enum values are provided for these (e.g.
  215. /// <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII values
  216. /// for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
  217. /// </para>
  218. /// <remarks>This property is the backing data for the <see cref="Key"/>. It is a <see cref="KeyCode"/> enum value.</remarks>
  219. public KeyCode KeyCode
  220. {
  221. get => _keyCode;
  222. init
  223. {
  224. #if DEBUG
  225. if (GetIsKeyCodeAtoZ (value) && (value & KeyCode.Space) != 0)
  226. {
  227. throw new ArgumentException (@$"Invalid KeyCode: {value} is invalid.", nameof (value));
  228. }
  229. #endif
  230. _keyCode = value;
  231. }
  232. }
  233. /// <summary>
  234. /// Helper for removing a shift modifier from a <see cref="Key"/>.
  235. /// <code>
  236. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  237. /// var AltDelete = ControlAltDelete.NoCtrl;
  238. /// </code>
  239. /// </summary>
  240. public Key NoAlt => new (this) { KeyCode = KeyCode & ~KeyCode.AltMask };
  241. /// <summary>
  242. /// Helper for removing a shift modifier from a <see cref="Key"/>.
  243. /// <code>
  244. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  245. /// var AltDelete = ControlAltDelete.NoCtrl;
  246. /// </code>
  247. /// </summary>
  248. public Key NoCtrl => new (this) { KeyCode = KeyCode & ~KeyCode.CtrlMask };
  249. /// <summary>
  250. /// Helper for removing a shift modifier from a <see cref="Key"/>.
  251. /// <code>
  252. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  253. /// var AltDelete = ControlAltDelete.NoCtrl;
  254. /// </code>
  255. /// </summary>
  256. public Key NoShift => new (this) { KeyCode = KeyCode & ~KeyCode.ShiftMask };
  257. /// <summary>
  258. /// Helper for specifying a shifted <see cref="Key"/>.
  259. /// <code>
  260. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  261. /// </code>
  262. /// </summary>
  263. public Key WithAlt => new (this) { KeyCode = KeyCode | KeyCode.AltMask };
  264. /// <summary>
  265. /// Helper for specifying a shifted <see cref="Key"/>.
  266. /// <code>
  267. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  268. /// </code>
  269. /// </summary>
  270. public Key WithCtrl => new (this) { KeyCode = KeyCode | KeyCode.CtrlMask };
  271. /// <summary>
  272. /// Helper for specifying a shifted <see cref="Key"/>.
  273. /// <code>
  274. /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
  275. /// </code>
  276. /// </summary>
  277. public Key WithShift => new (this) { KeyCode = KeyCode | KeyCode.ShiftMask };
  278. /// <summary>
  279. /// Tests if a KeyCode represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
  280. /// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which
  281. /// are special cased.
  282. /// </summary>
  283. /// <remarks>
  284. /// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90
  285. /// corresponding to the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g.
  286. /// <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the values are the same as the ASCII values for
  287. /// uppercase characters, these enum values represent *lowercase*, un-shifted characters.
  288. /// </remarks>
  289. public static bool GetIsKeyCodeAtoZ (KeyCode keyCode)
  290. {
  291. if ((keyCode & KeyCode.AltMask) != 0 || (keyCode & KeyCode.CtrlMask) != 0)
  292. {
  293. return false;
  294. }
  295. if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z)
  296. {
  297. return true;
  298. }
  299. return (keyCode & KeyCode.CharMask) is >= KeyCode.A and <= KeyCode.Z;
  300. }
  301. /// <summary>
  302. /// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>. Useful for determining if a key represents is a
  303. /// printable character.
  304. /// </summary>
  305. /// <remarks>
  306. /// <para>Keys with Ctrl or Alt modifiers will return <see langword="default"/>.</para>
  307. /// <para>
  308. /// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
  309. /// <see cref="KeyCode.ShiftMask"/> is set.
  310. /// </para>
  311. /// <para>
  312. /// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be
  313. /// <see langword="default"/>.
  314. /// </para>
  315. /// </remarks>
  316. /// <param name="key"></param>
  317. /// <returns>The key converted to a Rune. <see langword="default"/> if conversion is not possible.</returns>
  318. public static Rune ToRune (KeyCode key)
  319. {
  320. if (key is KeyCode.Null or KeyCode.SpecialMask
  321. || key.HasFlag (KeyCode.CtrlMask)
  322. || key.HasFlag (KeyCode.AltMask))
  323. {
  324. return default (Rune);
  325. }
  326. // Extract the base key code
  327. KeyCode baseKey = key;
  328. if (baseKey.HasFlag (KeyCode.ShiftMask))
  329. {
  330. baseKey &= ~KeyCode.ShiftMask;
  331. }
  332. switch (baseKey)
  333. {
  334. case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
  335. return new ((uint)(baseKey + 32));
  336. case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
  337. return new ((uint)baseKey);
  338. case > KeyCode.Null and < KeyCode.A:
  339. return new ((uint)baseKey);
  340. }
  341. if (Enum.IsDefined (typeof (KeyCode), baseKey))
  342. {
  343. return default (Rune);
  344. }
  345. return new ((uint)baseKey);
  346. }
  347. #region Operators
  348. /// <summary>
  349. /// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy because properties such
  350. /// as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
  351. /// </summary>
  352. /// <remarks>Uses <see cref="AsRune"/>.</remarks>
  353. /// <param name="kea"></param>
  354. public static explicit operator Rune (Key kea) { return kea.AsRune; }
  355. // BUGBUG: (Tig) I do not think this cast operator is really needed.
  356. /// <summary>
  357. /// Explicitly cast <see cref="Key"/> to a <see langword="uint"/>. The conversion is lossy because properties such
  358. /// as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
  359. /// </summary>
  360. /// <param name="kea"></param>
  361. public static explicit operator uint (Key kea) { return (uint)kea.KeyCode; }
  362. /// <summary>
  363. /// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy because properties such
  364. /// as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
  365. /// </summary>
  366. /// <param name="key"></param>
  367. public static explicit operator KeyCode (Key key) { return key.KeyCode; }
  368. /// <summary>Cast <see cref="KeyCode"/> to a <see cref="Key"/>.</summary>
  369. /// <param name="keyCode"></param>
  370. public static implicit operator Key (KeyCode keyCode) { return new (keyCode); }
  371. /// <summary>Cast <see langword="char"/> to a <see cref="Key"/>.</summary>
  372. /// <remarks>See <see cref="Key(char)"/> for more information.</remarks>
  373. /// <param name="ch"></param>
  374. public static implicit operator Key (char ch) { return new (ch); }
  375. /// <summary>Cast <see langword="string"/> to a <see cref="Key"/>.</summary>
  376. /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
  377. /// <param name="str"></param>
  378. public static implicit operator Key (string str) { return new (str); }
  379. /// <summary>Cast <see langword="int"/> to a <see cref="Key"/>.</summary>
  380. /// <remarks>See <see cref="Key(int)"/> for more information.</remarks>
  381. /// <param name="value"></param>
  382. public static implicit operator Key (int value) { return new (value); }
  383. /// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
  384. /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
  385. /// <param name="key"></param>
  386. public static implicit operator string (Key key) { return key.ToString (); }
  387. /// <inheritdoc/>
  388. public override bool Equals (object? obj)
  389. {
  390. if (obj is Key other)
  391. {
  392. return other._keyCode == _keyCode && other.Handled == Handled;
  393. }
  394. return false;
  395. }
  396. bool IEquatable<Key>.Equals (Key? other) { return Equals (other); }
  397. /// <inheritdoc/>
  398. public override int GetHashCode () { return _keyCode.GetHashCode (); }
  399. /// <summary>Compares two <see cref="Key"/>s for equality.</summary>
  400. /// <param name="a"></param>
  401. /// <param name="b"></param>
  402. /// <returns></returns>
  403. public static bool operator == (Key a, Key b) { return a!.Equals (b); }
  404. /// <summary>Compares two <see cref="Key"/>s for not equality.</summary>
  405. /// <param name="a"></param>
  406. /// <param name="b"></param>
  407. /// <returns></returns>
  408. public static bool operator != (Key? a, Key? b) { return !a!.Equals (b); }
  409. /// <summary>Compares two <see cref="Key"/>s for less-than.</summary>
  410. /// <param name="a"></param>
  411. /// <param name="b"></param>
  412. /// <returns></returns>
  413. public static bool operator < (Key a, Key b) { return a?.KeyCode < b?.KeyCode; }
  414. /// <summary>Compares two <see cref="Key"/>s for greater-than.</summary>
  415. /// <param name="a"></param>
  416. /// <param name="b"></param>
  417. /// <returns></returns>
  418. public static bool operator > (Key a, Key b) { return a?.KeyCode > b?.KeyCode; }
  419. /// <summary>Compares two <see cref="Key"/>s for greater-than-or-equal-to.</summary>
  420. /// <param name="a"></param>
  421. /// <param name="b"></param>
  422. /// <returns></returns>
  423. public static bool operator <= (Key a, Key b) { return a?.KeyCode <= b?.KeyCode; }
  424. /// <summary>Compares two <see cref="Key"/>s for greater-than-or-equal-to.</summary>
  425. /// <param name="a"></param>
  426. /// <param name="b"></param>
  427. /// <returns></returns>
  428. public static bool operator >= (Key a, Key b) { return a?.KeyCode >= b?.KeyCode; }
  429. #endregion Operators
  430. #region String conversion
  431. /// <summary>Pretty prints the Key.</summary>
  432. /// <returns></returns>
  433. public override string ToString () { return ToString (KeyCode, Separator); }
  434. private static string GetKeyString (KeyCode key)
  435. {
  436. if (key is KeyCode.Null or KeyCode.SpecialMask)
  437. {
  438. return string.Empty;
  439. }
  440. // Extract the base key (removing modifier flags)
  441. KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
  442. if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z)
  443. {
  444. return ((Rune)(uint)(key + 32)).ToString ();
  445. }
  446. if (key is > KeyCode.Space and < KeyCode.A)
  447. {
  448. return ((Rune)(uint)key).ToString ();
  449. }
  450. string? keyName = Enum.GetName (typeof (KeyCode), key);
  451. return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString ();
  452. }
  453. /// <summary>Formats a <see cref="KeyCode"/> as a string using the default separator of '+'</summary>
  454. /// <param name="key">The key to format.</param>
  455. /// <returns>
  456. /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
  457. /// name will be returned.
  458. /// </returns>
  459. public static string ToString (KeyCode key) { return ToString (key, Separator); }
  460. /// <summary>Formats a <see cref="KeyCode"/> as a string.</summary>
  461. /// <param name="key">The key to format.</param>
  462. /// <param name="separator">The character to use as a separator between modifier keys and the key itself.</param>
  463. /// <returns>
  464. /// The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
  465. /// name will be returned.
  466. /// </returns>
  467. public static string ToString (KeyCode key, Rune separator)
  468. {
  469. if (key is KeyCode.Null)
  470. {
  471. // Same as Key.IsValid
  472. return @"Null";
  473. }
  474. var sb = new StringBuilder ();
  475. // Extract the base key (removing modifier flags)
  476. KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
  477. // Extract and handle modifiers
  478. var hasModifiers = false;
  479. if ((key & KeyCode.CtrlMask) != 0)
  480. {
  481. sb.Append ($"Ctrl{separator}");
  482. hasModifiers = true;
  483. }
  484. if ((key & KeyCode.AltMask) != 0)
  485. {
  486. sb.Append ($"Alt{separator}");
  487. hasModifiers = true;
  488. }
  489. if ((key & KeyCode.ShiftMask) != 0 && !GetIsKeyCodeAtoZ (key))
  490. {
  491. sb.Append ($"Shift{separator}");
  492. hasModifiers = true;
  493. }
  494. // Handle special cases and modifiers on their own
  495. if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers))
  496. {
  497. if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z)
  498. {
  499. sb.Append (baseKey & ~KeyCode.Space);
  500. }
  501. else
  502. {
  503. // Append the actual key name
  504. sb.Append (GetKeyString (baseKey));
  505. }
  506. }
  507. return TrimEndSeparator (sb.ToString (), separator);
  508. }
  509. private static string TrimEndSeparator (string input, Rune separator)
  510. {
  511. // Trim the trailing separator (+). Unless there are two separators at the end.
  512. // "+" (don't trim)
  513. // "Ctrl+" (trim)
  514. // "Ctrl++" (trim)
  515. if (input.Length > 1 && !char.IsHighSurrogate (input [^2]) && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
  516. {
  517. return input [..^1];
  518. }
  519. return input;
  520. }
  521. private static readonly Dictionary<string, KeyCode> _modifierDict =
  522. new (StringComparer.InvariantCultureIgnoreCase)
  523. {
  524. { "Shift", KeyCode.ShiftMask }, { "Ctrl", KeyCode.CtrlMask }, { "Alt", KeyCode.AltMask }
  525. };
  526. /// <summary>Converts the provided string to a new <see cref="Key"/> instance.</summary>
  527. /// <param name="text">
  528. /// The text to analyze. Formats supported are "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X",
  529. /// "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", "X", and "120" (Unicode codepoint).
  530. /// <para>
  531. /// The separator can be any character, not just <see cref="Key.Separator"/> (e.g. "Ctrl@Alt@X").
  532. /// </para>
  533. /// </param>
  534. /// <param name="key">The parsed value.</param>
  535. /// <returns>A boolean value indicating whether parsing was successful.</returns>
  536. /// <remarks></remarks>
  537. public static bool TryParse (string text, out Key key)
  538. {
  539. if (string.IsNullOrEmpty (text))
  540. {
  541. key = Empty;
  542. return true;
  543. }
  544. switch (text)
  545. {
  546. case "Ctrl":
  547. key = KeyCode.CtrlMask;
  548. return true;
  549. case "Alt":
  550. key = KeyCode.AltMask;
  551. return true;
  552. case "Shift":
  553. key = KeyCode.ShiftMask;
  554. return true;
  555. }
  556. key = null!;
  557. Rune separator = Separator;
  558. // Perhaps the separator was written using a different Key.Separator? Does the string
  559. // start with "Ctrl", "Alt" or "Shift"? If so, get the char after the modifier string and use that as the separator.
  560. if (text.StartsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
  561. {
  562. separator = (Rune)text [4];
  563. }
  564. else if (text.StartsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
  565. {
  566. separator = (Rune)text [3];
  567. }
  568. else if (text.StartsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
  569. {
  570. separator = (Rune)text [5];
  571. }
  572. else if (text.EndsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
  573. {
  574. separator = (Rune)text [^5];
  575. }
  576. else if (text.EndsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
  577. {
  578. separator = (Rune)text [^4];
  579. }
  580. else if (text.EndsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
  581. {
  582. separator = (Rune)text [^6];
  583. }
  584. // Split the string into parts using the set Separator
  585. string [] parts = text.Split ((char)separator.Value);
  586. if (parts.Length is > 4)
  587. {
  588. // Invalid
  589. return false;
  590. }
  591. if (text.Length == 2 && char.IsHighSurrogate (text [^2]) && char.IsLowSurrogate (text [^1]))
  592. {
  593. // It's a surrogate pair and there is no modifiers
  594. key = new (new Rune (text [^2], text [^1]).Value);
  595. return true;
  596. }
  597. // e.g. "Ctrl++"
  598. if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
  599. {
  600. // Invalid
  601. return false;
  602. }
  603. if ((Rune)text [^1] == separator)
  604. {
  605. parts [^1] = separator.Value.ToString ();
  606. key = (char)separator.Value;
  607. }
  608. if (separator != Separator && (parts.Length is 1 || (key is { } && parts.Length is 2)))
  609. {
  610. parts = text.Split ((char)separator.Value);
  611. if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
  612. {
  613. // Invalid
  614. return false;
  615. }
  616. }
  617. var modifiers = KeyCode.Null;
  618. for (var index = 0; index < parts.Length; index++)
  619. {
  620. if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out KeyCode modifier))
  621. {
  622. modifiers |= modifier;
  623. parts [index] = string.Empty; // eat it
  624. }
  625. }
  626. // we now have the modifiers
  627. string partNotFound = parts.FirstOrDefault (p => !string.IsNullOrEmpty (p), string.Empty);
  628. var parsedKeyCode = KeyCode.Null;
  629. var parsedInt = 0;
  630. if (partNotFound.Length == 1)
  631. {
  632. var keyCode = (KeyCode)partNotFound [0];
  633. // if it's a single digit int, treat it as such
  634. if (int.TryParse (
  635. partNotFound,
  636. NumberStyles.Integer,
  637. CultureInfo.InvariantCulture,
  638. out parsedInt
  639. ))
  640. {
  641. keyCode = (KeyCode)((int)KeyCode.D0 + parsedInt);
  642. }
  643. else if (Enum.TryParse (partNotFound, false, out parsedKeyCode))
  644. {
  645. if (parsedKeyCode != KeyCode.Null)
  646. {
  647. if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
  648. {
  649. key = new (parsedKeyCode | KeyCode.ShiftMask);
  650. return true;
  651. }
  652. key = new (parsedKeyCode | modifiers);
  653. return true;
  654. }
  655. }
  656. if (GetIsKeyCodeAtoZ (keyCode) && (keyCode & KeyCode.Space) != 0)
  657. {
  658. keyCode &= ~KeyCode.Space;
  659. }
  660. key = new (keyCode | modifiers);
  661. return true;
  662. }
  663. if (Enum.TryParse (partNotFound, true, out parsedKeyCode))
  664. {
  665. if (parsedKeyCode != KeyCode.Null)
  666. {
  667. if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
  668. {
  669. key = new (parsedKeyCode | KeyCode.ShiftMask);
  670. return true;
  671. }
  672. if (GetIsKeyCodeAtoZ (parsedKeyCode) && (parsedKeyCode & KeyCode.Space) != 0)
  673. {
  674. parsedKeyCode = parsedKeyCode & ~KeyCode.Space;
  675. }
  676. key = new (parsedKeyCode | modifiers);
  677. return true;
  678. }
  679. }
  680. // if it's a number int, treat it as a unicode value
  681. if (int.TryParse (
  682. partNotFound,
  683. NumberStyles.Number,
  684. CultureInfo.InvariantCulture,
  685. out parsedInt
  686. ))
  687. {
  688. if (!Rune.IsValid (parsedInt))
  689. {
  690. return false;
  691. }
  692. if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
  693. {
  694. key = new ((KeyCode)parsedInt | KeyCode.ShiftMask);
  695. return true;
  696. }
  697. key = new ((KeyCode)parsedInt);
  698. return true;
  699. }
  700. if (!Enum.TryParse (partNotFound, true, out parsedKeyCode))
  701. {
  702. return false;
  703. }
  704. if (GetIsKeyCodeAtoZ (parsedKeyCode))
  705. {
  706. key = new (parsedKeyCode | (modifiers & ~KeyCode.Space));
  707. return true;
  708. }
  709. return false;
  710. }
  711. #endregion
  712. #region Standard Key Definitions
  713. /// <summary>An uninitialized The <see cref="Key"/> object.</summary>
  714. public new static Key Empty => new ();
  715. /// <summary>The <see cref="Key"/> object for the Backspace key.</summary>
  716. public static Key Backspace => new (KeyCode.Backspace);
  717. /// <summary>The <see cref="Key"/> object for the tab key (forwards tab key).</summary>
  718. public static Key Tab => new (KeyCode.Tab);
  719. /// <summary>The <see cref="Key"/> object for the return key.</summary>
  720. public static Key Enter => new (KeyCode.Enter);
  721. /// <summary>The <see cref="Key"/> object for the clear key.</summary>
  722. public static Key Clear => new (KeyCode.Clear);
  723. /// <summary>The <see cref="Key"/> object for the Escape key.</summary>
  724. public static Key Esc => new (KeyCode.Esc);
  725. /// <summary>The <see cref="Key"/> object for the Space bar key.</summary>
  726. public static Key Space => new (KeyCode.Space);
  727. /// <summary>The <see cref="Key"/> object for 0 key.</summary>
  728. public static Key D0 => new (KeyCode.D0);
  729. /// <summary>The <see cref="Key"/> object for 1 key.</summary>
  730. public static Key D1 => new (KeyCode.D1);
  731. /// <summary>The <see cref="Key"/> object for 2 key.</summary>
  732. public static Key D2 => new (KeyCode.D2);
  733. /// <summary>The <see cref="Key"/> object for 3 key.</summary>
  734. public static Key D3 => new (KeyCode.D3);
  735. /// <summary>The <see cref="Key"/> object for 4 key.</summary>
  736. public static Key D4 => new (KeyCode.D4);
  737. /// <summary>The <see cref="Key"/> object for 5 key.</summary>
  738. public static Key D5 => new (KeyCode.D5);
  739. /// <summary>The <see cref="Key"/> object for 6 key.</summary>
  740. public static Key D6 => new (KeyCode.D6);
  741. /// <summary>The <see cref="Key"/> object for 7 key.</summary>
  742. public static Key D7 => new (KeyCode.D7);
  743. /// <summary>The <see cref="Key"/> object for 8 key.</summary>
  744. public static Key D8 => new (KeyCode.D8);
  745. /// <summary>The <see cref="Key"/> object for 9 key.</summary>
  746. public static Key D9 => new (KeyCode.D9);
  747. /// <summary>The <see cref="Key"/> object for the A key (un-shifted). Use <c>Key.A.WithShift</c> for uppercase 'A'.</summary>
  748. public static Key A => new (KeyCode.A);
  749. /// <summary>The <see cref="Key"/> object for the B key (un-shifted). Use <c>Key.B.WithShift</c> for uppercase 'B'.</summary>
  750. public static Key B => new (KeyCode.B);
  751. /// <summary>The <see cref="Key"/> object for the C key (un-shifted). Use <c>Key.C.WithShift</c> for uppercase 'C'.</summary>
  752. public static Key C => new (KeyCode.C);
  753. /// <summary>The <see cref="Key"/> object for the D key (un-shifted). Use <c>Key.D.WithShift</c> for uppercase 'D'.</summary>
  754. public static Key D => new (KeyCode.D);
  755. /// <summary>The <see cref="Key"/> object for the E key (un-shifted). Use <c>Key.E.WithShift</c> for uppercase 'E'.</summary>
  756. public static Key E => new (KeyCode.E);
  757. /// <summary>The <see cref="Key"/> object for the F key (un-shifted). Use <c>Key.F.WithShift</c> for uppercase 'F'.</summary>
  758. public static Key F => new (KeyCode.F);
  759. /// <summary>The <see cref="Key"/> object for the G key (un-shifted). Use <c>Key.G.WithShift</c> for uppercase 'G'.</summary>
  760. public static Key G => new (KeyCode.G);
  761. /// <summary>The <see cref="Key"/> object for the H key (un-shifted). Use <c>Key.H.WithShift</c> for uppercase 'H'.</summary>
  762. public static Key H => new (KeyCode.H);
  763. /// <summary>The <see cref="Key"/> object for the I key (un-shifted). Use <c>Key.I.WithShift</c> for uppercase 'I'.</summary>
  764. public static Key I => new (KeyCode.I);
  765. /// <summary>The <see cref="Key"/> object for the J key (un-shifted). Use <c>Key.J.WithShift</c> for uppercase 'J'.</summary>
  766. public static Key J => new (KeyCode.J);
  767. /// <summary>The <see cref="Key"/> object for the K key (un-shifted). Use <c>Key.K.WithShift</c> for uppercase 'K'.</summary>
  768. public static Key K => new (KeyCode.K);
  769. /// <summary>The <see cref="Key"/> object for the L key (un-shifted). Use <c>Key.L.WithShift</c> for uppercase 'L'.</summary>
  770. public static Key L => new (KeyCode.L);
  771. /// <summary>The <see cref="Key"/> object for the M key (un-shifted). Use <c>Key.M.WithShift</c> for uppercase 'M'.</summary>
  772. public static Key M => new (KeyCode.M);
  773. /// <summary>The <see cref="Key"/> object for the N key (un-shifted). Use <c>Key.N.WithShift</c> for uppercase 'N'.</summary>
  774. public static Key N => new (KeyCode.N);
  775. /// <summary>The <see cref="Key"/> object for the O key (un-shifted). Use <c>Key.O.WithShift</c> for uppercase 'O'.</summary>
  776. public static Key O => new (KeyCode.O);
  777. /// <summary>The <see cref="Key"/> object for the P key (un-shifted). Use <c>Key.P.WithShift</c> for uppercase 'P'.</summary>
  778. public static Key P => new (KeyCode.P);
  779. /// <summary>The <see cref="Key"/> object for the Q key (un-shifted). Use <c>Key.Q.WithShift</c> for uppercase 'Q'.</summary>
  780. public static Key Q => new (KeyCode.Q);
  781. /// <summary>The <see cref="Key"/> object for the R key (un-shifted). Use <c>Key.R.WithShift</c> for uppercase 'R'.</summary>
  782. public static Key R => new (KeyCode.R);
  783. /// <summary>The <see cref="Key"/> object for the S key (un-shifted). Use <c>Key.S.WithShift</c> for uppercase 'S'.</summary>
  784. public static Key S => new (KeyCode.S);
  785. /// <summary>The <see cref="Key"/> object for the T key (un-shifted). Use <c>Key.T.WithShift</c> for uppercase 'T'.</summary>
  786. public static Key T => new (KeyCode.T);
  787. /// <summary>The <see cref="Key"/> object for the U key (un-shifted). Use <c>Key.U.WithShift</c> for uppercase 'U'.</summary>
  788. public static Key U => new (KeyCode.U);
  789. /// <summary>The <see cref="Key"/> object for the V key (un-shifted). Use <c>Key.V.WithShift</c> for uppercase 'V'.</summary>
  790. public static Key V => new (KeyCode.V);
  791. /// <summary>The <see cref="Key"/> object for the W key (un-shifted). Use <c>Key.W.WithShift</c> for uppercase 'W'.</summary>
  792. public static Key W => new (KeyCode.W);
  793. /// <summary>The <see cref="Key"/> object for the X key (un-shifted). Use <c>Key.X.WithShift</c> for uppercase 'X'.</summary>
  794. public static Key X => new (KeyCode.X);
  795. /// <summary>The <see cref="Key"/> object for the Y key (un-shifted). Use <c>Key.Y.WithShift</c> for uppercase 'Y'.</summary>
  796. public static Key Y => new (KeyCode.Y);
  797. /// <summary>The <see cref="Key"/> object for the Z key (un-shifted). Use <c>Key.Z.WithShift</c> for uppercase 'Z'.</summary>
  798. public static Key Z => new (KeyCode.Z);
  799. /// <summary>The <see cref="Key"/> object for the Delete key.</summary>
  800. public static Key Delete => new (KeyCode.Delete);
  801. /// <summary>The <see cref="Key"/> object for the Cursor up key.</summary>
  802. public static Key CursorUp => new (KeyCode.CursorUp);
  803. /// <summary>The <see cref="Key"/> object for Cursor down key.</summary>
  804. public static Key CursorDown => new (KeyCode.CursorDown);
  805. /// <summary>The <see cref="Key"/> object for Cursor left key.</summary>
  806. public static Key CursorLeft => new (KeyCode.CursorLeft);
  807. /// <summary>The <see cref="Key"/> object for Cursor right key.</summary>
  808. public static Key CursorRight => new (KeyCode.CursorRight);
  809. /// <summary>The <see cref="Key"/> object for Page Up key.</summary>
  810. public static Key PageUp => new (KeyCode.PageUp);
  811. /// <summary>The <see cref="Key"/> object for Page Down key.</summary>
  812. public static Key PageDown => new (KeyCode.PageDown);
  813. /// <summary>The <see cref="Key"/> object for Home key.</summary>
  814. public static Key Home => new (KeyCode.Home);
  815. /// <summary>The <see cref="Key"/> object for End key.</summary>
  816. public static Key End => new (KeyCode.End);
  817. /// <summary>The <see cref="Key"/> object for Insert Character key.</summary>
  818. public static Key InsertChar => new (KeyCode.Insert);
  819. /// <summary>The <see cref="Key"/> object for Delete Character key.</summary>
  820. public static Key DeleteChar => new (KeyCode.Delete);
  821. /// <summary>The <see cref="Key"/> object for Print Screen key.</summary>
  822. public static Key PrintScreen => new (KeyCode.PrintScreen);
  823. /// <summary>The <see cref="Key"/> object for F1 key.</summary>
  824. public static Key F1 => new (KeyCode.F1);
  825. /// <summary>The <see cref="Key"/> object for F2 key.</summary>
  826. public static Key F2 => new (KeyCode.F2);
  827. /// <summary>The <see cref="Key"/> object for F3 key.</summary>
  828. public static Key F3 => new (KeyCode.F3);
  829. /// <summary>The <see cref="Key"/> object for F4 key.</summary>
  830. public static Key F4 => new (KeyCode.F4);
  831. /// <summary>The <see cref="Key"/> object for F5 key.</summary>
  832. public static Key F5 => new (KeyCode.F5);
  833. /// <summary>The <see cref="Key"/> object for F6 key.</summary>
  834. public static Key F6 => new (KeyCode.F6);
  835. /// <summary>The <see cref="Key"/> object for F7 key.</summary>
  836. public static Key F7 => new (KeyCode.F7);
  837. /// <summary>The <see cref="Key"/> object for F8 key.</summary>
  838. public static Key F8 => new (KeyCode.F8);
  839. /// <summary>The <see cref="Key"/> object for F9 key.</summary>
  840. public static Key F9 => new (KeyCode.F9);
  841. /// <summary>The <see cref="Key"/> object for F10 key.</summary>
  842. public static Key F10 => new (KeyCode.F10);
  843. /// <summary>The <see cref="Key"/> object for F11 key.</summary>
  844. public static Key F11 => new (KeyCode.F11);
  845. /// <summary>The <see cref="Key"/> object for F12 key.</summary>
  846. public static Key F12 => new (KeyCode.F12);
  847. /// <summary>The <see cref="Key"/> object for F13 key.</summary>
  848. public static Key F13 => new (KeyCode.F13);
  849. /// <summary>The <see cref="Key"/> object for F14 key.</summary>
  850. public static Key F14 => new (KeyCode.F14);
  851. /// <summary>The <see cref="Key"/> object for F15 key.</summary>
  852. public static Key F15 => new (KeyCode.F15);
  853. /// <summary>The <see cref="Key"/> object for F16 key.</summary>
  854. public static Key F16 => new (KeyCode.F16);
  855. /// <summary>The <see cref="Key"/> object for F17 key.</summary>
  856. public static Key F17 => new (KeyCode.F17);
  857. /// <summary>The <see cref="Key"/> object for F18 key.</summary>
  858. public static Key F18 => new (KeyCode.F18);
  859. /// <summary>The <see cref="Key"/> object for F19 key.</summary>
  860. public static Key F19 => new (KeyCode.F19);
  861. /// <summary>The <see cref="Key"/> object for F20 key.</summary>
  862. public static Key F20 => new (KeyCode.F20);
  863. /// <summary>The <see cref="Key"/> object for F21 key.</summary>
  864. public static Key F21 => new (KeyCode.F21);
  865. /// <summary>The <see cref="Key"/> object for F22 key.</summary>
  866. public static Key F22 => new (KeyCode.F22);
  867. /// <summary>The <see cref="Key"/> object for F23 key.</summary>
  868. public static Key F23 => new (KeyCode.F23);
  869. /// <summary>The <see cref="Key"/> object for F24 key.</summary>
  870. public static Key F24 => new (KeyCode.F24);
  871. #endregion
  872. private static Rune _separator = new ('+');
  873. /// <summary>Gets or sets the separator character used when parsing and printing Keys. E.g. Ctrl+A. The default is '+'.</summary>
  874. [ConfigurationProperty (Scope = typeof (SettingsScope))]
  875. public static Rune Separator
  876. {
  877. get => _separator;
  878. set
  879. {
  880. if (_separator != value)
  881. {
  882. _separator = value == default (Rune) ? new ('+') : value;
  883. }
  884. }
  885. }
  886. }