Support custom syntax highlighting themes (#1499)

Related to #419

Gruvbox tmTheme added to test_site, it is taken from
https://github.com/Colorsublime/Colorsublime-Themes (MIT licensed)
This commit is contained in:
David 2021-09-13 20:08:48 +01:00 committed by GitHub
parent f0b131838f
commit 23064f57c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 556 additions and 51 deletions

View File

@ -1,9 +1,15 @@
use std::path::Path; use std::{path::Path, sync::Arc};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use syntect::parsing::{SyntaxSet, SyntaxSetBuilder}; use syntect::{
highlighting::{Theme, ThemeSet},
html::css_for_theme_with_class_style,
parsing::{SyntaxSet, SyntaxSetBuilder},
};
use errors::Result; use errors::{bail, Result};
use crate::highlighting::{CLASS_STYLE, THEME_SET};
pub const DEFAULT_HIGHLIGHT_THEME: &str = "base16-ocean-dark"; pub const DEFAULT_HIGHLIGHT_THEME: &str = "base16-ocean-dark";
@ -43,26 +49,92 @@ pub struct Markdown {
pub external_links_no_referrer: bool, pub external_links_no_referrer: bool,
/// Whether smart punctuation is enabled (changing quotes, dashes, dots etc in their typographic form) /// Whether smart punctuation is enabled (changing quotes, dashes, dots etc in their typographic form)
pub smart_punctuation: bool, pub smart_punctuation: bool,
/// A list of directories to search for additional `.sublime-syntax` and `.tmTheme` files in.
/// A list of directories to search for additional `.sublime-syntax` files in. pub extra_syntaxes_and_themes: Vec<String>,
pub extra_syntaxes: Vec<String>,
/// The compiled extra syntaxes into a syntax set /// The compiled extra syntaxes into a syntax set
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are need #[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are need
pub extra_syntax_set: Option<SyntaxSet>, pub extra_syntax_set: Option<SyntaxSet>,
/// The compiled extra themes into a theme set
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are need
pub extra_theme_set: Arc<Option<ThemeSet>>,
} }
impl Markdown { impl Markdown {
/// Attempt to load any extra syntax found in the extra syntaxes of the config /// Gets the configured highlight theme from the THEME_SET or the config's extra_theme_set
pub fn load_extra_syntaxes(&mut self, base_path: &Path) -> Result<()> { /// Returns None if the configured highlighting theme is set to use css
if self.extra_syntaxes.is_empty() { pub fn get_highlight_theme(&self) -> Option<&Theme> {
return Ok(()); if self.highlight_theme == "css" {
None
} else {
Some(self.get_highlight_theme_by_name(&self.highlight_theme))
}
}
/// Gets an arbitrary theme from the THEME_SET or the extra_theme_set
pub fn get_highlight_theme_by_name<'config>(&'config self, theme_name: &str) -> &'config Theme {
(*self.extra_theme_set)
.as_ref()
.and_then(|ts| ts.themes.get(theme_name))
.unwrap_or_else(|| &THEME_SET.themes[theme_name])
}
/// Attempt to load any extra syntaxes and themes found in the extra_syntaxes_and_themes folders
pub fn load_extra_syntaxes_and_highlight_themes(
&self,
base_path: &Path,
) -> Result<(Option<SyntaxSet>, Option<ThemeSet>)> {
if self.extra_syntaxes_and_themes.is_empty() {
return Ok((None, None));
} }
let mut ss = SyntaxSetBuilder::new(); let mut ss = SyntaxSetBuilder::new();
for dir in &self.extra_syntaxes { let mut ts = ThemeSet::new();
for dir in &self.extra_syntaxes_and_themes {
ss.add_from_folder(base_path.join(dir), true)?; ss.add_from_folder(base_path.join(dir), true)?;
ts.add_from_folder(base_path.join(dir))?;
}
let ss = ss.build();
Ok((
if ss.syntaxes().is_empty() { None } else { Some(ss) },
if ts.themes.is_empty() { None } else { Some(ts) },
))
}
pub fn export_theme_css(&self, theme_name: &str) -> String {
let theme = self.get_highlight_theme_by_name(theme_name);
css_for_theme_with_class_style(theme, CLASS_STYLE)
}
pub fn init_extra_syntaxes_and_highlight_themes(&mut self, path: &Path) -> Result<()> {
if self.highlight_theme == "css" {
return Ok(());
}
let (loaded_extra_syntaxes, loaded_extra_highlight_themes) =
self.load_extra_syntaxes_and_highlight_themes(path)?;
if let Some(extra_syntax_set) = loaded_extra_syntaxes {
self.extra_syntax_set = Some(extra_syntax_set);
}
if let Some(extra_theme_set) = loaded_extra_highlight_themes {
self.extra_theme_set = Arc::new(Some(extra_theme_set));
}
// validate that the chosen highlight_theme exists in the loaded highlight theme sets
if !THEME_SET.themes.contains_key(&self.highlight_theme) {
if let Some(extra) = &*self.extra_theme_set {
if !extra.themes.contains_key(&self.highlight_theme) {
bail!(
"Highlight theme {} not found in the extra theme set",
self.highlight_theme
)
}
} else {
bail!("Highlight theme {} not available.\n\
You can load custom themes by configuring `extra_syntaxes_and_themes` to include a list of folders containing '.tmTheme' files", self.highlight_theme)
}
} }
self.extra_syntax_set = Some(ss.build());
Ok(()) Ok(())
} }
@ -110,8 +182,9 @@ impl Default for Markdown {
external_links_no_follow: false, external_links_no_follow: false,
external_links_no_referrer: false, external_links_no_referrer: false,
smart_punctuation: false, smart_punctuation: false,
extra_syntaxes: Vec::new(), extra_syntaxes_and_themes: vec![],
extra_syntax_set: None, extra_syntax_set: None,
extra_theme_set: Arc::new(None),
} }
} }
} }

View File

@ -12,7 +12,6 @@ use globset::{Glob, GlobSet, GlobSetBuilder};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use toml::Value as Toml; use toml::Value as Toml;
use crate::highlighting::THEME_SET;
use crate::theme::Theme; use crate::theme::Theme;
use errors::{bail, Error, Result}; use errors::{bail, Error, Result};
use utils::fs::read_file; use utils::fs::read_file;
@ -106,6 +105,7 @@ pub struct SerializedConfig<'a> {
} }
impl Config { impl Config {
// any extra syntax and highlight themes have been loaded and validated already by the from_file method before parsing the config
/// Parses a string containing TOML to our Config struct /// Parses a string containing TOML to our Config struct
/// Any extra parameter will end up in the extra field /// Any extra parameter will end up in the extra field
pub fn parse(content: &str) -> Result<Config> { pub fn parse(content: &str) -> Result<Config> {
@ -118,15 +118,6 @@ impl Config {
bail!("A base URL is required in config.toml with key `base_url`"); bail!("A base URL is required in config.toml with key `base_url`");
} }
if config.markdown.highlight_theme != "css"
&& !THEME_SET.themes.contains_key(&config.markdown.highlight_theme)
{
bail!(
"Highlight theme {} defined in config does not exist.",
config.markdown.highlight_theme
);
}
languages::validate_code(&config.default_language)?; languages::validate_code(&config.default_language)?;
for code in config.languages.keys() { for code in config.languages.keys() {
languages::validate_code(code)?; languages::validate_code(code)?;
@ -166,7 +157,16 @@ impl Config {
let path = path.as_ref(); let path = path.as_ref();
let content = let content =
read_file(path).map_err(|e| errors::Error::chain("Failed to load config", e))?; read_file(path).map_err(|e| errors::Error::chain("Failed to load config", e))?;
Config::parse(&content)
let mut config = Config::parse(&content)?;
let config_dir = path
.parent()
.ok_or(Error::msg("Failed to find directory containing the config file."))?;
// this is the step at which missing extra syntax and highlighting themes are raised as errors
config.markdown.init_extra_syntaxes_and_highlight_themes(config_dir)?;
Ok(config)
} }
/// Makes a url, taking into account that the base url might have a trailing slash /// Makes a url, taking into account that the base url might have a trailing slash

View File

@ -1,10 +1,12 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use syntect::dumps::from_binary; use syntect::dumps::from_binary;
use syntect::highlighting::{Theme, ThemeSet}; use syntect::highlighting::{Theme, ThemeSet};
use syntect::html::ClassStyle;
use syntect::parsing::{SyntaxReference, SyntaxSet}; use syntect::parsing::{SyntaxReference, SyntaxSet};
use crate::config::Config; use crate::config::Config;
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
pub const CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "z-" };
lazy_static! { lazy_static! {
pub static ref SYNTAX_SET: SyntaxSet = { pub static ref SYNTAX_SET: SyntaxSet = {
@ -16,8 +18,6 @@ lazy_static! {
from_binary(include_bytes!("../../../sublime/themes/all.themedump")); from_binary(include_bytes!("../../../sublime/themes/all.themedump"));
} }
pub const CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "z-" };
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum HighlightSource { pub enum HighlightSource {
/// One of the built-in Zola syntaxes /// One of the built-in Zola syntaxes
@ -42,11 +42,7 @@ pub fn resolve_syntax_and_theme<'config>(
language: Option<&'_ str>, language: Option<&'_ str>,
config: &'config Config, config: &'config Config,
) -> SyntaxAndTheme<'config> { ) -> SyntaxAndTheme<'config> {
let theme = if config.markdown.highlight_theme != "css" { let theme = config.markdown.get_highlight_theme();
Some(&THEME_SET.themes[&config.markdown.highlight_theme])
} else {
None
};
if let Some(ref lang) = language { if let Some(ref lang) = language {
if let Some(ref extra_syntaxes) = config.markdown.extra_syntax_set { if let Some(ref extra_syntaxes) = config.markdown.extra_syntax_set {
@ -88,8 +84,3 @@ pub fn resolve_syntax_and_theme<'config>(
} }
} }
} }
pub fn export_theme_css(theme_name: &str) -> String {
let theme = &THEME_SET.themes[theme_name];
css_for_theme_with_class_style(theme, CLASS_STYLE)
}

View File

@ -106,10 +106,11 @@ fn bench_render_content_without_highlighting(b: &mut test::Bencher) {
let mut config = Config::default(); let mut config = Config::default();
config.markdown.highlight_code = false; config.markdown.highlight_code = false;
let current_page_permalink = ""; let current_page_permalink = "";
let lang = "";
let context = RenderContext::new( let context = RenderContext::new(
&tera, &tera,
&config, &config,
"", lang,
current_page_permalink, current_page_permalink,
&permalinks_ctx, &permalinks_ctx,
InsertAnchor::None, InsertAnchor::None,
@ -117,7 +118,6 @@ fn bench_render_content_without_highlighting(b: &mut test::Bencher) {
b.iter(|| render_content(CONTENT, &context).unwrap()); b.iter(|| render_content(CONTENT, &context).unwrap());
} }
#[bench]
fn bench_render_content_no_shortcode(b: &mut test::Bencher) { fn bench_render_content_no_shortcode(b: &mut test::Bencher) {
let tera = Tera::default(); let tera = Tera::default();
let content2 = CONTENT.replace(r#"{{ youtube(id="my_youtube_id") }}"#, ""); let content2 = CONTENT.replace(r#"{{ youtube(id="my_youtube_id") }}"#, "");
@ -125,10 +125,11 @@ fn bench_render_content_no_shortcode(b: &mut test::Bencher) {
config.markdown.highlight_code = false; config.markdown.highlight_code = false;
let permalinks_ctx = HashMap::new(); let permalinks_ctx = HashMap::new();
let current_page_permalink = ""; let current_page_permalink = "";
let lang = "";
let context = RenderContext::new( let context = RenderContext::new(
&tera, &tera,
&config, &config,
"", lang,
current_page_permalink, current_page_permalink,
&permalinks_ctx, &permalinks_ctx,
InsertAnchor::None, InsertAnchor::None,
@ -144,16 +145,15 @@ fn bench_render_shortcodes_one_present(b: &mut test::Bencher) {
let config = Config::default(); let config = Config::default();
let permalinks_ctx = HashMap::new(); let permalinks_ctx = HashMap::new();
let current_page_permalink = ""; let current_page_permalink = "";
let lang = "";
let context = RenderContext::new( let context = RenderContext::new(
&tera, &tera,
&config, &config,
"", lang,
current_page_permalink, current_page_permalink,
&permalinks_ctx, &permalinks_ctx,
InsertAnchor::None, InsertAnchor::None,
); );
b.iter(|| render_shortcodes(CONTENT, &context));
} }
#[bench] #[bench]
@ -165,10 +165,11 @@ fn bench_render_content_no_shortcode_with_emoji(b: &mut test::Bencher) {
config.markdown.render_emoji = true; config.markdown.render_emoji = true;
let permalinks_ctx = HashMap::new(); let permalinks_ctx = HashMap::new();
let current_page_permalink = ""; let current_page_permalink = "";
let lang = "";
let context = RenderContext::new( let context = RenderContext::new(
&tera, &tera,
&config, &config,
"", lang,
current_page_permalink, current_page_permalink,
&permalinks_ctx, &permalinks_ctx,
InsertAnchor::None, InsertAnchor::None,

View File

@ -26,7 +26,7 @@ pub(crate) struct ClassHighlighter<'config> {
} }
impl<'config> ClassHighlighter<'config> { impl<'config> ClassHighlighter<'config> {
pub fn new(syntax: &'config SyntaxReference, syntax_set: &'config SyntaxSet) -> Self { pub fn new(syntax: &SyntaxReference, syntax_set: &'config SyntaxSet) -> Self {
let parse_state = ParseState::new(syntax); let parse_state = ParseState::new(syntax);
Self { syntax_set, open_spans: 0, parse_state, scope_stack: ScopeStack::new() } Self { syntax_set, open_spans: 0, parse_state, scope_stack: ScopeStack::new() }
} }

View File

@ -14,7 +14,6 @@ use rayon::prelude::*;
use tera::{Context, Tera}; use tera::{Context, Tera};
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
use config::highlighting::export_theme_css;
use config::{get_config, Config}; use config::{get_config, Config};
use errors::{bail, Error, Result}; use errors::{bail, Error, Result};
use front_matter::InsertAnchor; use front_matter::InsertAnchor;
@ -74,7 +73,7 @@ impl Site {
let path = path.as_ref(); let path = path.as_ref();
let config_file = config_file.as_ref(); let config_file = config_file.as_ref();
let mut config = get_config(config_file)?; let mut config = get_config(config_file)?;
config.markdown.load_extra_syntaxes(path)?; config.markdown.load_extra_syntaxes_and_highlight_themes(path)?;
if let Some(theme) = config.theme.clone() { if let Some(theme) = config.theme.clone() {
// Grab data from the extra section of the theme // Grab data from the extra section of the theme
@ -691,7 +690,7 @@ impl Site {
for t in &self.config.markdown.highlight_themes_css { for t in &self.config.markdown.highlight_themes_css {
let p = self.static_path.join(&t.filename); let p = self.static_path.join(&t.filename);
if !p.exists() { if !p.exists() {
let content = export_theme_css(&t.theme); let content = &self.config.markdown.export_theme_css(&t.theme);
create_file(&p, &content)?; create_file(&p, &content)?;
} }
} }

View File

@ -150,7 +150,7 @@ Here is a full list of supported languages and their short names:
Note: due to some issues with the JavaScript syntax, the TypeScript syntax will be used instead. Note: due to some issues with the JavaScript syntax, the TypeScript syntax will be used instead.
If you want to highlight a language not on this list, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola). If you want to highlight a language not on this list, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola).
Alternatively, the `extra_syntaxes` configuration option can be used to add additional syntax files. Alternatively, the `extra_syntaxes_and_themes` configuration option can be used to add additional syntax (and theme) files.
If your site source is laid out as follows: If your site source is laid out as follows:
@ -169,7 +169,7 @@ If your site source is laid out as follows:
└── ... └── ...
``` ```
you would set your `extra_syntaxes` to `["syntaxes", "syntaxes/Sublime-Language1"]` to load `lang1.sublime-syntax` and `lang2.sublime-syntax`. you would set your `extra_syntaxes_and_themes` to `["syntaxes", "syntaxes/Sublime-Language1"]` to load `lang1.sublime-syntax` and `lang2.sublime-syntax`.
## Inline VS classed highlighting ## Inline VS classed highlighting
@ -347,3 +347,40 @@ Line 2 and 7 are comments that are not shown in the final output.
When line numbers are active, the code block is turned into a table with one row and two cells. The first cell contains the line number and the second cell contains the code. When line numbers are active, the code block is turned into a table with one row and two cells. The first cell contains the line number and the second cell contains the code.
Highlights are done via the `<mark>` HTML tag. When a line with line number is highlighted two `<mark>` tags are created: one around the line number(s) and one around the code. Highlights are done via the `<mark>` HTML tag. When a line with line number is highlighted two `<mark>` tags are created: one around the line number(s) and one around the code.
## Custom Highlighting Themes
The default *theme* for syntax highlighting is called `base16-ocean-dark`, you can choose another theme from the built in set of highlight themes using the `highlight_theme` configuration option.
For example, this documentation site currently uses the `kronuz` theme, which is built in.
```
[markdown]
highlight_code = true
highlight_theme = "kronuz"
```
Alternatively, the `extra_syntaxes_and_themes` configuration option can be used to add additional theme files.
You can load your own highlight theme from a TextMate `.tmTheme` file.
It works the same way as adding extra syntaxes. It should contain a list of paths to folders containing the .tmTheme files you want to include.
You would then set `highlight_theme` to the name of one of these files, without the `.tmTheme` extension.
If your site source is laid out as follows:
```
.
├── config.toml
├── content/
│   └── ...
├── static/
│   └── ...
├── highlight_themes/
│   ├── MyGroovyTheme/
│   │   └── theme1.tmTheme
│   ├── theme2.tmTheme
└── templates/
└── ...
```
you would set your `extra_highlight_themes` to `["highlight_themes", "highlight_themes/MyGroovyTheme"]` to load `theme1.tmTheme` and `theme2.tmTheme`.
Then choose one of them to use, say theme1, by setting `highlight_theme = theme1`.

View File

@ -236,6 +236,9 @@ Zola currently has the following highlight themes available:
Zola uses the Sublime Text themes, making it very easy to add more. Zola uses the Sublime Text themes, making it very easy to add more.
If you want a theme not listed above, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola). If you want a theme not listed above, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola).
Alternatively you can use the `extra_syntaxes_and_themes` configuration option to load your own custom themes from a .tmTheme file.
See [Syntax Highlighting](@/syntax-highlighting.md) for more details.
## Slugification strategies ## Slugification strategies
By default, Zola will turn every path, taxonomies and anchors to a slug, an ASCII representation with no special characters. By default, Zola will turn every path, taxonomies and anchors to a slug, an ASCII representation with no special characters.

View File

@ -13,7 +13,8 @@ ignored_content = ["*/ignored.md"]
[markdown] [markdown]
highlight_code = true highlight_code = true
extra_syntaxes = ["syntaxes"] highlight_theme = "custom_gruvbox"
extra_syntaxes_and_themes = ["syntaxes", "highlight_themes"]
[slugify] [slugify]
paths = "on" paths = "on"

View File

@ -10,6 +10,12 @@ for (int i = 0; ; i++ ) {
} }
``` ```
```
for (int i = 0; ; i++ ) {
if (i < 10)
}
```
```c ```c
for (int i = 0; ; i++ ) { for (int i = 0; ; i++ ) {
if (i < 10) if (i < 10)

View File

@ -0,0 +1,394 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Gruvbox-N</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#1a1a1a</string>
<key>caret</key>
<string>#908476</string>
<key>foreground</key>
<string>#EAD4AF</string>
<key>invisibles</key>
<string>#3B3836</string>
<key>lineHighlight</key>
<string>#3B3836</string>
<key>selection</key>
<string>#3B3836</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#908476</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#AAB11E</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Separator</string>
<key>scope</key>
<string>punctuation.separator.key-value</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CF8498</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Constant</string>
<key>scope</key>
<string>constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CC869B</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#EAD4AF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Other variable objct</string>
<key>scope</key>
<string>variable.other.object</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CAB990</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Other variable class</string>
<key>scope</key>
<string>variable.other.class, variable.other.constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#F1C050</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Object property</string>
<key>scope</key>
<string>meta.property.object, entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#EAD4AF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Arrows</string>
<key>scope</key>
<string>meta.function, meta.function.static.arrow, meta.function.arrow</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#EAD4AF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<string>keyword, string.regexp punctuation.definition</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FB4938</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<string>storage, storage.type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FB4938</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inline link</string>
<key>scope</key>
<string>markup.underline.link</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FB4938</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class name</string>
<key>scope</key>
<string>entity.name.class, entity.name.type.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#BABC52</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inherited class</string>
<key>scope</key>
<string>entity.other.inherited-class, tag.decorator, tag.decorator entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7BA093</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function name</string>
<key>scope</key>
<string>entity.name.function, meta.function entity.name.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8AB572</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function argument</string>
<key>scope</key>
<string>variable.parameter, meta.function storage.type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FD971F</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag name</string>
<key>scope</key>
<string>entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FB4938</string>
<key>fontStyle</key>
<string> italic </string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8AB572</string>
<key>fontStyle</key>
<string> italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library class/type</string>
<key>scope</key>
<string>support.type, support.class, support.function, variable.language, support.constant, string.regexp keyword.control</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#F1C050</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Template string element</string>
<key>scope</key>
<string>punctuation.template-string.element, string.regexp punctuation.definition.group, constant.character.escape</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8AB572</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FB4938</string>
<key>fontStyle</key>
<string />
<key>foreground</key>
<string>#F8F8F0</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid deprecated</string>
<key>scope</key>
<string>invalid.deprecated</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FD971F</string>
<key>foreground</key>
<string>#F8F8F0</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Operator</string>
<key>scope</key>
<string>keyword.operator, keyword.operator.logical, meta.property-name, meta.brace, punctuation.definition.parameters.begin, punctuation.definition.parameters.end, keyword.other.parenthesis</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CAB990</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Special operator</string>
<key>scope</key>
<string>keyword.operator.ternary</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7BA093</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Separator</string>
<key>scope</key>
<string>punctuation.separator.parameter</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#EAD4AF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Module</string>
<key>scope</key>
<string>keyword.operator.module</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FB4938</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Error</string>
<key>scope</key>
<string>sublimelinter.mark.error</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#D02000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Warning</string>
<key>scope</key>
<string>sublimelinter.mark.warning</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#DDB700</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>SublimeLinter Gutter Mark</string>
<key>scope</key>
<string>sublimelinter.gutter-mark</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FFFFFF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#70c060</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff changed</string>
<key>scope</key>
<string>markup.changed</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#DDB700</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FB4938</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>D8D5E82E-3D5B-46B5-B38E-8C841C21347D</string>
<key>colorSpaceName</key>
<string>sRGB</string>
</dict>
</plist>