2
2
// SPDX-License-Identifier: LGPL-3.0-only
3
3
4
4
using System ;
5
+ using System . Collections . Generic ;
6
+ using System . IO ;
5
7
using System . Runtime . InteropServices ;
8
+ using System . Text ;
6
9
7
10
namespace Nethermind . Runner ;
8
11
9
12
public static class ConsoleHelpers
10
13
{
14
+ private static LineInterceptingTextWriter _interceptingWriter ;
15
+ public static event EventHandler < string > ? LineWritten ;
16
+ public static string [ ] GetRecentMessages ( ) => _interceptingWriter . GetRecentMessages ( ) ;
17
+
11
18
public static void EnableConsoleColorOutput ( )
12
19
{
13
20
const int STD_OUTPUT_HANDLE = - 11 ;
14
21
const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 ;
15
22
16
23
Console . OutputEncoding = System . Text . Encoding . UTF8 ;
17
24
25
+ // Capture original out
26
+ TextWriter originalOut = Console . Out ;
27
+
28
+ // Create our intercepting writer
29
+ _interceptingWriter = new LineInterceptingTextWriter ( originalOut ) ;
30
+ _interceptingWriter . LineWritten += ( sender , line ) =>
31
+ {
32
+ LineWritten ? . Invoke ( sender , line ) ;
33
+ } ;
34
+
35
+ // Redirect Console.Out
36
+ Console . SetOut ( _interceptingWriter ) ;
37
+
18
38
if ( ! OperatingSystem . IsWindowsVersionAtLeast ( 10 ) )
19
39
return ;
20
40
@@ -41,3 +61,114 @@ public static void EnableConsoleColorOutput()
41
61
[ DllImport ( "kernel32.dll" ) ]
42
62
private static extern bool SetConsoleMode ( IntPtr hConsoleHandle , uint dwMode ) ;
43
63
}
64
+
65
+
66
+ public sealed class LineInterceptingTextWriter : TextWriter
67
+ {
68
+ // Event raised every time a full line ending with Environment.NewLine is written
69
+ public event EventHandler < string > ? LineWritten ;
70
+
71
+ // The "real" underlying writer (i.e., the original Console.Out)
72
+ private readonly TextWriter _underlyingWriter ;
73
+
74
+ // Buffer used to accumulate written data until we detect a new line
75
+ private readonly StringBuilder _buffer ;
76
+
77
+ public LineInterceptingTextWriter ( TextWriter underlyingWriter )
78
+ {
79
+ _underlyingWriter = underlyingWriter ?? throw new ArgumentNullException ( nameof ( underlyingWriter ) ) ;
80
+ _buffer = new StringBuilder ( ) ;
81
+ }
82
+
83
+ // You must override Encoding, even if just forwarding
84
+ public override Encoding Encoding => _underlyingWriter . Encoding ;
85
+
86
+ // Overriding WriteLine(string) is handy for direct calls to Console.WriteLine(...).
87
+ // However, you also want to handle the general case in Write(string).
88
+ public override void WriteLine ( string ? value )
89
+ {
90
+ Write ( value ) ;
91
+ Write ( Environment . NewLine ) ;
92
+ }
93
+
94
+ public override void Write ( string ? value )
95
+ {
96
+ if ( value == null )
97
+ {
98
+ return ;
99
+ }
100
+
101
+ // Append to the buffer
102
+ _buffer . Append ( value ) ;
103
+
104
+ // Pass the data along to the underlying writer
105
+ _underlyingWriter . Write ( value ) ;
106
+
107
+ // Check if we can extract lines from the buffer
108
+ CheckForLines ( ) ;
109
+ }
110
+
111
+ public override void Write ( char value )
112
+ {
113
+ _buffer . Append ( value ) ;
114
+ _underlyingWriter . Write ( value ) ;
115
+ CheckForLines ( ) ;
116
+ }
117
+
118
+ public override void Flush ( )
119
+ {
120
+ base . Flush ( ) ;
121
+ _underlyingWriter . Flush ( ) ;
122
+ }
123
+
124
+ private void CheckForLines ( )
125
+ {
126
+ // Environment.NewLine might be "\r\n" or "\n" depending on platform
127
+ // so let's find each occurrence and split it off
128
+ string newLine = Environment . NewLine ;
129
+
130
+ while ( true )
131
+ {
132
+ // Find the next index of the new line
133
+ int newLinePos = _buffer . ToString ( ) . IndexOf ( newLine , StringComparison . Ordinal ) ;
134
+
135
+ // If there's no complete new line, break
136
+ if ( newLinePos < 0 )
137
+ {
138
+ break ;
139
+ }
140
+
141
+ // Extract the line up to the new line
142
+ string line = _buffer . ToString ( 0 , newLinePos ) ;
143
+
144
+ // Remove that portion (including the new line) from the buffer
145
+ _buffer . Remove ( 0 , newLinePos + newLine . Length ) ;
146
+
147
+ // Raise the event
148
+ OnLineWritten ( line ) ;
149
+ }
150
+ }
151
+
152
+ public string [ ] GetRecentMessages ( )
153
+ {
154
+ lock ( _recentMessages )
155
+ {
156
+ return _recentMessages . ToArray ( ) ;
157
+ }
158
+ }
159
+
160
+ Queue < string > _recentMessages = new ( capacity : 100 ) ;
161
+ private void OnLineWritten ( string line )
162
+ {
163
+ lock ( _recentMessages )
164
+ {
165
+ if ( _recentMessages . Count > 100 )
166
+ {
167
+ _recentMessages . Dequeue ( ) ;
168
+ }
169
+ _recentMessages . Enqueue ( line ) ;
170
+ }
171
+ // Raise the event, if subscribed
172
+ LineWritten ? . Invoke ( this , line ) ;
173
+ }
174
+ }
0 commit comments