enable theming of the command line syntax (#3606)

* enable theming of the command line syntax

* added missing flatshape, sorted flatshapes for easier reading.

* sorted flat shapes again and saved it this time

* added sample rwb.json syntax them file to docs
This commit is contained in:
Darren Schroeder 2021-06-11 14:17:43 -05:00 committed by GitHub
parent b9ca3b2039
commit 2846e3f5d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 275 additions and 95 deletions

View File

@ -368,6 +368,12 @@ pub enum Color {
Rgb(u8, u8, u8), Rgb(u8, u8, u8),
} }
impl Default for Color {
fn default() -> Self {
Color::White
}
}
impl Color { impl Color {
/// Returns a `Style` with the foreground color set to this color. /// Returns a `Style` with the foreground color set to this color.
/// ///

View File

@ -84,8 +84,13 @@ impl rustyline::highlight::Highlighter for Helper {
} }
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
let cfg = &self.context.configs.lock();
if let Some(palette) = &cfg.syntax_config {
Painter::paint_string(line, &self.context.scope, palette)
} else {
Painter::paint_string(line, &self.context.scope, &DefaultPalette {}) Painter::paint_string(line, &self.context.scope, &DefaultPalette {})
} }
}
fn highlight_char(&self, _line: &str, _pos: usize) -> bool { fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
true true

View File

@ -1,13 +1,14 @@
use std::path::Path; use crate::shell::palette::ThemedPalette;
use nu_data::config::NuConfig; use nu_data::config::NuConfig;
use nu_protocol::ConfigPath; use nu_protocol::ConfigPath;
use std::path::Path;
/// ConfigHolder holds information which configs have been loaded. /// ConfigHolder holds information which configs have been loaded.
#[derive(Clone)] #[derive(Clone)]
pub struct ConfigHolder { pub struct ConfigHolder {
pub global_config: Option<NuConfig>, pub global_config: Option<NuConfig>,
pub local_configs: Vec<NuConfig>, pub local_configs: Vec<NuConfig>,
pub syntax_config: Option<ThemedPalette>,
} }
impl Default for ConfigHolder { impl Default for ConfigHolder {
@ -21,6 +22,7 @@ impl ConfigHolder {
ConfigHolder { ConfigHolder {
global_config: None, global_config: None,
local_configs: vec![], local_configs: vec![],
syntax_config: None,
} }
} }
@ -31,6 +33,13 @@ impl ConfigHolder {
} }
} }
pub fn syntax_colors(&self) -> ThemedPalette {
match &self.syntax_config {
Some(cfg) => cfg.clone(),
None => ThemedPalette::default(),
}
}
pub fn add_local_cfg(&mut self, cfg: NuConfig) { pub fn add_local_cfg(&mut self, cfg: NuConfig) {
self.local_configs.push(cfg); self.local_configs.push(cfg);
} }
@ -39,6 +48,10 @@ impl ConfigHolder {
self.global_config = Some(cfg); self.global_config = Some(cfg);
} }
pub fn set_syntax_colors(&mut self, cfg: ThemedPalette) {
self.syntax_config = Some(cfg);
}
pub fn remove_cfg(&mut self, cfg_path: &ConfigPath) { pub fn remove_cfg(&mut self, cfg_path: &ConfigPath) {
match cfg_path { match cfg_path {
ConfigPath::Global(_) => self.global_config = None, ConfigPath::Global(_) => self.global_config = None,

View File

@ -1,4 +1,5 @@
use crate::evaluate::scope::{Scope, ScopeFrame}; use crate::evaluate::scope::{Scope, ScopeFrame};
use crate::shell::palette::ThemedPalette;
use crate::shell::shell_manager::ShellManager; use crate::shell::shell_manager::ShellManager;
use crate::whole_stream_command::Command; use crate::whole_stream_command::Command;
use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder}; use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder};
@ -13,6 +14,8 @@ use nu_source::{Span, Tag};
use nu_stream::InputStream; use nu_stream::InputStream;
use nu_test_support::NATIVE_PATH_ENV_VAR; use nu_test_support::NATIVE_PATH_ENV_VAR;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::fs::File;
use std::io::BufReader;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
@ -212,6 +215,51 @@ impl EvaluationContext {
} }
} }
// The syntax_theme is really the file stem of a json file i.e.
// grape.json is the theme file and grape is the file stem and
// the syntax_theme and grape.json would be located in the same
// folder as the config.toml
// Let's open the config
let global_config = self.configs.lock().global_config();
// Get the root syntax_theme value
let syntax_theme = global_config.var("syntax_theme");
// If we have a syntax_theme let's process it
if let Some(theme_value) = syntax_theme {
// Append the .json to the syntax_theme to form the file name
let syntax_theme_filename = format!("{}.json", theme_value.convert_to_string());
// Load the syntax config json
let config_file_path = cfg_path.get_path();
// The syntax file should be in the same location as the config.toml
let syntax_file_path = if config_file_path.ends_with("config.toml") {
config_file_path
.display()
.to_string()
.replace("config.toml", &syntax_theme_filename)
} else {
"".to_string()
};
// if we have a syntax_file_path use it otherwise default
if Path::new(&syntax_file_path).exists() {
// eprintln!("Loading syntax file: [{:?}]", syntax_file_path);
let syntax_theme_file = File::open(syntax_file_path)?;
let mut reader = BufReader::new(syntax_theme_file);
let theme = ThemedPalette::new(&mut reader).unwrap_or_default();
// eprintln!("Theme: [{:?}]", theme);
self.configs.lock().set_syntax_colors(theme);
} else {
// If the file was missing, use the default
self.configs
.lock()
.set_syntax_colors(ThemedPalette::default())
}
} else {
// if there's no syntax_theme, use the default
self.configs
.lock()
.set_syntax_colors(ThemedPalette::default())
};
if !startup_scripts.is_empty() { if !startup_scripts.is_empty() {
self.run_scripts(startup_scripts, cfg_path.get_path().parent()); self.run_scripts(startup_scripts, cfg_path.get_path().parent());
} }

View File

@ -7,52 +7,43 @@ use std::ops::Deref;
use std::str::Bytes; use std::str::Bytes;
use std::{fmt, io}; use std::{fmt, io};
// FIXME: find a good home, as nu-engine may be too core for styling
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>>;
} }
#[derive(Debug, Clone, Default)]
pub struct DefaultPalette {} pub struct DefaultPalette {}
impl Palette for DefaultPalette { impl Palette for DefaultPalette {
fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>> { fn styles_for_shape(&self, shape: &Spanned<FlatShape>) -> Vec<Spanned<Style>> {
match &shape.item { match &shape.item {
FlatShape::OpenDelimiter(_) => single_style_span(Color::White.normal(), shape.span), FlatShape::BareMember => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::CloseDelimiter(_) => single_style_span(Color::White.normal(), shape.span), FlatShape::CloseDelimiter(_) => single_style_span(Color::White.normal(), shape.span),
FlatShape::ItVariable | FlatShape::Keyword => { FlatShape::Comment => single_style_span(Color::Green.bold(), shape.span),
single_style_span(Color::Purple.bold(), shape.span) FlatShape::Decimal => single_style_span(Color::Purple.bold(), shape.span),
} FlatShape::Dot => single_style_span(Style::new().fg(Color::White), shape.span),
FlatShape::Variable | FlatShape::Identifier => { FlatShape::DotDot => single_style_span(Color::Yellow.bold(), shape.span),
single_style_span(Color::Purple.normal(), shape.span)
}
FlatShape::Type => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Operator => single_style_span(Color::Yellow.normal(), shape.span),
FlatShape::DotDotLeftAngleBracket => { FlatShape::DotDotLeftAngleBracket => {
single_style_span(Color::Yellow.bold(), shape.span) single_style_span(Color::Yellow.bold(), shape.span)
} }
FlatShape::DotDot => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::Dot => single_style_span(Style::new().fg(Color::White), shape.span),
FlatShape::InternalCommand => single_style_span(Color::Cyan.bold(), shape.span),
FlatShape::ExternalCommand => single_style_span(Color::Cyan.normal(), shape.span), FlatShape::ExternalCommand => single_style_span(Color::Cyan.normal(), shape.span),
FlatShape::ExternalWord => single_style_span(Color::Green.bold(), shape.span), FlatShape::ExternalWord => single_style_span(Color::Green.bold(), shape.span),
FlatShape::BareMember => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::StringMember => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::String => single_style_span(Color::Green.normal(), shape.span),
FlatShape::Path => single_style_span(Color::Cyan.normal(), shape.span),
FlatShape::GlobPattern => single_style_span(Color::Cyan.bold(), shape.span),
FlatShape::Word => single_style_span(Color::Green.normal(), shape.span),
FlatShape::Pipe => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Flag => single_style_span(Color::Blue.bold(), shape.span), FlatShape::Flag => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::ShorthandFlag => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Int => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Decimal => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Whitespace | FlatShape::Separator => {
single_style_span(Color::White.normal(), shape.span)
}
FlatShape::Comment => single_style_span(Color::Green.bold(), shape.span),
FlatShape::Garbage => { FlatShape::Garbage => {
single_style_span(Style::new().fg(Color::White).on(Color::Red), shape.span) single_style_span(Style::new().fg(Color::White).on(Color::Red), shape.span)
} }
FlatShape::GlobPattern => single_style_span(Color::Cyan.bold(), shape.span),
FlatShape::Identifier => single_style_span(Color::Purple.normal(), shape.span),
FlatShape::Int => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::InternalCommand => single_style_span(Color::Cyan.bold(), shape.span),
FlatShape::ItVariable => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Keyword => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::OpenDelimiter(_) => single_style_span(Color::White.normal(), shape.span),
FlatShape::Operator => single_style_span(Color::Yellow.normal(), shape.span),
FlatShape::Path => single_style_span(Color::Cyan.normal(), shape.span),
FlatShape::Pipe => single_style_span(Color::Purple.bold(), shape.span),
FlatShape::Separator => single_style_span(Color::White.normal(), shape.span),
FlatShape::ShorthandFlag => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Size { number, unit } => vec![ FlatShape::Size { number, unit } => vec![
Spanned::<Style> { Spanned::<Style> {
span: *number, span: *number,
@ -63,21 +54,31 @@ impl Palette for DefaultPalette {
item: Color::Cyan.bold(), item: Color::Cyan.bold(),
}, },
], ],
FlatShape::String => single_style_span(Color::Green.normal(), shape.span),
FlatShape::StringMember => single_style_span(Color::Yellow.bold(), shape.span),
FlatShape::Type => single_style_span(Color::Blue.bold(), shape.span),
FlatShape::Variable => single_style_span(Color::Purple.normal(), shape.span),
FlatShape::Whitespace => single_style_span(Color::White.normal(), shape.span),
FlatShape::Word => single_style_span(Color::Green.normal(), shape.span),
} }
} }
} }
#[derive(Debug, Clone, Default)]
pub struct ThemedPalette { pub struct ThemedPalette {
theme: Theme, theme: Theme,
} }
impl ThemedPalette { 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> { pub fn new<R: io::Read>(reader: &mut R) -> Result<ThemedPalette, ThemeError> {
let theme = serde_json::from_reader(reader)?; let theme = serde_json::from_reader(reader)?;
Ok(ThemedPalette { theme }) Ok(ThemedPalette { theme })
} }
pub fn default() -> ThemedPalette {
let theme = Theme::default();
ThemedPalette { theme }
}
} }
impl Palette for ThemedPalette { impl Palette for ThemedPalette {
@ -168,41 +169,114 @@ impl From<serde_json::error::Error> for ThemeError {
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
struct Theme { struct Theme {
open_delimiter: ThemeColor, bare_member: ThemeColor,
close_delimiter: ThemeColor, close_delimiter: ThemeColor,
r#type: ThemeColor, comment: ThemeColor,
identifier: ThemeColor, decimal: ThemeColor,
it_variable: ThemeColor,
variable: ThemeColor,
operator: ThemeColor,
dot: ThemeColor, dot: ThemeColor,
dot_dot: ThemeColor, dot_dot: ThemeColor,
internal_command: ThemeColor, dot_dot_left_angle_bracket: ThemeColor,
external_command: ThemeColor, external_command: ThemeColor,
external_word: 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, flag: ThemeColor,
shorthand_flag: ThemeColor,
int: ThemeColor,
decimal: ThemeColor,
garbage: ThemeColor, garbage: ThemeColor,
whitespace: ThemeColor, glob_pattern: ThemeColor,
identifier: ThemeColor,
int: ThemeColor,
internal_command: ThemeColor,
it_variable: ThemeColor,
keyword: ThemeColor,
open_delimiter: ThemeColor,
operator: ThemeColor,
path: ThemeColor,
pipe: ThemeColor,
separator: ThemeColor, separator: ThemeColor,
comment: ThemeColor, shorthand_flag: ThemeColor,
size_number: ThemeColor, size_number: ThemeColor,
size_unit: ThemeColor, size_unit: ThemeColor,
string: ThemeColor,
string_member: ThemeColor,
r#type: ThemeColor,
variable: ThemeColor,
whitespace: ThemeColor,
word: ThemeColor,
} }
#[derive(Debug)] impl Default for Theme {
fn default() -> Self {
Theme {
bare_member: ThemeColor(Color::Yellow),
close_delimiter: ThemeColor(Color::White),
comment: ThemeColor(Color::Green),
decimal: ThemeColor(Color::Purple),
dot: ThemeColor(Color::White),
dot_dot: ThemeColor(Color::Yellow),
dot_dot_left_angle_bracket: ThemeColor(Color::Yellow),
external_command: ThemeColor(Color::Cyan),
external_word: ThemeColor(Color::Green),
flag: ThemeColor(Color::Blue),
garbage: ThemeColor(Color::Red),
glob_pattern: ThemeColor(Color::Cyan),
identifier: ThemeColor(Color::Purple),
int: ThemeColor(Color::Purple),
internal_command: ThemeColor(Color::Cyan),
it_variable: ThemeColor(Color::Purple),
keyword: ThemeColor(Color::Purple),
open_delimiter: ThemeColor(Color::White),
operator: ThemeColor(Color::Yellow),
path: ThemeColor(Color::Cyan),
pipe: ThemeColor(Color::Purple),
separator: ThemeColor(Color::Red),
shorthand_flag: ThemeColor(Color::Blue),
size_number: ThemeColor(Color::Purple),
size_unit: ThemeColor(Color::Cyan),
string: ThemeColor(Color::Green),
string_member: ThemeColor(Color::Yellow),
r#type: ThemeColor(Color::Blue),
variable: ThemeColor(Color::Purple),
whitespace: ThemeColor(Color::White),
word: ThemeColor(Color::Green),
// These should really be Syles and not colors
// leave this here for the next change to make
// ThemeColor, ThemeStyle.
// open_delimiter: ThemeColor(Color::White.normal()),
// close_delimiter: ThemeColor(Color::White.normal()),
// it_variable: ThemeColor(Color::Purple.bold()),
// variable: ThemeColor(Color::Purple.normal()),
// r#type: ThemeColor(Color::Blue.bold()),
// identifier: ThemeColor(Color::Purple.normal()),
// operator: ThemeColor(Color::Yellow.normal()),
// dot: ThemeColor(Color::White),
// dot_dot: ThemeColor(Color::Yellow.bold()),
// //missing DotDotLeftAngleBracket
// internal_command: ThemeColor(Color::Cyan.bold()),
// external_command: ThemeColor(Color::Cyan.normal()),
// external_word: ThemeColor(Color::Green.bold()),
// bare_member: ThemeColor(Color::Yellow.bold()),
// string: ThemeColor(Color::Green.normal()),
// string_member: ThemeColor(Color::Yellow.bold()),
// path: ThemeColor(Color::Cyan.normal()),
// glob_pattern: ThemeColor(Color::Cyan.bold()),
// word: ThemeColor(Color::Green.normal()),
// keyword: ThemeColor(Color::Purple.bold()),
// pipe: ThemeColor(Color::Purple.bold()),
// flag: ThemeColor(Color::Blue.bold()),
// shorthand_flag: ThemeColor(Color::Blue.bold()),
// int: ThemeColor(Color::Purple.bold()),
// decimal: ThemeColor(Color::Purple.bold()),
// garbage: ThemeColor(Style::new().fg(Color::White).on(Color::Red)),
// whitespace: ThemeColor(Color::White.normal()),
// separator: ThemeColor(Color::Red),
// comment: ThemeColor(Color::Green.bold()),
// size_number: ThemeColor(Color::Purple.bold()),
// size_unit: ThemeColor(Color::Cyan.bold()),
}
}
}
#[derive(Debug, Clone, Default)]
struct ThemeColor(Color); struct ThemeColor(Color);
impl Deref for ThemeColor { impl Deref for ThemeColor {
@ -287,36 +361,37 @@ mod tests {
fn create_themed_palette() { fn create_themed_palette() {
let json = r#" let json = r#"
{ {
"open_delimiter": "a359cc", "bare_member": "a359cc",
"close_delimiter": "a359cc", "close_delimiter": "a359cc",
"type": "a359cc", "comment": "a359cc",
"identifier": "a359cc", "decimal": "a359cc",
"it_variable": "a359cc",
"variable": "a359cc",
"operator": "a359cc",
"dot": "a359cc", "dot": "a359cc",
"dot_dot": "a359cc", "dot_dot": "a359cc",
"internal_command": "a359cc", "dot_dot_left_angle_bracket": "a359cc",
"external_command": "a359cc", "external_command": "a359cc",
"external_word": "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", "flag": "a359cc",
"shorthand_flag": "a359cc",
"int": "a359cc",
"decimal": "a359cc",
"garbage": "a359cc", "garbage": "a359cc",
"whitespace": "a359cc", "glob_pattern": "a359cc",
"identifier": "a359cc",
"int": "a359cc",
"internal_command": "a359cc",
"it_variable": "a359cc",
"keyword": "a359cc",
"open_delimiter": "a359cc",
"operator": "a359cc",
"path": "a359cc",
"pipe": "a359cc",
"separator": "a359cc", "separator": "a359cc",
"comment": "a359cc", "shorthand_flag": "a359cc",
"size_number": "a359cc", "size_number": "a359cc",
"size_unit": "a359cc" "size_unit": "a359cc",
"string": "a359cc",
"string_member": "a359cc",
"type": "a359cc",
"variable": "a359cc",
"whitespace": "a359cc",
"word": "a359cc"
}"#; }"#;
let mut json_reader = Cursor::new(json); let mut json_reader = Cursor::new(json);
let themed_palette = ThemedPalette::new(&mut json_reader).unwrap(); let themed_palette = ThemedPalette::new(&mut json_reader).unwrap();

View File

@ -1461,36 +1461,36 @@ pub enum Delimiter {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum FlatShape { pub enum FlatShape {
OpenDelimiter(Delimiter), BareMember,
CloseDelimiter(Delimiter), CloseDelimiter(Delimiter),
Type, Comment,
Identifier, Decimal,
ItVariable,
Variable,
Operator,
Dot, Dot,
DotDot, DotDot,
DotDotLeftAngleBracket, DotDotLeftAngleBracket,
InternalCommand,
ExternalCommand, ExternalCommand,
ExternalWord, ExternalWord,
BareMember,
StringMember,
String,
Path,
Word,
Keyword,
Pipe,
GlobPattern,
Flag, Flag,
ShorthandFlag,
Int,
Decimal,
Garbage, Garbage,
Whitespace, GlobPattern,
Identifier,
Int,
InternalCommand,
ItVariable,
Keyword,
OpenDelimiter(Delimiter),
Operator,
Path,
Pipe,
Separator, Separator,
Comment, ShorthandFlag,
Size { number: Span, unit: Span }, Size { number: Span, unit: Span },
String,
StringMember,
Type,
Variable,
Whitespace,
Word,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -0,0 +1,33 @@
{
"bare_member": "ff0000",
"close_delimiter": "ffffff",
"comment": "ff0000",
"decimal": "0000ff",
"dot": "ffffff",
"dot_dot": "0000ff",
"dot_dot_left_angle_bracket": "00ff00",
"external_command": "ffffff",
"external_word": "0000ff",
"flag": "0000ff",
"garbage": "ff0000",
"glob_pattern": "ffffff",
"identifier": "ff0000",
"int": "ffffff",
"internal_command": "ff0000",
"it_variable": "ffffff",
"keyword": "0000ff",
"open_delimiter": "ff0000",
"operator": "ff0000",
"path": "ff0000",
"pipe": "ff0000",
"separator": "0000ff",
"shorthand_flag": "ff0000",
"size_number": "ffffff",
"size_unit": "0000ff",
"string": "0000ff",
"string_member": "ffffff",
"type": "0000ff",
"variable": "0000ff",
"whitespace": "ffffff",
"word": "ffffff"
}