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:
Kurtis 2020-06-25 21:40:12 -07:00 committed by GitHub
parent 8dfc90a322
commit 80ce8acf57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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),
},
);
}
}