11mod api;
22mod parse;
33mod prompt;
4+ mod terminal;
45
56use std:: io:: {
6- Stderr ,
7+ IsTerminal ,
8+ Read ,
79 Write ,
8- stderr ,
10+ stdin ,
911} ;
1012use std:: process:: ExitCode ;
1113use std:: time:: Duration ;
@@ -17,11 +19,10 @@ use crossterm::style::{
1719 Print ,
1820} ;
1921use crossterm:: {
20- ExecutableCommand ,
21- QueueableCommand ,
2222 cursor,
23+ execute,
24+ queue,
2325 style,
24- terminal,
2526} ;
2627use eyre:: {
2728 Result ,
@@ -58,7 +59,7 @@ enum ApiResponse {
5859 End ,
5960}
6061
61- pub async fn chat ( input : String ) -> Result < ExitCode > {
62+ pub async fn chat ( mut input : String ) -> Result < ExitCode > {
6263 if !fig_util:: system_info:: in_cloudshell ( ) && !fig_auth:: is_logged_in ( ) . await {
6364 bail ! (
6465 "You are not logged in, please log in with {}" ,
@@ -68,76 +69,92 @@ pub async fn chat(input: String) -> Result<ExitCode> {
6869
6970 region_check ( "chat" ) ?;
7071
71- let mut stderr = stderr ( ) ;
72- let result = try_chat ( & mut stderr , input ) . await ;
72+ let stdin = stdin ( ) ;
73+ let is_interactive = stdin . is_terminal ( ) ;
7374
74- stderr
75- . queue ( style:: SetAttribute ( Attribute :: Reset ) ) ?
76- . queue ( style:: ResetColor ) ?
77- . flush ( )
78- . ok ( ) ;
75+ if !is_interactive {
76+ // append to input string any extra info that was provided.
77+ stdin. lock ( ) . read_to_string ( & mut input) . unwrap ( ) ;
78+ }
79+ let mut output = terminal:: new ( is_interactive) ;
80+ let result = try_chat ( & mut output, input, is_interactive) . await ;
81+
82+ if is_interactive {
83+ queue ! ( output, style:: SetAttribute ( Attribute :: Reset ) , style:: ResetColor ) . ok ( ) ;
84+ }
85+ output. flush ( ) . ok ( ) ;
7986
8087 result. map ( |_| ExitCode :: SUCCESS )
8188}
8289
83- async fn try_chat ( stderr : & mut Stderr , mut input : String ) -> Result < ( ) > {
84- let mut rl = rl ( ) ?;
90+ async fn try_chat < W : Write > ( output : & mut W , mut input : String , interactive : bool ) -> Result < ( ) > {
91+ let mut rl = if interactive { Some ( rl ( ) ?) } else { None } ;
8592 let client = StreamingClient :: new ( ) . await ?;
8693 let mut rx = None ;
8794 let mut conversation_id: Option < String > = None ;
8895 let mut message_id = None ;
8996
9097 loop {
91- // Make request with input
92- match input. trim ( ) {
93- "exit" | "quit" => {
94- if let Some ( conversation_id) = conversation_id {
95- fig_telemetry:: send_end_chat ( conversation_id. clone ( ) ) . await ;
96- }
97- return Ok ( ( ) ) ;
98- } ,
99- _ => ( ) ,
100- }
101-
102- if !input. is_empty ( ) {
103- stderr. queue ( style:: SetForegroundColor ( Color :: Magenta ) ) ?;
104- if input. contains ( "@history" ) {
105- stderr. queue ( style:: Print ( "Using shell history\n " ) ) ?;
98+ // Make request with input, otherwise used already provided buffer for input
99+ if !interactive {
100+ rx = Some ( send_message ( client. clone ( ) , input. clone ( ) , conversation_id. clone ( ) ) . await ?) ;
101+ } else {
102+ match input. trim ( ) {
103+ "exit" | "quit" => {
104+ if let Some ( conversation_id) = conversation_id {
105+ fig_telemetry:: send_end_chat ( conversation_id. clone ( ) ) . await ;
106+ }
107+ return Ok ( ( ) ) ;
108+ } ,
109+ _ => ( ) ,
106110 }
107111
108- if input. contains ( "@git" ) {
109- stderr. queue ( style:: Print ( "Using git context\n " ) ) ?;
110- }
112+ if !input. is_empty ( ) {
113+ queue ! ( output, style:: SetForegroundColor ( Color :: Magenta ) ) ?;
114+ if input. contains ( "@history" ) {
115+ queue ! ( output, style:: Print ( "Using shell history\n " ) ) ?;
116+ }
111117
112- if input. contains ( "@env" ) {
113- stderr. queue ( style:: Print ( "Using environment\n " ) ) ?;
114- }
118+ if input. contains ( "@git" ) {
119+ queue ! ( output, style:: Print ( "Using git context\n " ) ) ?;
120+ }
121+
122+ if input. contains ( "@env" ) {
123+ queue ! ( output, style:: Print ( "Using environment\n " ) ) ?;
124+ }
115125
116- rx = Some ( send_message ( client. clone ( ) , input, conversation_id. clone ( ) ) . await ?) ;
117- stderr
118- . queue ( style:: SetForegroundColor ( Color :: Reset ) ) ?
119- . execute ( style:: Print ( "\n " ) ) ?;
120- } else if fig_settings:: settings:: get_bool_or ( "chat.greeting.enabled" , true ) {
121- stderr. execute ( style:: Print ( format ! (
122- "
126+ rx = Some ( send_message ( client. clone ( ) , input. clone ( ) , conversation_id. clone ( ) ) . await ?) ;
127+ queue ! ( output, style:: SetForegroundColor ( Color :: Reset ) ) ?;
128+ execute ! ( output, style:: Print ( "\n " ) ) ?;
129+ } else if fig_settings:: settings:: get_bool_or ( "chat.greeting.enabled" , true ) {
130+ execute ! (
131+ output,
132+ style:: Print ( format!(
133+ "
123134Hi, I'm Amazon Q. I can answer questions about your shell and CLI tools!
124135You can include additional context by adding the following to your prompt:
125136
126137{} to pass your shell history
127138{} to pass information about your current git repository
128139{} to pass your shell environment
129-
130140" ,
131- "@history" . bold( ) ,
132- "@git" . bold( ) ,
133- "@env" . bold( )
134- ) ) ) ?;
141+ "@history" . bold( ) ,
142+ "@git" . bold( ) ,
143+ "@env" . bold( )
144+ ) )
145+ ) ?;
146+ }
135147 }
136148
137149 // Print response as we receive it
138150 if let Some ( rx) = & mut rx {
139- stderr. queue ( cursor:: Hide ) ?;
140- let mut spinner = Some ( Spinner :: new ( Spinners :: Dots , "Generating your answer..." . to_owned ( ) ) ) ;
151+ // compiler complains about unused variable for spinner (bad global state usage)
152+ let mut _spinner = if interactive {
153+ queue ! ( output, cursor:: Hide ) ?;
154+ Some ( Spinner :: new ( Spinners :: Dots , "Generating your answer..." . to_owned ( ) ) )
155+ } else {
156+ None
157+ } ;
141158
142159 let mut buf = String :: new ( ) ;
143160 let mut offset = 0 ;
@@ -175,23 +192,34 @@ You can include additional context by adding the following to your prompt:
175192 ended = true ;
176193 } ,
177194 ApiResponse :: Error ( error) => {
178- drop ( spinner. take ( ) ) ;
179- stderr. queue ( cursor:: MoveToColumn ( 0 ) ) ?;
180-
181- match error {
182- Some ( error) => stderr
183- . queue ( style:: SetForegroundColor ( Color :: Red ) ) ?
184- . queue ( style:: SetAttribute ( Attribute :: Bold ) ) ?
185- . queue ( style:: Print ( "error" ) ) ?
186- . queue ( style:: SetForegroundColor ( Color :: Reset ) ) ?
187- . queue ( style:: SetAttribute ( Attribute :: Reset ) ) ?
188- . queue ( style:: Print ( format ! ( ": {error}\n " ) ) ) ?,
189- None => stderr. queue ( style:: Print (
190- "Amazon Q is having trouble responding right now. Try again later." ,
191- ) ) ?,
192- } ;
193-
194- stderr. flush ( ) ?;
195+ if interactive {
196+ _spinner = None ;
197+ queue ! ( output, cursor:: MoveToColumn ( 0 ) ) ?;
198+
199+ match error {
200+ Some ( error) => {
201+ queue ! (
202+ output,
203+ style:: SetForegroundColor ( Color :: Red ) ,
204+ style:: SetAttribute ( Attribute :: Bold ) ,
205+ style:: Print ( "error" ) ,
206+ style:: SetForegroundColor ( Color :: Reset ) ,
207+ style:: SetAttribute ( Attribute :: Reset ) ,
208+ style:: Print ( format!( ": {error}\n " ) )
209+ ) ?;
210+ } ,
211+ None => {
212+ queue ! (
213+ output,
214+ style:: Print (
215+ "Amazon Q is having trouble responding right now. Try again later." ,
216+ )
217+ ) ?;
218+ } ,
219+ } ;
220+ }
221+
222+ output. flush ( ) ?;
195223 ended = true ;
196224 } ,
197225 }
@@ -203,19 +231,24 @@ You can include additional context by adding the following to your prompt:
203231 buf. push ( '\n' ) ;
204232 }
205233
206- if !buf. is_empty ( ) && spinner. take ( ) . is_some ( ) {
207- stderr
208- . queue ( terminal:: Clear ( terminal:: ClearType :: CurrentLine ) ) ?
209- . queue ( cursor:: MoveToColumn ( 0 ) ) ?
210- . queue ( cursor:: Show ) ?;
234+ if !buf. is_empty ( ) && interactive {
235+ _spinner = None ;
236+ queue ! (
237+ output,
238+ crossterm:: terminal:: Clear ( crossterm:: terminal:: ClearType :: CurrentLine ) ,
239+ cursor:: MoveToColumn ( 0 ) ,
240+ cursor:: Show
241+ ) ?;
211242 }
212243
213244 loop {
214245 let input = Partial :: new ( & buf[ offset..] ) ;
215- match interpret_markdown ( input, stderr as & mut Stderr , & mut state) {
246+ // fresh reborrow required on output
247+ match interpret_markdown ( input, & mut * output, & mut state) {
216248 Ok ( parsed) => {
217249 offset += parsed. offset_from ( & input) ;
218- stderr. lock ( ) . flush ( ) ?;
250+ output. flush ( ) ?;
251+ // output.lock().flush()?;
219252 state. newline = state. set_newline ;
220253 state. set_newline = false ;
221254 } ,
@@ -229,22 +262,28 @@ You can include additional context by adding the following to your prompt:
229262 }
230263
231264 if ended {
232- stderr
233- . queue ( style:: ResetColor ) ?
234- . queue ( style:: SetAttribute ( Attribute :: Reset ) ) ?
235- . queue ( Print ( "\n " ) ) ?;
236-
237- for ( i, citation) in & state. citations {
238- stderr
239- . queue ( style:: SetForegroundColor ( Color :: Blue ) ) ?
240- . queue ( style:: Print ( format ! ( "{i} " ) ) ) ?
241- . queue ( style:: SetForegroundColor ( Color :: DarkGrey ) ) ?
242- . queue ( style:: Print ( format ! ( "{citation}\n " ) ) ) ?
243- . queue ( style:: SetForegroundColor ( Color :: Reset ) ) ?;
244- }
245-
246- if !state. citations . is_empty ( ) {
247- stderr. execute ( Print ( "\n " ) ) ?;
265+ if interactive {
266+ queue ! (
267+ output,
268+ style:: ResetColor ,
269+ style:: SetAttribute ( Attribute :: Reset ) ,
270+ Print ( "\n " )
271+ ) ?;
272+
273+ for ( i, citation) in & state. citations {
274+ queue ! (
275+ output,
276+ style:: SetForegroundColor ( Color :: Blue ) ,
277+ style:: Print ( format!( "{i} " ) ) ,
278+ style:: SetForegroundColor ( Color :: DarkGrey ) ,
279+ style:: Print ( format!( "{citation}\n " ) ) ,
280+ style:: SetForegroundColor ( Color :: Reset )
281+ ) ?;
282+ }
283+
284+ if !state. citations . is_empty ( ) {
285+ execute ! ( output, Print ( "\n " ) ) ?;
286+ }
248287 }
249288
250289 if let ( Some ( conversation_id) , Some ( message_id) ) = ( & conversation_id, & message_id) {
@@ -256,14 +295,17 @@ You can include additional context by adding the following to your prompt:
256295 }
257296 }
258297
298+ if !interactive {
299+ break Ok ( ( ) ) ;
300+ }
259301 loop {
260- let readline = rl. readline ( PROMPT ) ;
302+ let readline = rl. as_mut ( ) . unwrap ( ) . readline ( PROMPT ) ;
261303 match readline {
262304 Ok ( line) => {
263305 if line. trim ( ) . is_empty ( ) {
264306 continue ;
265307 }
266- let _ = rl. add_history_entry ( line. as_str ( ) ) ;
308+ let _ = rl. as_mut ( ) . unwrap ( ) . add_history_entry ( line. as_str ( ) ) ;
267309 input = line;
268310 break ;
269311 } ,
0 commit comments