11use std:: {
2+ borrow:: Cow ,
23 io:: { self , IsTerminal , Read , Write } ,
34 sync:: {
45 Arc , Mutex ,
@@ -13,7 +14,14 @@ use divvun_runtime::{
1314} ;
1415use futures_util:: { FutureExt , StreamExt } ;
1516use pathos:: AppDirs ;
16- use rustyline:: error:: ReadlineError ;
17+ use rustyline:: {
18+ Helper ,
19+ completion:: Completer ,
20+ error:: ReadlineError ,
21+ highlight:: { CmdKind , Highlighter } ,
22+ hint:: Hinter ,
23+ validate:: Validator ,
24+ } ;
1725use serde_json:: Map ;
1826use termcolor:: Color ;
1927use tokio:: { io:: AsyncReadExt as _, sync:: RwLock } ;
@@ -25,17 +33,72 @@ use crate::{
2533
2634use super :: utils;
2735
36+ // Themed helper for rustyline that applies background/foreground colors
37+ struct ThemedHelper {
38+ background : String ,
39+ foreground : String ,
40+ }
41+
42+ impl ThemedHelper {
43+ fn new ( colors : Option < & syntax_highlight:: CommandColors > ) -> Self {
44+ if let Some ( colors) = colors {
45+ Self {
46+ background : colors. background . clone ( ) ,
47+ foreground : colors. foreground . clone ( ) ,
48+ }
49+ } else {
50+ Self {
51+ background : String :: new ( ) ,
52+ foreground : String :: new ( ) ,
53+ }
54+ }
55+ }
56+ }
57+
58+ impl Completer for ThemedHelper {
59+ type Candidate = String ;
60+ }
61+
62+ impl Hinter for ThemedHelper {
63+ type Hint = String ;
64+ }
65+
66+ impl Validator for ThemedHelper { }
67+
68+ impl Highlighter for ThemedHelper {
69+ fn highlight_prompt < ' b , ' s : ' b , ' p : ' b > (
70+ & ' s self ,
71+ prompt : & ' p str ,
72+ _default : bool ,
73+ ) -> Cow < ' b , str > {
74+ if self . background . is_empty ( ) {
75+ Cow :: Borrowed ( prompt)
76+ } else {
77+ Cow :: Owned ( format ! ( "{}{}" , self . background, prompt) )
78+ }
79+ }
80+
81+ fn highlight < ' l > ( & self , line : & ' l str , _pos : usize ) -> Cow < ' l , str > {
82+ if self . background . is_empty ( ) {
83+ Cow :: Borrowed ( line)
84+ } else {
85+ Cow :: Owned ( format ! ( "{}{}{}" , self . background, self . foreground, line) )
86+ }
87+ }
88+
89+ fn highlight_char ( & self , _line : & str , _pos : usize , _kind : CmdKind ) -> bool {
90+ !self . background . is_empty ( )
91+ }
92+ }
93+
94+ impl Helper for ThemedHelper { }
95+
2896fn format_input_highlighted (
2997 input : & Input ,
3098 command : Option < & Command > ,
3199 theme : Option < & str > ,
32100 override_bg : Option < syntax_highlight:: ThemeColor > ,
33101) -> String {
34- eprintln ! (
35- "DEBUG format_input_highlighted: override_bg.is_some() = {}" ,
36- override_bg. is_some( )
37- ) ;
38-
39102 if !syntax_highlight:: supports_color ( ) {
40103 return format ! ( "{:#}" , input) ;
41104 }
@@ -72,6 +135,8 @@ fn print_input_highlighted(
72135
73136 let formatted = format_input_highlighted ( input, command, shell. theme ( ) , theme_bg) ;
74137 io:: Write :: write_all ( shell. out ( ) , formatted. as_bytes ( ) ) ?;
138+ writeln ! ( shell. out( ) ) ?;
139+ shell. out ( ) . flush ( ) ?;
75140 Ok ( ( ) )
76141}
77142
@@ -140,28 +205,33 @@ async fn run_repl(
140205
141206 let history_path = dirs. data_dir ( ) . join ( "repl_history" ) ;
142207
143- let mut rl = rustyline:: DefaultEditor :: new ( ) . map_err ( |e| Arc :: new ( e. into ( ) ) ) ?;
208+ // Extract command colors from theme BEFORE creating editor
209+ let ( cmd_colors, theme_bg) = shell
210+ . theme ( )
211+ . and_then ( |theme_name| {
212+ syntax_highlight:: get_theme_by_name ( theme_name)
213+ . map ( |theme| syntax_highlight:: extract_command_colors ( theme) )
214+ } )
215+ . map ( |( colors, bg) | ( Some ( colors) , Some ( bg) ) )
216+ . unwrap_or ( ( None , None ) ) ;
217+
218+ // Create themed editor
219+ let helper = ThemedHelper :: new ( cmd_colors. as_ref ( ) ) ;
220+ let mut rl = rustyline:: Editor :: new ( ) . map_err ( |e| Arc :: new ( e. into ( ) ) ) ?;
221+ rl. set_helper ( Some ( helper) ) ;
144222 if rl. load_history ( & history_path) . is_err ( ) {
145223 // Do nothing
146224 }
147225
148226 let is_stepping = Arc :: new ( AtomicBool :: new ( false ) ) ;
149227
150228 // Print welcome message with theme background if available
151- if let Some ( theme_name) = shell. theme ( ) {
152- if let Some ( theme) = syntax_highlight:: get_theme_by_name ( theme_name) {
153- let ( colors, _) = syntax_highlight:: extract_command_colors ( theme) ;
154- println ! (
155- "{}Divvun Runtime v{} - type :help for commands\x1b [K\x1b [0m" ,
156- colors. background,
157- env!( "CARGO_PKG_VERSION" )
158- ) ;
159- } else {
160- println ! (
161- "Divvun Runtime v{} - type :help for commands" ,
162- env!( "CARGO_PKG_VERSION" )
163- ) ;
164- }
229+ if let Some ( ref colors) = cmd_colors {
230+ println ! (
231+ "{}Divvun Runtime v{} - type :help for commands\x1b [K\x1b [0m" ,
232+ colors. background,
233+ env!( "CARGO_PKG_VERSION" )
234+ ) ;
165235 } else {
166236 println ! (
167237 "Divvun Runtime v{} - type :help for commands" ,
@@ -181,26 +251,11 @@ async fn run_repl(
181251 let tap_breakpoint = breakpoint. clone ( ) ;
182252 let theme = shell. theme ( ) . map ( |s| s. to_string ( ) ) ;
183253
184- // Extract command colors from theme
185- let ( cmd_colors, theme_bg) = shell
186- . theme ( )
187- . and_then ( |theme_name| {
188- syntax_highlight:: get_theme_by_name ( theme_name)
189- . map ( |theme| syntax_highlight:: extract_command_colors ( theme) )
190- } )
191- . map ( |( colors, bg) | ( Some ( colors) , Some ( bg) ) )
192- . unwrap_or ( ( None , None ) ) ;
193-
194- eprintln ! ( "DEBUG: shell.theme() = {:?}" , shell. theme( ) ) ;
195- eprintln ! ( "DEBUG: cmd_colors.is_some() = {}" , cmd_colors. is_some( ) ) ;
196- eprintln ! ( "DEBUG: theme_bg.is_some() = {}" , theme_bg. is_some( ) ) ;
254+ // Prompt is simple - ThemedHelper::highlight_prompt applies theming
255+ let prompt = ">> " ;
197256
198- // Create themed prompt before moving cmd_colors into closure
199- let prompt = if let Some ( ref colors) = cmd_colors {
200- format ! ( "{}>> \x1b [0m" , colors. background)
201- } else {
202- ">> " . to_string ( )
203- } ;
257+ // Clone cmd_colors before it's moved into the tap closure
258+ let output_cmd_colors = cmd_colors. clone ( ) ;
204259
205260 let tap = Arc :: new ( move |key : & str , cmd : & Command , event : & InputEvent | {
206261 let current_events_clone = current_events_clone. clone ( ) ;
@@ -209,11 +264,6 @@ async fn run_repl(
209264 let cmd_colors_clone = cmd_colors. clone ( ) ;
210265 let theme_bg_clone = theme_bg;
211266
212- eprintln ! (
213- "DEBUG in tap: theme_bg_clone.is_some() = {}" ,
214- theme_bg_clone. is_some( )
215- ) ;
216-
217267 // Print with theme colors applied to background
218268 if let Some ( ref colors) = cmd_colors_clone {
219269 println ! (
@@ -230,15 +280,13 @@ async fn run_repl(
230280 InputEvent :: Input ( input) => {
231281 let formatted =
232282 format_input_highlighted ( input, Some ( cmd) , theme. as_deref ( ) , theme_bg_clone) ;
233- if cmd_colors_clone. is_some ( ) {
234- println ! ( "{}\x1b [K\x1b [0m" , formatted) ;
235- } else {
236- println ! ( "{}" , formatted) ;
237- }
283+ // format_input_highlighted returns content with \x1b[K per line and final \x1b[0m
284+ println ! ( "{}" , formatted) ;
238285 }
239286 _ => {
240287 if let Some ( ref colors) = cmd_colors_clone {
241- println ! ( "{}{:#}\x1b [K\x1b [0m" , colors. background, event) ;
288+ print ! ( "{}{:#}\x1b [K" , colors. background, event) ;
289+ println ! ( "\x1b [0m" ) ;
242290 } else {
243291 println ! ( "{:#}" , event) ;
244292 }
@@ -460,7 +508,20 @@ async fn run_repl(
460508 while let Some ( input) = stream. next ( ) . await {
461509 match input {
462510 Ok ( input) => {
463- shell. print ( & "<-" , None , Color :: Green , false ) ?;
511+ if let Some ( ref colors) = output_cmd_colors {
512+ // Bold green [result] with themed background, newlines before and after
513+ println ! (
514+ "{}\x1b [1;32m\x1b [0m{}\x1b [K" ,
515+ colors. background, colors. background
516+ ) ;
517+ println ! (
518+ "{}\x1b [1;32m[result]\x1b [0m{}\x1b [K" ,
519+ colors. background, colors. background
520+ ) ;
521+ } else {
522+ println ! ( ) ;
523+ println ! ( "\x1b [1;32m[result]\x1b [0m" ) ;
524+ }
464525 print_input_highlighted ( shell, & input, output_cmd) ?;
465526
466527 if let Some ( path) = args. output_path . as_deref ( ) {
0 commit comments