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, pub quotes: Vec, 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 { 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 { 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), ];