use std::sync::Arc;
use std::time::Duration;
use rich::measure::Measurement;
use rich::{Console, ConsoleOptions, Renderable, Segment, Text};
use crate::component::{Component, Context, Flow, View};
use crate::components::{PreviewLayout, Select, Theme};
use crate::event::Event;
use crate::item::{Item, Preview};
use crate::keymap::Keymap;
use crate::policy::{LineIo, NotInteractive};
pub const THEME_SAMPLE: &str = "[bold]Theme preview[/]
[info]info[/] [success]success[/] [warning]warning[/] [error]error[/]
[repr.number]1234[/] [repr.str]'a string'[/] [repr.bool_true]True[/] [repr.bool_false]False[/] [repr.none]None[/]
[repr.url]https://example.com[/] [repr.path]/usr/local/bin[/]
[json.key]\"name\"[/]: [json.str]\"rich\"[/], [json.key]\"ok\"[/]: [json.bool_true]true[/]
[log.time][12:00:01][/] [logging.level.info]INFO[/] [log.message]started[/]
[logging.level.warning]WARNING[/] [logging.level.error]ERROR[/] [rule.line]────────[/]
[markdown.h1]Heading[/] [markdown.code]code[/] [markdown.link]link[/]";
struct Themed {
theme: rich::Theme,
sample: Arc<dyn Renderable + Send + Sync>,
}
impl Themed {
fn console(&self, outer: &Console, options: &ConsoleOptions) -> Console {
let mut theme = rich_ext::theme::extended_theme();
theme.extend_from(&self.theme);
Console::builder()
.width(options.max_width.max(1))
.force_terminal(true)
.color_system(outer.color_system())
.no_color(outer.no_color())
.theme(theme)
.build()
}
}
impl Renderable for Themed {
fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
let inner = self.console(console, options);
let options = inner.options().update_width(options.max_width.max(1));
inner.render(&*self.sample, Some(&options))
}
fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
let inner = self.console(console, options);
self.sample.measure(&inner, options)
}
}
#[must_use = "a component does nothing until it is run or mounted"]
pub struct ThemePicker {
select: Select<String>,
}
impl ThemePicker {
pub fn new<I, S>(prompt: impl Into<String>, themes: I) -> ThemePicker
where
I: IntoIterator<Item = (S, rich::Theme)>,
S: Into<String>,
{
let sample = Text::from_markup(THEME_SAMPLE).expect("the sample is valid markup");
Self::with_sample(prompt, themes, Arc::new(sample))
}
pub fn with_sample<I, S>(
prompt: impl Into<String>,
themes: I,
sample: Arc<dyn Renderable + Send + Sync>,
) -> ThemePicker
where
I: IntoIterator<Item = (S, rich::Theme)>,
S: Into<String>,
{
let items: Vec<Item<String>> = themes
.into_iter()
.map(|(name, theme)| {
let name = name.into();
let preview = Preview::Renderable(Arc::new(Themed {
theme,
sample: Arc::clone(&sample),
}));
Item::new(name.clone(), name).preview(preview)
})
.collect();
ThemePicker {
select: Select::new(prompt, items),
}
}
pub fn from_registry(
prompt: impl Into<String>,
registry: &rich_ext::registry::ExtensionRegistry,
) -> ThemePicker {
let themes = std::iter::once(("default".to_string(), rich::Theme::new())).chain(
registry.theme_names().into_iter().filter_map(|name| {
let theme = registry.theme(name)?.clone();
Some((name.to_string(), theme))
}),
);
ThemePicker::new(prompt, themes)
}
pub fn height(mut self, rows: usize) -> Self {
self.select = self.select.height(rows);
self
}
pub fn preview(mut self, layout: PreviewLayout) -> Self {
self.select = self.select.preview(layout);
self
}
pub fn preview_height(mut self, rows: usize) -> Self {
self.select = self.select.preview_height(rows);
self
}
pub fn theme(mut self, theme: Theme) -> Self {
self.select = self.select.theme(theme);
self
}
pub fn default(mut self, name: &str) -> Self {
if let Some(index) = self
.select
.items()
.iter()
.position(|item| item.label == name)
{
self.select = self.select.default(index);
}
self
}
pub fn with_mouse(mut self, on: bool) -> Self {
self.select = self.select.with_mouse(on);
self
}
pub fn names(&self) -> Vec<&str> {
self.select
.items()
.iter()
.map(|item| item.label.as_str())
.collect()
}
}
impl Component for ThemePicker {
type Output = String;
fn keymap(&self) -> Keymap {
self.select.visible_keymap()
}
fn handle(&mut self, event: &Event, context: &Context<'_>) -> Flow<String> {
self.select.handle(event, context)
}
fn render(&self, context: &Context<'_>) -> View {
self.select.render(context)
}
fn tick(&self) -> Option<Duration> {
self.select.tick_interval()
}
fn mouse(&self) -> bool {
Component::mouse(&self.select)
}
fn default_value(&self) -> Option<String> {
self.select.default_value()
}
fn prompt(&mut self, io: &mut dyn LineIo) -> Result<Option<String>, NotInteractive> {
self.select.prompt(io)
}
}