mod cli;
mod config;
#[cfg(test)]
mod tests;
use std::{
path::Path,
sync::atomic::{AtomicBool, Ordering},
};
use crate::color;
pub use cli::{Cli, Commands, ConfigCmd, Position, Size, ThemeType, View};
pub use config::{Config, DebugSection, FontOptions, KeybindingsSection, MetricsExporter};
use crate::history::History;
use anyhow::Result;
use clap::Parser;
use serde::Deserialize;
use smart_debug::SmartDebug;
static RENDER_ELEMENT_BOUNDS: AtomicBool = AtomicBool::new(false);
#[must_use]
pub fn get_render_element_bounds() -> bool {
RENDER_ELEMENT_BOUNDS.load(Ordering::SeqCst)
}
pub fn set_render_element_bounds(b: bool) {
RENDER_ELEMENT_BOUNDS.store(b, Ordering::SeqCst);
}
#[derive(Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ResolvedTheme {
Dark,
#[default]
Light,
}
impl ResolvedTheme {
fn new(theme_ty: ThemeType) -> Option<Self> {
match theme_ty {
ThemeType::Auto => Self::try_detect(),
ThemeType::Dark => Some(Self::Dark),
ThemeType::Light => Some(Self::Light),
}
}
fn try_detect() -> Option<Self> {
match dark_light::detect() {
Ok(dark_light::Mode::Unspecified) => None,
Ok(dark_light::Mode::Dark) => Some(Self::Dark),
Ok(dark_light::Mode::Light) => Some(Self::Light),
Err(e) => {
tracing::warn!("Failed to detect system theme: {}", e);
None
}
}
}
}
#[derive(SmartDebug, PartialEq)]
pub struct Opts {
pub history: History,
#[debug(skip)]
pub theme: color::Theme,
pub decorations: Option<bool>,
pub scale: Option<f32>,
pub page_width: Option<f32>,
pub lines_to_scroll: f32,
pub font_opts: FontOptions,
pub keybindings: KeybindingsSection,
pub color_scheme: Option<ResolvedTheme>,
pub metrics: Option<MetricsExporter>,
pub position: Option<Position>,
pub size: Option<Size>,
}
impl Opts {
pub fn parse_and_load_from(args: View, config: Config) -> Result<Self> {
#[cfg(test)]
{
let (_, _) = (args, config);
panic!("Use `Opts::parse_and_load_with_system_theme()`");
}
#[cfg(not(test))]
{
let system_color_scheme = ResolvedTheme::try_detect();
Self::parse_and_load_inner(args, config, system_color_scheme)
}
}
#[cfg(test)]
pub fn parse_and_load_with_system_theme(
args: View,
config: Config,
theme: Option<ResolvedTheme>,
) -> Result<Self> {
Self::parse_and_load_inner(args, config, theme)
}
fn parse_and_load_inner(
args: View,
config: Config,
fallback_theme: Option<ResolvedTheme>,
) -> Result<Self> {
let Config {
theme: config_theme,
decorations: config_decorations,
scale: config_scale,
page_width: config_page_width,
lines_to_scroll,
light_theme,
dark_theme,
font_options,
keybindings,
debug,
window,
} = config;
let View {
file_path,
theme: args_theme,
decorations,
scale: args_scale,
config: _,
page_width: args_page_width,
size: v_size,
position: v_position,
} = args;
let DebugSection {
metrics,
render_element_bounds,
} = debug;
set_render_element_bounds(render_element_bounds);
let history = History::new(&file_path)?;
let resolved_theme = args_theme
.or(config_theme)
.and_then(ResolvedTheme::new)
.or(fallback_theme);
let theme = {
let (maybe_theme, fallback_values) = match resolved_theme {
Some(ResolvedTheme::Dark) => (dark_theme, color::Theme::dark_default()),
None | Some(ResolvedTheme::Light) => (light_theme, color::Theme::light_default()),
};
match maybe_theme {
Some(theme) => theme.merge(fallback_values)?,
None => fallback_values,
}
};
let decorations = decorations.or(config_decorations);
let scale = args_scale.or(config_scale);
let font_opts = font_options.unwrap_or_default();
let page_width = args_page_width.or(config_page_width);
let lines_to_scroll = lines_to_scroll.into();
let (position, size) = if let Some(window) = window {
(v_position.or(window.position), v_size.or(window.size))
} else {
(v_position, v_size)
};
Ok(Self {
history,
theme,
decorations,
scale,
page_width,
lines_to_scroll,
font_opts,
keybindings,
color_scheme: resolved_theme,
metrics,
position,
size,
})
}
pub fn program_args(file_path: &Path) -> Vec<String> {
let current_args = Cli::parse().into_view().expect("Should contain an view!");
let mut args = Vec::new();
args.push(file_path.display().to_string());
if let Some(theme) = current_args.theme {
args.push("--theme".to_owned());
args.push(theme.as_str().to_owned());
}
if let Some(scale) = current_args.scale {
args.push("--scale".to_owned());
args.push(scale.to_string());
}
if let Some(config) = current_args.config {
args.push("--config".to_owned());
args.push(config.display().to_string());
}
if let Some(page_width) = current_args.page_width {
args.push("-w".to_owned());
args.push(page_width.to_string());
}
args
}
}