OutputBase.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace Terminal.Gui.Drivers;
  7. /// <summary>
  8. /// Abstract base class to assist with implementing <see cref="IConsoleOutput"/>.
  9. /// </summary>
  10. public abstract class OutputBase
  11. {
  12. private CursorVisibility? _cachedCursorVisibility;
  13. // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
  14. private TextStyle _redrawTextStyle = TextStyle.None;
  15. /// <inheritdoc cref="IConsoleOutput.Write(IOutputBuffer)"/>
  16. public virtual void Write (IOutputBuffer buffer)
  17. {
  18. if (ConsoleDriver.RunningUnitTests)
  19. {
  20. return;
  21. }
  22. //if (Console.WindowHeight < 1
  23. // || buffer.Contents.Length != buffer.Rows * buffer.Cols
  24. // || buffer.Rows != Console.WindowHeight)
  25. //{
  26. // // return;
  27. //}
  28. var top = 0;
  29. var left = 0;
  30. int rows = buffer.Rows;
  31. int cols = buffer.Cols;
  32. var output = new StringBuilder ();
  33. Attribute? redrawAttr = null;
  34. int lastCol = -1;
  35. CursorVisibility? savedVisibility = _cachedCursorVisibility;
  36. SetCursorVisibility (CursorVisibility.Invisible);
  37. const int maxCharsPerRune = 2;
  38. Span<char> runeBuffer = stackalloc char [maxCharsPerRune];
  39. for (int row = top; row < rows; row++)
  40. {
  41. //if (Console.WindowHeight < 1)
  42. //{
  43. // return;
  44. //}
  45. if (!SetCursorPositionImpl (0, row))
  46. {
  47. return;
  48. }
  49. output.Clear ();
  50. for (int col = left; col < cols; col++)
  51. {
  52. lastCol = -1;
  53. var outputWidth = 0;
  54. for (; col < cols; col++)
  55. {
  56. if (!buffer.Contents [row, col].IsDirty)
  57. {
  58. if (output.Length > 0)
  59. {
  60. WriteToConsole (output, ref lastCol, row, ref outputWidth);
  61. }
  62. else if (lastCol == -1)
  63. {
  64. lastCol = col;
  65. }
  66. if (lastCol + 1 < cols)
  67. {
  68. lastCol++;
  69. }
  70. continue;
  71. }
  72. if (lastCol == -1)
  73. {
  74. lastCol = col;
  75. }
  76. Attribute attr = buffer.Contents [row, col].Attribute.Value;
  77. // Performance: Only send the escape sequence if the attribute has changed.
  78. if (attr != redrawAttr)
  79. {
  80. redrawAttr = attr;
  81. AppendOrWriteAttribute (output, attr, _redrawTextStyle);
  82. _redrawTextStyle = attr.Style;
  83. }
  84. outputWidth++;
  85. // Avoid Rune.ToString() by appending the rune chars.
  86. Rune rune = buffer.Contents [row, col].Rune;
  87. int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
  88. ReadOnlySpan<char> runeChars = runeBuffer [..runeCharsWritten];
  89. output.Append (runeChars);
  90. if (buffer.Contents [row, col].CombiningMarks.Count > 0)
  91. {
  92. // AtlasEngine does not support NON-NORMALIZED combining marks in a way
  93. // compatible with the driver architecture. Any CMs (except in the first col)
  94. // are correctly combined with the base char, but are ALSO treated as 1 column
  95. // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
  96. //
  97. // For now, we just ignore the list of CMs.
  98. //foreach (var combMark in Contents [row, col].CombiningMarks) {
  99. // output.Append (combMark);
  100. //}
  101. // WriteToConsole (output, ref lastCol, row, ref outputWidth);
  102. }
  103. else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
  104. {
  105. WriteToConsole (output, ref lastCol, row, ref outputWidth);
  106. SetCursorPositionImpl (col - 1, row);
  107. }
  108. buffer.Contents [row, col].IsDirty = false;
  109. }
  110. }
  111. if (output.Length > 0)
  112. {
  113. SetCursorPositionImpl (lastCol, row);
  114. Write (output);
  115. }
  116. }
  117. foreach (SixelToRender s in Application.Sixel)
  118. {
  119. if (!string.IsNullOrWhiteSpace (s.SixelData))
  120. {
  121. SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
  122. Console.Out.Write (s.SixelData);
  123. }
  124. }
  125. SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
  126. _cachedCursorVisibility = savedVisibility;
  127. }
  128. /// <summary>
  129. /// Changes the color and text style of the console to the given <paramref name="attr"/> and <paramref name="redrawTextStyle"/>.
  130. /// If command can be buffered in line with other output (e.g. CSI sequence) then it should be appended to <paramref name="output"/>
  131. /// otherwise the relevant output state should be flushed directly (e.g. by calling relevant win 32 API method)
  132. /// </summary>
  133. /// <param name="output"></param>
  134. /// <param name="attr"></param>
  135. /// <param name="redrawTextStyle"></param>
  136. protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
  137. private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
  138. {
  139. SetCursorPositionImpl (lastCol, row);
  140. Write (output);
  141. output.Clear ();
  142. lastCol += outputWidth;
  143. outputWidth = 0;
  144. }
  145. /// <summary>
  146. /// Output the contents of the <paramref name="output"/> to the console.
  147. /// </summary>
  148. /// <param name="output"></param>
  149. protected abstract void Write (StringBuilder output);
  150. /// <summary>
  151. /// When overriden in derived class, positions the terminal output cursor to the specified point on the screen.
  152. /// </summary>
  153. /// <param name="screenPositionX">Column to move cursor to</param>
  154. /// <param name="screenPositionY">Row to move cursor to</param>
  155. /// <returns></returns>
  156. protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
  157. /// <summary>
  158. /// Changes the visibility of the cursor in the terminal to the specified <paramref name="visibility"/> e.g.
  159. /// the flashing indicator, invisible, box indicator etc.
  160. /// </summary>
  161. /// <param name="visibility"></param>
  162. public abstract void SetCursorVisibility (CursorVisibility visibility);
  163. }