Skip to content

Commit 17f8c9e

Browse files
committed
Fix theming on terminal issues
1 parent 630afee commit 17f8c9e

File tree

4 files changed

+233
-120
lines changed

4 files changed

+233
-120
lines changed

cli/src/command/run.rs

Lines changed: 113 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
borrow::Cow,
23
io::{self, IsTerminal, Read, Write},
34
sync::{
45
Arc, Mutex,
@@ -13,7 +14,14 @@ use divvun_runtime::{
1314
};
1415
use futures_util::{FutureExt, StreamExt};
1516
use 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+
};
1725
use serde_json::Map;
1826
use termcolor::Color;
1927
use tokio::{io::AsyncReadExt as _, sync::RwLock};
@@ -25,17 +33,72 @@ use crate::{
2533

2634
use 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+
2896
fn 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() {

crates/syntax-highlight/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub fn get_default_theme_for_background(is_dark: bool) -> &'static str {
4242
if is_dark {
4343
"base16-mocha.dark"
4444
} else {
45-
"InspiredGitHub"
45+
"base16-ocean.light"
4646
}
4747
}
4848

@@ -144,6 +144,7 @@ pub const ANSI_RESET: &str = "\x1b[0m";
144144
#[derive(Clone, Debug)]
145145
pub struct CommandColors {
146146
pub background: String,
147+
pub foreground: String,
147148
pub module: String,
148149
pub command: String,
149150
pub type_ann: String,
@@ -189,8 +190,16 @@ pub fn extract_command_colors(theme: &Theme) -> (CommandColors, Color) {
189190
a: 255,
190191
});
191192

193+
let foreground = theme.settings.foreground.unwrap_or(Color {
194+
r: 255,
195+
g: 255,
196+
b: 255,
197+
a: 255,
198+
});
199+
192200
let colors = CommandColors {
193201
background: color_to_ansi_bg(background),
202+
foreground: color_to_ansi_fg(foreground),
194203
module: color_to_ansi_fg(get_scope_color("entity.name.namespace")),
195204
command: color_to_ansi_fg(get_scope_color("entity.name.function")),
196205
type_ann: color_to_ansi_fg(get_scope_color("storage.type")),

0 commit comments

Comments
 (0)