From 128655b3d0b423c422aba0b76e65ed52b97f4209 Mon Sep 17 00:00:00 2001 From: harehare Date: Tue, 29 Sep 2026 19:54:06 +0900 Subject: [PATCH] feat(theme): use cyan accent by default and add --accent option Restyle scrollbars with thin track and thumb symbols, and make the accent color (title bar, scrollbar, popups) configurable. --- README.md | 1 + src/lib.rs | 2 +- src/main.rs | 10 +++++++-- src/pager.rs | 6 ++++++ src/theme.rs | 61 +++++++++++++++++++++++++++++++++++++--------------- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 44580d2..ee6deb1 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ echo "# Hello\n\n\`\`\`rust\nfn main() {}\n\`\`\`" | mq-view mq-view --theme dark README.md # force the dark palette mq-view --theme light README.md # force the light palette mq-view --theme auto README.md # default: guess from the COLORFGBG env var, fall back to dark +mq-view --accent blue README.md # accent color: cyan (default), blue, green, magenta, orange mq-view --no-color README.md # disable all ANSI color output (also respects $NO_COLOR) ``` diff --git a/src/lib.rs b/src/lib.rs index e6eaba6..f27e0de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub use pager::run_pager; pub use renderer::{ RenderConfig, render_markdown, render_markdown_to_string, render_markdown_with_config, }; -pub use theme::{Theme, ThemeMode}; +pub use theme::{AccentColor, Theme, ThemeMode}; /// Runs an mq query against `content` and re-serializes the resulting nodes /// back to Markdown source, so the result can be fed into the same parse diff --git a/src/main.rs b/src/main.rs index c07ec8b..94730d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ use clap::Parser; use miette::{IntoDiagnostic, Result}; use mq_markdown::Markdown; -use mq_view::{RenderConfig, Theme, ThemeMode, render_markdown_with_config, run_pager}; +use mq_view::{ + AccentColor, RenderConfig, Theme, ThemeMode, render_markdown_with_config, run_pager, +}; use std::fs; use std::io::{self, BufWriter, Write}; use std::io::{IsTerminal, Read}; @@ -30,6 +32,10 @@ pub struct Args { #[arg(long = "theme", value_enum, default_value_t = ThemeMode::Auto)] theme: ThemeMode, + /// Accent color for the title bar, scrollbar and popups. + #[arg(long = "accent", value_enum, default_value_t = AccentColor::Cyan)] + accent: AccentColor, + /// Disable all ANSI color output #[arg(long = "no-color")] no_color: bool, @@ -65,7 +71,7 @@ fn main() -> Result<()> { let config = RenderConfig { header_full_width_highlight: !args.no_header_highlight, - theme: Theme::resolve(args.theme), + theme: Theme::resolve_with_accent(args.theme, args.accent), no_color, line_numbers: args.line_numbers, ..RenderConfig::default() diff --git a/src/pager.rs b/src/pager.rs index a93e207..80aa748 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -1031,7 +1031,13 @@ fn draw_scrollbar( theme: &Theme, ) { let mut state = ScrollbarState::new(total).position(position); + let (track, thumb) = match orientation { + ScrollbarOrientation::VerticalRight | ScrollbarOrientation::VerticalLeft => ("│", "┃"), + _ => ("─", "━"), + }; let scrollbar = Scrollbar::new(orientation) + .track_symbol(Some(track)) + .thumb_symbol(thumb) .begin_symbol(None) .end_symbol(None) .track_style(Style::default().fg(to_ratatui(theme.ui_muted))) diff --git a/src/theme.rs b/src/theme.rs index 34c0977..afa5ddc 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -15,6 +15,36 @@ pub enum ThemeMode { Light, } +/// Accent color for the title bar, scrollbar and popups. Each variant has a +/// dark-background and a light-background shade. +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, Default)] +pub enum AccentColor { + #[default] + Cyan, + Blue, + Green, + Magenta, + Orange, +} + +impl AccentColor { + fn color(self, light: bool) -> Color { + let (r, g, b) = match (self, light) { + (Self::Cyan, false) => (0, 139, 153), + (Self::Cyan, true) => (0, 115, 130), + (Self::Blue, false) => (0, 0, 238), + (Self::Blue, true) => (0, 90, 200), + (Self::Green, false) => (30, 140, 70), + (Self::Green, true) => (20, 110, 50), + (Self::Magenta, false) => (170, 50, 170), + (Self::Magenta, true) => (140, 30, 140), + (Self::Orange, false) => (200, 100, 0), + (Self::Orange, true) => (170, 80, 0), + }; + Color::TrueColor { r, g, b } + } +} + /// Indexed the same way as `SyntaxHighlighter`'s tree-sitter highlight /// capture list (attribute, constant, function.builtin, ...). pub type SyntaxPalette = [(u8, u8, u8); 26]; @@ -222,7 +252,7 @@ impl Theme { g: 140, b: 0, }, - ui_accent: Color::TrueColor { r: 0, g: 0, b: 238 }, + ui_accent: AccentColor::Cyan.color(false), ui_muted: Color::TrueColor { r: 128, g: 128, @@ -350,11 +380,7 @@ impl Theme { g: 90, b: 0, }, - ui_accent: Color::TrueColor { - r: 0, - g: 90, - b: 200, - }, + ui_accent: AccentColor::Cyan.color(true), ui_muted: Color::TrueColor { r: 110, g: 110, @@ -367,17 +393,18 @@ impl Theme { /// Terminals that don't set `COLORFGBG` (iTerm2, Kitty, Alacritty, ...) /// fall back to `Dark` under `Auto`. pub fn resolve(mode: ThemeMode) -> Self { - match mode { - ThemeMode::Dark => Self::dark(), - ThemeMode::Light => Self::light(), - ThemeMode::Auto => { - if is_light_background() { - Self::light() - } else { - Self::dark() - } - } - } + Self::resolve_with_accent(mode, AccentColor::default()) + } + + pub fn resolve_with_accent(mode: ThemeMode, accent: AccentColor) -> Self { + let light = match mode { + ThemeMode::Dark => false, + ThemeMode::Light => true, + ThemeMode::Auto => is_light_background(), + }; + let mut theme = if light { Self::light() } else { Self::dark() }; + theme.ui_accent = accent.color(light); + theme } }