forked from extern/nushell
Add ThemedPalette (#1873)
* add theme module * reorganize theme palette code * improve tests * move to newtype implementation for ThemeColor and fix Palette name. * add dead code ignore for now * fix allow dead code macro * remove redundant import and unnecessary return * fix ok_or clippy error Co-authored-by: Kurtis Nusbaum <kcommiter@gmail.com>
This commit is contained in:
parent
8dfc90a322
commit
80ce8acf57
@ -1,6 +1,11 @@
|
||||
use ansi_term::{Color, Style};
|
||||
use nu_protocol::hir::FlatShape;
|
||||
use nu_source::{Span, Spanned};
|
||||
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::error::Error;
|
||||
use std::ops::Deref;
|
||||
use std::str::Bytes;
|
||||
use std::{fmt, io};
|
||||
|
||||
pub trait Palette {
|
||||
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>>;
|
||||
@ -58,6 +63,268 @@ impl Palette for DefaultPalette {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThemedPalette {
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
impl ThemedPalette {
|
||||
// remove this once we start actually loading themes.
|
||||
#[allow(dead_code)]
|
||||
pub fn new<R: io::Read>(reader: &mut R) -> Result<ThemedPalette, ThemeError> {
|
||||
let theme = serde_json::from_reader(reader)?;
|
||||
Ok(ThemedPalette { theme })
|
||||
}
|
||||
}
|
||||
|
||||
impl Palette for ThemedPalette {
|
||||
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>> {
|
||||
match &shape.item {
|
||||
FlatShape::OpenDelimiter(_) => {
|
||||
single_style_span(self.theme.open_delimiter.normal(), shape.span)
|
||||
}
|
||||
FlatShape::CloseDelimiter(_) => {
|
||||
single_style_span(self.theme.close_delimiter.normal(), shape.span)
|
||||
}
|
||||
FlatShape::ItVariable => single_style_span(self.theme.it_variable.bold(), shape.span),
|
||||
FlatShape::Keyword => single_style_span(self.theme.keyword.bold(), shape.span),
|
||||
FlatShape::Variable => single_style_span(self.theme.variable.normal(), shape.span),
|
||||
FlatShape::Identifier => single_style_span(self.theme.identifier.normal(), shape.span),
|
||||
FlatShape::Type => single_style_span(self.theme.r#type.bold(), shape.span),
|
||||
FlatShape::Operator => single_style_span(self.theme.operator.normal(), shape.span),
|
||||
FlatShape::DotDot => single_style_span(self.theme.dot_dot.bold(), shape.span),
|
||||
FlatShape::Dot => single_style_span(Style::new().fg(*self.theme.dot), shape.span),
|
||||
FlatShape::InternalCommand => {
|
||||
single_style_span(self.theme.internal_command.bold(), shape.span)
|
||||
}
|
||||
FlatShape::ExternalCommand => {
|
||||
single_style_span(self.theme.external_command.normal(), shape.span)
|
||||
}
|
||||
FlatShape::ExternalWord => {
|
||||
single_style_span(self.theme.external_word.bold(), shape.span)
|
||||
}
|
||||
FlatShape::BareMember => single_style_span(self.theme.bare_member.bold(), shape.span),
|
||||
FlatShape::StringMember => {
|
||||
single_style_span(self.theme.string_member.bold(), shape.span)
|
||||
}
|
||||
FlatShape::String => single_style_span(self.theme.string.normal(), shape.span),
|
||||
FlatShape::Path => single_style_span(self.theme.path.normal(), shape.span),
|
||||
FlatShape::GlobPattern => single_style_span(self.theme.glob_pattern.bold(), shape.span),
|
||||
FlatShape::Word => single_style_span(self.theme.word.normal(), shape.span),
|
||||
FlatShape::Pipe => single_style_span(self.theme.pipe.bold(), shape.span),
|
||||
FlatShape::Flag => single_style_span(self.theme.flag.bold(), shape.span),
|
||||
FlatShape::ShorthandFlag => {
|
||||
single_style_span(self.theme.shorthand_flag.bold(), shape.span)
|
||||
}
|
||||
FlatShape::Int => single_style_span(self.theme.int.bold(), shape.span),
|
||||
FlatShape::Decimal => single_style_span(self.theme.decimal.bold(), shape.span),
|
||||
FlatShape::Whitespace => single_style_span(self.theme.whitespace.normal(), shape.span),
|
||||
FlatShape::Separator => single_style_span(self.theme.separator.normal(), shape.span),
|
||||
FlatShape::Comment => single_style_span(self.theme.comment.bold(), shape.span),
|
||||
FlatShape::Garbage => single_style_span(
|
||||
Style::new().fg(*self.theme.garbage).on(Color::Red),
|
||||
shape.span,
|
||||
),
|
||||
FlatShape::Size { number, unit } => vec![
|
||||
Spanned::<Style> {
|
||||
span: *number,
|
||||
item: self.theme.size_number.bold(),
|
||||
},
|
||||
Spanned::<Style> {
|
||||
span: *unit,
|
||||
item: self.theme.size_unit.bold(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThemeError {
|
||||
serde_err: serde_json::error::Error,
|
||||
}
|
||||
|
||||
impl fmt::Display for ThemeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "failure to load theme")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ThemeError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(&self.serde_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::error::Error> for ThemeError {
|
||||
fn from(serde_err: serde_json::error::Error) -> Self {
|
||||
ThemeError { serde_err }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Theme {
|
||||
open_delimiter: ThemeColor,
|
||||
close_delimiter: ThemeColor,
|
||||
r#type: ThemeColor,
|
||||
identifier: ThemeColor,
|
||||
it_variable: ThemeColor,
|
||||
variable: ThemeColor,
|
||||
operator: ThemeColor,
|
||||
dot: ThemeColor,
|
||||
dot_dot: ThemeColor,
|
||||
internal_command: ThemeColor,
|
||||
external_command: ThemeColor,
|
||||
external_word: ThemeColor,
|
||||
bare_member: ThemeColor,
|
||||
string_member: ThemeColor,
|
||||
string: ThemeColor,
|
||||
path: ThemeColor,
|
||||
word: ThemeColor,
|
||||
keyword: ThemeColor,
|
||||
pipe: ThemeColor,
|
||||
glob_pattern: ThemeColor,
|
||||
flag: ThemeColor,
|
||||
shorthand_flag: ThemeColor,
|
||||
int: ThemeColor,
|
||||
decimal: ThemeColor,
|
||||
garbage: ThemeColor,
|
||||
whitespace: ThemeColor,
|
||||
separator: ThemeColor,
|
||||
comment: ThemeColor,
|
||||
size_number: ThemeColor,
|
||||
size_unit: ThemeColor,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ThemeColor(Color);
|
||||
|
||||
impl Deref for ThemeColor {
|
||||
type Target = Color;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ThemeColor {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str("TODO: IMPLEMENT SERIALIZATION")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ThemeColor {
|
||||
fn deserialize<D>(deserializer: D) -> Result<ThemeColor, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
ThemeColor::from_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeColor {
|
||||
fn from_str<E>(s: &str) -> Result<ThemeColor, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let mut bytes = s.bytes();
|
||||
let r = ThemeColor::xtoi(&mut bytes)?;
|
||||
let g = ThemeColor::xtoi(&mut bytes)?;
|
||||
let b = ThemeColor::xtoi(&mut bytes)?;
|
||||
Ok(ThemeColor(Color::RGB(r, g, b)))
|
||||
}
|
||||
|
||||
fn xtoi<E>(b: &mut Bytes) -> Result<u8, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let upper = b
|
||||
.next()
|
||||
.ok_or_else(|| E::custom("color string too short"))?;
|
||||
let lower = b
|
||||
.next()
|
||||
.ok_or_else(|| E::custom("color string too short"))?;
|
||||
let mut val = ThemeColor::numerical_value(upper)?;
|
||||
val = (val << 4) | ThemeColor::numerical_value(lower)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn numerical_value<E>(character: u8) -> Result<u8, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
match character {
|
||||
b'0'..=b'9' => Ok(character - b'0'),
|
||||
b'a'..=b'z' => Ok(character - (b'a' - 10)),
|
||||
_ => Err(E::custom(format!("invalid charater {}", character))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn single_style_span(style: Style, span: Span) -> Vec<Spanned<Style>> {
|
||||
vec![Spanned::<Style> { span, item: style }]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Palette, ThemedPalette};
|
||||
use ansi_term::Color;
|
||||
use nu_protocol::hir::FlatShape;
|
||||
use nu_source::{Span, Spanned};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn create_themed_palette() {
|
||||
let json = r#"
|
||||
{
|
||||
"open_delimiter": "a359cc",
|
||||
"close_delimiter": "a359cc",
|
||||
"type": "a359cc",
|
||||
"identifier": "a359cc",
|
||||
"it_variable": "a359cc",
|
||||
"variable": "a359cc",
|
||||
"operator": "a359cc",
|
||||
"dot": "a359cc",
|
||||
"dot_dot": "a359cc",
|
||||
"internal_command": "a359cc",
|
||||
"external_command": "a359cc",
|
||||
"external_word": "a359cc",
|
||||
"bare_member": "a359cc",
|
||||
"string_member": "a359cc",
|
||||
"string": "a359cc",
|
||||
"path": "a359cc",
|
||||
"word": "a359cc",
|
||||
"keyword": "a359cc",
|
||||
"pipe": "a359cc",
|
||||
"glob_pattern": "a359cc",
|
||||
"flag": "a359cc",
|
||||
"shorthand_flag": "a359cc",
|
||||
"int": "a359cc",
|
||||
"decimal": "a359cc",
|
||||
"garbage": "a359cc",
|
||||
"whitespace": "a359cc",
|
||||
"separator": "a359cc",
|
||||
"comment": "a359cc",
|
||||
"size_number": "a359cc",
|
||||
"size_unit": "a359cc"
|
||||
}"#;
|
||||
let mut json_reader = Cursor::new(json);
|
||||
let themed_palette = ThemedPalette::new(&mut json_reader).unwrap();
|
||||
let test_shape = Spanned {
|
||||
item: FlatShape::Type,
|
||||
span: Span::new(4, 9),
|
||||
};
|
||||
let styled = themed_palette.styles_for_shape(&test_shape);
|
||||
assert_eq!(styled.len(), 1);
|
||||
assert_eq!(
|
||||
styled[0],
|
||||
Spanned {
|
||||
item: Color::RGB(163, 89, 204).bold(),
|
||||
span: Span::new(4, 9),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user