1- use crossterm:: {
2- execute,
3- style,
4- } ;
1+ use std:: { io:: Write as _, marker:: PhantomData } ;
52
6- use crate :: protocol:: Event ;
3+ use crate :: protocol:: { Event , LegacyPassThroughOutput } ;
74
85#[ derive( thiserror:: Error , Debug ) ]
96pub enum ConduitError {
@@ -33,40 +30,63 @@ impl ViewEnd {
3330 /// Method to facilitate in the interim
3431 /// It takes possible messages from the old even loop and queues write to the output provided
3532 /// This blocks the current thread and consumes the [ViewEnd]
36- pub fn into_legacy_mode ( self , mut output : impl std:: io:: Write ) -> Result < ( ) , ConduitError > {
33+ pub fn into_legacy_mode (
34+ self ,
35+ mut stderr : std:: io:: Stderr ,
36+ mut stdout : std:: io:: Stdout ,
37+ ) -> Result < ( ) , ConduitError > {
3738 while let Ok ( event) = self . receiver . recv ( ) {
38- let content = match event {
39- Event :: Custom ( custom) => custom. value . to_string ( ) ,
40- Event :: TextMessageContent ( content) => content. delta ,
41- Event :: TextMessageChunk ( chunk) => {
42- if let Some ( content) = chunk. delta {
43- content
44- } else {
45- continue ;
46- }
47- } ,
48- _ => continue ,
49- } ;
50-
51- execute ! ( & mut output, style:: Print ( content) ) ?;
39+ if let Event :: LegacyPassThrough ( content) = event {
40+ match content {
41+ LegacyPassThroughOutput :: Stderr ( content) => {
42+ stderr. write_all ( & content) ?;
43+ stderr. flush ( ) ?;
44+ } ,
45+ LegacyPassThroughOutput :: Stdout ( content) => {
46+ stdout. write_all ( & content) ?;
47+ stdout. flush ( ) ?;
48+ } ,
49+ }
50+ }
5251 }
5352
5453 Ok ( ( ) )
5554 }
5655}
5756
57+ #[ derive( Clone , Debug ) ]
58+ pub struct DestinationStdout ;
59+ #[ derive( Clone , Debug ) ]
60+ pub struct DestinationStderr ;
61+ #[ derive( Clone , Debug ) ]
62+ pub struct DestinationStructuredOutput ;
63+
64+ pub type InputReceiver = std:: sync:: mpsc:: Receiver < Vec < u8 > > ;
65+
5866/// This compliments the [ViewEnd]. It can be thought of as the "other end" of a pipe.
5967/// The control would own this.
60- pub struct ControlEnd {
68+ #[ derive( Debug ) ]
69+ pub struct ControlEnd < T > {
6170 pub current_event : Option < Event > ,
6271 /// Used by the control to send state changes to the view
6372 pub sender : std:: sync:: mpsc:: Sender < Event > ,
64- /// To receive user input from the view
65- // TODO: later on we will need replace this byte array with an actual event type from ACP
66- pub receiver : std:: sync:: mpsc:: Receiver < Vec < u8 > > ,
73+ /// Phantom data to specify the destination type for pass-through operations.
74+ /// This allows the type system to track whether this ControlEnd is configured
75+ /// for stdout or stderr output without runtime overhead.
76+ pass_through_destination : PhantomData < T > ,
6777}
6878
69- impl ControlEnd {
79+ impl < T > Clone for ControlEnd < T > {
80+ fn clone ( & self ) -> Self {
81+ Self {
82+ current_event : self . current_event . clone ( ) ,
83+ sender : self . sender . clone ( ) ,
84+ pass_through_destination : PhantomData ,
85+ }
86+ }
87+ }
88+
89+ impl < T > ControlEnd < T > {
7090 /// Primes the [ControlEnd] with the state passed in
7191 /// This api is intended to serve as an interim solution to bridge the gap between the current
7292 /// code base, which heavily relies on crossterm apis to print directly to the terminal and the
@@ -81,13 +101,33 @@ impl ControlEnd {
81101 }
82102}
83103
84- impl std:: io:: Write for ControlEnd {
104+ impl ControlEnd < DestinationStderr > {
105+ pub fn as_stdout ( & self ) -> ControlEnd < DestinationStdout > {
106+ ControlEnd {
107+ current_event : self . current_event . clone ( ) ,
108+ sender : self . sender . clone ( ) ,
109+ pass_through_destination : PhantomData ,
110+ }
111+ }
112+ }
113+
114+ impl ControlEnd < DestinationStdout > {
115+ pub fn as_stderr ( & self ) -> ControlEnd < DestinationStderr > {
116+ ControlEnd {
117+ current_event : self . current_event . clone ( ) ,
118+ sender : self . sender . clone ( ) ,
119+ pass_through_destination : PhantomData ,
120+ }
121+ }
122+ }
123+
124+ impl std:: io:: Write for ControlEnd < DestinationStderr > {
85125 fn write ( & mut self , buf : & [ u8 ] ) -> std:: io:: Result < usize > {
86- // We'll default to custom event
87- // This hardly matters because in legacy mode we are simply extracting the bytes and
88- // dumping it to output
89126 if self . current_event . is_none ( ) {
90- self . current_event . replace ( Event :: Custom ( Default :: default ( ) ) ) ;
127+ self . current_event
128+ . replace ( Event :: LegacyPassThrough ( LegacyPassThroughOutput :: Stderr (
129+ Default :: default ( ) ,
130+ ) ) ) ;
91131 }
92132
93133 let current_event = self
@@ -103,26 +143,74 @@ impl std::io::Write for ControlEnd {
103143 }
104144
105145 fn flush ( & mut self ) -> std:: io:: Result < ( ) > {
106- let current_state = self . current_event . take ( ) . ok_or ( std:: io:: Error :: other ( "No state set" ) ) ?;
146+ let current_state =
147+ self . current_event
148+ . take ( )
149+ . unwrap_or ( Event :: LegacyPassThrough ( LegacyPassThroughOutput :: Stderr (
150+ Default :: default ( ) ,
151+ ) ) ) ;
107152
108153 self . sender . send ( current_state) . map_err ( std:: io:: Error :: other)
109154 }
110155}
111156
112- /// Creates a bidirectional communication channel between view and control layers.
157+ impl std:: io:: Write for ControlEnd < DestinationStdout > {
158+ fn write ( & mut self , buf : & [ u8 ] ) -> std:: io:: Result < usize > {
159+ if self . current_event . is_none ( ) {
160+ self . current_event
161+ . replace ( Event :: LegacyPassThrough ( LegacyPassThroughOutput :: Stdout (
162+ Default :: default ( ) ,
163+ ) ) ) ;
164+ }
165+
166+ let current_event = self
167+ . current_event
168+ . as_mut ( )
169+ . ok_or ( std:: io:: Error :: other ( "No event set" ) ) ?;
170+
171+ current_event
172+ . insert_content ( buf)
173+ . map_err ( |_e| std:: io:: Error :: other ( "Error inserting content" ) ) ?;
174+
175+ Ok ( buf. len ( ) )
176+ }
177+
178+ fn flush ( & mut self ) -> std:: io:: Result < ( ) > {
179+ let current_state =
180+ self . current_event
181+ . take ( )
182+ . unwrap_or ( Event :: LegacyPassThrough ( LegacyPassThroughOutput :: Stdout (
183+ Default :: default ( ) ,
184+ ) ) ) ;
185+
186+ self . sender . send ( current_state) . map_err ( std:: io:: Error :: other)
187+ }
188+ }
189+
190+ /// Creates a set of legacy conduits for communication between view and control layers.
113191///
114- /// This function establishes a message-passing conduit that enables:
115- /// - The view layer to send user input (as bytes) to the control layer
116- /// - The control layer to send state changes to the view layer
192+ /// This function establishes the communication channels needed for the legacy mode operation,
193+ /// where the view layer and control layer can exchange events and byte data.
117194///
118195/// # Returns
196+ ///
119197/// A tuple containing:
120- /// - `ViewEnd<S>`: The view-side endpoint for sending input and receiving state updates
121- /// - `ControlEnd<S>`: The control-side endpoint for receiving input and sending state updates
198+ /// - `ViewEnd`: The view-side endpoint for sending input and receiving state changes
199+ /// - `InputReceiver`: A receiver for raw byte input from the view
200+ /// - `ControlEnd<DestinationStderr>`: Control endpoint configured for stderr output
201+ /// - `ControlEnd<DestinationStdout>`: Control endpoint configured for stdout output
202+ ///
203+ /// # Example
122204///
123- /// # Type Parameters
124- /// - `S`: The state type that implements `ViewState` trait
125- pub fn get_conduit_pair ( ) -> ( ViewEnd , ControlEnd ) {
205+ /// ```rust
206+ /// let (view_end, input_receiver, stderr_control, stdout_control) = get_legacy_conduits();
207+ /// ```
208+ pub fn get_legacy_conduits ( ) -> (
209+ ViewEnd ,
210+ InputReceiver ,
211+ ControlEnd < DestinationStderr > ,
212+ ControlEnd < DestinationStdout > ,
213+ ) {
126214 let ( state_tx, state_rx) = std:: sync:: mpsc:: channel :: < Event > ( ) ;
127215 let ( byte_tx, byte_rx) = std:: sync:: mpsc:: channel :: < Vec < u8 > > ( ) ;
128216
@@ -131,10 +219,16 @@ pub fn get_conduit_pair() -> (ViewEnd, ControlEnd) {
131219 sender : byte_tx,
132220 receiver : state_rx,
133221 } ,
222+ byte_rx,
223+ ControlEnd {
224+ current_event : None ,
225+ sender : state_tx. clone ( ) ,
226+ pass_through_destination : PhantomData ,
227+ } ,
134228 ControlEnd {
135229 current_event : None ,
136230 sender : state_tx,
137- receiver : byte_rx ,
231+ pass_through_destination : PhantomData ,
138232 } ,
139233 )
140234}
@@ -154,22 +248,10 @@ impl InterimEvent for Event {
154248 debug_assert ! ( self . is_compatible_with_legacy_event_loop( ) ) ;
155249
156250 match self {
157- Self :: Custom ( _custom) => {
158- // custom events are defined in this UI crate
159- // TODO: use an enum as implement AsRef for it
160- // match custom.name.as_str() {
161- // _ => {},
162- // }
163- } ,
164- Self :: TextMessageContent ( msg_content) => {
165- let str = String :: from_utf8 ( content. to_vec ( ) ) ?;
166- msg_content. delta . push_str ( & str) ;
167- } ,
168- Self :: TextMessageChunk ( chunk) => {
169- let str = String :: from_utf8 ( content. to_vec ( ) ) ?;
170- if let Some ( d) = chunk. delta . as_mut ( ) {
171- d. push_str ( & str) ;
172- }
251+ Self :: LegacyPassThrough ( buf) => match buf {
252+ LegacyPassThroughOutput :: Stdout ( buf) | LegacyPassThroughOutput :: Stderr ( buf) => {
253+ buf. extend_from_slice ( content) ;
254+ } ,
173255 } ,
174256 _ => unreachable ! ( ) ,
175257 }
0 commit comments