diff --git a/src/forms/syntax.rs b/src/forms/syntax.rs index 7c27d37..3607621 100644 --- a/src/forms/syntax.rs +++ b/src/forms/syntax.rs @@ -11,182 +11,134 @@ pub(crate) struct Syntax { pub constants: &'static [&'static str], } -/// C, Rust, JavaScript, Go, Java, Swift and kin. -pub(crate) const C_LIKE: Syntax = Syntax { - comment: "//", - word_comment: false, - keywords: &[ - "as", - "async", - "await", - "break", - "case", - "catch", - "class", - "const", - "continue", - "do", - "else", - "enum", - "export", - "extends", - "fn", - "for", - "from", - "func", - "function", - "if", - "impl", - "import", - "in", - "interface", - "let", - "match", - "mut", - "new", - "package", - "pub", - "return", - "static", - "struct", - "switch", - "throw", - "trait", - "try", - "type", - "use", - "var", - "where", - "while", - "yield", - ], - constants: &[ - "false", - "nil", - "null", - "self", - "Self", - "this", - "true", - "undefined", - "None", - "Some", - ], -}; +impl Syntax { + const fn new( + comment: &'static str, + keywords: &'static [&'static str], + constants: &'static [&'static str], + ) -> Self { + Self { + comment, + word_comment: false, + keywords, + constants, + } + } +} -const PYTHON: Syntax = Syntax { - comment: "#", - word_comment: false, - keywords: &[ - "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", - ], - constants: &["True", "False", "None", "self"], -}; +#[rustfmt::skip] +const RUST: Syntax = Syntax::new("//", &[ + "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern", + "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", + "return", "static", "struct", "super", "trait", "type", "unsafe", "use", "where", "while", +], &["true", "false", "self", "Self", "None", "Some", "Ok", "Err"]); -const SHELL: Syntax = Syntax { - comment: "#", +#[rustfmt::skip] +const SCRIPT: Syntax = Syntax::new("//", &[ + "as", "async", "await", "break", "case", "catch", "class", "const", "continue", "default", + "delete", "do", "else", "export", "extends", "finally", "for", "from", "function", "if", + "import", "in", "instanceof", "interface", "let", "new", "of", "return", "static", "switch", + "throw", "try", "type", "typeof", "var", "void", "while", "yield", +], &["true", "false", "null", "undefined", "this", "NaN"]); + +/// C, C++, Java, Go, Swift, Kotlin, C# and kin. +#[rustfmt::skip] +pub(crate) const C_LIKE: Syntax = Syntax::new("//", &[ + "break", "case", "catch", "class", "const", "continue", "default", "defer", "do", "else", + "enum", "extends", "final", "for", "func", "fun", "go", "if", "implements", "import", + "interface", "let", "namespace", "new", "package", "private", "protected", "public", "return", + "static", "struct", "switch", "throw", "try", "typedef", "union", "using", "val", "var", + "void", "while", +], &["true", "false", "null", "nil", "nullptr", "this", "self"]); + +#[rustfmt::skip] +const PYTHON: Syntax = Syntax::new("#", &[ + "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", +], &["True", "False", "None", "self"]); + +#[rustfmt::skip] +const RUBY: Syntax = Syntax::new("#", &[ + "alias", "and", "begin", "break", "case", "class", "def", "do", "else", "elsif", "end", + "ensure", "for", "if", "in", "module", "next", "not", "or", "redo", "rescue", "retry", + "return", "then", "unless", "until", "when", "while", "yield", +], &["true", "false", "nil", "self"]); + +#[rustfmt::skip] +const SHELL: Syntax = Syntax { word_comment: true, ..Syntax::new("#", &[ + "case", "do", "done", "elif", "else", "esac", "export", "fi", "for", "function", "if", "in", + "local", "return", "then", "until", "while", +], &["true", "false"]) }; + +/// `#` languages, CMake and Make: no word list. +const HASH: Syntax = Syntax::new("#", &[], &["true", "false"]); + +/// YAML, whose `#` starts a comment only after a space. +const YAML: Syntax = Syntax { word_comment: true, - keywords: &[ - "case", "do", "done", "elif", "else", "esac", "export", "fi", "for", "function", "if", - "in", "local", "return", "then", "until", "while", - ], - constants: &["true", "false"], + ..Syntax::new("#", &[], &["true", "false", "null", "yes", "no"]) }; -/// TOML, YAML, INI and other settings: keys are not words. -const SETTINGS: Syntax = Syntax { - comment: "#", - word_comment: true, - keywords: &[], - constants: &["true", "false", "null", "yes", "no", "on", "off"], -}; +const INI: Syntax = Syntax::new(";", &[], &["true", "false", "yes", "no", "on", "off"]); -const INI: Syntax = Syntax { - comment: ";", - ..SETTINGS -}; +#[rustfmt::skip] +const SQL: Syntax = Syntax::new("--", &[ + "SELECT", "FROM", "WHERE", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AND", "OR", + "NOT", "AS", "GROUP", "BY", "ORDER", "HAVING", "LIMIT", "OFFSET", "INSERT", "INTO", "VALUES", + "UPDATE", "SET", "DELETE", "CREATE", "TABLE", "INDEX", "DROP", "ALTER", "DESC", "ASC", + "DISTINCT", "UNION", "WITH", "CASE", "WHEN", "THEN", "ELSE", "END", "IS", "IN", "LIKE", + "BETWEEN", "EXISTS", +], &["NULL", "TRUE", "FALSE"]); -const SQL: Syntax = Syntax { - comment: "--", - word_comment: false, - keywords: &[ - "SELECT", "FROM", "WHERE", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AND", "OR", - "NOT", "AS", "GROUP", "BY", "ORDER", "HAVING", "LIMIT", "OFFSET", "INSERT", "INTO", - "VALUES", "UPDATE", "SET", "DELETE", "CREATE", "TABLE", "INDEX", "DROP", "ALTER", "DESC", - "ASC", "DISTINCT", "UNION", "WITH", "CASE", "WHEN", "THEN", "ELSE", "END", "IS", "IN", - "LIKE", "BETWEEN", "EXISTS", - ], - constants: &["NULL", "TRUE", "FALSE"], -}; +#[rustfmt::skip] +const LUA: Syntax = Syntax::new("--", &[ + "and", "break", "do", "else", "elseif", "end", "for", "function", "goto", "if", "in", "local", + "not", "or", "repeat", "return", "then", "until", "while", +], &["true", "false", "nil"]); -const LUA: Syntax = Syntax { - comment: "--", - word_comment: false, - keywords: &[ - "and", "break", "do", "else", "elseif", "end", "for", "function", "goto", "if", "in", - "local", "not", "or", "repeat", "return", "then", "until", "while", - ], - constants: &["true", "false", "nil"], -}; +#[rustfmt::skip] +const HASKELL: Syntax = Syntax::new("--", &[ + "case", "class", "data", "deriving", "do", "else", "if", "import", "in", "instance", "let", + "module", "newtype", "of", "then", "type", "where", +], &["True", "False"]); -const LISP: Syntax = Syntax { - comment: ";", - word_comment: false, - keywords: &[ - "defun", "defn", "define", "lambda", "let", "if", "cond", "quote", - ], - constants: &["nil", "t", "true", "false"], -}; +#[rustfmt::skip] +const LISP: Syntax = Syntax::new(";", &[ + "defun", "defn", "define", "lambda", "let", "if", "cond", +], &["nil", "t"]); -/// Ruby, Perl, CMake and other `#` languages. -const HASH: Syntax = Syntax { - comment: "#", - ..C_LIKE -}; +#[rustfmt::skip] +const ERLANG: Syntax = Syntax::new("%", &[ + "after", "begin", "case", "catch", "end", "fun", "if", "of", "receive", "try", "when", +], &["true", "false"]); -const TEX: Syntax = Syntax { - comment: "%", - word_comment: false, - keywords: &[], - constants: &[], -}; +const TEX: Syntax = Syntax::new("%", &[], &[]); /// Extensions of prose and plain text, which read plain. +#[rustfmt::skip] const PROSE: &[&str] = &[ - "md", "markdown", "mdown", "mkd", "txt", "text", "rst", "adoc", "asciidoc", "org", "csv", - "tsv", "log", + "md", "markdown", "mdown", "mkd", "txt", "text", "rst", "adoc", "asciidoc", "org", "csv", "tsv", + "log", ]; /// Languages by extension; any other code reads C-like. +#[rustfmt::skip] const BY_EXTENSION: &[(&Syntax, &[&str])] = &[ + (&RUST, &["rs"]), + (&SCRIPT, &["js", "mjs", "cjs", "jsx", "ts", "tsx", "mts"]), (&PYTHON, &["py", "pyi", "pyw"]), + (&RUBY, &["rb", "rake", "gemspec"]), (&SHELL, &["sh", "bash", "zsh", "fish", "ksh"]), - ( - &HASH, - &[ - "rb", "pl", "r", "cmake", "mk", "nix", "ex", "exs", "jl", "nim", "cr", "tcl", "ps1", - ], - ), - ( - &SETTINGS, - &[ - "toml", - "yaml", - "yml", - "conf", - "cfg", - "gitignore", - "env", - "properties", - ], - ), + (&HASH, &["pl", "r", "cmake", "mk", "nix", "ex", "exs", "jl", "nim", "cr", "tcl", "ps1", + "toml", "conf", "cfg", "gitignore", "env", "properties"]), + (&YAML, &["yaml", "yml"]), (&INI, &["ini"]), (&SQL, &["sql"]), (&LUA, &["lua"]), + (&HASKELL, &["hs", "lhs", "elm"]), (&LISP, &["clj", "cljs", "lisp", "el", "scm"]), + (&ERLANG, &["erl", "hrl"]), (&TEX, &["tex", "sty", "bib"]), ]; @@ -195,13 +147,10 @@ pub(crate) fn syntax_of(path: &str) -> Option<&'static Syntax> { let name = path.rsplit_once('/').map_or(path, |(_, name)| name); // Named files first: CMakeLists.txt is not prose. match name { - "Makefile" | "GNUmakefile" | "Dockerfile" | "CMakeLists.txt" => return Some(&SHELL), + "Makefile" | "GNUmakefile" | "Dockerfile" | "CMakeLists.txt" => return Some(&HASH), _ => {} } - let ext = match name.rsplit_once('.') { - Some((_, ext)) => ext.to_ascii_lowercase(), - None => return None, - }; + let ext = name.rsplit_once('.')?.1.to_ascii_lowercase(); if PROSE.contains(&ext.as_str()) { return None; } diff --git a/src/git/rows.rs b/src/git/rows.rs index 711f8c1..9ff1847 100644 --- a/src/git/rows.rs +++ b/src/git/rows.rs @@ -184,6 +184,34 @@ mod tests { Some(syntax.keyword), "a TOML key" ); + // Words belong to their own language. + assert_ne!( + color("let from = 42;", "main.rs", cx)[1].1, + Some(syntax.keyword), + "Rust's from" + ); + assert_eq!( + color("def hello", "a.rb", cx)[0].1, + Some(syntax.keyword), + "Ruby's def" + ); + assert_eq!( + color("-- return 42", "Main.hs", cx), + [(0..12, Some(syntax.comment))] + ); + assert_eq!( + color("% return 42", "main.erl", cx), + [(0..11, Some(syntax.comment))] + ); + // A comment may follow code at once outside Shell. + assert_eq!( + color("answer = 42# return 99", "config.toml", cx).last(), + Some(&(11..22, Some(syntax.comment))) + ); + assert_eq!( + color("set(x 1)# return 42", "CMakeLists.txt", cx).last(), + Some(&(8..19, Some(syntax.comment))) + ); // Shell comments start words only. let shell = color("echo foo#bar 42", "run.sh", cx); assert!(