Skip to content

Commit 33d4f3d

Browse files
committed
Merged.
More POC
2 parents 0430fdc + f84c528 commit 33d4f3d

File tree

20 files changed

+481
-154
lines changed

20 files changed

+481
-154
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using BenchmarkDotNet.Attributes;
2+
using Tui = Terminal.Gui;
3+
4+
namespace Terminal.Gui.Benchmarks.ConsoleDrivers.EscSeqUtils;
5+
6+
[MemoryDiagnoser]
7+
// Hide useless column from results.
8+
[HideColumns ("writer")]
9+
public class CSI_SetVsWrite
10+
{
11+
[Benchmark (Baseline = true)]
12+
[ArgumentsSource (nameof (TextWriterSource))]
13+
public TextWriter Set (TextWriter writer)
14+
{
15+
writer.Write (Tui.EscSeqUtils.CSI_SetCursorPosition (1, 1));
16+
return writer;
17+
}
18+
19+
[Benchmark]
20+
[ArgumentsSource (nameof (TextWriterSource))]
21+
public TextWriter Write (TextWriter writer)
22+
{
23+
Tui.EscSeqUtils.CSI_WriteCursorPosition (writer, 1, 1);
24+
return writer;
25+
}
26+
27+
public static IEnumerable<object> TextWriterSource ()
28+
{
29+
return [StringWriter.Null];
30+
}
31+
}

Terminal.Gui/Application/Application.Run.cs

+1
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ internal static void LayoutAndDrawImpl (bool forceDraw = false)
431431
if (PopoverHost is { Visible: true })
432432
{
433433
//PopoverHost.SetNeedsDraw();
434+
//PopoverHost.SetNeedsLayout ();
434435
tops.Insert (0, PopoverHost);
435436
}
436437

Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public void AddRune (Rune rune)
217217
if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
218218
{
219219
// Just add this mark to the list
220-
Contents [Row, Col - 1].CombiningMarks.Add (rune);
220+
Contents [Row, Col - 1].AddCombiningMark (rune);
221221

222222
// Ignore. Don't move to next column (let the driver figure out what to do).
223223
}
@@ -240,7 +240,7 @@ public void AddRune (Rune rune)
240240
else
241241
{
242242
// It didn't normalize. Add it to the Cell to left's CM list
243-
Contents [Row, Col - 1].CombiningMarks.Add (rune);
243+
Contents [Row, Col - 1].AddCombiningMark (rune);
244244

245245
// Ignore. Don't move to next column (let the driver figure out what to do).
246246
}
@@ -298,7 +298,7 @@ public void AddRune (Rune rune)
298298
else if (!Clip.Contains (Col, Row))
299299
{
300300
// Our 1st column is outside the clip, so we can't display a wide character.
301-
Contents [Row, Col+1].Rune = Rune.ReplacementChar;
301+
Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
302302
}
303303
else
304304
{

Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#nullable enable
2-
using Terminal.Gui.ConsoleDrivers;
2+
using System.Globalization;
33
using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
44

55
namespace Terminal.Gui;
@@ -154,13 +154,13 @@ public enum ClearScreenOptions
154154
/// <summary>
155155
/// Control sequence for disabling mouse events.
156156
/// </summary>
157-
public static string CSI_DisableMouseEvents { get; set; } =
157+
public static readonly string CSI_DisableMouseEvents =
158158
CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
159159

160160
/// <summary>
161161
/// Control sequence for enabling mouse events.
162162
/// </summary>
163-
public static string CSI_EnableMouseEvents { get; set; } =
163+
public static readonly string CSI_EnableMouseEvents =
164164
CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
165165

166166
/// <summary>
@@ -1688,6 +1688,32 @@ public static void CSI_AppendCursorPosition (StringBuilder builder, int row, int
16881688
builder.Append ($"{CSI}{row};{col}H");
16891689
}
16901690

1691+
/// <summary>
1692+
/// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column
1693+
/// of the y line
1694+
/// </summary>
1695+
/// <param name="writer">TextWriter where to write the cursor position sequence.</param>
1696+
/// <param name="row">Origin is (1,1).</param>
1697+
/// <param name="col">Origin is (1,1).</param>
1698+
public static void CSI_WriteCursorPosition (TextWriter writer, int row, int col)
1699+
{
1700+
const int maxInputBufferSize =
1701+
// CSI (2) + ';' + 'H'
1702+
4 +
1703+
// row + col (2x int sign + int max value)
1704+
2 + 20;
1705+
Span<char> buffer = stackalloc char[maxInputBufferSize];
1706+
if (!buffer.TryWrite (CultureInfo.InvariantCulture, $"{CSI}{row};{col}H", out int charsWritten))
1707+
{
1708+
string tooLongCursorPositionSequence = $"{CSI}{row};{col}H";
1709+
throw new InvalidOperationException (
1710+
$"{nameof(CSI_WriteCursorPosition)} buffer (len: {buffer.Length}) is too short for cursor position sequence '{tooLongCursorPositionSequence}' (len: {tooLongCursorPositionSequence.Length}).");
1711+
}
1712+
1713+
ReadOnlySpan<char> cursorPositionSequence = buffer[..charsWritten];
1714+
writer.Write (cursorPositionSequence);
1715+
}
1716+
16911717
//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
16921718
//ESC [ s - ANSISYSSC Save Cursor – Ansi.sys emulation **With no parameters, performs a save cursor operation like DECSC
16931719
//ESC [ u - ANSISYSRC Restore Cursor – Ansi.sys emulation **With no parameters, performs a restore cursor operation like DECRC

Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface IConsoleOutput : IDisposable
1111
/// <see cref="IOutputBuffer"/> overload.
1212
/// </summary>
1313
/// <param name="text"></param>
14-
void Write (string text);
14+
void Write (ReadOnlySpan<char> text);
1515

1616
/// <summary>
1717
/// Write the contents of the <paramref name="buffer"/> to the console

Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs

+29-21
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ public NetOutput ()
3434
}
3535

3636
/// <inheritdoc/>
37-
public void Write (string text) { Console.Write (text); }
37+
public void Write (ReadOnlySpan<char> text)
38+
{
39+
Console.Out.Write (text);
40+
}
3841

3942
/// <inheritdoc/>
4043
public void Write (IOutputBuffer buffer)
@@ -57,6 +60,9 @@ public void Write (IOutputBuffer buffer)
5760
CursorVisibility? savedVisibility = _cachedCursorVisibility;
5861
SetCursorVisibility (CursorVisibility.Invisible);
5962

63+
const int maxCharsPerRune = 2;
64+
Span<char> runeBuffer = stackalloc char[maxCharsPerRune];
65+
6066
for (int row = top; row < rows; row++)
6167
{
6268
if (Console.WindowHeight < 1)
@@ -115,26 +121,28 @@ public void Write (IOutputBuffer buffer)
115121
{
116122
redrawAttr = attr;
117123

118-
output.Append (
119-
EscSeqUtils.CSI_SetForegroundColorRGB (
120-
attr.Foreground.R,
121-
attr.Foreground.G,
122-
attr.Foreground.B
123-
)
124-
);
125-
126-
output.Append (
127-
EscSeqUtils.CSI_SetBackgroundColorRGB (
128-
attr.Background.R,
129-
attr.Background.G,
130-
attr.Background.B
131-
)
132-
);
124+
EscSeqUtils.CSI_AppendForegroundColorRGB (
125+
output,
126+
attr.Foreground.R,
127+
attr.Foreground.G,
128+
attr.Foreground.B
129+
);
130+
131+
EscSeqUtils.CSI_AppendBackgroundColorRGB (
132+
output,
133+
attr.Background.R,
134+
attr.Background.G,
135+
attr.Background.B
136+
);
133137
}
134138

135139
outputWidth++;
140+
141+
// Avoid Rune.ToString() by appending the rune chars.
136142
Rune rune = buffer.Contents [row, col].Rune;
137-
output.Append (rune);
143+
int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
144+
ReadOnlySpan<char> runeChars = runeBuffer[..runeCharsWritten];
145+
output.Append (runeChars);
138146

139147
if (buffer.Contents [row, col].CombiningMarks.Count > 0)
140148
{
@@ -162,7 +170,7 @@ public void Write (IOutputBuffer buffer)
162170
if (output.Length > 0)
163171
{
164172
SetCursorPositionImpl (lastCol, row);
165-
Console.Write (output);
173+
Console.Out.Write (output);
166174
}
167175
}
168176

@@ -171,7 +179,7 @@ public void Write (IOutputBuffer buffer)
171179
if (!string.IsNullOrWhiteSpace (s.SixelData))
172180
{
173181
SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
174-
Console.Write (s.SixelData);
182+
Console.Out.Write (s.SixelData);
175183
}
176184
}
177185

@@ -185,7 +193,7 @@ public void Write (IOutputBuffer buffer)
185193
private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
186194
{
187195
SetCursorPositionImpl (lastCol, row);
188-
Console.Write (output);
196+
Console.Out.Write (output);
189197
output.Clear ();
190198
lastCol += outputWidth;
191199
outputWidth = 0;
@@ -222,7 +230,7 @@ private bool SetCursorPositionImpl (int col, int row)
222230

223231
// + 1 is needed because non-Windows is based on 1 instead of 0 and
224232
// Console.CursorTop/CursorLeft isn't reliable.
225-
Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
233+
EscSeqUtils.CSI_WriteCursorPosition (Console.Out, row + 1, col + 1);
226234

227235
return true;
228236
}

Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ public void AddRune (Rune rune)
164164
if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
165165
{
166166
// Just add this mark to the list
167-
Contents [Row, Col - 1].CombiningMarks.Add (rune);
167+
Contents [Row, Col - 1].AddCombiningMark (rune);
168168

169169
// Ignore. Don't move to next column (let the driver figure out what to do).
170170
}
@@ -187,7 +187,7 @@ public void AddRune (Rune rune)
187187
else
188188
{
189189
// It didn't normalize. Add it to the Cell to left's CM list
190-
Contents [Row, Col - 1].CombiningMarks.Add (rune);
190+
Contents [Row, Col - 1].AddCombiningMark (rune);
191191

192192
// Ignore. Don't move to next column (let the driver figure out what to do).
193193
}

Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs

+28-20
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#nullable enable
2+
using System.Buffers;
23
using System.ComponentModel;
34
using System.Runtime.InteropServices;
45
using Microsoft.Extensions.Logging;
56
using static Terminal.Gui.WindowsConsole;
67

78
namespace Terminal.Gui;
89

9-
internal class WindowsOutput : IConsoleOutput
10+
internal partial class WindowsOutput : IConsoleOutput
1011
{
11-
[DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
12-
private static extern bool WriteConsole (
12+
[LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
13+
[return: MarshalAs (UnmanagedType.Bool)]
14+
private static partial bool WriteConsole (
1315
nint hConsoleOutput,
14-
string lpbufer,
16+
ReadOnlySpan<char> lpbufer,
1517
uint numberOfCharsToWriten,
1618
out uint lpNumberOfCharsWritten,
1719
nint lpReserved
@@ -84,7 +86,7 @@ public WindowsOutput ()
8486
}
8587
}
8688

87-
public void Write (string str)
89+
public void Write (ReadOnlySpan<char> str)
8890
{
8991
if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
9092
{
@@ -183,7 +185,6 @@ public void Write (IOutputBuffer buffer)
183185

184186
public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
185187
{
186-
var stringBuilder = new StringBuilder ();
187188

188189
//Debug.WriteLine ("WriteToConsole");
189190

@@ -213,10 +214,10 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
213214
}
214215
else
215216
{
216-
stringBuilder.Clear ();
217+
StringBuilder stringBuilder = new();
217218

218219
stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
219-
stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0));
220+
EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0);
220221

221222
Attribute? prev = null;
222223

@@ -227,8 +228,8 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
227228
if (attr != prev)
228229
{
229230
prev = attr;
230-
stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B));
231-
stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B));
231+
EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
232+
EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
232233
}
233234

234235
if (info.Char != '\x1b')
@@ -247,14 +248,20 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
247248
stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
248249
stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
249250

250-
var s = stringBuilder.ToString ();
251+
// TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed.
252+
char [] rentedWriteArray = ArrayPool<char>.Shared.Rent (minimumLength: stringBuilder.Length);
253+
try
254+
{
255+
Span<char> writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length);
256+
stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
251257

252-
// TODO: requires extensive testing if we go down this route
253-
// If console output has changed
254-
//if (s != _lastWrite)
255-
//{
256-
// supply console with the new content
257-
result = WriteConsole (_screenBuffer, s, (uint)s.Length, out uint _, nint.Zero);
258+
// Supply console with the new content.
259+
result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
260+
}
261+
finally
262+
{
263+
ArrayPool<char>.Shared.Return (rentedWriteArray);
264+
}
258265

259266
foreach (SixelToRender sixel in Application.Sixel)
260267
{
@@ -297,9 +304,10 @@ public Size GetWindowSize ()
297304
/// <inheritdoc/>
298305
public void SetCursorVisibility (CursorVisibility visibility)
299306
{
300-
var sb = new StringBuilder ();
301-
sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
302-
Write (sb.ToString ());
307+
string cursorVisibilitySequence = visibility != CursorVisibility.Invisible
308+
? EscSeqUtils.CSI_ShowCursor
309+
: EscSeqUtils.CSI_HideCursor;
310+
Write (cursorVisibilitySequence);
303311
}
304312

305313
private Point _lastCursorPosition;

Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Terminal.Gui;
88

9-
internal class WindowsConsole
9+
internal partial class WindowsConsole
1010
{
1111
private CancellationTokenSource? _inputReadyCancellationTokenSource;
1212
private readonly BlockingCollection<InputRecord> _inputQueue = new (new ConcurrentQueue<InputRecord> ());
@@ -926,10 +926,11 @@ public static extern bool WriteConsoleOutput (
926926
ref SmallRect lpWriteRegion
927927
);
928928

929-
[DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
930-
private static extern bool WriteConsole (
929+
[LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
930+
[return: MarshalAs (UnmanagedType.Bool)]
931+
private static partial bool WriteConsole (
931932
nint hConsoleOutput,
932-
string lpbufer,
933+
ReadOnlySpan<char> lpbufer,
933934
uint NumberOfCharsToWriten,
934935
out uint lpNumberOfCharsWritten,
935936
nint lpReserved

0 commit comments

Comments
 (0)