537 lines
19 KiB
Rust
537 lines
19 KiB
Rust
use std::collections::HashSet;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum Language {
|
|
C,
|
|
Coffeescript,
|
|
Cpp,
|
|
Csharp,
|
|
Dart,
|
|
Fish,
|
|
Go,
|
|
Java,
|
|
Javascript,
|
|
Json,
|
|
Kotlin,
|
|
Markdown,
|
|
Perl,
|
|
Php,
|
|
Python,
|
|
Ron,
|
|
Ruby,
|
|
Rust,
|
|
Shell,
|
|
Swift,
|
|
Toml,
|
|
Typescript,
|
|
}
|
|
|
|
impl Language {
|
|
pub const ALL: [Language; 22] = [
|
|
Language::C,
|
|
Language::Coffeescript,
|
|
Language::Cpp,
|
|
Language::Csharp,
|
|
Language::Dart,
|
|
Language::Fish,
|
|
Language::Go,
|
|
Language::Java,
|
|
Language::Javascript,
|
|
Language::Json,
|
|
Language::Kotlin,
|
|
Language::Markdown,
|
|
Language::Perl,
|
|
Language::Php,
|
|
Language::Python,
|
|
Language::Ron,
|
|
Language::Ruby,
|
|
Language::Rust,
|
|
Language::Shell,
|
|
Language::Swift,
|
|
Language::Toml,
|
|
Language::Typescript,
|
|
];
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct Rules {
|
|
pub keywords: HashSet<&'static str>,
|
|
pub line_comments: Vec<&'static str>,
|
|
pub line_comments_at_word_start: bool,
|
|
pub block_comment: Option<BlockComment>,
|
|
pub quotes: Vec<Quote>,
|
|
pub attributes: Attributes,
|
|
pub raw_strings: bool,
|
|
/// Rust: `'` opens a character literal only when a backslash or one
|
|
/// character and a `'` follow. Otherwise it is a lifetime or a label.
|
|
pub lifetimes: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct BlockComment {
|
|
pub open: &'static str,
|
|
pub close: &'static str,
|
|
pub nests: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Quote {
|
|
pub open: &'static str,
|
|
pub close: &'static str,
|
|
pub escapes: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
pub enum Attributes {
|
|
#[default]
|
|
None,
|
|
AtWord,
|
|
HashBracket,
|
|
HashLine,
|
|
LineBracket,
|
|
}
|
|
|
|
const C_STYLE: BlockComment = BlockComment {
|
|
open: "/*",
|
|
close: "*/",
|
|
nests: false,
|
|
};
|
|
const NESTING: BlockComment = BlockComment {
|
|
open: "/*",
|
|
close: "*/",
|
|
nests: true,
|
|
};
|
|
|
|
const DOUBLE: Quote = Quote {
|
|
open: "\"",
|
|
close: "\"",
|
|
escapes: true,
|
|
};
|
|
const SINGLE: Quote = Quote {
|
|
open: "'",
|
|
close: "'",
|
|
escapes: true,
|
|
};
|
|
const TRIPLE_DOUBLE: Quote = Quote {
|
|
open: "\"\"\"",
|
|
close: "\"\"\"",
|
|
escapes: true,
|
|
};
|
|
const TRIPLE_SINGLE: Quote = Quote {
|
|
open: "'''",
|
|
close: "'''",
|
|
escapes: true,
|
|
};
|
|
|
|
fn words(list: &'static str) -> HashSet<&'static str> {
|
|
list.split_whitespace().collect()
|
|
}
|
|
|
|
pub fn rules_for(language: Language) -> Rules {
|
|
match language {
|
|
Language::C => Rules {
|
|
keywords: words(KEYWORDS_C),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
attributes: Attributes::HashLine,
|
|
..Default::default()
|
|
},
|
|
Language::Cpp => Rules {
|
|
keywords: words(KEYWORDS_CPP),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
attributes: Attributes::HashLine,
|
|
..Default::default()
|
|
},
|
|
Language::Csharp => Rules {
|
|
keywords: words(KEYWORDS_CSHARP),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
..Default::default()
|
|
},
|
|
Language::Coffeescript => Rules {
|
|
keywords: words(KEYWORDS_COFFEESCRIPT),
|
|
line_comments: vec!["#"],
|
|
block_comment: Some(BlockComment {
|
|
open: "###",
|
|
close: "###",
|
|
nests: false,
|
|
}),
|
|
quotes: vec![TRIPLE_DOUBLE, TRIPLE_SINGLE, DOUBLE, SINGLE],
|
|
..Default::default()
|
|
},
|
|
Language::Dart => Rules {
|
|
keywords: words(KEYWORDS_DART),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(NESTING),
|
|
quotes: vec![TRIPLE_DOUBLE, TRIPLE_SINGLE, DOUBLE, SINGLE],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Fish => Rules {
|
|
keywords: words(KEYWORDS_FISH),
|
|
line_comments: vec!["#"],
|
|
line_comments_at_word_start: true,
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
..Default::default()
|
|
},
|
|
Language::Go => Rules {
|
|
keywords: words(KEYWORDS_GO),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![
|
|
DOUBLE,
|
|
SINGLE,
|
|
Quote {
|
|
open: "`",
|
|
close: "`",
|
|
escapes: false,
|
|
},
|
|
],
|
|
..Default::default()
|
|
},
|
|
Language::Java => Rules {
|
|
keywords: words(KEYWORDS_JAVA),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Javascript => Rules {
|
|
keywords: words(KEYWORDS_JAVASCRIPT),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![
|
|
DOUBLE,
|
|
SINGLE,
|
|
Quote {
|
|
open: "`",
|
|
close: "`",
|
|
escapes: true,
|
|
},
|
|
],
|
|
..Default::default()
|
|
},
|
|
Language::Json => Rules {
|
|
keywords: words(KEYWORDS_JSON),
|
|
quotes: vec![DOUBLE],
|
|
..Default::default()
|
|
},
|
|
Language::Kotlin => Rules {
|
|
keywords: words(KEYWORDS_KOTLIN),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(NESTING),
|
|
quotes: vec![
|
|
Quote {
|
|
open: "\"\"\"",
|
|
close: "\"\"\"",
|
|
escapes: false,
|
|
},
|
|
DOUBLE,
|
|
SINGLE,
|
|
],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Perl => Rules {
|
|
keywords: words(KEYWORDS_PERL),
|
|
line_comments: vec!["#"],
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
..Default::default()
|
|
},
|
|
Language::Php => Rules {
|
|
keywords: words(KEYWORDS_PHP),
|
|
line_comments: vec!["//", "#"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Python => Rules {
|
|
keywords: words(KEYWORDS_PYTHON),
|
|
line_comments: vec!["#"],
|
|
quotes: vec![TRIPLE_DOUBLE, TRIPLE_SINGLE, DOUBLE, SINGLE],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Ron => Rules {
|
|
keywords: words(KEYWORDS_RON),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(NESTING),
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
attributes: Attributes::HashBracket,
|
|
raw_strings: true,
|
|
..Default::default()
|
|
},
|
|
Language::Ruby => Rules {
|
|
keywords: words(KEYWORDS_RUBY),
|
|
line_comments: vec!["#"],
|
|
quotes: vec![DOUBLE, SINGLE],
|
|
..Default::default()
|
|
},
|
|
Language::Rust => Rules {
|
|
keywords: words(KEYWORDS_RUST),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(NESTING),
|
|
// No `'` here: `lifetimes` decides when one opens a character literal.
|
|
quotes: vec![DOUBLE],
|
|
attributes: Attributes::HashBracket,
|
|
raw_strings: true,
|
|
lifetimes: true,
|
|
..Default::default()
|
|
},
|
|
Language::Shell => Rules {
|
|
keywords: words(KEYWORDS_SHELL),
|
|
line_comments: vec!["#"],
|
|
line_comments_at_word_start: true,
|
|
quotes: vec![
|
|
DOUBLE,
|
|
Quote {
|
|
open: "'",
|
|
close: "'",
|
|
escapes: false,
|
|
},
|
|
],
|
|
..Default::default()
|
|
},
|
|
Language::Swift => Rules {
|
|
keywords: words(KEYWORDS_SWIFT),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(NESTING),
|
|
quotes: vec![TRIPLE_DOUBLE, DOUBLE],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Toml => Rules {
|
|
keywords: words(KEYWORDS_TOML),
|
|
line_comments: vec!["#"],
|
|
quotes: vec![
|
|
TRIPLE_DOUBLE,
|
|
Quote {
|
|
open: "'''",
|
|
close: "'''",
|
|
escapes: false,
|
|
},
|
|
DOUBLE,
|
|
Quote {
|
|
open: "'",
|
|
close: "'",
|
|
escapes: false,
|
|
},
|
|
],
|
|
attributes: Attributes::LineBracket,
|
|
..Default::default()
|
|
},
|
|
Language::Typescript => Rules {
|
|
keywords: words(KEYWORDS_TYPESCRIPT),
|
|
line_comments: vec!["//"],
|
|
block_comment: Some(C_STYLE),
|
|
quotes: vec![
|
|
DOUBLE,
|
|
SINGLE,
|
|
Quote {
|
|
open: "`",
|
|
close: "`",
|
|
escapes: true,
|
|
},
|
|
],
|
|
attributes: Attributes::AtWord,
|
|
..Default::default()
|
|
},
|
|
Language::Markdown => Rules::default(),
|
|
}
|
|
}
|
|
|
|
const KEYWORDS_C: &str =
|
|
"auto break case char const continue default do double else enum extern float for goto if
|
|
int long register return short signed sizeof static struct switch typedef union unsigned
|
|
void volatile while";
|
|
|
|
const KEYWORDS_CPP: &str =
|
|
"asm auto bool break case catch char class const const_cast continue default delete do
|
|
double dynamic_cast else enum explicit export extern false float for friend goto if inline
|
|
int long mutable namespace new operator private protected public register reinterpret_cast
|
|
return short signed sizeof static static_cast struct switch template this throw true try
|
|
typedef typeid typename union unsigned using virtual void volatile wchar_t while";
|
|
|
|
const KEYWORDS_CSHARP: &str =
|
|
"abstract as base bool break byte case catch char checked class const continue decimal
|
|
default delegate do double else enum event explicit extern false finally fixed float for
|
|
foreach goto if implicit in int interface internal is lock long namespace new null object
|
|
operator out override params private protected public readonly ref return sbyte sealed short
|
|
sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked
|
|
unsafe ushort using virtual void volatile while";
|
|
|
|
const KEYWORDS_COFFEESCRIPT: &str =
|
|
"Infinity NaN and arguments await break by case catch class continue debugger delete defer
|
|
default do else export extends false finally for function if import in instanceof is isnt
|
|
let loop new no not null of on or package return super switch this throw true try typeof
|
|
unless undefined var wait when with yield";
|
|
|
|
const KEYWORDS_DART: &str =
|
|
"abstract as assert async await base break case catch class const continue covariant
|
|
default deferred do dynamic else enum export extends external factory false final finally
|
|
for get if implements import in interface is late library mixin new null on operator part
|
|
required rethrow return sealed set show static super switch this throw true try var void
|
|
when with while yield";
|
|
|
|
const KEYWORDS_FISH: &str =
|
|
"and begin break builtin case command continue else end exec for function if in not or
|
|
return switch while set echo test string math read source";
|
|
|
|
const KEYWORDS_GO: &str =
|
|
"break case chan const continue default defer else fallthrough false for func go goto if
|
|
import interface map package range return select struct switch true type var";
|
|
|
|
const KEYWORDS_JAVA: &str =
|
|
"abstract assert boolean break byte case catch char class const continue default do double
|
|
else enum extends final finally float for goto if implements import instanceof int interface
|
|
long native new null package private protected public return short static strictfp super
|
|
switch synchronized this throw throws transient try void volatile while";
|
|
|
|
const KEYWORDS_JAVASCRIPT: &str =
|
|
"async await boolean break case catch class const continue debugger default delete do else
|
|
enum export extends false finally for function if implements import in instanceof interface
|
|
let new null package private protected public return super switch this throw true try typeof
|
|
var void while with yield";
|
|
|
|
const KEYWORDS_JSON: &str = "true false null";
|
|
|
|
const KEYWORDS_KOTLIN: &str =
|
|
"actual abstract annotation as break by catch class companion const constructor continue
|
|
coroutine crossinline data delegate dynamic do else enum expect external false final finally
|
|
for fun get if import in infix inline interface internal is lazy lateinit native null object
|
|
open operator out override package private protected public reified return sealed set super
|
|
suspend tailrec this throw true try typealias typeof val var vararg when while yield";
|
|
|
|
const KEYWORDS_PERL: &str =
|
|
"__DATA__ __END__ __FILE__ __LINE__ __PACKAGE__ and cmp continue do else elsif eq eval for
|
|
foreach goto gt if last le lt my ne next no not or package redo ref return sub unless until
|
|
use while xor";
|
|
|
|
const KEYWORDS_PHP: &str =
|
|
"__halt_compiler abstract and array as break callable case catch class clone const continue
|
|
declare default die do echo else elseif empty enddeclare endfor endforeach endif endswitch
|
|
endwhile eval exit extends final finally fn for foreach function global goto if implements
|
|
include include_once instanceof insteadof interface isset list match new or print private
|
|
protected public require require_once return static switch throw trait try unset use var
|
|
while xor yield";
|
|
|
|
const KEYWORDS_PYTHON: &str =
|
|
"False True and as assert async await break class continue def del elif else except finally
|
|
for from global if import in is lambda nonlocal not or pass raise return try while with
|
|
yield";
|
|
|
|
const KEYWORDS_RON: &str = "true false Some None inf NaN";
|
|
|
|
const KEYWORDS_RUBY: &str =
|
|
"__ENCODING__ __END__ __FILE__ __LINE__ BEGIN END alias and begin break case class def do
|
|
else elsif end ensure false for if in module next nil not or redo rescue retry return self
|
|
super then true undef unless until when while yield";
|
|
|
|
const KEYWORDS_RUST: &str =
|
|
"as async await break const continue crate dyn else enum extern false fn for if impl in
|
|
let loop match mod move mut pub ref return Self self static struct super trait true type
|
|
union unsafe use where while abstract become box do final macro override priv try typeof
|
|
unsized virtual yield";
|
|
|
|
const KEYWORDS_SHELL: &str =
|
|
"alias bg bind break builtin caller cd command compgen complete compopt continue declare
|
|
dirs disown echo enable eval exec exit export fc fg getopts hash help history jobs kill let
|
|
local logout popd printf pushd pwd read readonly return set shift shopt source suspend
|
|
test";
|
|
|
|
const KEYWORDS_SWIFT: &str =
|
|
"_ associatedtype class deinit enum extension fileprivate func import init inout internal
|
|
let open operator private precedencegroup protocol public rethrows static struct subscript
|
|
typealias var break case catch continue default defer do else fallthrough for guard if in
|
|
repeat return throw switch where while Any as await false is nil self Self super throws true
|
|
try associativity convenience didSet dynamic final get indirect infix lazy left mutating none
|
|
nonmutating optional override postfix precedence prefix Protocol required right set some Type
|
|
unowned weak willSet";
|
|
|
|
const KEYWORDS_TOML: &str = "true false inf nan";
|
|
|
|
const KEYWORDS_TYPESCRIPT: &str =
|
|
"abstract as asserts await break case catch class const constructor continue debugger
|
|
default delete do else enum export extends false finally for from function get if implements
|
|
import in infer instanceof interface is keyof let module namespace new null number object
|
|
package private protected public readonly require global return set static string super
|
|
switch this throw true try type typeof undefined unique unknown var void while with yield";
|
|
|
|
/// The highlighter's language for a fence's info word, or `None` for one it
|
|
/// has no rules for. Also what `super::file_language` reads for a file's
|
|
/// extension -- one table, so a language added for fences is a language
|
|
/// added for files.
|
|
pub fn fence_language(name: Option<&str>) -> Option<Language> {
|
|
let name = name?.trim().to_lowercase();
|
|
FENCE_LANGUAGES
|
|
.iter()
|
|
.find(|(alias, _)| *alias == name)
|
|
.map(|(_, language)| *language)
|
|
}
|
|
|
|
/// The extension is the part after the *last* dot, which is what makes
|
|
/// `build.gradle.kts` Kotlin. A leading dot is not one: `.bashrc` has no
|
|
/// extension, it has a name that starts with a dot. A name with no dot at
|
|
/// all -- `Makefile` -- is likewise `None`.
|
|
pub fn file_language(name: &str) -> Option<Language> {
|
|
let dot = name.rfind('.')?;
|
|
if dot < 1 {
|
|
return None;
|
|
}
|
|
fence_language(Some(&name[dot + 1..]))
|
|
}
|
|
|
|
const FENCE_LANGUAGES: &[(&str, Language)] = &[
|
|
("kotlin", Language::Kotlin),
|
|
("kt", Language::Kotlin),
|
|
("kts", Language::Kotlin),
|
|
("rust", Language::Rust),
|
|
("rs", Language::Rust),
|
|
("sh", Language::Shell),
|
|
("bash", Language::Shell),
|
|
("shell", Language::Shell),
|
|
("zsh", Language::Shell),
|
|
("console", Language::Shell),
|
|
("python", Language::Python),
|
|
("py", Language::Python),
|
|
("javascript", Language::Javascript),
|
|
("js", Language::Javascript),
|
|
("jsx", Language::Javascript),
|
|
("typescript", Language::Typescript),
|
|
("ts", Language::Typescript),
|
|
("tsx", Language::Typescript),
|
|
("java", Language::Java),
|
|
("c", Language::C),
|
|
("h", Language::C),
|
|
("cpp", Language::Cpp),
|
|
("c++", Language::Cpp),
|
|
("cc", Language::Cpp),
|
|
("hpp", Language::Cpp),
|
|
("csharp", Language::Csharp),
|
|
("cs", Language::Csharp),
|
|
("c#", Language::Csharp),
|
|
("go", Language::Go),
|
|
("golang", Language::Go),
|
|
("swift", Language::Swift),
|
|
("dart", Language::Dart),
|
|
("ruby", Language::Ruby),
|
|
("rb", Language::Ruby),
|
|
("php", Language::Php),
|
|
("perl", Language::Perl),
|
|
("pl", Language::Perl),
|
|
("coffeescript", Language::Coffeescript),
|
|
("coffee", Language::Coffeescript),
|
|
("ron", Language::Ron),
|
|
("toml", Language::Toml),
|
|
("fish", Language::Fish),
|
|
("json", Language::Json),
|
|
("markdown", Language::Markdown),
|
|
("md", Language::Markdown),
|
|
];
|