SCROLLING!!!!!!!!!!!

This commit is contained in:
Borodinov Ilya 2024-05-18 23:09:05 +03:00
parent 20889e9d1f
commit ebe77bd035
Signed by: noth
GPG key ID: 75503B2EF596D1BD
13 changed files with 370 additions and 101 deletions

109
Cargo.lock generated
View file

@ -306,6 +306,15 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "backtrace-ext"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
dependencies = [
"backtrace",
]
[[package]]
name = "base64"
version = "0.22.1"
@ -1278,7 +1287,7 @@ version = "0.1.0"
dependencies = [
"funnylog-macros",
"log",
"owo-colors",
"owo-colors 4.0.0",
"parking_lot",
]
@ -1684,6 +1693,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is-terminal"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
@ -1694,6 +1714,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is_ci"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "isahc"
version = "1.7.2"
@ -1980,8 +2006,17 @@ version = "5.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
dependencies = [
"backtrace",
"backtrace-ext",
"is-terminal",
"miette-derive",
"once_cell",
"owo-colors 3.5.0",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"terminal_size",
"textwrap",
"thiserror",
"unicode-width",
]
@ -2032,6 +2067,7 @@ dependencies = [
"lazy_static",
"linkme",
"log",
"miette",
"ming_macros",
"num_cpus",
"oo7",
@ -2168,7 +2204,9 @@ dependencies = [
"careless",
"funnylog",
"inkjet",
"kdl",
"log",
"miette",
"ming",
"serde",
"widestring",
@ -2440,6 +2478,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "owo-colors"
version = "4.0.0"
@ -3105,14 +3149,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_kdl"
version = "0.1.0"
dependencies = [
"kdl",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.19"
@ -3220,6 +3256,12 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smawk"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
[[package]]
name = "smithay-client-toolkit"
version = "0.18.1"
@ -3329,6 +3371,34 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "supports-color"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
dependencies = [
"is-terminal",
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
dependencies = [
"is-terminal",
]
[[package]]
name = "supports-unicode"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f850c19edd184a205e883199a261ed44471c81e39bd95b1357f5febbef00e77a"
dependencies = [
"is-terminal",
]
[[package]]
name = "svg_fmt"
version = "0.4.2"
@ -3427,6 +3497,27 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.60"

View file

@ -12,6 +12,8 @@ log.workspace = true
serde = { workspace = true, features = ["derive"] }
inkjet = { version = "0.10.5", features = ["language-rust", "language-toml", "language-zig"], default-features = false }
careless = { git = "https://codeberg.org/minky/careless", version = "0.1.0" }
kdl = { version = "4.6.0", default-features = false }
miette = { workspace = true, features = ["fancy"] }
[workspace]
members = [
@ -30,7 +32,6 @@ license = "GPL-3.0"
collections.path = "crates/collections"
ming.path = "crates/ming"
ming_macros.path = "crates/ming_macros"
serde_kdl.path = "crates/serde_kdl"
refineable.path = "crates/refineable"
util.path = "crates/util"
@ -48,6 +49,7 @@ tempfile = "3.9"
unicase = "2.6"
derive_more = "0.99.17"
anyhow = "1"
miette = "5.7.0"
ctor = "0.2.6"
log = "0.4"
itertools = "0.12"

View file

@ -10,6 +10,9 @@ The following list of features and tasks required to have them is highly opinion
- [ ] Barebones editor
* [x] Make it compile
* [x] Treesitter
* [x] Scrolling
* [ ] Cursor
- [ ] Vim-like keybindings and motions
- [ ] A Telescope-ey file finder
- [ ] Something like [harpoon](https://github.com/theprimeagen/harpoon) and maybe a topbar with marks

View file

@ -62,6 +62,7 @@ util.workspace = true
uuid.workspace = true
waker-fn = "1.1.0"
smol = "2.0.0"
miette.workspace = true
[dev-dependencies]
backtrace = "0.3"

View file

@ -1,4 +1,4 @@
use anyhow::bail;
use miette::{bail, IntoDiagnostic};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt;
@ -132,7 +132,7 @@ impl From<Hsla> for Rgba {
}
impl TryFrom<&'_ str> for Rgba {
type Error = anyhow::Error;
type Error = miette::Error;
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
const RGB: usize = "rgb".len();
@ -148,11 +148,11 @@ impl TryFrom<&'_ str> for Rgba {
let (r, g, b, a) = match hex.len() {
RGB | RGBA => {
let r = u8::from_str_radix(&hex[0..1], 16)?;
let g = u8::from_str_radix(&hex[1..2], 16)?;
let b = u8::from_str_radix(&hex[2..3], 16)?;
let r = u8::from_str_radix(&hex[0..1], 16).into_diagnostic()?;
let g = u8::from_str_radix(&hex[1..2], 16).into_diagnostic()?;
let b = u8::from_str_radix(&hex[2..3], 16).into_diagnostic()?;
let a = if hex.len() == RGBA {
u8::from_str_radix(&hex[3..4], 16)?
u8::from_str_radix(&hex[3..4], 16).into_diagnostic()?
} else {
0xf
};
@ -166,11 +166,11 @@ impl TryFrom<&'_ str> for Rgba {
(duplicate(r), duplicate(g), duplicate(b), duplicate(a))
}
RRGGBB | RRGGBBAA => {
let r = u8::from_str_radix(&hex[0..2], 16)?;
let g = u8::from_str_radix(&hex[2..4], 16)?;
let b = u8::from_str_radix(&hex[4..6], 16)?;
let r = u8::from_str_radix(&hex[0..2], 16).into_diagnostic()?;
let g = u8::from_str_radix(&hex[2..4], 16).into_diagnostic()?;
let b = u8::from_str_radix(&hex[4..6], 16).into_diagnostic()?;
let a = if hex.len() == RRGGBBAA {
u8::from_str_radix(&hex[6..8], 16)?
u8::from_str_radix(&hex[6..8], 16).into_diagnostic()?
} else {
0xff
};

View file

@ -1,12 +0,0 @@
[package]
name = "serde_kdl"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
kdl = "4.6.0"
serde.workspace = true
[lints]
workspace = true

View file

@ -1,14 +0,0 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -15,9 +15,9 @@ lazy_static::lazy_static! {
} else if cfg!(target_os = "linux") {
dirs::config_dir()
.expect("failed to determine XDG_CONFIG_HOME directory")
.join("zed")
.join("nite")
} else {
HOME.join(".config").join("zed")
HOME.join(".config").join("nite")
};
pub static ref CONVERSATIONS_DIR: PathBuf = if cfg!(target_os = "macos") {
CONFIG_DIR.join("conversations")
@ -36,7 +36,7 @@ lazy_static::lazy_static! {
} else if cfg!(target_os = "linux") {
dirs::data_local_dir()
.expect("failed to determine XDG_DATA_DIR directory")
.join("zed")
.join("nite")
} else if cfg!(target_os = "windows") {
dirs::data_local_dir()
.expect("failed to determine LocalAppData directory")
@ -67,8 +67,8 @@ lazy_static::lazy_static! {
pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt");
pub static ref LOG: PathBuf = LOGS_DIR.join("Zed.log");
pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old");
pub static ref LOCAL_SETTINGS_RELATIVE_PATH: &'static Path = Path::new(".zed/settings.json");
pub static ref LOCAL_TASKS_RELATIVE_PATH: &'static Path = Path::new(".zed/tasks.json");
pub static ref LOCAL_SETTINGS_RELATIVE_PATH: &'static Path = Path::new(".nite/settings.json");
pub static ref LOCAL_TASKS_RELATIVE_PATH: &'static Path = Path::new(".nite/tasks.json");
pub static ref LOCAL_VSCODE_TASKS_RELATIVE_PATH: &'static Path = Path::new(".vscode/tasks.json");
pub static ref TEMP_DIR: PathBuf = if cfg!(target_os = "windows") {
dirs::cache_dir()
@ -77,9 +77,9 @@ lazy_static::lazy_static! {
} else if cfg!(target_os = "linux") {
dirs::cache_dir()
.expect("failed to determine XDG_CACHE_HOME directory")
.join("zed")
.join("nite")
} else {
HOME.join(".cache").join("zed")
HOME.join(".cache").join("nite")
};
}
@ -612,7 +612,7 @@ mod tests {
#[test]
fn project_search() {
let path = Path::new("/Users/someonetoignore/work/zed/zed.dev/node_modules");
let path = Path::new("/Users/someonetoignore/work/nite/nite.dev/node_modules");
let path_matcher = PathMatcher::new("**/node_modules/**").unwrap();
assert!(
path_matcher.is_match(path),

View file

@ -1,6 +1,8 @@
use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
use careless::{try_f64_to_f32, try_iter::TryIterator, TryOption};
use kdl::*;
use miette::{bail, IntoDiagnostic};
use super::*;
@ -11,16 +13,51 @@ pub struct EditorSettings {
style: EditorStyle,
}
#[derive(Clone, Copy, Debug, Deserialize)]
#[derive(Clone, Copy, Debug)]
pub struct Highlight {
pub color: Option<Rgba>,
#[serde(default)]
pub style: Option<FontStyle>,
#[serde(default)]
pub weight: Option<FontWeight>,
}
impl Highlight {
pub fn from_kdl_node(node: &KdlNode) -> miette::Result<Self> {
Ok(Self {
color: TryOption::from_opt(node.get(0).or_else(|| node.get("color")))
.and_then_res(|entry| rgba_from_kdl(entry.value()))
.into_result_opt()?,
style: TryOption::<_, miette::Error>::from_opt(node.get("style"))
.and_then_res(|entry| {
entry
.value()
.as_string()
.context("style value wasn't a string")
})
.and_then_res(|str| {
Ok(if str.eq_ignore_ascii_case("normal") {
FontStyle::Normal
} else if str.eq_ignore_ascii_case("italic") {
FontStyle::Italic
} else if str.eq_ignore_ascii_case("oblique") {
FontStyle::Oblique
} else {
bail!("invalid FontStyle value: {str}")
})
})
.into_result_opt()?,
weight: TryOption::from_opt(node.get("weight"))
.map(|entry| entry.value())
.and_then(|value| {
TryOption::from_opt(value.as_f64()).and_then_res(|value| {
try_f64_to_f32(value)
.context("font weight value (f64) didn't fit into a f32")
.map(FontWeight)
})
})
.into_result_opt()?,
})
}
pub fn apply_to_run(&self, run: &mut TextRun) {
if let Some(color) = self.color {
run.color = color.into();
@ -36,24 +73,67 @@ impl Highlight {
}
}
#[derive(Clone, Debug, Deserialize)]
#[derive(Clone, Debug)]
pub struct Theme {
pub name: String,
pub background_color: Rgba,
pub text_color: Rgba,
pub highlights: HashMap<String, Highlight>,
#[serde(skip)]
pub highlight_indices: Vec<Highlight>
pub highlight_indices: Vec<Highlight>,
}
fn rgba_from_kdl(value: &KdlValue) -> miette::Result<Rgba> {
if let Some(i64) = value.as_i64() {
Ok(rgba(
i64.try_into()
.into_diagnostic()
.context("number too big to fit in a u32")?,
))
} else if let Some(str) = value.as_string() {
Rgba::try_from(str)
} else {
bail!("invalid KdlValue for Rgba: {value}")
}
}
impl Theme {
pub fn load(text: &str) -> Result<Self, kaydle::serde::de::Error> {
kaydle::serde::from_str(text).map(|mut me: Self| {
me.highlight_indices = inkjet::constants::HIGHLIGHT_NAMES.iter().flat_map(|hl| {
me.highlights.get(*hl).cloned()
}).collect();
pub fn load(text: &str) -> miette::Result<Self> {
let kdl = text.parse()?;
me
Self::from_kdl_document(kdl)
}
pub fn from_kdl_document(document: KdlDocument) -> miette::Result<Self> {
let highlight_indices = match document.get("highlights").and_then(|node| node.children()) {
Some(doc) => {
let mut indices = vec![];
for name in inkjet::constants::HIGHLIGHT_NAMES {
let Some(node) = doc.get(*name) else { continue };
indices.push(Highlight::from_kdl_node(node)?);
}
indices
}
None => Default::default(),
};
Ok(Self {
name: document
.get_arg("name")
.context("no name attr")?
.as_string()
.context("name wasn't a string")?
.to_string(),
background_color: document
.get_arg("background_color")
.context("no background_color attr")
.and_then(rgba_from_kdl)?,
text_color: document
.get_arg("text_color")
.context("no background_color attr")
.and_then(rgba_from_kdl)?,
highlight_indices,
})
}
}
@ -107,7 +187,7 @@ impl Editor {
settings,
logger,
cursor: Point::default(),
scroll: Point::default()
scroll: Point::default(),
})
}
}

View file

@ -1,3 +1,5 @@
use std::fmt::write;
use super::*;
pub struct EditorElement {
@ -22,7 +24,9 @@ impl IntoElement for EditorElement {
}
}
pub struct EditorLayout {}
pub struct EditorLayout {
hb: Hitbox,
}
pub struct EditorRequestLayout {
styled: Vec<(LayoutId, StyledText, Bounds<Pixels>)>,
}
@ -45,6 +49,7 @@ impl Element for EditorElement {
&TextStyle {
font_size: self.style.font_size,
font_family: self.style.font_family.clone(),
color: editor.settings.read(cx).style.theme.text_color.into(),
..Default::default()
},
&self.style.theme,
@ -89,10 +94,22 @@ impl Element for EditorElement {
request_layout
.styled
.iter_mut()
.for_each(|(_id, styled, bounds)| styled.prepaint(None, *bounds, &mut (), cx));
.for_each(|(id, styled, bounds)| {
styled.prepaint(
None,
{
*bounds = cx.layout_bounds(*id);
*bounds
},
&mut (),
cx,
)
});
});
EditorLayout {}
EditorLayout {
hb: cx.insert_hitbox(bounds, true),
}
}
fn paint(
@ -114,15 +131,56 @@ impl Element for EditorElement {
let view = self.editor.clone();
let font_size = self.style.font_size.to_pixels(px(4.));
cx.on_mouse_event(move |scroll: &ScrollWheelEvent, _dispatch, cx| {
log::trace!("{scroll:#?}");
view.update(cx, |editor, cx| match scroll.delta {
ScrollDelta::Lines(lines) => {
let hb = prepaint.hb.clone();
}
ScrollDelta::Pixels(pix) => {
let px_y = (pix.y.ceil().0 / font_size.0) as i32;
editor.cursor.y = editor.cursor.y.saturating_add_signed(px_y as isize)
let styled_bounds = request_layout
.styled
.iter()
.map(|(_, _, b)| *b)
.collect::<Vec<_>>();
cx.on_mouse_event(move |scroll: &ScrollWheelEvent, dispatch, cx| {
if !dispatch.bubble() || !hb.is_hovered(cx) {
return;
}
view.update(cx, |editor, cx| {
let prev_editor_scroll = editor.scroll;
let change = match scroll.delta {
ScrollDelta::Lines(pnt) => {
let mut lines = pnt.y.abs();
let mut do_scroll = px(0.);
for bounds in styled_bounds.iter() {
if lines <= 1. {
do_scroll += px(bounds.size.height.0 * lines);
break;
}
do_scroll += bounds.size.height;
lines -= 1.;
}
point(px(0.), do_scroll * pnt.y.signum())
}
ScrollDelta::Pixels(pix) => pix,
};
editor.scroll = (editor.scroll + change).clamp(
&point(bounds.size.width, bounds.size.height).negate(),
&Point::default(),
);
log::trace!(
"[{prev_editor_scroll:#?} -> {:#?}] {scroll:#?}",
editor.scroll
);
cx.stop_propagation();
if !change.is_zero() {
cx.refresh();
}
});
});
@ -133,14 +191,12 @@ impl Element for EditorElement {
..Default::default()
}),
|cx| {
cx.with_element_offset(self.editor.read(cx).scroll, |cx| {
request_layout
.styled
.iter_mut()
.for_each(|(_id, styled, bounds)| {
styled.paint(None, *bounds, &mut (), &mut (), cx)
});
});
request_layout
.styled
.iter_mut()
.for_each(|(_id, styled, bounds)| {
styled.paint(None, *bounds, &mut (), &mut (), cx)
});
},
);
}

View file

@ -3,11 +3,22 @@
use std::sync::Arc;
use funnylog::{filter::LevelFilter, span, Drain, Level};
use ming::{*, prelude::*};
use miette::{Diagnostic, Report, WrapErr};
use ming::{prelude::*, *, Context};
mod buffer;
mod editor;
trait MietteContext<T> {
fn context(self, msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Result<T, Report>;
}
impl<T> MietteContext<T> for Option<T> {
fn context(self, msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Result<T, Report> {
self.ok_or_else(|| Report::msg(msg))
}
}
struct Nite {
editor: View<editor::Editor>,
logger: Logger,
@ -37,6 +48,7 @@ impl Render for Nite {
}
fn main() {
miette::set_panic_hook();
let drain = Arc::new(
funnylog::terminal::TerminalConfig::default()
.to_stderr()

View file

@ -1,24 +1,50 @@
name "Catppuccin Mocha"
background_color "#1e1e2e"
text_color "#cdd6f4"
highlights {
attribute "#f9e2af"
boolean "#fab387"
comment "#7f849c"
comment.doc "#7f849c"
comment "#7f849c" font_style="italic"
comment.doc "#7f849c" font_style="italic"
constant "#fab387"
constructor "#89b4fa"
embedded "#eba0ac"
emphasis "#f38ba8"
emphasis "#f38ba8" font_style="italic"
emphasis.strong "#f38ba8" font_weight=700
enum "#94e2d5" font_weight=700
function "#89b4fa"
hint "#94e2d5"
function "#89b4fa" font_style="italic"
hint "#94e2d5" font_style="italic"
keyword "#cba6f7"
link_text "#89b4fa"
link_uri "#89b4fa"
number "#fab387"
operator "#89dceb"
predictive "#585b70"
predoc "#f38ba8"
primary "#eba0ac"
property "#89b4fa"
punctuation "#94e2d5"
punctuation.bracket "#94e2d5"
punctuation.delimiter "#9399b2"
punctuation.list_marker "#94e2d5"
punctuation.special "#94e2d5"
punctuation.special.symbol "#f38ba8"
string "#a6e3a1"
string.escape "#f5c2e7"
string.regex "#f5c2e7"
string.special "#f5c2e7"
string.special.symbol "#f2cdcd"
tag "#89b4fa"
text.literal "#a6e3a1"
title "#cdd6f4"
type "#f9e2af"
type.interface "#f9e2af"
type.super "#f9e2af"
variable "#cdd6f4"
variable.member "#cdd6f4"
variable.parameter "#cdd6f4"
variable.special "#dcb8d5" font_style="italic"
variant "#f38ba8"
}

View file

@ -7,16 +7,13 @@ whiskers:
- accent
filename: "{{flavor.identifier}}-{{accent}}{{variant}}.kdl"
---
{%set c = flavor.colors-%}
{%set accent = c[accent]-%}
{%if variant == "-no-italics"%}
{%set italic = "font_style=italic"-%}
{%else%}
{%set italic = ""-%}
{%else%}
{%set italic = 'font_style="italic"'-%}
{%endif%}
name "Catppuccin {{flavor.name}} {%- if variant == "-no-italics" %} (no italics) {%- endif -%}"
background_color "#{{c.base.hex}}"
text_color "#{{c.text.hex}}"
@ -37,4 +34,31 @@ highlights {
link_text "#{{c.blue.hex}}"
link_uri "#{{c.blue.hex}}"
number "#{{c.peach.hex}}"
operator "#{{c.sky.hex}}"
predictive "#{{c.surface2.hex}}"
predoc "#{{c.red.hex}}"
primary "#{{c.maroon.hex}}"
property "#{{c.blue.hex}}"
punctuation "#{{c.teal.hex}}"
punctuation.bracket "#{{c.teal.hex}}"
punctuation.delimiter "#{{c.overlay2.hex}}"
punctuation.list_marker "#{{c.teal.hex}}"
punctuation.special "#{{c.teal.hex}}"
punctuation.special.symbol "#{{c.red.hex}}"
string "#{{c.green.hex}}"
string.escape "#{{c.pink.hex}}"
string.regex "#{{c.pink.hex}}"
string.special "#{{c.pink.hex}}"
string.special.symbol "#{{c.flamingo.hex}}"
tag "#{{c.blue.hex}}"
text.literal "#{{c.green.hex}}"
title "#{{c.text.hex}}"
type "#{{c.yellow.hex}}"
type.interface "#{{c.yellow.hex}}"
type.super "#{{c.yellow.hex}}"
variable "#{{c.text.hex}}"
variable.member "#{{c.text.hex}}"
variable.parameter "#{{c.text.hex}}"
variable.special "#{{ c.text | mix(color=c.red,amount=0.6) | get(key='hex') }}" {{italic}}
variant "#{{c.red.hex}}"
}