@@ -20,6 +20,15 @@ use crossbeam_channel::unbounded;
20
20
21
21
use crate :: Dimensions ;
22
22
23
+ /// Represents the target stream for output
24
+ #[ derive( Copy , Clone , Debug ) ]
25
+ pub enum OutputTarget {
26
+ /// Main output stream (default: stderr)
27
+ Main ,
28
+ /// Auxiliary output stream (default: stdout)
29
+ Aux ,
30
+ }
31
+
23
32
pub trait SuperConsoleOutput : Send + Sync + ' static {
24
33
/// Called before rendering will occur. This has a chance to prevent rendering by returning
25
34
/// false.
@@ -29,6 +38,14 @@ pub trait SuperConsoleOutput: Send + Sync + 'static {
29
38
/// clearing. This should flush if possible.
30
39
fn output ( & mut self , buffer : Vec < u8 > ) -> anyhow:: Result < ( ) > ;
31
40
41
+ /// Called to produce to a specific output target.
42
+ ///This may be called without should_render if we are finalizing or clearing. This should flush if possible.
43
+ /// Default implementation sends all to main output for backwards compatibility
44
+ fn output_to ( & mut self , buffer : Vec < u8 > , target : OutputTarget ) -> anyhow:: Result < ( ) > {
45
+ let _ = target;
46
+ self . output ( buffer)
47
+ }
48
+
32
49
/// How big is the terminal to write to.
33
50
fn terminal_size ( & self ) -> anyhow:: Result < Dimensions > {
34
51
Ok ( crossterm:: terminal:: size ( ) ?. into ( ) )
@@ -48,11 +65,16 @@ pub trait SuperConsoleOutput: Send + Sync + 'static {
48
65
pub struct BlockingSuperConsoleOutput {
49
66
/// Stream to write to.
50
67
stream : Box < dyn Write + Send + ' static + Sync > ,
68
+ /// Auxiliary stream to write to.
69
+ aux_stream : Box < dyn Write + Send + ' static + Sync > ,
51
70
}
52
71
53
72
impl BlockingSuperConsoleOutput {
54
- pub fn new ( stream : Box < dyn Write + Send + ' static + Sync > ) -> Self {
55
- Self { stream }
73
+ pub fn new (
74
+ stream : Box < dyn Write + Send + ' static + Sync > ,
75
+ aux_stream : Box < dyn Write + Send + ' static + Sync > ,
76
+ ) -> Self {
77
+ Self { stream, aux_stream }
56
78
}
57
79
}
58
80
@@ -62,9 +84,20 @@ impl SuperConsoleOutput for BlockingSuperConsoleOutput {
62
84
}
63
85
64
86
fn output ( & mut self , buffer : Vec < u8 > ) -> anyhow:: Result < ( ) > {
65
- self . stream . write_all ( & buffer) ? ;
66
- self . stream . flush ( ) ? ;
87
+ self . output_to ( buffer, OutputTarget :: Main )
88
+ }
67
89
90
+ fn output_to ( & mut self , buffer : Vec < u8 > , target : OutputTarget ) -> anyhow:: Result < ( ) > {
91
+ match target {
92
+ OutputTarget :: Main => {
93
+ self . stream . write_all ( & buffer) ?;
94
+ self . stream . flush ( ) ?;
95
+ }
96
+ OutputTarget :: Aux => {
97
+ self . aux_stream . write_all ( & buffer) ?;
98
+ self . aux_stream . flush ( ) ?;
99
+ }
100
+ }
68
101
Ok ( ( ) )
69
102
}
70
103
@@ -88,7 +121,7 @@ impl SuperConsoleOutput for BlockingSuperConsoleOutput {
88
121
/// - When an error occurs, the next fallible call will return it.
89
122
pub ( crate ) struct NonBlockingSuperConsoleOutput {
90
123
/// A channel to send frames for writing.
91
- sender : Sender < Vec < u8 > > ,
124
+ sender : Sender < ( Vec < u8 > , OutputTarget ) > ,
92
125
/// A channel back for errors encountered by the thread doing the writing.
93
126
errors : Receiver < io:: Error > ,
94
127
/// The thread doing the writing. It owns the other end of the aforementioned channels and will
@@ -97,19 +130,29 @@ pub(crate) struct NonBlockingSuperConsoleOutput {
97
130
}
98
131
99
132
impl NonBlockingSuperConsoleOutput {
100
- pub fn new ( stream : Box < dyn Write + Send + ' static + Sync > ) -> anyhow:: Result < Self > {
101
- Self :: new_for_writer ( stream)
133
+ pub fn new (
134
+ stream : Box < dyn Write + Send + ' static + Sync > ,
135
+ aux_stream : Box < dyn Write + Send + ' static + Sync > ,
136
+ ) -> anyhow:: Result < Self > {
137
+ Self :: new_for_writer ( stream, aux_stream)
102
138
}
103
139
104
- fn new_for_writer ( mut stream : Box < dyn Write + Send + ' static + Sync > ) -> anyhow:: Result < Self > {
105
- let ( sender, receiver) = bounded :: < Vec < u8 > > ( 1 ) ;
140
+ fn new_for_writer (
141
+ mut stream : Box < dyn Write + Send + ' static + Sync > ,
142
+ mut aux_stream : Box < dyn Write + Send + ' static + Sync > ,
143
+ ) -> anyhow:: Result < Self > {
144
+ let ( sender, receiver) = bounded :: < ( Vec < u8 > , OutputTarget ) > ( 1 ) ;
106
145
let ( error_sender, errors) = unbounded :: < io:: Error > ( ) ;
107
146
108
147
let handle = std:: thread:: Builder :: new ( )
109
148
. name ( "superconsole-io" . to_owned ( ) )
110
149
. spawn ( move || {
111
- for frame in receiver. into_iter ( ) {
112
- match stream. write_all ( & frame) . and_then ( |( ) | stream. flush ( ) ) {
150
+ for ( data, output_target) in receiver. into_iter ( ) {
151
+ let out_stream = match output_target {
152
+ OutputTarget :: Main => & mut stream,
153
+ OutputTarget :: Aux => & mut aux_stream,
154
+ } ;
155
+ match out_stream. write_all ( & data) . and_then ( |( ) | stream. flush ( ) ) {
113
156
Ok ( ( ) ) => { }
114
157
Err ( e) => {
115
158
// This can only fail if the sender disconnected, in which case they'll
@@ -140,12 +183,16 @@ impl SuperConsoleOutput for NonBlockingSuperConsoleOutput {
140
183
/// Attempt to send out a frame. If we called should_render, this won't block. If we didn't,
141
184
/// then it may block.
142
185
fn output ( & mut self , buffer : Vec < u8 > ) -> anyhow:: Result < ( ) > {
186
+ self . output_to ( buffer, OutputTarget :: Main )
187
+ }
188
+
189
+ fn output_to ( & mut self , buffer : Vec < u8 > , target : OutputTarget ) -> anyhow:: Result < ( ) > {
143
190
if let Ok ( err) = self . errors . try_recv ( ) {
144
191
return Err ( anyhow:: Error :: from ( err) . context ( "Superconsole I/O thread errored" ) ) ;
145
192
}
146
193
147
194
self . sender
148
- . send ( buffer)
195
+ . send ( ( buffer, target ) )
149
196
. context ( "Superconsole I/O thread has crashed" ) ?;
150
197
151
198
Ok ( ( ) )
@@ -221,29 +268,44 @@ mod tests {
221
268
222
269
#[ test]
223
270
fn test_non_blocking_output_errors_on_next_output ( ) -> anyhow:: Result < ( ) > {
224
- let ( writer, drain) = TestWriter :: new ( ) ;
271
+ fn test_send_target ( target0 : OutputTarget , target1 : OutputTarget ) -> anyhow:: Result < ( ) > {
272
+ let ( writer, drain) = TestWriter :: new ( ) ;
273
+ let aux_writer = writer. clone ( ) ;
274
+
275
+ let mut output = NonBlockingSuperConsoleOutput :: new_for_writer (
276
+ Box :: new ( writer) ,
277
+ Box :: new ( aux_writer) ,
278
+ ) ?;
225
279
226
- let mut output = NonBlockingSuperConsoleOutput :: new_for_writer ( Box :: new ( writer) ) ?;
280
+ // Send a first message, this will go into write()
281
+ assert ! ( output. should_render( ) ) ;
282
+ output. output_to ( msg ( ) , target0) ?;
227
283
228
- // Send a first message, this will go into write()
229
- assert ! ( output. should_render( ) ) ;
230
- output. output ( msg ( ) ) ?;
284
+ // Send a second message, this will stay in the channel.
285
+ output. output_to ( msg ( ) , target1) ?;
231
286
232
- // Send a second message, this will stay in the channel.
233
- output. output ( msg ( ) ) ?;
287
+ // Now, kill the output
288
+ assert ! ( !output. should_render( ) ) ;
289
+ drop ( drain) ;
234
290
235
- // Now, kill the output
236
- assert ! ( !output. should_render( ) ) ;
237
- drop ( drain) ;
291
+ // We expect that should_render() will eventually return true.
292
+ while !output. should_render ( ) {
293
+ std:: thread:: yield_now ( ) ;
294
+ }
238
295
239
- // We expect that should_render() will eventually return true.
240
- while !output. should_render ( ) {
241
- std:: thread:: yield_now ( ) ;
296
+ // Likewise, we expect that sending output and finalizing wold fail.
297
+ assert ! ( output. output( Vec :: new( ) ) . is_err( ) ) ;
298
+ assert ! ( Box :: new( output) . finalize( ) . is_err( ) ) ;
299
+
300
+ Ok ( ( ) )
242
301
}
243
302
244
- // Likewise, we expect that sending output and finalizing wold fail.
245
- assert ! ( output. output( Vec :: new( ) ) . is_err( ) ) ;
246
- assert ! ( Box :: new( output) . finalize( ) . is_err( ) ) ;
303
+ // Test all combinations of targets
304
+ for target0 in [ OutputTarget :: Main , OutputTarget :: Aux ] {
305
+ for target1 in [ OutputTarget :: Main , OutputTarget :: Aux ] {
306
+ test_send_target ( target0, target1) ?
307
+ }
308
+ }
247
309
248
310
Ok ( ( ) )
249
311
}
0 commit comments