emma
/
breed
Archived
forked from mars/breed
1
0
Fork 0

Compare commits

...

17 Commits

9 changed files with 474 additions and 167 deletions

View File

@ -7,4 +7,4 @@ strip = true # strip symbols from the binary
opt-level = "z" # optimize for size
lto = true # link time optimization
codegen-units = 1 # decrease parallelization
panic = "abort"
# panic = "abort"

View File

@ -11,10 +11,14 @@ libc = "0.2.141"
notify = "5"
parking_lot = "0.12"
ropey = "1.6"
toml = "0.7"
yacexits = "0.1.5"
[dependencies.syntect]
version = "5"
default-features = false
features = ["default-syntaxes", "parsing", "regex-onig"]
[dependencies.toml]
version = "0.7"
default-features = false
features = ["parse"]

View File

@ -1,2 +0,0 @@
[toolchain]
channel = "nightly"

View File

@ -19,7 +19,7 @@
use std::collections::HashMap;
use crate::{Direction, InsertState, Mode, State};
use crate::{Direction, InsertState, Mode, NormalState, State};
pub type Action = fn(&mut State);
@ -29,6 +29,23 @@ pub fn load_actions() -> HashMap<String, Action> {
("move_line_down", move_line_down),
("move_line_up", move_line_up),
("move_char_right", move_char_right),
("page_up", page_up),
("page_down", page_down),
("goto_line_start", goto_line_start),
("goto_line_end_newline", goto_line_end_newline),
("command_mode", command_mode),
("normal_mode", normal_mode),
("visual_mode", visual_mode),
("insert_mode", insert_mode),
("append_mode", append_mode),
("insert_at_line_start", insert_at_line_start),
("insert_at_line_end", insert_at_line_end),
("open_below", open_below),
("open_above", open_above),
("undo", undo),
("redo", redo),
("delete_char_backward", delete_char_backward),
("insert_newline", insert_newline),
];
let mut actions = HashMap::with_capacity(list.len());
@ -55,14 +72,58 @@ pub fn move_line_up(state: &mut State) {
state.move_cursor(Direction::Up);
}
pub fn page_up(state: &mut State) {
state.set_error("page_up is unimplemented");
}
pub fn page_down(state: &mut State) {
state.set_error("page_down is unimplemented");
}
pub fn goto_line_start(state: &mut State) {
state.set_error("goto_line_start is unimplemented");
}
pub fn goto_line_end_newline(state: &mut State) {
state.set_error("goto_line_end_newline is unimplemented");
}
pub fn command_mode(state: &mut State) {
state.mode = Mode::Command(Default::default());
}
pub fn normal_mode(state: &mut State) {
match state.mode {
Mode::Insert(InsertState { append: true }) => {
state.move_cursor(Direction::Left);
}
_ => {}
}
state.mode = Mode::Normal(NormalState::default());
}
pub fn visual_mode(state: &mut State) {
state.mode = Mode::Visual;
}
pub fn insert_mode(state: &mut State) {
state.mode = Mode::Insert(InsertState { append: false });
}
pub fn append_mode(state: &mut State) {
state.move_cursor(Direction::Right);
state.mode = Mode::Insert(InsertState { append: true });
}
pub fn insert_at_line_start(state: &mut State) {
state.set_error("insert_at_line_start is unimplemented");
}
pub fn insert_at_line_end(state: &mut State) {
state.set_error("insert_at_line_end is unimplemented");
}
pub fn open_below(state: &mut State) {
state.cursor.line += 1;
state.cursor.column = 0;
@ -75,3 +136,80 @@ pub fn open_above(state: &mut State) {
state.buffer.insert_char(state.cursor, '\n');
state.mode = Mode::Insert(InsertState { append: false });
}
pub fn undo(state: &mut State) {
state.set_error("undo is unimplemented");
}
pub fn redo(state: &mut State) {
state.set_error("redo is unimplemented");
}
pub fn delete_char_backward(state: &mut State) {
state.move_cursor(Direction::Left);
state.buffer.remove(state.cursor);
}
pub fn delete_char_forward(state: &mut State) {
state.buffer.remove(state.cursor);
}
pub fn insert_newline(state: &mut State) {
state.buffer.insert_char(state.cursor, '\n');
state.cursor.line += 1;
state.cursor.column = 0;
}
#[cfg(test)]
mod tests {
use super::*;
fn state(src: &str) -> State {
State::from_str(None, src).unwrap()
}
#[test]
fn backspace_empty() {
let mut state = state("");
delete_char_backward(&mut state);
}
#[test]
fn append_on_newline() {
let mut state = state("test\ntest\n");
state.cursor.column = 4;
append_mode(&mut state);
assert_eq!(state.cursor.line, 1);
assert_eq!(state.cursor.column, 0);
}
#[test]
fn append_at_eof() {
let mut state = state("test\n");
state.cursor.column = 4;
append_mode(&mut state);
assert_eq!(state.cursor.line, 1);
assert_eq!(state.cursor.column, 0);
}
#[test]
fn move_line_up_preserve_column() {
let mut state = state("test\n\ntest\n");
state.cursor.line = 2;
state.cursor.column = 3;
move_line_up(&mut state);
move_line_up(&mut state);
assert_eq!(state.cursor.line, 0);
assert_eq!(state.cursor.column, 3);
}
#[test]
fn move_line_down_preserve_column() {
let mut state = state("test\n\ntest\n");
state.cursor.column = 3;
move_line_down(&mut state);
move_line_down(&mut state);
assert_eq!(state.cursor.line, 2);
assert_eq!(state.cursor.column, 3);
}
}

View File

@ -74,15 +74,17 @@ impl Buffer {
cols: u16,
rows: u16,
scroll: Cursor,
cursor: Cursor,
out: &mut impl Write,
) -> crossterm::Result<u32> {
self.parse();
let mut styles = self.styles.lock();
let linenr_style = styles.get_scope("ui.linenr");
let linenr_style = styles.get_scope("ui.linenr").clone();
let linenr_width = self.text.len_lines().ilog10() + 1;
let gutter_width = linenr_width + 1;
let text_width = cols as usize - gutter_width as usize;
let line_padding: String = std::iter::repeat(' ').take(text_width).collect();
out.queue(cursor::MoveTo(0, 0))?;
@ -94,22 +96,40 @@ impl Buffer {
}
let row = row as usize + scroll.line;
let is_selected = row == cursor.line;
let linenr_style = if is_selected {
styles.get_scope("ui.linenr.selected").clone()
} else {
linenr_style
};
let line_style = if is_selected {
styles.get_scope("ui.cursorline.primary").clone()
} else {
Default::default()
};
let linenr = format!("{:width$} ", row, width = linenr_width as usize);
linenr_style.print_styled(out, &linenr)?;
let lhs = scroll.column;
let width = line.len_chars();
let mut remaining = text_width;
line_buf.clear();
if lhs < width {
let window = text_width.min(width - lhs);
let rhs = lhs + window;
let styled = &self.styled[row];
for (style, range) in styled.iter() {
for (chunk_style, range) in styled.iter() {
let range = range.start.max(lhs)..range.end.min(rhs);
if range.start < range.end {
// this range is in view so display
let content = line.slice(range.clone());
let mut style = line_style.clone();
style.apply(chunk_style);
style.print_styled(&mut line_buf, content)?;
remaining = text_width.saturating_sub(range.end);
}
if range.end >= rhs {
@ -119,6 +139,9 @@ impl Buffer {
}
}
let padding = &line_padding[0..remaining];
line_style.print_styled(&mut line_buf, padding)?;
out.write_all(&line_buf)?;
out.queue(cursor::MoveToNextLine(1))?;
}
@ -199,11 +222,11 @@ impl Buffer {
}
pub fn clamped_cursor(&self, cursor: Cursor) -> Cursor {
let line_end = self.text.line(cursor.line).len_chars().saturating_sub(1);
Cursor {
line: cursor.line,
column: cursor
.column
.min(self.text.line(cursor.line).len_chars() - 1),
column: cursor.column.min(line_end),
}
}

View File

@ -24,17 +24,22 @@ use crossbeam_channel::{unbounded, Receiver, RecvError, Sender};
use notify::{Event, EventKind, RecommendedWatcher, Watcher};
use parking_lot::Mutex;
use crate::keybinds::Keybinds;
use crate::theme::{StyleStore, Theme};
pub struct Config {
/// Whether or not to wrap lines when moving the cursor left and right.
pub move_linewrap: bool,
/// Key bindings.
pub keybinds: Keybinds,
}
impl Default for Config {
fn default() -> Self {
Self {
move_linewrap: true,
keybinds: Default::default(),
}
}
}

202
src/keybinds.rs Normal file
View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 2023 Marceline Cramer
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::collections::HashMap;
use crossterm::event::KeyCode;
use toml::{Table, Value};
use crate::actions::{load_actions, Action};
pub type Key = KeyCode;
pub type KeyMap = HashMap<Key, Keybind>;
pub struct Keybinds {
pub normal: ModeKeys,
pub insert: ModeKeys,
pub visual: ModeKeys,
}
impl Default for Keybinds {
fn default() -> Self {
use crate::actions::*;
use KeyCode::*;
let basic_nav = &[
(Esc, normal_mode as Action),
(Left, move_char_left),
(Down, move_line_down),
(Up, move_line_up),
(Right, move_char_right),
(PageUp, page_up),
(PageDown, page_down),
(Home, goto_line_start),
(End, goto_line_end_newline),
];
let normalish_keys = &[
(Char(':'), command_mode as Action),
(Char('h'), move_char_left),
(Char('j'), move_line_down),
(Char('k'), move_line_up),
(Char('l'), move_char_right),
(Char('i'), insert_mode),
(Char('a'), append_mode),
(Char('I'), insert_at_line_start),
(Char('A'), insert_at_line_end),
(Char('o'), open_below),
(Char('O'), open_above),
(Char('u'), undo),
(Char('U'), redo),
]
.iter()
.chain(basic_nav);
let insert_keys = &[
(Backspace, delete_char_backward as Action),
(Delete, delete_char_forward),
(Enter, insert_newline),
]
.iter()
.chain(basic_nav);
Self {
normal: normalish_keys.clone().into(),
insert: insert_keys.clone().into(),
visual: normalish_keys.clone().into(),
}
}
}
#[derive(Clone, Default)]
pub struct ModeKeys {
pub minor_modes: HashMap<Key, KeyMap>,
pub map: KeyMap,
}
impl<'a, T> From<T> for ModeKeys
where
T: IntoIterator<Item = &'a (Key, Action)>,
{
fn from(iter: T) -> Self {
let minor_modes = HashMap::new();
let mut map = KeyMap::new();
for (key, action) in iter {
map.insert(*key, Keybind::Action(*action));
}
Self { minor_modes, map }
}
}
impl ModeKeys {
pub fn apply_table(values: Table) -> Result<Self, String> {
let mut keys = Self::default();
for (key, value) in values {
let key = parse_key(&key)?;
match value {
Value::Table(table) => {
let map = parse_key_map(table)?;
keys.minor_modes.insert(key, map);
}
Value::String(keybind) => {
let bind = Keybind::try_from(keybind.as_str())?;
keys.map.insert(key, bind);
}
value => return Err(format!("Expected string or table but got {:?}", value)),
}
}
Ok(keys)
}
}
#[derive(Clone)]
pub enum Keybind {
Action(Action),
Command(String),
}
impl TryFrom<Value> for Keybind {
type Error = String;
fn try_from(value: Value) -> Result<Self, String> {
value
.as_str()
.ok_or_else(|| format!("Expected string but got {:?}", value))?
.try_into()
}
}
impl TryFrom<&str> for Keybind {
type Error = String;
fn try_from(keybind: &str) -> Result<Self, String> {
if keybind.starts_with(':') {
Ok(Keybind::Command(keybind[1..].to_string()))
} else {
// TODO static once cell
let actions = load_actions();
actions
.get(keybind)
.map(|cb| Keybind::Action(cb.clone()))
.ok_or_else(|| format!("Couldn't find action {:?}", keybind))
}
}
}
pub fn parse_key_map(values: Table) -> Result<KeyMap, String> {
let mut map = KeyMap::with_capacity(values.len());
for (key, value) in values {
let key = parse_key(&key)?;
let bind = Keybind::try_from(value)?;
map.insert(key, bind);
}
Ok(map)
}
pub fn parse_key(key: &str) -> Result<Key, String> {
use KeyCode::*;
let key = match key {
key if key.len() == 1 => Char(key.chars().nth(0).unwrap()),
"backspace" => Backspace,
"space" => Char(' '),
"ret" => Enter,
"minus" => Char('-'),
"left" => Left,
"right" => Right,
"up" => Up,
"down" => Down,
"home" => Home,
"end" => End,
"pageup" => PageUp,
"pagedown" => PageDown,
"tab" => Tab,
"del" => Delete,
"ins" => Insert,
"null" => Null,
"esc" => Esc,
key => return Err(format!("Unrecognized key name {:?}", key)),
};
Ok(key)
}

View File

@ -31,17 +31,21 @@ use crossbeam_channel::Sender;
use crossterm::{
cursor,
event::{Event, KeyCode, KeyEvent},
terminal, Result, QueueableCommand,
terminal, QueueableCommand, Result,
};
use keybinds::Keybind;
use parking_lot::Mutex;
use ropey::Rope;
use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE};
mod actions;
mod buffer;
mod config;
mod keybinds;
mod theme;
use buffer::Buffer;
use config::Config;
use theme::StyleStore;
#[derive(Copy, Clone, Debug, Default)]
@ -102,10 +106,12 @@ pub enum Direction {
pub struct State {
pub styles: Arc<Mutex<StyleStore>>,
pub config: Config,
pub buffer: Buffer,
pub cursor: Cursor,
pub file: Option<OsString>,
pub mode: Mode,
pub last_saved: Rope,
pub scroll: Cursor,
pub size: (usize, usize),
pub quit: bool,
@ -116,15 +122,18 @@ impl State {
pub fn from_str(file_name: Option<OsString>, text: &str) -> Result<Self> {
let styles = Arc::new(Mutex::new(StyleStore::default()));
let buffer = Buffer::from_str(styles.clone(), text);
let last_saved = buffer.as_ref().clone();
let (cols, rows) = terminal::size()?;
let theme_tx = config::ThemeWatcher::spawn(styles.clone());
Ok(Self {
styles,
config: Default::default(),
buffer,
cursor: Cursor::default(),
file: file_name,
mode: Mode::default(),
last_saved,
scroll: Cursor::default(),
size: (cols as usize, rows as usize),
quit: false,
@ -166,7 +175,9 @@ impl State {
// draw buffer
let buffer_rows = if show_status_bar { rows - 1 } else { rows };
let lr_width = self.buffer.draw(cols, buffer_rows, self.scroll, out)?;
let lr_width = self
.buffer
.draw(cols, buffer_rows, self.scroll, self.cursor, out)?;
// draw cursor
let cursor_pos = set_cursor_pos.unwrap_or_else(|| {
@ -188,6 +199,13 @@ impl State {
Ok(())
}
/// Sets the state to `Mode::Normal` with an error message.
pub fn set_error(&mut self, error: impl ToString) {
self.mode = Mode::Normal(NormalState {
error: Some(error.to_string()),
});
}
pub fn on_event(&mut self, event: Event) {
match &self.mode {
Mode::Normal(state) => self.on_normal_event(event, state.clone()),
@ -200,28 +218,16 @@ impl State {
fn on_normal_event(&mut self, event: Event, mut state: NormalState) {
// reset the error from the last event
state.error = None;
self.mode = Mode::Normal(state);
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char(':') => {
self.mode = Mode::Command(Default::default());
Event::Key(KeyEvent { code, .. }) => {
if let Some(keybind) = self.config.keybinds.visual.map.get(&code) {
self.execute_keybind(keybind.clone());
}
KeyCode::Char('v') => {
self.mode = Mode::Visual;
}
KeyCode::Char('i') => actions::insert_mode(self),
KeyCode::Char('a') => actions::append_mode(self),
KeyCode::Char('o') => actions::open_below(self),
KeyCode::Char('O') => actions::open_above(self),
code => self.on_any_key(code),
},
}
event => self.on_any_event(event),
}
match self.mode {
Mode::Normal(_) => self.mode = Mode::Normal(state),
_ => {}
}
}
fn on_command_event(&mut self, event: Event, mut state: CommandState) {
@ -248,12 +254,16 @@ impl State {
}
KeyCode::Enter => {
// TODO add to command history
let result = self.queue_command(&state.buf);
let result = self.execute_command(&state.buf);
let error = result.err();
self.mode = Mode::Normal(NormalState { error });
return;
}
code => return self.on_any_key(code),
KeyCode::Esc => {
self.mode = Mode::default();
return;
}
_ => {}
},
event => return self.on_any_event(event),
}
@ -263,43 +273,25 @@ impl State {
fn on_visual_event(&mut self, event: Event) {
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Esc => {
self.mode = Mode::default();
Event::Key(KeyEvent { code, .. }) => {
if let Some(keybind) = self.config.keybinds.visual.map.get(&code) {
self.execute_keybind(keybind.clone());
}
code => self.on_any_key(code),
},
}
event => self.on_any_event(event),
}
}
fn on_insert_event(&mut self, event: Event, state: InsertState) {
fn on_insert_event(&mut self, event: Event, _state: InsertState) {
match event {
Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char(c) => {
self.buffer.insert_char(self.cursor, c);
self.move_cursor(Direction::Right)
}
KeyCode::Backspace => {
self.move_cursor(Direction::Left);
self.buffer.remove(self.cursor);
}
KeyCode::Delete => {
self.buffer.remove(self.cursor);
}
KeyCode::Enter => {
self.buffer.insert_char(self.cursor, '\n');
self.cursor.line += 1;
self.cursor.column = 0;
}
KeyCode::Esc => {
if state.append {
self.move_cursor(Direction::Left);
Event::Key(KeyEvent { code, .. }) => match self.config.keybinds.insert.map.get(&code) {
Some(keybind) => self.execute_keybind(keybind.clone()),
None => {
if let KeyCode::Char(c) = code {
self.buffer.insert_char(self.cursor, c);
self.move_cursor(Direction::Right);
}
self.mode = Mode::default();
}
code => self.on_any_key(code),
},
event => self.on_any_event(event),
}
@ -310,28 +302,15 @@ impl State {
Event::Resize(cols, rows) => {
self.size = (cols as usize, rows as usize);
}
Event::Key(KeyEvent { code, .. }) => self.on_any_key(code),
_ => {}
}
}
fn on_any_key(&mut self, code: KeyCode) {
match code {
KeyCode::Esc => self.mode = Mode::default(),
KeyCode::Char('h') | KeyCode::Left => actions::move_char_left(self),
KeyCode::Char('j') | KeyCode::Down => actions::move_line_down(self),
KeyCode::Char('k') | KeyCode::Up => actions::move_line_up(self),
KeyCode::Char('l') | KeyCode::Right => actions::move_char_right(self),
_ => {}
}
}
fn write_buffer(&mut self, file: OsString) -> Result<()> {
self.last_saved = self.buffer.as_ref().clone();
let out = self.buffer.as_ref().bytes().collect::<Vec<u8>>();
let mut handle = OpenOptions::new().write(true).open(file)?;
handle.write_all(out.as_slice())?;
Ok(())
}
@ -349,7 +328,19 @@ impl State {
self.write_buffer(handle).map_err(|err| format!("{}", err))
}
fn queue_command(&mut self, command: &str) -> std::result::Result<(), String> {
fn execute_keybind(&mut self, keybind: Keybind) {
match keybind {
Keybind::Action(action) => action(self),
Keybind::Command(command) => {
let result = self.execute_command(&command);
let error = result.err();
self.mode = Mode::Normal(NormalState { error });
return;
}
}
}
fn execute_command(&mut self, command: &str) -> std::result::Result<(), String> {
let command_parts = command.split(' ').collect::<Vec<&str>>();
let command = match command_parts.get(0) {
@ -360,7 +351,14 @@ impl State {
let args = &command_parts[1..];
match *command {
"q" => self.quit = true,
"q!" => self.quit = true,
"q" => {
if self.last_saved == *self.buffer.as_ref() {
self.quit = true;
} else {
return Err("Buffer is unsaved (add ! to override)".into());
}
}
"w" => return self.write_command(command, args),
"wq" => {
self.write_command(command, args)?;
@ -382,7 +380,8 @@ impl State {
}
fn move_cursor(&mut self, direction: Direction) {
self.buffer.move_cursor(&mut self.cursor, direction, true);
let wrap = self.config.move_linewrap;
self.buffer.move_cursor(&mut self.cursor, direction, wrap);
self.scroll_to_cursor();
}

View File

@ -1,92 +1,30 @@
"ui.linenr" = "gray"
"ui.cursorline" = {}
"ui.linenr" = "black"
"ui.linenr.selected" = "white"
"error" = "love"
"error" = "red"
"attribute" = "iris"
"type" = "foam"
# "type.builtin" = ""
"constructor" = "foam"
"comment" = { fg = "muted", modifiers = ["italic"]}
"constant" = "foam"
"constant.builtin" = "rose"
"constant.character" = "gold"
"constant.character.escape" = "pine"
"constant.numeric" = "gold"
"entity.name" = "rose"
"invalid" = "love"
"keyword" = "pine"
"keyword.operator" = "subtle"
"label" = "foam"
"operator" = "subtle"
"punctuation" = "subtle"
"string" = "gold"
# "string.regexp" = ""
# "string.special" = ""
# "string.special.path" = ""
# "string.special.url" = ""
# "string.special.symbol" = ""
"variable" = "text"
"variable.builtin" = "love"
"variable.parameter" = "iris"
"markup.heading.marker" = "muted"
"markup.heading" = { fg = "iris", modifiers = ["bold"] }
"markup.heading.1" = { fg = "iris", modifiers = ["bold"] }
"markup.heading.2" = { fg = "foam", modifiers = ["bold"] }
"markup.heading.3" = { fg = "rose", modifiers = ["bold"] }
"markup.heading.4" = { fg = "gold", modifiers = ["bold"] }
"markup.heading.5" = { fg = "pine", modifiers = ["bold"] }
"markup.heading.6" = { fg = "foam", modifiers = ["bold"] }
# "markup.heading.completion" = ""
# "markup.heading.hover" = ""
"markup.list" = "muted"
# "markup.list.unnumbered" = ""
# "markup.list.numbered" = ""
"markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link" = "iris"
"markup.link.url" = { fg = "iris", underline = { color = "iris", style = "line" } }
"markup.link.label" = "subtle"
"markup.link.text" = "text"
"markup.quote" = "subtle"
"markup.raw" = "subtle"
# "markup.raw.inline" = {}
# "markup.raw.inline.completion" = {}
# "markup.raw.inline.hover" = {}
# "markup.raw.block" = {}
# "markup.normal" = ""
# "markup.normal.completion" = ""
# "markup.normal.hover" = ""
"diff" = "overlay"
"diff.plus" = "foam"
"diff.minus" = "love"
"diff.delta" = "highlight_high"
# "diff.delta.moved" = ""
[palette]
base = "#191724"
surface = "#1f1d2e"
overlay = "#26233a"
muted = "#6e6a86"
subtle = "#908caa"
text = "#e0def4"
love = "#eb6f92"
love_10 = "#311f30"
gold = "#f6c177"
gold_10 = "#30282c"
rose = "#ebbcba"
rose_10 = "#2f2834"
pine = "#31748f"
pine_10 = "#1a2030"
foam = "#9ccfd8"
foam_10 = "#252937"
iris = "#c4a7e7"
iris_10 = "#2b2539"
highlight_low = "#21202e"
highlight_med = "#403d52"
highlight_high = "#524f67"
"comment" = { fg = "black", modifiers = ["italics"] }
"constant" = "yellow"
"constant.character.escape" = "light-cyan"
"constant.language" = "magenta"
"constant.numeric" = "blue"
"entity.name" = "blue"
"entity.other.attribute-name" = "red"
"entity.other.inherited-class" = "red"
"invalid.deprecated" = "red"
"invalid" = "red"
"keyword" = "magenta"
"keyword.control" = "magenta"
"keyword.declaration" = "blue"
"keyword.operator" = "blue"
"meta.path" = "blue"
"punctuation" = "gray"
"punctuation.definition.string.begin" = "green"
"punctuation.definition.string.end" = "green"
"storage" = "magenta"
"storage.type" = "magenta"
"string" = "green"
"support" = "yellow"
"variable" = "green"
"variable.language" = "magenta"