[Wip] CLI & Daemon

This commit is contained in:
Alex 2026-07-25 18:23:36 +02:00
commit 6a535099bb
Signed by: alex
SSH key fingerprint: SHA256:D1+Ub8o0v4K5y1JNivW8IxEOelqLSvPmUzBbDIoZkRQ
44 changed files with 4417 additions and 303 deletions

View file

@ -13,6 +13,19 @@ pub struct UiConfig {
/// Whether opening the interactive UI should launch a locally installed daemon.
#[serde(default)]
pub daemon_start_policy: DaemonStartPolicy,
#[serde(default)]
pub color: TerminalPolicy,
#[serde(default)]
pub unicode: TerminalPolicy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum TerminalPolicy {
#[default]
Auto,
Always,
Never,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]

View file

@ -3,10 +3,81 @@ mod model;
mod name;
mod presets;
pub use config::{DaemonStartPolicy, UiConfig};
pub use config::{DaemonStartPolicy, TerminalPolicy, UiConfig};
pub use model::*;
pub use name::ThemeName;
pub fn resolve(name: ThemeName) -> ResolvedTheme {
presets::resolve(name)
}
pub fn resolve_with_capabilities(
name: ThemeName,
color_enabled: bool,
unicode_enabled: bool,
) -> ResolvedTheme {
let mut theme = if color_enabled {
presets::resolve(name)
} else {
presets::resolve(ThemeName::Monospace)
};
theme.name = name;
theme.unicode = unicode_enabled;
if !unicode_enabled {
if matches!(theme.console.cursor, CursorPresentation::Character { .. }) {
theme.console.cursor = CursorPresentation::Character {
glyph: "|",
style: theme.console.text,
};
}
}
theme
}
/// Resolve a theme against the terminal's color depth. Surface uses RGB
/// colors, so a portable ANSI preset is selected when truecolor is absent.
pub fn resolve_with_terminal_profile(
name: ThemeName,
color_enabled: bool,
unicode_enabled: bool,
truecolor_enabled: bool,
) -> ResolvedTheme {
let effective = if color_enabled && !truecolor_enabled && matches!(name, ThemeName::Surface) {
ThemeName::Ansi
} else {
name
};
resolve_with_capabilities(effective, color_enabled, unicode_enabled)
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::style::Color;
#[test]
fn no_color_policy_removes_palette_dependencies() {
let theme = resolve_with_capabilities(ThemeName::Surface, false, true);
assert_eq!(theme.name, ThemeName::Surface);
assert_eq!(theme.status.error.fg, None);
assert_eq!(theme.surfaces.panel.bg, None);
}
#[test]
fn ascii_policy_replaces_character_cursor() {
let theme = resolve_with_capabilities(ThemeName::Monospace, false, false);
assert!(!theme.unicode);
match theme.console.cursor {
CursorPresentation::Character { glyph, .. } => assert_eq!(glyph, "|"),
CursorPresentation::StyledCell(_) => panic!("expected an ASCII character cursor"),
}
assert_ne!(theme.graphs.ram, Color::Blue);
}
#[test]
fn surface_uses_ansi_fallback_without_truecolor() {
let theme = resolve_with_terminal_profile(ThemeName::Surface, true, true, false);
assert_eq!(theme.name, ThemeName::Ansi);
assert_eq!(theme.surfaces.panel.bg, None);
}
}

View file

@ -24,6 +24,13 @@ pub struct BorderStyles {
pub title: Style,
}
#[derive(Clone, Debug)]
pub struct SurfaceStyles {
pub canvas: Style, pub toolbar: Style, pub panel: Style, pub panel_alternate: Style,
pub panel_focused: Style, pub panel_selected: Style, pub footer: Style, pub overlay: Style,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChromeMode { Bordered, Surfaces }
#[derive(Clone, Debug)]
pub struct ChoiceItemStyle {
pub marker: Style,
pub label: Style,
@ -119,6 +126,9 @@ pub struct TextSemantics {
#[derive(Clone, Debug)]
pub struct ResolvedTheme {
pub name: ThemeName,
pub unicode: bool,
pub surfaces: SurfaceStyles,
pub chrome: ChromeMode,
pub text: TextStyles,
pub status: StatusStyles,
pub choices: ChoiceStyles,

View file

@ -1,6 +1,6 @@
use super::{
BorderStyles, ButtonStyles, ChoiceItemStyle, ChoiceStyles, ConsoleStyles, CursorPresentation,
GraphStyles, LogStyles, MarkdownStyles, MarkerSet, ResolvedTheme, StatusStyles, TextStyles,
ChromeMode, GraphStyles, LogStyles, MarkdownStyles, MarkerSet, ResolvedTheme, StatusStyles, SurfaceStyles, TextStyles,
ThemeName,
};
use ratatui::style::{Color, Modifier, Style};
@ -45,6 +45,9 @@ fn base(
};
ResolvedTheme {
name,
unicode: true,
surfaces: SurfaceStyles { canvas: Style::default(), toolbar: Style::default(), panel: Style::default(), panel_alternate: Style::default(), panel_focused: focused, panel_selected: selected, footer: Style::default(), overlay: Style::default() },
chrome: ChromeMode::Bordered,
text: TextStyles {
normal,
muted,
@ -295,6 +298,14 @@ pub fn resolve(name: ThemeName) -> ResolvedTheme {
);
theme.console.cursor =
CursorPresentation::StyledCell(plain.fg(Color::Black).bg(Color::Yellow));
theme.chrome = ChromeMode::Surfaces;
theme.surfaces = SurfaceStyles {
canvas: plain.bg(Color::Black), toolbar: plain.fg(Color::White).bg(Color::DarkGray),
panel: plain.fg(Color::White).bg(Color::Rgb(30, 35, 45)),
panel_alternate: plain.fg(Color::White).bg(Color::Rgb(45, 52, 66)),
panel_focused: plain.fg(Color::White).bg(Color::Rgb(48, 58, 78)),
panel_selected: selected, footer: plain.fg(Color::DarkGray).bg(Color::Black), overlay: plain.fg(Color::White).bg(Color::Rgb(45, 52, 66)),
};
theme
}
}