use crate::style::{ClassStyle, InlineStyle, StyleBuilder};
use std::{borrow::Borrow, marker::PhantomData, rc::Rc};
use yew::{html, Classes, Component, Html, Properties, Context};
const CSS_ANSI_CONTAINER: &str = "font-family:monospace;";
#[derive(Clone, Debug, PartialEq, Properties)]
pub struct AnsiProps<S: Clone + PartialEq> {
#[prop_or_default]
pub class: Classes,
pub text: S,
#[prop_or_default]
pub no_default_style: bool,
}
#[derive(Debug)]
pub struct AnsiRenderer<Text, Builder>
where
Text: Clone + PartialEq,
Builder: StyleBuilder,
{
props: AnsiProps<Text>,
segments: Vec<(ClassStyle, String)>,
_builder: PhantomData<Builder>,
}
impl<Text, Builder> AnsiRenderer<Text, Builder>
where
Text: Borrow<str> + Clone + PartialEq,
Builder: StyleBuilder,
{
fn update_segments(&mut self) {
let s = &self.props.text;
self.segments.clear();
for (effect, content) in crate::get_sgr_segments(s.borrow()) {
self.segments
.push((effect.to_class_style::<Builder>(), content.to_owned()))
}
}
fn render_segment((class_style, content): &(ClassStyle, String)) -> Html {
let class = class_style.class.clone();
let style = class_style.style.clone().unwrap_or_default();
html! {
<span class={ class } style={ style }>
{ content }
</span>
}
}
}
impl<Text, Builder> Component for AnsiRenderer<Text, Builder>
where
Text: Borrow<str> + Clone + PartialEq + 'static,
Builder: StyleBuilder + 'static,
{
type Message = ();
type Properties = AnsiProps<Text>;
fn create(ctx: &Context<Self>) -> Self {
let mut instance = Self {
props: ctx.props().clone(),
segments: Vec::new(),
_builder: PhantomData::default(),
};
instance.update_segments();
instance
}
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
true
}
fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
let update_segments = self.props.text != ctx.props().text;
let should_render = if &self.props == ctx.props() {
false
} else {
self.props = ctx.props().clone();
true
};
if update_segments {
self.update_segments();
}
should_render
}
fn view(&self, ctx: &Context<Self>) -> Html {
let style = if ctx.props().no_default_style {
""
} else {
CSS_ANSI_CONTAINER
};
html! {
<pre class={ ctx.props().class.clone() } style={ style }>
{ for self.segments.iter().map(Self::render_segment) }
</pre>
}
}
}
pub type Ansi<Builder = InlineStyle> = AnsiRenderer<String, Builder>;
pub type AnsiRc<Builder = InlineStyle> = AnsiRenderer<Rc<String>, Builder>;
pub type AnsiStatic<Builder = InlineStyle> = AnsiRenderer<&'static str, Builder>;