mod color;
#[cfg(unix)]
mod osc;
use std::time::Duration;
use anyhow::Result;
use crate::config::Config;
use crate::env::commands::format_env_export;
pub use color::{Theme, parse_colorfgbg, parse_theme_str};
const OSC_QUERY_BUDGET: Duration = Duration::from_millis(200);
#[cfg(unix)]
fn query_terminal_theme(budget: Duration) -> Option<Theme> {
osc::query_terminal_theme(budget)
}
#[cfg(not(unix))]
fn query_terminal_theme(_budget: Duration) -> Option<Theme> {
None
}
pub fn resolve_local_terminal_theme_for_injection() -> Option<Theme> {
if let Ok(existing) = std::env::var("SHINE_TERMINAL_THEME")
&& let Some(theme) = parse_theme_str(existing.trim())
{
return Some(theme);
}
query_terminal_theme(OSC_QUERY_BUDGET)
}
fn auto_sync_enabled(config: &Config) -> bool {
match std::env::var("SHINE_SYNC_TERMINAL_THEME") {
Ok(value) => value.trim() != "0",
Err(_) => config.sync_terminal_theme,
}
}
fn resolve_bat_theme_override(theme: Theme) -> String {
let (var, default) = match theme {
Theme::Light => ("SHINE_BAT_LIGHT_THEME", "GitHub"),
Theme::Dark => ("SHINE_BAT_DARK_THEME", "OneHalfDark"),
};
std::env::var(var)
.ok()
.filter(|value| !value.is_empty())
.unwrap_or_else(|| default.to_string())
}
fn resolve_theme() -> Option<Theme> {
if let Ok(existing) = std::env::var("SHINE_TERMINAL_THEME")
&& let Some(theme) = parse_theme_str(existing.trim())
{
return Some(theme);
}
if let Ok(colorfgbg) = std::env::var("COLORFGBG")
&& let Some(theme) = parse_colorfgbg(&colorfgbg)
{
return Some(theme);
}
query_terminal_theme(OSC_QUERY_BUDGET)
}
pub async fn handle_sync(auto: bool, quiet: bool) -> Result<()> {
let config = Config::load_global_runtime_for_dry_run().await?;
if auto && !auto_sync_enabled(&config) {
return Ok(());
}
let Some(theme) = resolve_theme() else {
if !quiet {
eprintln!("shine: could not determine terminal theme; leaving BAT_THEME unchanged");
}
return Ok(());
};
println!(
"{}",
format_env_export(&config.shell_type, "SHINE_TERMINAL_THEME", theme.as_str())
);
if std::env::var("BAT_THEME").is_ok() {
return Ok(());
}
let bat_theme = resolve_bat_theme_override(theme);
println!(
"{}",
format_env_export(&config.shell_type, "BAT_THEME", &bat_theme)
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::env_lock;
#[allow(clippy::await_holding_lock)]
#[tokio::test(flavor = "current_thread")]
async fn handle_sync_skips_when_auto_and_env_var_disabled() {
let _guard = env_lock();
unsafe { std::env::set_var("SHINE_SYNC_TERMINAL_THEME", "0") };
unsafe { std::env::set_var("SHINE_TERMINAL_THEME", "dark") };
let result = handle_sync(true, true).await;
assert!(result.is_ok());
unsafe { std::env::remove_var("SHINE_SYNC_TERMINAL_THEME") };
unsafe { std::env::remove_var("SHINE_TERMINAL_THEME") };
}
#[allow(clippy::await_holding_lock)]
#[tokio::test(flavor = "current_thread")]
async fn handle_sync_manual_ignores_env_disable() {
let _guard = env_lock();
unsafe { std::env::set_var("SHINE_SYNC_TERMINAL_THEME", "0") };
unsafe { std::env::set_var("SHINE_TERMINAL_THEME", "dark") };
unsafe { std::env::remove_var("BAT_THEME") };
let result = handle_sync(false, true).await;
assert!(result.is_ok());
unsafe { std::env::remove_var("SHINE_SYNC_TERMINAL_THEME") };
unsafe { std::env::remove_var("SHINE_TERMINAL_THEME") };
}
#[test]
fn resolve_bat_theme_override_uses_published_env_vars() {
let _guard = env_lock();
unsafe { std::env::set_var("SHINE_BAT_LIGHT_THEME", "Solarized") };
assert_eq!(resolve_bat_theme_override(Theme::Light), "Solarized");
unsafe { std::env::remove_var("SHINE_BAT_LIGHT_THEME") };
assert_eq!(resolve_bat_theme_override(Theme::Light), "GitHub");
assert_eq!(resolve_bat_theme_override(Theme::Dark), "OneHalfDark");
}
#[test]
fn resolve_bat_theme_override_ignores_empty_env_var() {
let _guard = env_lock();
unsafe { std::env::set_var("SHINE_BAT_DARK_THEME", "") };
assert_eq!(resolve_bat_theme_override(Theme::Dark), "OneHalfDark");
unsafe { std::env::remove_var("SHINE_BAT_DARK_THEME") };
}
#[test]
fn resolve_theme_prefers_already_exported_var_over_colorfgbg() {
let _guard = env_lock();
unsafe { std::env::set_var("SHINE_TERMINAL_THEME", "light") };
unsafe { std::env::set_var("COLORFGBG", "15;0") }; assert_eq!(resolve_theme(), Some(Theme::Light));
unsafe { std::env::remove_var("SHINE_TERMINAL_THEME") };
unsafe { std::env::remove_var("COLORFGBG") };
}
#[test]
fn resolve_theme_falls_back_to_colorfgbg() {
let _guard = env_lock();
unsafe { std::env::remove_var("SHINE_TERMINAL_THEME") };
unsafe { std::env::set_var("COLORFGBG", "0;15") };
assert_eq!(resolve_theme(), Some(Theme::Light));
unsafe { std::env::remove_var("COLORFGBG") };
}
}