EscSeqUtils.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. using System.Diagnostics;
  2. using System.Globalization;
  3. // ReSharper disable InconsistentNaming
  4. namespace Terminal.Gui.Drivers;
  5. /// <summary>
  6. /// Provides a platform-independent API for managing ANSI escape sequences.
  7. /// </summary>
  8. /// <remarks>
  9. /// Useful resources:
  10. /// * https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
  11. /// * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  12. /// * https://vt100.net/
  13. /// </remarks>
  14. public static class EscSeqUtils
  15. {
  16. /// <summary>
  17. /// Escape key code (ASCII 27/0x1B).
  18. /// </summary>
  19. public const char KeyEsc = (char)KeyCode.Esc;
  20. /// <summary>
  21. /// ESC [ - The CSI (Control Sequence Introducer).
  22. /// </summary>
  23. public const string CSI = "\u001B[";
  24. #region Screen Window Buffer
  25. /// <summary>
  26. /// Options for ANSI ESC "[xJ" - Clears part of the screen.
  27. /// </summary>
  28. public enum ClearScreenOptions
  29. {
  30. /// <summary>
  31. /// If n is 0 (or missing), clear from cursor to end of screen.
  32. /// </summary>
  33. CursorToEndOfScreen = 0,
  34. /// <summary>
  35. /// If n is 1, clear from cursor to beginning of the screen.
  36. /// </summary>
  37. CursorToBeginningOfScreen = 1,
  38. /// <summary>
  39. /// If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
  40. /// </summary>
  41. EntireScreen = 2,
  42. /// <summary>
  43. /// If n is 3, clear entire screen and delete all lines saved in the scrollback buffer
  44. /// </summary>
  45. EntireScreenAndScrollbackBuffer = 3
  46. }
  47. /// <summary>
  48. /// ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll)
  49. /// </summary>
  50. /// <remarks>
  51. /// From
  52. /// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
  53. /// Use Alternate Screen Buffer, xterm.
  54. /// </remarks>
  55. public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h";
  56. /// <summary>
  57. /// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll)
  58. /// </summary>
  59. /// <remarks>
  60. /// From
  61. /// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
  62. /// Use Normal Screen Buffer, xterm. Clear the screen first if in the Alternate Screen Buffer.
  63. /// </remarks>
  64. public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l";
  65. /// <summary>
  66. /// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll)
  67. /// </summary>
  68. /// <remarks>
  69. /// From
  70. /// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
  71. /// Use Normal Screen Buffer and restore cursor as in DECRC, xterm.
  72. /// resource.This combines the effects of the 1047 and 1048 modes.
  73. /// </remarks>
  74. public static readonly string CSI_RestoreCursorAndRestoreAltBufferWithBackscroll = CSI + "?1049l";
  75. /// <summary>
  76. /// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll)
  77. /// </summary>
  78. /// <remarks>
  79. /// From
  80. /// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
  81. /// Save cursor as in DECSC, xterm. After saving the cursor, switch to the Alternate Screen Buffer,
  82. /// clearing it first.
  83. /// This control combines the effects of the 1047 and 1048 modes.
  84. /// Use this with terminfo-based applications rather than the 47 mode.
  85. /// </remarks>
  86. public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h";
  87. /// <summary>
  88. /// ESC [ x J - Clears part of the screen. See <see cref="ClearScreenOptions"/>.
  89. /// </summary>
  90. /// <param name="option"></param>
  91. /// <returns></returns>
  92. public static string CSI_ClearScreen (ClearScreenOptions option) { return $"{CSI}{(int)option}J"; }
  93. /// <summary>
  94. /// ESC [ 8 ; height ; width t - Set Terminal Window Size
  95. /// https://terminalguide.namepad.de/seq/csi_st-8/
  96. /// </summary>
  97. public static string CSI_SetTerminalWindowSize (int height, int width) { return $"{CSI}8;{height};{width}t"; }
  98. #endregion Screen Window Buffer
  99. #region Mouse
  100. /// <summary>
  101. /// ESC [ ? 1003 l - Disable any mouse event tracking.
  102. /// </summary>
  103. public static readonly string CSI_DisableAnyEventMouse = CSI + "?1003l";
  104. /// <summary>
  105. /// ESC [ ? 1006 l - Disable SGR (Select Graphic Rendition).
  106. /// </summary>
  107. public static readonly string CSI_DisableSgrExtModeMouse = CSI + "?1006l";
  108. /// <summary>
  109. /// ESC [ ? 1015 l - Disable URXVT (Unicode Extended Virtual Terminal).
  110. /// </summary>
  111. public static readonly string CSI_DisableUrxvtExtModeMouse = CSI + "?1015l";
  112. /// <summary>
  113. /// ESC [ ? 1003 h - Enable mouse event tracking.
  114. /// </summary>
  115. public static readonly string CSI_EnableAnyEventMouse = CSI + "?1003h";
  116. /// <summary>
  117. /// ESC [ ? 1006 h - Enable SGR (Select Graphic Rendition).
  118. /// </summary>
  119. public static readonly string CSI_EnableSgrExtModeMouse = CSI + "?1006h";
  120. /// <summary>
  121. /// ESC [ ? 1015 h - Enable URXVT (Unicode Extended Virtual Terminal).
  122. /// </summary>
  123. public static readonly string CSI_EnableUrxvtExtModeMouse = CSI + "?1015h";
  124. /// <summary>
  125. /// Control sequence for disabling mouse events.
  126. /// </summary>
  127. public static readonly string CSI_DisableMouseEvents =
  128. CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
  129. /// <summary>
  130. /// Control sequence for enabling mouse events.
  131. /// </summary>
  132. public static readonly string CSI_EnableMouseEvents =
  133. CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
  134. #endregion Mouse
  135. #region Keyboard
  136. /// <summary>
  137. /// Helper to set the Control key states based on the char.
  138. /// </summary>
  139. /// <param name="ch">The char value.</param>
  140. /// <returns></returns>
  141. public static ConsoleKeyInfo MapChar (char ch) { return MapConsoleKeyInfo (new (ch, ConsoleKey.None, false, false, false)); }
  142. /// <summary>
  143. /// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
  144. /// </summary>
  145. /// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
  146. /// <returns>The <see cref="ConsoleKeyInfo"/> modified.</returns>
  147. public static ConsoleKeyInfo MapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
  148. {
  149. ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
  150. var key = ConsoleKey.None;
  151. char keyChar = consoleKeyInfo.KeyChar;
  152. switch ((uint)keyChar)
  153. {
  154. case 0:
  155. if (consoleKeyInfo.Key == (ConsoleKey)64)
  156. { // Ctrl+Space in Windows.
  157. newConsoleKeyInfo = new (
  158. consoleKeyInfo.KeyChar,
  159. ConsoleKey.Spacebar,
  160. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  161. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  162. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  163. }
  164. else if (consoleKeyInfo.Key == ConsoleKey.None)
  165. {
  166. newConsoleKeyInfo = new (
  167. consoleKeyInfo.KeyChar,
  168. ConsoleKey.Spacebar,
  169. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  170. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  171. true);
  172. }
  173. break;
  174. case uint n when n is > 0 and <= KeyEsc:
  175. if (consoleKeyInfo is { Key: 0, KeyChar: '\u001B' })
  176. {
  177. key = ConsoleKey.Escape;
  178. newConsoleKeyInfo = new (
  179. consoleKeyInfo.KeyChar,
  180. key,
  181. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  182. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  183. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  184. }
  185. else if (consoleKeyInfo is { Key: 0, KeyChar: '\t' })
  186. {
  187. key = ConsoleKey.Tab;
  188. newConsoleKeyInfo = new (
  189. consoleKeyInfo.KeyChar,
  190. key,
  191. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  192. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  193. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  194. }
  195. else if (consoleKeyInfo is { Key: 0, KeyChar: '\r' })
  196. {
  197. key = ConsoleKey.Enter;
  198. newConsoleKeyInfo = new (
  199. consoleKeyInfo.KeyChar,
  200. key,
  201. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  202. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  203. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  204. }
  205. else if (consoleKeyInfo is { Key: 0, KeyChar: '\n' })
  206. {
  207. key = ConsoleKey.Enter;
  208. newConsoleKeyInfo = new (
  209. consoleKeyInfo.KeyChar,
  210. key,
  211. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  212. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  213. true);
  214. }
  215. else if (consoleKeyInfo is { Key: 0, KeyChar: '\b' })
  216. {
  217. key = ConsoleKey.Backspace;
  218. newConsoleKeyInfo = new (
  219. consoleKeyInfo.KeyChar,
  220. key,
  221. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  222. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  223. true);
  224. }
  225. else if (consoleKeyInfo.Key == 0)
  226. {
  227. key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
  228. newConsoleKeyInfo = new (
  229. consoleKeyInfo.KeyChar,
  230. key,
  231. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  232. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  233. true);
  234. }
  235. break;
  236. case uint n when n is >= '\u001c' and <= '\u001f':
  237. key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + 24);
  238. newConsoleKeyInfo = new (
  239. (char)key,
  240. key,
  241. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  242. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  243. true);
  244. break;
  245. case 127: // DEL
  246. key = ConsoleKey.Backspace;
  247. newConsoleKeyInfo = new (
  248. consoleKeyInfo.KeyChar,
  249. key,
  250. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  251. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  252. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  253. break;
  254. default:
  255. //uint ck = ConsoleKeyMapping.MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey);
  256. //if (isConsoleKey)
  257. {
  258. key = consoleKeyInfo.Key;// (ConsoleKey)ck;
  259. }
  260. newConsoleKeyInfo = new (
  261. keyChar,
  262. key,
  263. GetShiftMod (consoleKeyInfo.Modifiers),
  264. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  265. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  266. break;
  267. }
  268. return newConsoleKeyInfo;
  269. bool GetShiftMod (ConsoleModifiers modifiers)
  270. {
  271. if (consoleKeyInfo.KeyChar is >= (char)ConsoleKey.A and <= (char)ConsoleKey.Z && modifiers == ConsoleModifiers.None)
  272. {
  273. return true;
  274. }
  275. return (modifiers & ConsoleModifiers.Shift) != 0;
  276. }
  277. }
  278. internal static KeyCode MapKey (ConsoleKeyInfo keyInfo)
  279. {
  280. switch (keyInfo.Key)
  281. {
  282. case ConsoleKey.Multiply:
  283. case ConsoleKey.Add:
  284. case ConsoleKey.Separator:
  285. case ConsoleKey.Subtract:
  286. case ConsoleKey.Decimal:
  287. case ConsoleKey.Divide:
  288. case ConsoleKey.OemPeriod:
  289. case ConsoleKey.OemComma:
  290. case ConsoleKey.OemPlus:
  291. case ConsoleKey.OemMinus:
  292. case ConsoleKey.Packet:
  293. case ConsoleKey.Oem1:
  294. case ConsoleKey.Oem2:
  295. case ConsoleKey.Oem3:
  296. case ConsoleKey.Oem4:
  297. case ConsoleKey.Oem5:
  298. case ConsoleKey.Oem6:
  299. case ConsoleKey.Oem7:
  300. case ConsoleKey.Oem8:
  301. case ConsoleKey.Oem102:
  302. if (keyInfo.KeyChar == 0)
  303. {
  304. // All Oem* produce a valid KeyChar and is not guaranteed to be printable ASCII, but it’s never just '\0' (null).
  305. // If that happens it's because Console.ReadKey is misreporting for AltGr + non-character keys
  306. // or if it's a combine key waiting for the next input which will determine the respective KeyChar.
  307. // This behavior only happens on Windows and not on Unix-like systems.
  308. if (keyInfo.Key != ConsoleKey.Multiply
  309. && keyInfo.Key != ConsoleKey.Add
  310. && keyInfo.Key != ConsoleKey.Decimal
  311. && keyInfo.Key != ConsoleKey.Subtract
  312. && keyInfo.Key != ConsoleKey.Divide
  313. && keyInfo.Key != ConsoleKey.OemPeriod
  314. && keyInfo.Key != ConsoleKey.OemComma
  315. && keyInfo.Key != ConsoleKey.OemPlus
  316. && keyInfo.Key != ConsoleKey.OemMinus
  317. && keyInfo.Key != ConsoleKey.Oem1
  318. && keyInfo.Key != ConsoleKey.Oem2
  319. && keyInfo.Key != ConsoleKey.Oem3
  320. && keyInfo.Key != ConsoleKey.Oem4
  321. && keyInfo.Key != ConsoleKey.Oem5
  322. && keyInfo.Key != ConsoleKey.Oem6
  323. && keyInfo.Key != ConsoleKey.Oem7
  324. && keyInfo.Key != ConsoleKey.Oem102)
  325. {
  326. // If the keyChar is 0, keyInfo.Key value is not a printable character.
  327. Debug.Assert (keyInfo.Key == 0);
  328. }
  329. return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
  330. }
  331. if (keyInfo.Modifiers != ConsoleModifiers.Shift)
  332. {
  333. // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
  334. return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
  335. }
  336. // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
  337. // and passing on Shift would be redundant.
  338. return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
  339. }
  340. // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
  341. if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
  342. {
  343. if (keyInfo is { Modifiers: ConsoleModifiers.Control, Key: ConsoleKey.I })
  344. {
  345. return KeyCode.Tab;
  346. }
  347. return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
  348. }
  349. // Handle control keys (e.g. CursorUp)
  350. if (keyInfo.Key != ConsoleKey.None
  351. && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))
  352. {
  353. return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
  354. }
  355. if ((ConsoleKey)keyInfo.KeyChar is >= ConsoleKey.A and <= ConsoleKey.Z)
  356. {
  357. // Shifted
  358. keyInfo = new (
  359. keyInfo.KeyChar,
  360. (ConsoleKey)keyInfo.KeyChar,
  361. true,
  362. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
  363. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
  364. }
  365. if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z)
  366. {
  367. // Unshifted
  368. keyInfo = new (
  369. keyInfo.KeyChar,
  370. (ConsoleKey)(keyInfo.KeyChar - 32),
  371. false,
  372. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
  373. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
  374. }
  375. if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z)
  376. {
  377. if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
  378. || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
  379. {
  380. // DotNetDriver doesn't support Shift-Ctrl/Shift-Alt combos
  381. return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
  382. }
  383. if (keyInfo.Modifiers == ConsoleModifiers.Shift)
  384. {
  385. // If ShiftMask is on add the ShiftMask
  386. if (char.IsUpper (keyInfo.KeyChar))
  387. {
  388. return (KeyCode)keyInfo.Key | KeyCode.ShiftMask;
  389. }
  390. }
  391. return (KeyCode)keyInfo.Key;
  392. }
  393. return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
  394. }
  395. #endregion Keyboard
  396. #region Cursor
  397. //ESC [ M - RI Reverse Index – Performs the reverse operation of \n, moves cursor up one line, maintains horizontal position, scrolls buffer if necessary*
  398. /// <summary>
  399. /// ESC [ 7 - Save Cursor Position in Memory**
  400. /// </summary>
  401. public static readonly string CSI_SaveCursorPosition = CSI + "7";
  402. /// <summary>
  403. /// ESC [ 8 - DECSR Restore Cursor Position from Memory**
  404. /// </summary>
  405. public static readonly string CSI_RestoreCursorPosition = CSI + "8";
  406. //ESC [ < n > A - CUU - Cursor Up Cursor up by < n >
  407. //ESC [ < n > B - CUD - Cursor Down Cursor down by < n >
  408. //ESC [ < n > C - CUF - Cursor Forward Cursor forward (Right) by < n >
  409. //ESC [ < n > D - CUB - Cursor Backward Cursor backward (Left) by < n >
  410. //ESC [ < n > E - CNL - Cursor Next Line - Cursor down < n > lines from current position
  411. //ESC [ < n > F - CPL - Cursor Previous Line Cursor up < n > lines from current position
  412. //ESC [ < n > G - CHA - Cursor Horizontal Absolute Cursor moves to < n > th position horizontally in the current line
  413. //ESC [ < n > d - VPA - Vertical Line Position Absolute Cursor moves to the < n > th position vertically in the current column
  414. /// <summary>
  415. /// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column
  416. /// of the y line
  417. /// </summary>
  418. /// <param name="row">Origin is (1,1).</param>
  419. /// <param name="col">Origin is (1,1).</param>
  420. /// <returns></returns>
  421. public static string CSI_SetCursorPosition (int row, int col) { return $"{CSI}{row};{col}H"; }
  422. /// <summary>
  423. /// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column
  424. /// of the y line
  425. /// </summary>
  426. /// <param name="builder">StringBuilder where to append the cursor position sequence.</param>
  427. /// <param name="row">Origin is (1,1).</param>
  428. /// <param name="col">Origin is (1,1).</param>
  429. public static void CSI_AppendCursorPosition (StringBuilder builder, int row, int col)
  430. {
  431. // InterpolatedStringHandler is composed in stack, skipping the string allocation.
  432. builder.Append ($"{CSI}{row};{col}H");
  433. }
  434. /// <summary>
  435. /// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column
  436. /// of the y line
  437. /// </summary>
  438. /// <param name="writer">TextWriter where to write the cursor position sequence.</param>
  439. /// <param name="row">Origin is (1,1).</param>
  440. /// <param name="col">Origin is (1,1).</param>
  441. public static void CSI_WriteCursorPosition (TextWriter writer, int row, int col)
  442. {
  443. const int maxInputBufferSize =
  444. // CSI (2) + ';' + 'H'
  445. 4
  446. +
  447. // row + col (2x int sign + int max value)
  448. 2
  449. + 20;
  450. Span<char> buffer = stackalloc char [maxInputBufferSize];
  451. if (!buffer.TryWrite (CultureInfo.InvariantCulture, $"{CSI}{row};{col}H", out int charsWritten))
  452. {
  453. var tooLongCursorPositionSequence = $"{CSI}{row};{col}H";
  454. throw new InvalidOperationException (
  455. $"{nameof (CSI_WriteCursorPosition)} buffer (len: {buffer.Length}) is too short for cursor position sequence '{tooLongCursorPositionSequence}' (len: {tooLongCursorPositionSequence.Length}).");
  456. }
  457. ReadOnlySpan<char> cursorPositionSequence = buffer [..charsWritten];
  458. writer.Write (cursorPositionSequence);
  459. }
  460. //ESC [ <y> ; <x> f - HVP Horizontal Vertical Position* Cursor moves to<x>; <y> coordinate within the viewport, where <x> is the column of the<y> line
  461. //ESC [ s - ANSISYSSC Save Cursor – Ansi.sys emulation **With no parameters, performs a save cursor operation like DECSC
  462. //ESC [ u - ANSISYSRC Restore Cursor – Ansi.sys emulation **With no parameters, performs a restore cursor operation like DECRC
  463. //ESC [ ? 12 h - ATT160 Text Cursor Enable Blinking Start the cursor blinking
  464. //ESC [ ? 12 l - ATT160 Text Cursor Disable Blinking Stop blinking the cursor
  465. /// <summary>
  466. /// ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show Show the cursor
  467. /// </summary>
  468. public static readonly string CSI_ShowCursor = CSI + "?25h";
  469. /// <summary>
  470. /// ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide Hide the cursor
  471. /// </summary>
  472. public static readonly string CSI_HideCursor = CSI + "?25l";
  473. //ESC [ ? 12 h - ATT160 Text Cursor Enable Blinking Start the cursor blinking
  474. //ESC [ ? 12 l - ATT160 Text Cursor Disable Blinking Stop blinking the cursor
  475. //ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show Show the cursor
  476. //ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide Hide the cursor
  477. /// <summary>
  478. /// Styles for ANSI ESC "[x q" - Set Cursor Style
  479. /// </summary>
  480. public enum DECSCUSR_Style
  481. {
  482. /// <summary>
  483. /// DECSCUSR - User Shape - Default cursor shape configured by the user
  484. /// </summary>
  485. UserShape = 0,
  486. /// <summary>
  487. /// DECSCUSR - Blinking Block - Blinking block cursor shape
  488. /// </summary>
  489. BlinkingBlock = 1,
  490. /// <summary>
  491. /// DECSCUSR - Steady Block - Steady block cursor shape
  492. /// </summary>
  493. SteadyBlock = 2,
  494. /// <summary>
  495. /// DECSCUSR - Blinking Underline - Blinking underline cursor shape
  496. /// </summary>
  497. BlinkingUnderline = 3,
  498. /// <summary>
  499. /// DECSCUSR - Steady Underline - Steady underline cursor shape
  500. /// </summary>
  501. SteadyUnderline = 4,
  502. /// <summary>
  503. /// DECSCUSR - Blinking Bar - Blinking bar cursor shape
  504. /// </summary>
  505. BlinkingBar = 5,
  506. /// <summary>
  507. /// DECSCUSR - Steady Bar - Steady bar cursor shape
  508. /// </summary>
  509. SteadyBar = 6
  510. }
  511. /// <summary>
  512. /// ESC [ n SP q - Select Cursor Style (DECSCUSR)
  513. /// https://terminalguide.namepad.de/seq/csi_sq_t_space/
  514. /// </summary>
  515. /// <param name="style"></param>
  516. /// <returns></returns>
  517. public static string CSI_SetCursorStyle (DECSCUSR_Style style) { return $"{CSI}{(int)style} q"; }
  518. #endregion Cursor
  519. #region Colors
  520. /// <summary>
  521. /// ESC [ (n) m - SGR - Set Graphics Rendition - Set the format of the screen and text as specified by (n)
  522. /// This command is special in that the (n) position can accept between 0 and 16 parameters separated by semicolons.
  523. /// When no parameters are specified, it is treated the same as a single 0 parameter.
  524. /// https://terminalguide.namepad.de/seq/csi_sm/
  525. /// </summary>
  526. public static string CSI_SetGraphicsRendition (params int [] parameters) { return $"{CSI}{string.Join (";", parameters)}m"; }
  527. /// <summary>
  528. /// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])"/> to set the foreground color.
  529. /// </summary>
  530. /// <param name="code">One of the 16 color codes.</param>
  531. /// <returns></returns>
  532. public static string CSI_SetForegroundColor (AnsiColorCode code) { return CSI_SetGraphicsRendition ((int)code); }
  533. /// <summary>
  534. /// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])"/> to set the background color.
  535. /// </summary>
  536. /// <param name="code">One of the 16 color codes.</param>
  537. /// <returns></returns>
  538. public static string CSI_SetBackgroundColor (AnsiColorCode code) { return CSI_SetGraphicsRendition ((int)code + 10); }
  539. /// <summary>
  540. /// ESC[38;5;{id}m - Set foreground color (256 colors)
  541. /// </summary>
  542. public static string CSI_SetForegroundColor256 (int color) { return $"{CSI}38;5;{color}m"; }
  543. /// <summary>
  544. /// ESC[48;5;{id}m - Set background color (256 colors)
  545. /// </summary>
  546. public static string CSI_SetBackgroundColor256 (int color) { return $"{CSI}48;5;{color}m"; }
  547. /// <summary>
  548. /// ESC[38;2;{r};{g};{b}m Set foreground color as RGB.
  549. /// </summary>
  550. public static string CSI_SetForegroundColorRGB (int r, int g, int b) { return $"{CSI}38;2;{r};{g};{b}m"; }
  551. /// <summary>
  552. /// ESC[38;2;{r};{g};{b}m Append foreground color as RGB to StringBuilder.
  553. /// </summary>
  554. public static void CSI_AppendForegroundColorRGB (StringBuilder builder, int r, int g, int b)
  555. {
  556. // InterpolatedStringHandler is composed in stack, skipping the string allocation.
  557. builder.Append ($"{CSI}38;2;{r};{g};{b}m");
  558. }
  559. /// <summary>
  560. /// ESC[48;2;{r};{g};{b}m Set background color as RGB.
  561. /// </summary>
  562. public static string CSI_SetBackgroundColorRGB (int r, int g, int b) { return $"{CSI}48;2;{r};{g};{b}m"; }
  563. /// <summary>
  564. /// ESC[48;2;{r};{g};{b}m Append background color as RGB to StringBuilder.
  565. /// </summary>
  566. public static void CSI_AppendBackgroundColorRGB (StringBuilder builder, int r, int g, int b)
  567. {
  568. // InterpolatedStringHandler is composed in stack, skipping the string allocation.
  569. builder.Append ($"{CSI}48;2;{r};{g};{b}m");
  570. }
  571. #endregion Colors
  572. #region Text Styles
  573. /// <summary>
  574. /// Appends an ANSI SGR (Select Graphic Rendition) escape sequence to switch printed text from one
  575. /// <see cref="TextStyle"/> to another.
  576. /// </summary>
  577. /// <param name="output"><see cref="StringBuilder"/> to add escape sequence to.</param>
  578. /// <param name="prev">Previous <see cref="TextStyle"/> to change away from.</param>
  579. /// <param name="next">Next <see cref="TextStyle"/> to change to.</param>
  580. /// <remarks>
  581. /// <para>
  582. /// Unlike colors, most text styling options are not mutually exclusive with each other, and can be applied
  583. /// independently. This creates a problem when
  584. /// switching from one style to another: For instance, if your previous style is just bold, and your next style is
  585. /// just italic, then simply adding the
  586. /// sequence to enable italic text would cause the text to remain bold. This method automatically handles this
  587. /// problem, enabling and disabling styles as
  588. /// necessary to apply exactly the next style.
  589. /// </para>
  590. /// </remarks>
  591. internal static void CSI_AppendTextStyleChange (StringBuilder output, TextStyle prev, TextStyle next)
  592. {
  593. // Do nothing if styles are the same, as no changes are necessary.
  594. if (prev == next)
  595. {
  596. return;
  597. }
  598. // Bitwise operations to determine flag changes. A ^ B are the flags different between two flag sets. These different flags that exist in the next flag
  599. // set (diff & next) are the ones that were enabled in the switch, those that exist in the previous flag set (diff & prev) are the ones that were
  600. // disabled.
  601. TextStyle diff = prev ^ next;
  602. TextStyle enabled = diff & next;
  603. TextStyle disabled = diff & prev;
  604. // List of escape codes to apply.
  605. List<int> sgr = new ();
  606. if (disabled != TextStyle.None)
  607. {
  608. // Special case: Both bold and faint have the same disabling code. While unusual, it can be valid to have both enabled at the same time, so when
  609. // one and only one of them is being disabled, we need to re-enable the other afterward. We can check what flags remain enabled by taking
  610. // prev & next, as this is the set of flags both have.
  611. if (disabled.HasFlag (TextStyle.Bold))
  612. {
  613. sgr.Add (22);
  614. if ((prev & next).HasFlag (TextStyle.Faint))
  615. {
  616. sgr.Add (2);
  617. }
  618. }
  619. if (disabled.HasFlag (TextStyle.Faint))
  620. {
  621. sgr.Add (22);
  622. if ((prev & next).HasFlag (TextStyle.Bold))
  623. {
  624. sgr.Add (1);
  625. }
  626. }
  627. if (disabled.HasFlag (TextStyle.Italic))
  628. {
  629. sgr.Add (23);
  630. }
  631. if (disabled.HasFlag (TextStyle.Underline))
  632. {
  633. sgr.Add (24);
  634. }
  635. if (disabled.HasFlag (TextStyle.Blink))
  636. {
  637. sgr.Add (25);
  638. }
  639. if (disabled.HasFlag (TextStyle.Reverse))
  640. {
  641. sgr.Add (27);
  642. }
  643. if (disabled.HasFlag (TextStyle.Strikethrough))
  644. {
  645. sgr.Add (29);
  646. }
  647. }
  648. if (enabled != TextStyle.None)
  649. {
  650. if (enabled.HasFlag (TextStyle.Bold))
  651. {
  652. sgr.Add (1);
  653. }
  654. if (enabled.HasFlag (TextStyle.Faint))
  655. {
  656. sgr.Add (2);
  657. }
  658. if (enabled.HasFlag (TextStyle.Italic))
  659. {
  660. sgr.Add (3);
  661. }
  662. if (enabled.HasFlag (TextStyle.Underline))
  663. {
  664. sgr.Add (4);
  665. }
  666. if (enabled.HasFlag (TextStyle.Blink))
  667. {
  668. sgr.Add (5);
  669. }
  670. if (enabled.HasFlag (TextStyle.Reverse))
  671. {
  672. sgr.Add (7);
  673. }
  674. if (enabled.HasFlag (TextStyle.Strikethrough))
  675. {
  676. sgr.Add (9);
  677. }
  678. }
  679. output.Append ("\x1b[");
  680. output.Append (string.Join (';', sgr));
  681. output.Append ('m');
  682. }
  683. #endregion Text Styles
  684. #region Requests
  685. /// <summary>
  686. /// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR)
  687. /// https://terminalguide.namepad.de/seq/csi_sn__p-6/
  688. /// The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) ; 1 R
  689. /// </summary>
  690. public static readonly AnsiEscapeSequence CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" };
  691. /// <summary>
  692. /// The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) R
  693. /// </summary>
  694. public const string CSI_RequestCursorPositionReport_Terminator = "R";
  695. /// <summary>
  696. /// ESC [ 0 c - Send Device Attributes (Primary DA)
  697. /// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Application-Program-Command-functions
  698. /// https://www.xfree86.org/current/ctlseqs.html
  699. /// Windows Terminal v1.17 and below emits “\x1b[?1;0c”, indicating "VT101 with No Options".
  700. /// Windows Terminal v1.18+ emits: \x1b[?61;6;7;22;23;24;28;32;42c"
  701. /// See https://github.com/microsoft/terminal/pull/14906
  702. /// 61 - The device conforms to level 1 of the character cell display architecture
  703. /// (See https://github.com/microsoft/terminal/issues/15693#issuecomment-1633304497)
  704. /// 6 = Selective erase
  705. /// 7 = Soft fonts
  706. /// 22 = Color text
  707. /// 23 = Greek character sets
  708. /// 24 = Turkish character sets
  709. /// 28 = Rectangular area operations
  710. /// 32 = Text macros
  711. /// 42 = ISO Latin-2 character set
  712. /// The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
  713. /// <see cref="CSI_SendDeviceAttributes2"/>
  714. /// </summary>
  715. public static readonly AnsiEscapeSequence CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" };
  716. /// <summary>
  717. /// ESC [ > 0 c - Send Device Attributes (Secondary DA)
  718. /// Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220)
  719. /// </summary>
  720. public static readonly AnsiEscapeSequence CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" };
  721. /// <summary>
  722. /// CSI 16 t - Request sixel resolution (width and height in pixels)
  723. /// </summary>
  724. public static readonly AnsiEscapeSequence CSI_RequestSixelResolution = new () { Request = CSI + "16t", Terminator = "t" };
  725. /// <summary>
  726. /// CSI 14 t - Request window size in pixels (width x height)
  727. /// </summary>
  728. public static readonly AnsiEscapeSequence CSI_RequestWindowSizeInPixels = new () { Request = CSI + "14t", Terminator = "t" };
  729. /// <summary>
  730. /// CSI 1 8 t | yes | yes | yes | report window size in chars
  731. /// https://terminalguide.namepad.de/seq/csi_st-18/
  732. /// The terminator indicating a reply to <see cref="CSI_ReportWindowSizeInChars"/> : ESC [ 8 ; height ; width t
  733. /// </summary>
  734. public static readonly AnsiEscapeSequence CSI_ReportWindowSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" };
  735. /// <summary>
  736. /// The terminator indicating a reply to <see cref="CSI_ReportWindowSizeInChars"/> : ESC [ 8 ; height ; width t
  737. /// </summary>
  738. public const string CSI_ReportWindowSizeInChars_Terminator = "t";
  739. /// <summary>
  740. /// The value of the response to <see cref="CSI_ReportWindowSizeInChars"/> indicating value 1 and 2 are the terminal
  741. /// size in chars.
  742. /// </summary>
  743. public const string CSI_ReportWindowSizeInChars_ResponseValue = "8";
  744. #endregion Requests
  745. #region OSC
  746. /// <summary>
  747. /// OSC (Operating System Command) escape sequence prefix.
  748. /// </summary>
  749. /// <remarks>
  750. /// OSC sequences are used for operating system-specific commands like setting window title,
  751. /// hyperlinks (OSC 8), and other terminal emulator features.
  752. /// </remarks>
  753. public const string OSC = "\u001B]";
  754. /// <summary>
  755. /// String Terminator (ST) - terminates OSC sequences.
  756. /// </summary>
  757. /// <remarks>
  758. /// Can also be represented as BEL (0x07) in some terminals, but ST is more modern.
  759. /// </remarks>
  760. public const string ST = "\u001B\\";
  761. /// <summary>
  762. /// Starts a hyperlink using OSC 8 escape sequence.
  763. /// </summary>
  764. /// <param name="url">The URL to link to (e.g., "https://github.com").</param>
  765. /// <param name="id">Optional hyperlink ID for matching start/end pairs. Use null for automatic matching.</param>
  766. /// <returns>The OSC 8 start sequence.</returns>
  767. /// <remarks>
  768. /// OSC 8 format: ESC ] 8 ; params ; URL ST
  769. /// Supported in Windows Terminal, iTerm2, and other modern terminals.
  770. /// Must be followed by visible text, then terminated with <see cref="OSC_EndHyperlink"/>.
  771. /// </remarks>
  772. public static string OSC_StartHyperlink (string url, string? id = null)
  773. {
  774. // Format: ESC ] 8 ; params ; URL ST
  775. // params can include "id=value" for matching start/end
  776. string parameters = string.IsNullOrEmpty (id) ? "" : $"id={id}";
  777. return $"{OSC}8;{parameters};{url}{ST}";
  778. }
  779. /// <summary>
  780. /// Ends a hyperlink using OSC 8 escape sequence.
  781. /// </summary>
  782. /// <returns>The OSC 8 end sequence.</returns>
  783. /// <remarks>
  784. /// This terminates the hyperlink started by <see cref="OSC_StartHyperlink"/>.
  785. /// Format: ESC ] 8 ; ; ST (empty URL ends the hyperlink).
  786. /// </remarks>
  787. public static string OSC_EndHyperlink ()
  788. {
  789. // Format: ESC ] 8 ; ; ST (empty URL ends hyperlink)
  790. return $"{OSC}8;;{ST}";
  791. }
  792. #endregion OSC
  793. /// <summary>
  794. /// Convert a <see cref="ConsoleKeyInfo"/> array to string.
  795. /// </summary>
  796. /// <param name="consoleKeyInfos"></param>
  797. /// <returns>The string representing the array.</returns>
  798. public static string ToString (ConsoleKeyInfo [] consoleKeyInfos)
  799. {
  800. StringBuilder sb = new ();
  801. foreach (ConsoleKeyInfo keyChar in consoleKeyInfos)
  802. {
  803. sb.Append (keyChar.KeyChar);
  804. }
  805. return sb.ToString ();
  806. }
  807. }