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 ansi_term::{Color, Style};
|
||||||
use nu_protocol::hir::FlatShape;
|
use nu_protocol::hir::FlatShape;
|
||||||
use nu_source::{Span, Spanned};
|
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 {
|
pub trait Palette {
|
||||||
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>>;
|
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>> {
|
fn single_style_span(style: Style, span: Span) -> Vec<Spanned<Style>> {
|
||||||
vec![Spanned::<Style> { span, item: 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