use hikari_core::{
Language, LanguagePlugin, Selector,
langs::{LangTable, TablePlugin},
};
pub const BLUE: Language = Language("blue");
pub static BLUE_RESERVED_WORDS: &[&str] = &[
"assert",
"case",
"def",
"defmacro",
"fn",
"if",
"quote",
"test",
"unless",
"unquote",
"unquote_splice",
"do",
"else",
"elsif",
"end",
];
pub static BLUE_TABLE: LangTable = LangTable {
keywords: BLUE_RESERVED_WORDS,
line_comments: &["#"],
block_comment: None,
string_delims: &['"'],
colon_keywords: true,
};
pub static BLUE_SELECTORS: &[Selector] =
&[Selector::Extension("b"), Selector::Filename("Bluefile")];
#[must_use]
pub fn escriba_local() -> Vec<Box<dyn LanguagePlugin>> {
vec![Box::new(TablePlugin {
language: BLUE,
selectors: BLUE_SELECTORS,
table: &BLUE_TABLE,
})]
}
#[cfg(test)]
mod tests {
use super::*;
use hikari_core::{Ecosystem, HlClass};
fn local_only() -> Ecosystem {
let mut eco = Ecosystem::new();
for p in escriba_local() {
eco.register(p);
}
eco
}
fn classes(path: &str, src: &str) -> Vec<(String, HlClass)> {
let hl = local_only().highlighter_for_path(path);
hl.highlight(src)
.into_iter()
.map(|s| {
(
src[s.span.start as usize..s.span.end as usize].to_string(),
s.class,
)
})
.filter(|(t, _)| !t.trim().is_empty())
.collect()
}
fn class_of(path: &str, src: &str, token: &str) -> Option<HlClass> {
classes(path, src)
.into_iter()
.find(|(t, _)| t == token)
.map(|(_, c)| c)
}
#[test]
fn dot_b_resolves_to_blue() {
assert_eq!(local_only().resolve("scratch.b"), BLUE);
assert_eq!(local_only().resolve("/a/b/c/spec/strings.b"), BLUE);
}
#[test]
fn bluefile_resolves_to_blue_by_name() {
assert_eq!(local_only().resolve("Bluefile"), BLUE);
assert_eq!(local_only().resolve("bidamas/retsu/Bluefile"), BLUE);
}
#[test]
fn unrelated_paths_stay_plain() {
assert_eq!(local_only().resolve("main.rs"), hikari_core::PLAIN_TEXT);
assert_eq!(local_only().resolve("notes.txt"), hikari_core::PLAIN_TEXT);
assert_eq!(local_only().resolve("x.blue"), hikari_core::PLAIN_TEXT);
}
#[test]
fn form_heads_and_block_words_paint_as_keywords() {
let src = "def f(x)\n if x\n x\n else\n 0\n end\nend\n";
for word in ["def", "if", "else", "end"] {
assert_eq!(
class_of("f.b", src, word),
Some(HlClass::Keyword),
"expected `{word}` to paint as a keyword",
);
}
}
#[test]
fn hash_starts_a_line_comment() {
let src = "# blue's own configuration\ndef f\nend\n";
let spans = classes("blue.b", src);
assert_eq!(
spans.first().map(|(_, c)| *c),
Some(HlClass::Comment { multiline: false }),
);
assert_eq!(class_of("blue.b", src, "def"), Some(HlClass::Keyword));
}
#[test]
fn symbols_paint_as_keyword_args() {
assert_eq!(
class_of("f.b", "x = :ok\n", ":ok"),
Some(HlClass::KeywordArg),
);
}
#[test]
fn strings_and_numbers_paint() {
let src = "x = \"hello\"\ny = 42\nz = 1.5\n";
assert_eq!(class_of("f.b", src, "\"hello\""), Some(HlClass::Str));
assert_eq!(
class_of("f.b", src, "42"),
Some(HlClass::Numeric { float: false }),
);
assert_eq!(
class_of("f.b", src, "1.5"),
Some(HlClass::Numeric { float: true }),
);
}
#[test]
fn literals_stay_boolean_not_keyword() {
for word in ["true", "false", "nil"] {
assert_eq!(
class_of("f.b", "x = true\ny = false\nz = nil\n", word),
Some(HlClass::Boolean),
"`{word}` must stay Boolean — see BLUE_RESERVED_WORDS",
);
}
}
#[test]
fn lowered_infix_callees_are_not_surface_keywords() {
let src = "a = not(b)\n";
assert_eq!(class_of("f.b", src, "not"), Some(HlClass::Variable));
assert!(!BLUE_RESERVED_WORDS.contains(&"and"));
assert!(!BLUE_RESERVED_WORDS.contains(&"or"));
}
#[test]
fn reserved_word_set_is_the_transcribed_upstream_set() {
assert_eq!(BLUE_RESERVED_WORDS.len(), 15);
let mut sorted = BLUE_RESERVED_WORDS.to_vec();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), 15, "no duplicate reserved words");
}
}