FakeDriver.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. #nullable enable
  2. //
  3. // FakeDriver.cs: A fake IConsoleDriver for unit tests.
  4. //
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. namespace Terminal.Gui.Drivers;
  8. /// <summary>Implements a mock IConsoleDriver for unit testing</summary>
  9. public class FakeDriver : ConsoleDriver
  10. {
  11. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
  12. public class Behaviors
  13. {
  14. public Behaviors (
  15. bool useFakeClipboard = false,
  16. bool fakeClipboardAlwaysThrowsNotSupportedException = false,
  17. bool fakeClipboardIsSupportedAlwaysTrue = false
  18. )
  19. {
  20. UseFakeClipboard = useFakeClipboard;
  21. FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
  22. FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
  23. // double check usage is correct
  24. Debug.Assert (!useFakeClipboard && !fakeClipboardAlwaysThrowsNotSupportedException);
  25. Debug.Assert (!useFakeClipboard && !fakeClipboardIsSupportedAlwaysTrue);
  26. }
  27. public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
  28. public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
  29. public bool UseFakeClipboard { get; internal set; }
  30. }
  31. public static Behaviors FakeBehaviors { get; } = new ();
  32. public override bool SupportsTrueColor => false;
  33. /// <inheritdoc/>
  34. public override void WriteRaw (string ansi) { }
  35. public FakeDriver ()
  36. {
  37. // FakeDriver implies UnitTests
  38. RunningUnitTests = true;
  39. //base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
  40. //base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
  41. if (FakeBehaviors.UseFakeClipboard)
  42. {
  43. Clipboard = new FakeClipboard (
  44. FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException,
  45. FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse
  46. );
  47. }
  48. else
  49. {
  50. if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  51. {
  52. Clipboard = new WindowsClipboard ();
  53. }
  54. else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  55. {
  56. Clipboard = new MacOSXClipboard ();
  57. }
  58. else
  59. {
  60. if (PlatformDetection.IsWSLPlatform ())
  61. {
  62. Clipboard = new WSLClipboard ();
  63. }
  64. else
  65. {
  66. Clipboard = new UnixClipboard ();
  67. }
  68. }
  69. }
  70. }
  71. public override void End ()
  72. {
  73. FakeConsole.ResetColor ();
  74. FakeConsole.Clear ();
  75. }
  76. public override void Init ()
  77. {
  78. FakeConsole.MockKeyPresses.Clear ();
  79. //Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
  80. //Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
  81. FakeConsole.Clear ();
  82. SetScreenSize (80,25);
  83. ResizeScreen ();
  84. ClearContents ();
  85. CurrentAttribute = new (Color.White, Color.Black);
  86. }
  87. public override bool UpdateScreen ()
  88. {
  89. var updated = false;
  90. int savedRow = FakeConsole.CursorTop;
  91. int savedCol = FakeConsole.CursorLeft;
  92. bool savedCursorVisible = FakeConsole.CursorVisible;
  93. var top = 0;
  94. var left = 0;
  95. int rows = Rows;
  96. int cols = Cols;
  97. var output = new StringBuilder ();
  98. var redrawAttr = new Attribute ();
  99. int lastCol = -1;
  100. for (int row = top; row < rows; row++)
  101. {
  102. if (!_dirtyLines! [row])
  103. {
  104. continue;
  105. }
  106. updated = true;
  107. FakeConsole.CursorTop = row;
  108. FakeConsole.CursorLeft = 0;
  109. _dirtyLines [row] = false;
  110. output.Clear ();
  111. for (int col = left; col < cols; col++)
  112. {
  113. lastCol = -1;
  114. var outputWidth = 0;
  115. for (; col < cols; col++)
  116. {
  117. if (!Contents! [row, col].IsDirty)
  118. {
  119. if (output.Length > 0)
  120. {
  121. WriteToConsole (output, ref lastCol, row, ref outputWidth);
  122. }
  123. else if (lastCol == -1)
  124. {
  125. lastCol = col;
  126. }
  127. if (lastCol + 1 < cols)
  128. {
  129. lastCol++;
  130. }
  131. continue;
  132. }
  133. if (lastCol == -1)
  134. {
  135. lastCol = col;
  136. }
  137. Attribute attr = Contents [row, col].Attribute!.Value;
  138. // Performance: Only send the escape sequence if the attribute has changed.
  139. if (attr != redrawAttr)
  140. {
  141. redrawAttr = attr;
  142. FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor16 ();
  143. FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor16 ();
  144. }
  145. outputWidth++;
  146. Rune rune = Contents [row, col].Rune;
  147. output.Append (rune.ToString ());
  148. if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
  149. {
  150. WriteToConsole (output, ref lastCol, row, ref outputWidth);
  151. FakeConsole.CursorLeft--;
  152. }
  153. Contents [row, col].IsDirty = false;
  154. }
  155. }
  156. if (output.Length > 0)
  157. {
  158. FakeConsole.CursorTop = row;
  159. FakeConsole.CursorLeft = lastCol;
  160. foreach (char c in output.ToString ())
  161. {
  162. FakeConsole.Write (c);
  163. }
  164. }
  165. }
  166. FakeConsole.CursorTop = 0;
  167. FakeConsole.CursorLeft = 0;
  168. //SetCursorVisibility (savedVisibility);
  169. void WriteToConsole (StringBuilder outputSb, ref int lastColumn, int row, ref int outputWidth)
  170. {
  171. FakeConsole.CursorTop = row;
  172. FakeConsole.CursorLeft = lastColumn;
  173. foreach (char c in outputSb.ToString ())
  174. {
  175. FakeConsole.Write (c);
  176. }
  177. outputSb.Clear ();
  178. lastColumn += outputWidth;
  179. outputWidth = 0;
  180. }
  181. FakeConsole.CursorTop = savedRow;
  182. FakeConsole.CursorLeft = savedCol;
  183. FakeConsole.CursorVisible = savedCursorVisible;
  184. return updated;
  185. }
  186. private CursorVisibility _savedCursorVisibility;
  187. /// <inheritdoc/>
  188. public override bool GetCursorVisibility (out CursorVisibility visibility)
  189. {
  190. visibility = FakeConsole.CursorVisible
  191. ? CursorVisibility.Default
  192. : CursorVisibility.Invisible;
  193. return FakeConsole.CursorVisible;
  194. }
  195. /// <inheritdoc/>
  196. public override bool SetCursorVisibility (CursorVisibility visibility)
  197. {
  198. _savedCursorVisibility = visibility;
  199. return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
  200. }
  201. private bool EnsureCursorVisibility ()
  202. {
  203. if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
  204. {
  205. GetCursorVisibility (out CursorVisibility cursorVisibility);
  206. _savedCursorVisibility = cursorVisibility;
  207. SetCursorVisibility (CursorVisibility.Invisible);
  208. return false;
  209. }
  210. SetCursorVisibility (_savedCursorVisibility);
  211. return FakeConsole.CursorVisible;
  212. }
  213. private readonly AnsiResponseParser _parser = new ();
  214. /// <inheritdoc/>
  215. internal override IAnsiResponseParser GetParser () { return _parser; }
  216. /// <summary>
  217. /// Sets the screen size for testing purposes. Only available in FakeDriver.
  218. /// This method updates the driver's dimensions and triggers the ScreenChanged event.
  219. /// </summary>
  220. /// <param name="width">The new width in columns.</param>
  221. /// <param name="height">The new height in rows.</param>
  222. public override void SetScreenSize (int width, int height) { SetBufferSize (width, height); }
  223. public void SetBufferSize (int width, int height)
  224. {
  225. FakeConsole.SetBufferSize (width, height);
  226. Cols = width;
  227. Rows = height;
  228. SetConsoleSize (width, height);
  229. ProcessResize ();
  230. }
  231. public void SetConsoleSize (int width, int height)
  232. {
  233. FakeConsole.SetConsoleSize (width, height);
  234. FakeConsole.SetBufferSize (width, height);
  235. if (width != Cols || height != Rows)
  236. {
  237. SetBufferSize (width, height);
  238. Cols = width;
  239. Rows = height;
  240. }
  241. ProcessResize ();
  242. }
  243. public void SetWindowPosition (int left, int top)
  244. {
  245. if (Left > 0 || Top > 0)
  246. {
  247. Left = 0;
  248. Top = 0;
  249. }
  250. FakeConsole.SetWindowPosition (Left, Top);
  251. }
  252. private void ProcessResize ()
  253. {
  254. ResizeScreen ();
  255. ClearContents ();
  256. OnSizeChanged (new (new (Cols, Rows)));
  257. }
  258. public virtual void ResizeScreen ()
  259. {
  260. if (FakeConsole.WindowHeight > 0)
  261. {
  262. // Can raise an exception while it is still resizing.
  263. try
  264. {
  265. FakeConsole.CursorTop = 0;
  266. FakeConsole.CursorLeft = 0;
  267. FakeConsole.WindowTop = 0;
  268. FakeConsole.WindowLeft = 0;
  269. }
  270. catch (IOException)
  271. {
  272. return;
  273. }
  274. catch (ArgumentOutOfRangeException)
  275. {
  276. return;
  277. }
  278. }
  279. // CONCURRENCY: Unsynchronized access to Clip is not safe.
  280. Clip = new (Screen);
  281. }
  282. public override void UpdateCursor ()
  283. {
  284. if (!EnsureCursorVisibility ())
  285. {
  286. return;
  287. }
  288. // Prevents the exception to size changing during resizing.
  289. try
  290. {
  291. // BUGBUG: Why is this using BufferWidth/Height and now Cols/Rows?
  292. if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight)
  293. {
  294. FakeConsole.SetCursorPosition (Col, Row);
  295. }
  296. }
  297. catch (IOException)
  298. { }
  299. catch (ArgumentOutOfRangeException)
  300. { }
  301. }
  302. #region Not Implemented
  303. public override void Suspend ()
  304. {
  305. //throw new NotImplementedException ();
  306. }
  307. #endregion
  308. #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
  309. }