Git hosting and a container registry in one Rust binary (axum + Astro)
Languages: classify with Linguist's own data (vendor, documentation and generated rules, shebangs, modelines, heuristics, groups), git check-attr for .gitattributes, admin languages report
10 files changed, +1405 -627
+0-24Cargo.lock
| @@ -440,16 +440,6 @@ dependencies = [ | ||
| 440 | 440 | "syn 3.0.6", |
| 441 | 441 | ] |
| 442 | 442 | |
| 443 | -[[package]] | |
| 444 | -name = "bstr" | |
| 445 | -version = "1.13.1" | |
| 446 | -source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 447 | -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" | |
| 448 | -dependencies = [ | |
| 449 | - "memchr", | |
| 450 | - "serde_core", | |
| 451 | -] | |
| 452 | - | |
| 453 | 443 | [[package]] |
| 454 | 444 | name = "bumpalo" |
| 455 | 445 | version = "3.20.3" |
| @@ -1502,19 +1492,6 @@ dependencies = [ | ||
| 1502 | 1492 | "zeroize", |
| 1503 | 1493 | ] |
| 1504 | 1494 | |
| 1505 | -[[package]] | |
| 1506 | -name = "globset" | |
| 1507 | -version = "0.4.20" | |
| 1508 | -source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1509 | -checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" | |
| 1510 | -dependencies = [ | |
| 1511 | - "aho-corasick", | |
| 1512 | - "bstr", | |
| 1513 | - "log", | |
| 1514 | - "regex-automata", | |
| 1515 | - "regex-syntax", | |
| 1516 | -] | |
| 1517 | - | |
| 1518 | 1495 | [[package]] |
| 1519 | 1496 | name = "group" |
| 1520 | 1497 | version = "0.14.0" |
| @@ -1997,7 +1974,6 @@ dependencies = [ | ||
| 1997 | 1974 | "dotenvy", |
| 1998 | 1975 | "flate2", |
| 1999 | 1976 | "futures", |
| 2000 | - "globset", | |
| 2001 | 1977 | "hex", |
| 2002 | 1978 | "hmac", |
| 2003 | 1979 | "irongit-shared", |
+21-0README.md
| @@ -84,6 +84,26 @@ commits count on your heatmap. It runs as a background job on the server | ||
| 84 | 84 | import tokens are revoked when the job ends. Sign in with GitHub and "Connect |
| 85 | 85 | GitHub" need `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` (see secrets.md). |
| 86 | 86 | |
| 87 | +## Language stats | |
| 88 | + | |
| 89 | +Repo pages show a GitHub-style language bar and profiles a chart of every | |
| 90 | +language across the account's repositories. Files are classified with | |
| 91 | +GitHub Linguist's own data (`backend/src/languages/linguist.json`): its | |
| 92 | +language table (extensions, filenames, shebang interpreters, groups, colors), | |
| 93 | +its vendored, documentation and generated rules, and its content heuristics | |
| 94 | +for shared extensions like `.h` or `.sql`. `.gitattributes` overrides | |
| 95 | +(`linguist-vendored`, `linguist-language=...`) are honored. Results are | |
| 96 | +cached per commit and recomputed when the default branch moves. | |
| 97 | + | |
| 98 | +```sh | |
| 99 | +irongit admin languages huncholane/irongit --files # every file and why it counts or not | |
| 100 | +irongit admin languages path/to/repo.git # any git directory, no database | |
| 101 | +python3 scripts/gen-linguist.py v9.8.0 # update to a newer Linguist release | |
| 102 | +``` | |
| 103 | + | |
| 104 | +After regenerating the data, add a migration with `delete from | |
| 105 | +repo_languages;` so every repository is recounted. | |
| 106 | + | |
| 87 | 107 | ## Tests |
| 88 | 108 | |
| 89 | 109 | ```sh |
| @@ -106,6 +126,7 @@ backend/ | ||
| 106 | 126 | src/transport.rs spawning upload-pack/receive-pack with hooks and limits |
| 107 | 127 | src/hook.rs pre-receive size and quota checks |
| 108 | 128 | src/push.rs post-push: events, default branch, heatmap contributions |
| 129 | + src/languages/ language stats (Linguist data and rules) | |
| 109 | 130 | src/ssh.rs built-in SSH server |
| 110 | 131 | src/lfs.rs Git LFS batch API |
| 111 | 132 | src/registry/ OCI distribution API (docker push/pull) |
+0-1backend/Cargo.toml
| @@ -51,4 +51,3 @@ clap = { version = "4.6.7", features = ["derive", "env"] } | ||
| 51 | 51 | lettre = { version = "0.11.23", default-features = false, features = ["tokio1-rustls-tls", "smtp-transport", "builder", "hostname"] } |
| 52 | 52 | axum-extra = { version = "0.12.6", features = ["cookie", "typed-header"] } |
| 53 | 53 | async-compression = { version = "0.4.50", features = ["tokio", "gzip"] } |
| 54 | -globset = "0.4.20" |
+3-0backend/migrations/0006_languages_linguist.sql
| @@ -0,0 +1,3 @@ | ||
| 1 | +-- Language stats now follow Linguist's own data (vendor, documentation and | |
| 2 | +-- generated rules, shebangs, heuristics, language groups); recount them all. | |
| 3 | +delete from repo_languages; |
+0-602backend/src/languages.rs
| @@ -1,602 +0,0 @@ | ||
| 1 | -//! Language breakdown of a repository, the way GitHub's Linguist does it: | |
| 2 | -//! bytes per language across the default branch, leaving out vendored, | |
| 3 | -//! generated and documentation files and data or prose formats (JSON, YAML, | |
| 4 | -//! Markdown). A repo's `.gitattributes` can override any of that with | |
| 5 | -//! `linguist-vendored`, `linguist-generated`, `linguist-documentation` and | |
| 6 | -//! `linguist-language=<Name>`. | |
| 7 | -//! | |
| 8 | -//! Results are cached per commit in `repo_languages`, so a page view costs a | |
| 9 | -//! lookup; recomputing walks the tree with `git ls-tree -r -l` (sizes only, | |
| 10 | -//! no file contents). | |
| 11 | - | |
| 12 | -use std::process::Stdio; | |
| 13 | - | |
| 14 | -use globset::{Glob, GlobSet, GlobSetBuilder}; | |
| 15 | -use serde::{Deserialize, Serialize}; | |
| 16 | - | |
| 17 | -use crate::{git::Git, models::Repo, state::AppState}; | |
| 18 | - | |
| 19 | -/// Languages that count, with GitHub's colors. Data and prose formats are | |
| 20 | -/// deliberately absent: they never appear in the breakdown. | |
| 21 | -const LANGUAGES: &[(&str, &str)] = &[ | |
| 22 | - ("V", "#4f87c4"), | |
| 23 | - ("Typst", "#239dad"), | |
| 24 | - ("Starlark", "#76d275"), | |
| 25 | - ("Reason", "#ff5847"), | |
| 26 | - ("ReScript", "#ed5051"), | |
| 27 | - ("QML", "#44a51c"), | |
| 28 | - ("Odin", "#60AFFE"), | |
| 29 | - ("Mojo", "#ff4c1f"), | |
| 30 | - ("Meson", "#007800"), | |
| 31 | - ("Haxe", "#df7900"), | |
| 32 | - ("Gleam", "#ffaff3"), | |
| 33 | - ("Cairo", "#ff4a48"), | |
| 34 | - ("Bicep", "#519aba"), | |
| 35 | - ("Apex", "#1797c0"), | |
| 36 | - ("Assembly", "#6E4C13"), | |
| 37 | - ("Astro", "#ff5a03"), | |
| 38 | - ("Awk", "#c30e9b"), | |
| 39 | - ("Batchfile", "#C1F12E"), | |
| 40 | - ("C", "#555555"), | |
| 41 | - ("C#", "#178600"), | |
| 42 | - ("C++", "#f34b7d"), | |
| 43 | - ("CMake", "#DA3434"), | |
| 44 | - ("CSS", "#663399"), | |
| 45 | - ("Clojure", "#db5855"), | |
| 46 | - ("CoffeeScript", "#244776"), | |
| 47 | - ("Common Lisp", "#3fb68b"), | |
| 48 | - ("Crystal", "#000100"), | |
| 49 | - ("Cuda", "#3A4E3A"), | |
| 50 | - ("D", "#ba595e"), | |
| 51 | - ("Dart", "#00B4AB"), | |
| 52 | - ("Dockerfile", "#384d54"), | |
| 53 | - ("EJS", "#a91e50"), | |
| 54 | - ("Elixir", "#6e4a7e"), | |
| 55 | - ("Elm", "#60B5CC"), | |
| 56 | - ("Emacs Lisp", "#c065db"), | |
| 57 | - ("Erlang", "#B83998"), | |
| 58 | - ("F#", "#b845fc"), | |
| 59 | - ("Fortran", "#4d41b1"), | |
| 60 | - ("GDScript", "#355570"), | |
| 61 | - ("GLSL", "#5686a5"), | |
| 62 | - ("Go", "#00ADD8"), | |
| 63 | - ("Groovy", "#4298b8"), | |
| 64 | - ("HCL", "#844FBA"), | |
| 65 | - ("HLSL", "#aace60"), | |
| 66 | - ("HTML", "#e34c26"), | |
| 67 | - ("Handlebars", "#f7931e"), | |
| 68 | - ("Haskell", "#5e5086"), | |
| 69 | - ("Java", "#b07219"), | |
| 70 | - ("JavaScript", "#f1e05a"), | |
| 71 | - ("Julia", "#a270ba"), | |
| 72 | - ("Jupyter Notebook", "#DA5B0B"), | |
| 73 | - ("Kotlin", "#A97BFF"), | |
| 74 | - ("Less", "#1d365d"), | |
| 75 | - ("Liquid", "#67b8de"), | |
| 76 | - ("Lua", "#000080"), | |
| 77 | - ("MDX", "#fcb32c"), | |
| 78 | - ("Makefile", "#427819"), | |
| 79 | - ("Nim", "#ffc200"), | |
| 80 | - ("Nix", "#7e7eff"), | |
| 81 | - ("OCaml", "#ef7a08"), | |
| 82 | - ("Objective-C", "#438eff"), | |
| 83 | - ("Objective-C++", "#6866fb"), | |
| 84 | - ("PHP", "#4F5D95"), | |
| 85 | - ("PLpgSQL", "#336790"), | |
| 86 | - ("Pascal", "#E3F171"), | |
| 87 | - ("Perl", "#0298c3"), | |
| 88 | - ("PowerShell", "#012456"), | |
| 89 | - ("Pug", "#a86454"), | |
| 90 | - ("PureScript", "#1D222D"), | |
| 91 | - ("Python", "#3572A5"), | |
| 92 | - ("R", "#198CE7"), | |
| 93 | - ("Racket", "#3c5caa"), | |
| 94 | - ("Ruby", "#701516"), | |
| 95 | - ("Rust", "#dea584"), | |
| 96 | - ("SCSS", "#c6538c"), | |
| 97 | - ("Sass", "#a53b70"), | |
| 98 | - ("Scala", "#c22d40"), | |
| 99 | - ("Scheme", "#1e4aec"), | |
| 100 | - ("Shell", "#89e051"), | |
| 101 | - ("Slint", "#2379F4"), | |
| 102 | - ("Solidity", "#AA6746"), | |
| 103 | - ("Svelte", "#ff3e00"), | |
| 104 | - ("Swift", "#F05138"), | |
| 105 | - ("Tcl", "#e4cc98"), | |
| 106 | - ("TeX", "#3D6117"), | |
| 107 | - ("Twig", "#c1d026"), | |
| 108 | - ("TypeScript", "#3178c6"), | |
| 109 | - ("Vala", "#a56de2"), | |
| 110 | - ("Vim Script", "#199f4b"), | |
| 111 | - ("Visual Basic .NET", "#945db7"), | |
| 112 | - ("Vue", "#41b883"), | |
| 113 | - ("WGSL", "#1a5e9a"), | |
| 114 | - ("Zig", "#ec915c"), | |
| 115 | -]; | |
| 116 | - | |
| 117 | -/// GitHub's color for a language, or a neutral grey for unknown names. | |
| 118 | -pub fn color(name: &str) -> &'static str { | |
| 119 | - LANGUAGES.iter().find(|(n, _)| *n == name).map(|(_, c)| *c).unwrap_or("#8b949e") | |
| 120 | -} | |
| 121 | - | |
| 122 | -fn known(name: &str) -> Option<&'static str> { | |
| 123 | - LANGUAGES.iter().find(|(n, _)| n.eq_ignore_ascii_case(name)).map(|(n, _)| *n) | |
| 124 | -} | |
| 125 | - | |
| 126 | -/// Language of a file by its name, then its extension. | |
| 127 | -pub fn detect(path: &str) -> Option<&'static str> { | |
| 128 | - let name = path.rsplit('/').next().unwrap_or(path); | |
| 129 | - let by_name = match name { | |
| 130 | - "Dockerfile" | "Containerfile" => Some("Dockerfile"), | |
| 131 | - "Makefile" | "makefile" | "GNUmakefile" => Some("Makefile"), | |
| 132 | - "CMakeLists.txt" => Some("CMake"), | |
| 133 | - "Rakefile" | "Gemfile" | "Vagrantfile" | "Podfile" | "Fastfile" => Some("Ruby"), | |
| 134 | - "Jenkinsfile" => Some("Groovy"), | |
| 135 | - "meson.build" => Some("Meson"), | |
| 136 | - "BUILD" | "BUILD.bazel" | "WORKSPACE" => Some("Starlark"), | |
| 137 | - "PKGBUILD" | ".bashrc" | ".bash_profile" | ".zshrc" | ".profile" => Some("Shell"), | |
| 138 | - _ if name.starts_with("Dockerfile.") || name.ends_with(".Dockerfile") || name.ends_with(".dockerfile") => Some("Dockerfile"), | |
| 139 | - _ => None, | |
| 140 | - }; | |
| 141 | - if by_name.is_some() { | |
| 142 | - return by_name; | |
| 143 | - } | |
| 144 | - let extension = name.rsplit_once('.').map(|(_, e)| e.to_ascii_lowercase())?; | |
| 145 | - Some(match extension.as_str() { | |
| 146 | - "rs" => "Rust", | |
| 147 | - "ts" | "tsx" | "mts" | "cts" => "TypeScript", | |
| 148 | - "js" | "jsx" | "mjs" | "cjs" => "JavaScript", | |
| 149 | - "py" | "pyw" | "pyi" => "Python", | |
| 150 | - "go" => "Go", | |
| 151 | - "java" => "Java", | |
| 152 | - "kt" | "kts" => "Kotlin", | |
| 153 | - "swift" => "Swift", | |
| 154 | - "c" | "h" => "C", | |
| 155 | - "cc" | "cpp" | "cxx" | "c++" | "hpp" | "hh" | "hxx" | "ino" => "C++", | |
| 156 | - "cs" => "C#", | |
| 157 | - "m" => "Objective-C", | |
| 158 | - "mm" => "Objective-C++", | |
| 159 | - "rb" | "rake" | "gemspec" => "Ruby", | |
| 160 | - "php" => "PHP", | |
| 161 | - "html" | "htm" | "xhtml" => "HTML", | |
| 162 | - "css" => "CSS", | |
| 163 | - "scss" => "SCSS", | |
| 164 | - "sass" => "Sass", | |
| 165 | - "less" => "Less", | |
| 166 | - "sh" | "bash" | "zsh" | "ksh" | "fish" => "Shell", | |
| 167 | - "ps1" | "psm1" => "PowerShell", | |
| 168 | - "bat" | "cmd" => "Batchfile", | |
| 169 | - "lua" => "Lua", | |
| 170 | - "dart" => "Dart", | |
| 171 | - "vue" => "Vue", | |
| 172 | - "svelte" => "Svelte", | |
| 173 | - "astro" => "Astro", | |
| 174 | - "ex" | "exs" => "Elixir", | |
| 175 | - "erl" | "hrl" => "Erlang", | |
| 176 | - "hs" | "lhs" => "Haskell", | |
| 177 | - "scala" | "sc" => "Scala", | |
| 178 | - "clj" | "cljs" | "cljc" | "edn" => "Clojure", | |
| 179 | - "ml" | "mli" => "OCaml", | |
| 180 | - "fs" | "fsi" | "fsx" => "F#", | |
| 181 | - "zig" => "Zig", | |
| 182 | - "nim" => "Nim", | |
| 183 | - "jl" => "Julia", | |
| 184 | - "r" => "R", | |
| 185 | - "pl" | "pm" => "Perl", | |
| 186 | - "cmake" => "CMake", | |
| 187 | - "nix" => "Nix", | |
| 188 | - "tf" | "hcl" => "HCL", | |
| 189 | - "vim" => "Vim Script", | |
| 190 | - "el" => "Emacs Lisp", | |
| 191 | - "tex" | "sty" | "cls" => "TeX", | |
| 192 | - "ipynb" => "Jupyter Notebook", | |
| 193 | - "sol" => "Solidity", | |
| 194 | - "glsl" | "vert" | "frag" => "GLSL", | |
| 195 | - "wgsl" => "WGSL", | |
| 196 | - "hlsl" => "HLSL", | |
| 197 | - "cu" | "cuh" => "Cuda", | |
| 198 | - "asm" | "s" | "nasm" => "Assembly", | |
| 199 | - "groovy" | "gradle" => "Groovy", | |
| 200 | - "cr" => "Crystal", | |
| 201 | - "elm" => "Elm", | |
| 202 | - "coffee" => "CoffeeScript", | |
| 203 | - "mdx" => "MDX", | |
| 204 | - "hbs" | "handlebars" => "Handlebars", | |
| 205 | - "pug" | "jade" => "Pug", | |
| 206 | - "twig" => "Twig", | |
| 207 | - "liquid" => "Liquid", | |
| 208 | - "ejs" => "EJS", | |
| 209 | - "purs" => "PureScript", | |
| 210 | - "rkt" => "Racket", | |
| 211 | - "scm" | "ss" => "Scheme", | |
| 212 | - "lisp" | "lsp" | "cl" => "Common Lisp", | |
| 213 | - "d" => "D", | |
| 214 | - "f" | "f90" | "f95" | "for" => "Fortran", | |
| 215 | - "pas" | "pp" => "Pascal", | |
| 216 | - "vb" => "Visual Basic .NET", | |
| 217 | - "vala" => "Vala", | |
| 218 | - "gd" => "GDScript", | |
| 219 | - "tcl" => "Tcl", | |
| 220 | - "awk" => "Awk", | |
| 221 | - "pgsql" | "plpgsql" => "PLpgSQL", | |
| 222 | - "slint" => "Slint", | |
| 223 | - "bicep" => "Bicep", | |
| 224 | - "cairo" => "Cairo", | |
| 225 | - "gleam" => "Gleam", | |
| 226 | - "hx" => "Haxe", | |
| 227 | - "mojo" => "Mojo", | |
| 228 | - "odin" => "Odin", | |
| 229 | - "qml" => "QML", | |
| 230 | - "res" | "resi" => "ReScript", | |
| 231 | - "re" | "rei" => "Reason", | |
| 232 | - "star" | "bzl" => "Starlark", | |
| 233 | - "typ" => "Typst", | |
| 234 | - "v" => "V", | |
| 235 | - _ => return None, | |
| 236 | - }) | |
| 237 | -} | |
| 238 | - | |
| 239 | -/// Linguist's vendored, generated and documentation paths (the common ones). | |
| 240 | -fn excluded_by_default(path: &str) -> bool { | |
| 241 | - let lower = path.to_ascii_lowercase(); | |
| 242 | - let segments: Vec<&str> = lower.split('/').collect(); | |
| 243 | - let dirs = &segments[..segments.len().saturating_sub(1)]; | |
| 244 | - let name = segments.last().copied().unwrap_or(""); | |
| 245 | - const VENDOR_DIRS: &[&str] = &[ | |
| 246 | - "node_modules", "vendor", "vendors", "third_party", "third-party", "thirdparty", "bower_components", "jspm_packages", | |
| 247 | - "dist", "target", "__pycache__", ".venv", "venv", "site-packages", ".yarn", ".pnpm-store", "godeps", "pods", "carthage", | |
| 248 | - ".git", ".github", "external", "deps", | |
| 249 | - ]; | |
| 250 | - if dirs.iter().any(|d| VENDOR_DIRS.contains(d)) { | |
| 251 | - return true; | |
| 252 | - } | |
| 253 | - // Documentation at the root, as Linguist treats it. | |
| 254 | - if matches!(dirs.first(), Some(&"doc") | Some(&"docs") | Some(&"documentation") | Some(&"examples")) { | |
| 255 | - return true; | |
| 256 | - } | |
| 257 | - name.contains(".min.") || name.ends_with("-min.js") || name.ends_with(".bundle.js") || name.ends_with(".pb.go") || name.ends_with("_pb2.py") | |
| 258 | - || name.contains(".generated.") || name.ends_with(".d.ts") || name == "package-lock.json" | |
| 259 | -} | |
| 260 | - | |
| 261 | -/// Path overrides from the root `.gitattributes`. | |
| 262 | -#[derive(Default)] | |
| 263 | -struct Overrides { | |
| 264 | - rules: Vec<(GlobSet, Rule)>, | |
| 265 | -} | |
| 266 | - | |
| 267 | -#[derive(Clone)] | |
| 268 | -enum Rule { | |
| 269 | - Exclude(bool), | |
| 270 | - Language(&'static str), | |
| 271 | -} | |
| 272 | - | |
| 273 | -impl Overrides { | |
| 274 | - fn parse(text: &str) -> Self { | |
| 275 | - let mut rules = Vec::new(); | |
| 276 | - for line in text.lines() { | |
| 277 | - let line = line.trim(); | |
| 278 | - if line.is_empty() || line.starts_with('#') { | |
| 279 | - continue; | |
| 280 | - } | |
| 281 | - let mut parts = line.split_whitespace(); | |
| 282 | - let Some(pattern) = parts.next() else { continue }; | |
| 283 | - let glob = if let Some(rooted) = pattern.strip_prefix('/') { | |
| 284 | - rooted.to_string() | |
| 285 | - } else if pattern.contains('/') { | |
| 286 | - pattern.to_string() | |
| 287 | - } else { | |
| 288 | - format!("**/{pattern}") | |
| 289 | - }; | |
| 290 | - let glob = if glob.ends_with('/') { format!("{glob}**") } else { glob }; | |
| 291 | - let Ok(set) = Glob::new(&glob).and_then(|g| GlobSetBuilder::new().add(g).build()) else { continue }; | |
| 292 | - for attribute in parts { | |
| 293 | - let (negated, attribute) = match attribute.strip_prefix('-') { | |
| 294 | - Some(rest) => (true, rest), | |
| 295 | - None => (false, attribute), | |
| 296 | - }; | |
| 297 | - let (key, value) = attribute.split_once('=').unwrap_or((attribute, "true")); | |
| 298 | - let enabled = !negated && value != "false"; | |
| 299 | - let rule = match key { | |
| 300 | - "linguist-vendored" | "linguist-generated" | "linguist-documentation" => Rule::Exclude(enabled), | |
| 301 | - "linguist-language" => match known(&value.replace('-', " ")) { | |
| 302 | - Some(language) => Rule::Language(language), | |
| 303 | - None => continue, | |
| 304 | - }, | |
| 305 | - _ => continue, | |
| 306 | - }; | |
| 307 | - rules.push((set.clone(), rule)); | |
| 308 | - } | |
| 309 | - } | |
| 310 | - Self { rules } | |
| 311 | - } | |
| 312 | - | |
| 313 | - /// Whether any rule mentions this path (then the repo has decided and | |
| 314 | - /// content heuristics stay out of it). | |
| 315 | - fn decides(&self, path: &str) -> bool { | |
| 316 | - self.rules.iter().any(|(set, _)| set.is_match(path)) | |
| 317 | - } | |
| 318 | - | |
| 319 | - /// Later lines win, as in git. | |
| 320 | - fn apply(&self, path: &str, excluded: bool, language: Option<&'static str>) -> (bool, Option<&'static str>) { | |
| 321 | - let (mut excluded, mut language) = (excluded, language); | |
| 322 | - for (set, rule) in &self.rules { | |
| 323 | - if set.is_match(path) { | |
| 324 | - match rule { | |
| 325 | - Rule::Exclude(on) => excluded = *on, | |
| 326 | - Rule::Language(name) => language = Some(name), | |
| 327 | - } | |
| 328 | - } | |
| 329 | - } | |
| 330 | - (excluded, language) | |
| 331 | - } | |
| 332 | -} | |
| 333 | - | |
| 334 | -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | |
| 335 | -pub struct LanguageBytes { | |
| 336 | - pub name: String, | |
| 337 | - pub bytes: u64, | |
| 338 | -} | |
| 339 | - | |
| 340 | -/// Bytes per language in `commit`, largest first. | |
| 341 | -pub async fn compute(git: &Git, commit: &str) -> anyhow::Result<Vec<LanguageBytes>> { | |
| 342 | - let overrides = match git.object_at(commit, ".gitattributes").await? { | |
| 343 | - Some((kind, sha, size)) if kind == "blob" && size < 256 * 1024 => Overrides::parse(&String::from_utf8_lossy(&git.read_blob(&sha).await?)), | |
| 344 | - _ => Overrides::default(), | |
| 345 | - }; | |
| 346 | - let output = git | |
| 347 | - .command() | |
| 348 | - .args(["ls-tree", "-r", "-l", "-z", "--end-of-options", commit]) | |
| 349 | - .stdout(Stdio::piped()) | |
| 350 | - .stderr(Stdio::null()) | |
| 351 | - .output() | |
| 352 | - .await?; | |
| 353 | - anyhow::ensure!(output.status.success(), "git ls-tree failed"); | |
| 354 | - // First pass: which files count, and which JS/CSS need a minified check. | |
| 355 | - let mut counted: Vec<(&'static str, u64, String)> = Vec::new(); | |
| 356 | - for record in output.stdout.split(|b| *b == 0).filter(|r| !r.is_empty()) { | |
| 357 | - let record = String::from_utf8_lossy(record); | |
| 358 | - let Some((meta, path)) = record.split_once('\t') else { continue }; | |
| 359 | - let mut fields = meta.split_whitespace(); | |
| 360 | - let (Some(mode), Some(kind), _, Some(size)) = (fields.next(), fields.next(), fields.next(), fields.next()) else { continue }; | |
| 361 | - // Blobs only: no submodules, no symlinks. | |
| 362 | - if kind != "blob" || mode == "120000" { | |
| 363 | - continue; | |
| 364 | - } | |
| 365 | - let Ok(size) = size.parse::<u64>() else { continue }; | |
| 366 | - let Some(sha) = meta.split_whitespace().nth(2) else { continue }; | |
| 367 | - let (excluded, language) = overrides.apply(path, excluded_by_default(path), detect(path)); | |
| 368 | - if excluded { | |
| 369 | - continue; | |
| 370 | - } | |
| 371 | - if let Some(language) = language { | |
| 372 | - // Content is only checked where the repo has not said otherwise. | |
| 373 | - let check = size <= CONTENT_CHECK_MAX_FILE && !overrides.decides(path); | |
| 374 | - counted.push((language, size, if check { sha.to_string() } else { String::new() })); | |
| 375 | - } | |
| 376 | - } | |
| 377 | - // Read file contents within a budget; past it, keep to minified JS/CSS. | |
| 378 | - let mut budget = CONTENT_CHECK_BUDGET; | |
| 379 | - let candidates: Vec<(String, bool)> = counted | |
| 380 | - .iter() | |
| 381 | - .filter(|(_, _, sha)| !sha.is_empty()) | |
| 382 | - .filter_map(|(language, size, sha)| { | |
| 383 | - let minify_check = matches!(*language, "JavaScript" | "CSS") && *size > 1024; | |
| 384 | - let wanted = budget >= *size || minify_check; | |
| 385 | - if wanted { | |
| 386 | - budget = budget.saturating_sub(*size); | |
| 387 | - } | |
| 388 | - wanted.then(|| (sha.clone(), minify_check)) | |
| 389 | - }) | |
| 390 | - .collect(); | |
| 391 | - let generated = generated_blobs(git, candidates).await.unwrap_or_default(); | |
| 392 | - let mut totals: std::collections::HashMap<&'static str, u64> = std::collections::HashMap::new(); | |
| 393 | - for (language, size, sha) in counted { | |
| 394 | - if !sha.is_empty() && generated.contains(&sha) { | |
| 395 | - continue; | |
| 396 | - } | |
| 397 | - *totals.entry(language).or_default() += size; | |
| 398 | - } | |
| 399 | - let mut out: Vec<LanguageBytes> = totals.into_iter().filter(|(_, b)| *b > 0).map(|(name, bytes)| LanguageBytes { name: name.into(), bytes }).collect(); | |
| 400 | - out.sort_by(|a, b| b.bytes.cmp(&a.bytes).then_with(|| a.name.cmp(&b.name))); | |
| 401 | - Ok(out) | |
| 402 | -} | |
| 403 | - | |
| 404 | -/// Files larger than this are never read for the content checks. | |
| 405 | -const CONTENT_CHECK_MAX_FILE: u64 = 8 * 1024 * 1024; | |
| 406 | -/// Total bytes read per computation before checks narrow to JS/CSS. | |
| 407 | -const CONTENT_CHECK_BUDGET: u64 = 64 * 1024 * 1024; | |
| 408 | - | |
| 409 | -/// A generated-file marker in the first lines, as Linguist recognises them: | |
| 410 | -/// Go's "Code generated ... DO NOT EDIT." convention and the headers protoc, | |
| 411 | -/// Thrift, Cython and Relay-style `@generated` tools write. | |
| 412 | -fn has_generated_header(content: &[u8]) -> bool { | |
| 413 | - let head = String::from_utf8_lossy(&content[..content.len().min(2048)]).to_ascii_lowercase(); | |
| 414 | - head.lines().take(10).any(|line| { | |
| 415 | - (line.contains("code generated") && line.contains("do not edit")) | |
| 416 | - || line.contains("@generated") | |
| 417 | - || line.contains("generated by the protocol buffer compiler") | |
| 418 | - || line.contains("autogenerated by thrift") | |
| 419 | - || line.contains("generated by cython") | |
| 420 | - }) | |
| 421 | -} | |
| 422 | - | |
| 423 | -/// Linguist's content rules: a file with a generated header, or a JS/CSS file | |
| 424 | -/// whose lines average more than 110 characters (a bundle or minified | |
| 425 | -/// library), does not count. Reads the candidates in one `cat-file --batch`. | |
| 426 | -async fn generated_blobs(git: &Git, blobs: Vec<(String, bool)>) -> anyhow::Result<std::collections::HashSet<String>> { | |
| 427 | - use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; | |
| 428 | - let mut out = std::collections::HashSet::new(); | |
| 429 | - if blobs.is_empty() { | |
| 430 | - return Ok(out); | |
| 431 | - } | |
| 432 | - let mut child = git.command().args(["cat-file", "--batch"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn()?; | |
| 433 | - let mut stdin = child.stdin.take().ok_or_else(|| anyhow::anyhow!("stdin"))?; | |
| 434 | - let wanted: Vec<String> = blobs.iter().map(|(sha, _)| sha.clone()).collect(); | |
| 435 | - let writer = tokio::spawn(async move { | |
| 436 | - for sha in wanted { | |
| 437 | - if stdin.write_all(format!("{sha}\n").as_bytes()).await.is_err() { | |
| 438 | - break; | |
| 439 | - } | |
| 440 | - } | |
| 441 | - }); | |
| 442 | - let mut reader = BufReader::new(child.stdout.take().ok_or_else(|| anyhow::anyhow!("stdout"))?); | |
| 443 | - let mut header = String::new(); | |
| 444 | - for (sha, minify_check) in &blobs { | |
| 445 | - header.clear(); | |
| 446 | - if reader.read_line(&mut header).await? == 0 { | |
| 447 | - break; | |
| 448 | - } | |
| 449 | - // "<sha> missing" carries no body to read past. | |
| 450 | - let Some(size) = header.split_whitespace().nth(2).and_then(|s| s.parse::<usize>().ok()) else { continue }; | |
| 451 | - let mut content = vec![0u8; size + 1]; | |
| 452 | - reader.read_exact(&mut content).await?; | |
| 453 | - let content = &content[..size]; | |
| 454 | - let lines = content.split(|b| *b == b'\n').filter(|l| !l.is_empty()).count().max(1); | |
| 455 | - let minified = *minify_check && content.len() / lines > 110; | |
| 456 | - if minified || has_generated_header(content) { | |
| 457 | - out.insert(sha.clone()); | |
| 458 | - } | |
| 459 | - } | |
| 460 | - let _ = writer.await; | |
| 461 | - let _ = child.wait().await; | |
| 462 | - Ok(out) | |
| 463 | -} | |
| 464 | - | |
| 465 | -/// The cached breakdown for the repo's default branch, recomputed when the | |
| 466 | -/// branch has moved since. Also keeps `repos.primary_language` current. | |
| 467 | -pub async fn for_repo(state: &AppState, repo: &Repo) -> anyhow::Result<Vec<LanguageBytes>> { | |
| 468 | - let git = Git::new(repo.disk_path(&state.config)); | |
| 469 | - let Some(head) = git.resolve_commit(&format!("refs/heads/{}", repo.default_branch)).await? else { | |
| 470 | - return Ok(Vec::new()); | |
| 471 | - }; | |
| 472 | - let cached: Option<(String, sqlx::types::Json<Vec<LanguageBytes>>)> = | |
| 473 | - sqlx::query_as("select commit_sha, stats from repo_languages where repo_id = $1").bind(repo.id).fetch_optional(&state.db).await?; | |
| 474 | - if let Some((sha, stats)) = cached { | |
| 475 | - if sha == head { | |
| 476 | - return Ok(stats.0); | |
| 477 | - } | |
| 478 | - } | |
| 479 | - let started = std::time::Instant::now(); | |
| 480 | - let stats = compute(&git, &head).await?; | |
| 481 | - let primary = stats.first().map(|l| l.name.clone()); | |
| 482 | - sqlx::query( | |
| 483 | - "insert into repo_languages (repo_id, commit_sha, stats) values ($1, $2, $3) | |
| 484 | - on conflict (repo_id) do update set commit_sha = excluded.commit_sha, stats = excluded.stats, computed_at = now()", | |
| 485 | - ) | |
| 486 | - .bind(repo.id) | |
| 487 | - .bind(&head) | |
| 488 | - .bind(sqlx::types::Json(&stats)) | |
| 489 | - .execute(&state.db) | |
| 490 | - .await?; | |
| 491 | - sqlx::query("update repos set primary_language = $2 where id = $1").bind(repo.id).bind(&primary).execute(&state.db).await?; | |
| 492 | - tracing::debug!(repo = %repo.full_name(), languages = stats.len(), ms = started.elapsed().as_millis() as u64, "language stats computed"); | |
| 493 | - Ok(stats) | |
| 494 | -} | |
| 495 | - | |
| 496 | -/// Computes stats for repos that have none yet (after an upgrade), quietly in | |
| 497 | -/// the background, one at a time. | |
| 498 | -pub fn spawn_backfill(state: AppState) { | |
| 499 | - tokio::spawn(async move { | |
| 500 | - let repos: Vec<Repo> = match sqlx::query_as(concat!( | |
| 501 | - crate::repo_select!(), | |
| 502 | - " where not r.is_empty and not exists (select 1 from repo_languages l where l.repo_id = r.id) order by r.id" | |
| 503 | - )) | |
| 504 | - .fetch_all(&state.db) | |
| 505 | - .await | |
| 506 | - { | |
| 507 | - Ok(repos) => repos, | |
| 508 | - Err(error) => { | |
| 509 | - tracing::warn!(%error, "language backfill query failed"); | |
| 510 | - return; | |
| 511 | - } | |
| 512 | - }; | |
| 513 | - if repos.is_empty() { | |
| 514 | - return; | |
| 515 | - } | |
| 516 | - tracing::info!(repos = repos.len(), "computing language stats for existing repositories"); | |
| 517 | - for repo in repos { | |
| 518 | - if let Err(error) = for_repo(&state, &repo).await { | |
| 519 | - tracing::warn!(repo = %repo.full_name(), ?error, "language stats failed"); | |
| 520 | - } | |
| 521 | - } | |
| 522 | - }); | |
| 523 | -} | |
| 524 | - | |
| 525 | -/// Percentages for display: languages under 0.1% are folded into "Other". | |
| 526 | -pub fn percentages(stats: &[LanguageBytes]) -> Vec<(String, f64)> { | |
| 527 | - let total: u64 = stats.iter().map(|l| l.bytes).sum(); | |
| 528 | - if total == 0 { | |
| 529 | - return Vec::new(); | |
| 530 | - } | |
| 531 | - let mut out = Vec::new(); | |
| 532 | - let mut other = 0.0; | |
| 533 | - for language in stats { | |
| 534 | - let pct = language.bytes as f64 * 100.0 / total as f64; | |
| 535 | - if pct < 0.1 || out.len() >= 8 { | |
| 536 | - other += pct; | |
| 537 | - } else { | |
| 538 | - out.push((language.name.clone(), pct)); | |
| 539 | - } | |
| 540 | - } | |
| 541 | - if other >= 0.05 { | |
| 542 | - out.push(("Other".to_string(), other)); | |
| 543 | - } | |
| 544 | - out | |
| 545 | -} | |
| 546 | - | |
| 547 | -#[cfg(test)] | |
| 548 | -mod tests { | |
| 549 | - use super::*; | |
| 550 | - | |
| 551 | - #[test] | |
| 552 | - fn detects_by_name_and_extension() { | |
| 553 | - assert_eq!(detect("src/main.rs"), Some("Rust")); | |
| 554 | - assert_eq!(detect("web/App.tsx"), Some("TypeScript")); | |
| 555 | - assert_eq!(detect("Dockerfile"), Some("Dockerfile")); | |
| 556 | - assert_eq!(detect("deploy/Dockerfile.prod"), Some("Dockerfile")); | |
| 557 | - assert_eq!(detect("frontend/src/pages/index.astro"), Some("Astro")); | |
| 558 | - assert_eq!(detect("README.md"), None); | |
| 559 | - assert_eq!(detect("package.json"), None); | |
| 560 | - assert_eq!(detect("config.yaml"), None); | |
| 561 | - } | |
| 562 | - | |
| 563 | - #[test] | |
| 564 | - fn vendored_and_docs_are_excluded() { | |
| 565 | - assert!(excluded_by_default("node_modules/react/index.js")); | |
| 566 | - assert!(excluded_by_default("app/vendor/lib.rb")); | |
| 567 | - assert!(excluded_by_default("static/jquery.min.js")); | |
| 568 | - assert!(excluded_by_default("docs/conf.py")); | |
| 569 | - assert!(!excluded_by_default("src/docs/page.rs")); | |
| 570 | - assert!(!excluded_by_default("src/lib.rs")); | |
| 571 | - } | |
| 572 | - | |
| 573 | - #[test] | |
| 574 | - fn gitattributes_override() { | |
| 575 | - let overrides = Overrides::parse("*.js linguist-vendored\ngen/** linguist-generated\n*.inc linguist-language=PHP\nkeep.js -linguist-vendored\n"); | |
| 576 | - assert_eq!(overrides.apply("a/b.js", false, Some("JavaScript")), (true, Some("JavaScript"))); | |
| 577 | - assert_eq!(overrides.apply("keep.js", false, Some("JavaScript")), (false, Some("JavaScript"))); | |
| 578 | - assert!(overrides.apply("gen/x.rs", false, Some("Rust")).0); | |
| 579 | - assert_eq!(overrides.apply("lib/a.inc", false, None), (false, Some("PHP"))); | |
| 580 | - } | |
| 581 | - | |
| 582 | - #[test] | |
| 583 | - fn generated_headers() { | |
| 584 | - assert!(has_generated_header(b"// Code generated by gen_sort_variants.go; DO NOT EDIT.\n\npackage x")); | |
| 585 | - assert!(has_generated_header(b"// @generated by relay-compiler\n")); | |
| 586 | - assert!(has_generated_header(b"# Generated by the protocol buffer compiler. DO NOT EDIT!\n")); | |
| 587 | - assert!(!has_generated_header(b"// Package sort implements sorting.\npackage sort")); | |
| 588 | - } | |
| 589 | - | |
| 590 | - #[test] | |
| 591 | - fn small_languages_fold_into_other() { | |
| 592 | - let stats = vec![ | |
| 593 | - LanguageBytes { name: "Rust".into(), bytes: 9000 }, | |
| 594 | - LanguageBytes { name: "Shell".into(), bytes: 995 }, | |
| 595 | - LanguageBytes { name: "Nix".into(), bytes: 5 }, | |
| 596 | - ]; | |
| 597 | - let pct = percentages(&stats); | |
| 598 | - assert_eq!(pct[0].0, "Rust"); | |
| 599 | - assert!((pct[0].1 - 90.0).abs() < 0.01); | |
| 600 | - assert_eq!(pct.last().unwrap().0, "Other"); | |
| 601 | - } | |
| 602 | -} |
+1-0backend/src/languages/linguist.json
| @@ -0,0 +1 @@ | ||
| 1 | +{"source":"github-linguist/linguist v9.7.0","languages":[{"name":"1C Enterprise","type":"programming","color":"#814CCC","ext":[".bsl",".os"]},{"name":"2-Dimensional Array","type":"data","color":"#38761D","ext":[".2da"]},{"name":"4D","type":"programming","color":"#004289","ext":[".4dm"]},{"name":"ABAP","type":"programming","color":"#E8274B","ext":[".abap"]},{"name":"ABAP CDS","type":"programming","color":"#555e25","ext":[".asddls"]},{"name":"ABNF","type":"data","ext":[".abnf"]},{"name":"AGS Script","type":"programming","color":"#B9D9FF","ext":[".asc",".ash"],"aliases":["ags"]},{"name":"AIDL","type":"programming","color":"#34EB6B","ext":[".aidl"],"interp":["aidl"]},{"name":"AL","type":"programming","color":"#3AA2B5","ext":[".al"]},{"name":"ALGOL","type":"programming","color":"#D1E0DB","ext":[".alg"]},{"name":"AMPL","type":"programming","color":"#E6EFBB","ext":[".ampl",".mod"]},{"name":"ANTLR","type":"programming","color":"#9DC3FF","ext":[".g4"]},{"name":"API Blueprint","type":"markup","color":"#2ACCA8","ext":[".apib"]},{"name":"APL","type":"programming","color":"#5A8164","ext":[".apl",".dyalog"],"interp":["apl","aplx","dyalog"]},{"name":"ASL","type":"programming","ext":[".asl",".dsl"]},{"name":"ASN.1","type":"data","ext":[".asn",".asn1"]},{"name":"ASP.NET","type":"programming","color":"#9400ff","ext":[".asax",".ascx",".ashx",".asmx",".aspx",".axd"],"aliases":["aspx","aspx-vb"]},{"name":"ATS","type":"programming","color":"#1ac620","ext":[".dats",".hats",".sats"],"aliases":["ats2"]},{"name":"ActionScript","type":"programming","color":"#882B0F","ext":[".as"],"aliases":["actionscript 3","actionscript3","as3"]},{"name":"Ada","type":"programming","color":"#02f88c","ext":[".adb",".ada",".ads"],"aliases":["ada95","ada2005"]},{"name":"Adblock Filter List","type":"data","color":"#800000","ext":[".txt"],"aliases":["ad block filters","ad block","adb","adblock"]},{"name":"Adobe Font Metrics","type":"data","color":"#fa0f00","ext":[".afm"],"aliases":["acfm","adobe composite font metrics","adobe multiple font metrics","amfm"]},{"name":"Agda","type":"programming","color":"#315665","ext":[".agda"]},{"name":"Aiken","type":"programming","color":"#640ff8","ext":[".ak"]},{"name":"Aleo","type":"programming","color":"#154BF9","ext":[".aleo"]},{"name":"Alloy","type":"programming","color":"#64C800","ext":[".als"]},{"name":"Alpine Abuild","type":"programming","color":"#0D597F","group":"Shell","files":["APKBUILD"],"aliases":["abuild","apkbuild"]},{"name":"Altium Designer","type":"data","color":"#A89663","ext":[".OutJob",".PcbDoc",".PrjPCB",".SchDoc"],"aliases":["altium"]},{"name":"AngelScript","type":"programming","color":"#C7D7DC","ext":[".as",".angelscript"]},{"name":"Answer Set Programming","type":"programming","color":"#A9CC29","ext":[".lp"],"interp":["clingo"]},{"name":"Ant Build System","type":"data","color":"#A9157E","files":["ant.xml","build.xml"]},{"name":"Antlers","type":"markup","color":"#ff269e","ext":[".antlers.html",".antlers.php",".antlers.xml"]},{"name":"ApacheConf","type":"data","color":"#d12127","ext":[".apacheconf",".vhost"],"files":[".htaccess","apache2.conf","httpd.conf"],"aliases":["aconf","apache"]},{"name":"Apex","type":"programming","color":"#1797c0","ext":[".cls",".apex",".trigger"]},{"name":"Apollo Guidance Computer","type":"programming","color":"#0B3D91","group":"Assembly","ext":[".agc"]},{"name":"AppleScript","type":"programming","color":"#101F1F","ext":[".applescript",".scpt"],"interp":["osascript"],"aliases":["apples","osascript"]},{"name":"Arc","type":"programming","color":"#aa2afe","ext":[".arc"]},{"name":"AsciiDoc","type":"prose","color":"#73a0c5","ext":[".asciidoc",".adoc",".asc"]},{"name":"AspectJ","type":"programming","color":"#a957b0","ext":[".aj"]},{"name":"Assembly","type":"programming","color":"#6E4C13","ext":[".asm",".a51",".i",".inc",".nas",".nasm",".s"],"aliases":["asm","nasm"]},{"name":"Astro","type":"markup","color":"#ff5a03","ext":[".astro"]},{"name":"Asymptote","type":"programming","color":"#ff0000","ext":[".asy"],"interp":["asy"]},{"name":"Augeas","type":"programming","color":"#9CC134","ext":[".aug"]},{"name":"AutoHotkey","type":"programming","color":"#6594b9","ext":[".ahk",".ah1",".ah2",".ahkl"],"aliases":["ahk"]},{"name":"AutoIt","type":"programming","color":"#1C3552","ext":[".au3"],"aliases":["au3","AutoIt3","AutoItScript"]},{"name":"Avro IDL","type":"data","color":"#0040FF","ext":[".avdl"]},{"name":"Awk","type":"programming","color":"#c30e9b","ext":[".awk",".auk",".gawk",".mawk",".nawk"],"interp":["awk","gawk","mawk","nawk"]},{"name":"B","type":"programming","color":"#da7666","ext":[".b"]},{"name":"B (Formal Method)","type":"programming","color":"#8aa8c5","ext":[".mch"]},{"name":"B4X","type":"programming","color":"#00e4ff","ext":[".bas"],"aliases":["basic for android"]},{"name":"BAML","type":"programming","color":"#a855f7","ext":[".baml"]},{"name":"BASIC","type":"programming","color":"#ff0000","ext":[".bas"]},{"name":"BBCode","type":"markup","color":"#CAFF42","ext":[".bbcode"]},{"name":"BIRD2","type":"data","color":"#b6d7e4","files":["bird.conf"],"aliases":["bird","bird3"]},{"name":"BQN","type":"programming","color":"#2b7067","ext":[".bqn"]},{"name":"Ballerina","type":"programming","color":"#FF5000","ext":[".bal"]},{"name":"Batchfile","type":"programming","color":"#C1F12E","ext":[".bat",".cmd"],"files":["gradlew.bat","mvnw.cmd"],"aliases":["bat","batch","dosbatch","winbatch"]},{"name":"Beef","type":"programming","color":"#a52f4e","ext":[".bf"]},{"name":"Befunge","type":"programming","ext":[".befunge",".bf"]},{"name":"Berry","type":"programming","color":"#15A13C","ext":[".be"],"aliases":["be"]},{"name":"BibTeX","type":"markup","color":"#778899","group":"TeX","ext":[".bib",".bibtex"]},{"name":"BibTeX Style","type":"programming","ext":[".bst"]},{"name":"Bicep","type":"programming","color":"#519aba","ext":[".bicep",".bicepparam"]},{"name":"Bikeshed","type":"markup","color":"#5562ac","ext":[".bs"]},{"name":"Bison","type":"programming","color":"#6A463F","group":"Yacc","ext":[".bison"]},{"name":"BitBake","type":"programming","color":"#00bce4","ext":[".bb",".bbappend",".bbclass",".inc"]},{"name":"Blade","type":"markup","color":"#f7523f","ext":[".blade",".blade.php"]},{"name":"BlitzBasic","type":"programming","color":"#00FFAE","ext":[".bb",".decls"],"aliases":["b3d","blitz3d","blitzplus","bplus"]},{"name":"BlitzMax","type":"programming","color":"#cd6400","ext":[".bmx"],"aliases":["bmax"]},{"name":"Blueprint","type":"markup","color":"#3584E4","ext":[".blp"],"aliases":["blp"]},{"name":"Bluespec","type":"programming","color":"#12223c","ext":[".bsv"],"aliases":["bluespec bsv","bsv"]},{"name":"Bluespec BH","type":"programming","color":"#12223c","group":"Bluespec","ext":[".bs"],"aliases":["bh","bluespec classic"]},{"name":"Boo","type":"programming","color":"#d4bec1","ext":[".boo"]},{"name":"Boogie","type":"programming","color":"#c80fa0","ext":[".bpl"],"interp":["boogie"]},{"name":"Brainfuck","type":"programming","color":"#2F2530","ext":[".b",".bf"]},{"name":"BrighterScript","type":"programming","color":"#66AABB","ext":[".bs"]},{"name":"Brightscript","type":"programming","color":"#662D91","ext":[".brs"]},{"name":"Browserslist","type":"data","color":"#ffd539","files":[".browserslistrc","browserslist"]},{"name":"Bru","type":"markup","color":"#F4AA41","ext":[".bru"]},{"name":"BuildStream","type":"data","color":"#006bff","ext":[".bst"]},{"name":"C","type":"programming","color":"#555555","ext":[".c",".cats",".h",".h.in",".idc"],"interp":["tcc"]},{"name":"C#","type":"programming","color":"#7355dd","ext":[".cs",".cake",".cs.pp",".csx",".linq"],"aliases":["csharp","cake","cakescript"]},{"name":"C++","type":"programming","color":"#f34b7d","ext":[".cpp",".c++",".cc",".cp",".cppm",".cxx",".h",".h++",".hh",".hpp",".hxx",".inc",".inl",".ino",".ipp",".ixx",".re",".tcc",".tpp",".txx"],"aliases":["cpp"]},{"name":"C-ObjDump","type":"data","ext":[".c-objdump"]},{"name":"C2hs Haskell","type":"programming","group":"Haskell","ext":[".chs"],"aliases":["c2hs"]},{"name":"C3","type":"programming","color":"#2563eb","ext":[".c3"]},{"name":"CAP CDS","type":"programming","color":"#0092d1","ext":[".cds"],"aliases":["cds"]},{"name":"CIL","type":"data","ext":[".cil"]},{"name":"CLIPS","type":"programming","color":"#00A300","ext":[".clp"]},{"name":"CMake","type":"programming","color":"#DA3434","ext":[".cmake",".cmake.in"],"files":["CMakeLists.txt"]},{"name":"COBOL","type":"programming","ext":[".cob",".cbl",".ccp",".cobol",".cpy"]},{"name":"CODEOWNERS","type":"data","files":["CODEOWNERS"]},{"name":"COLLADA","type":"data","color":"#F1A42B","ext":[".dae"]},{"name":"CQL","type":"programming","color":"#006091","ext":[".cql"]},{"name":"CSON","type":"data","color":"#244776","ext":[".cson"]},{"name":"CSS","type":"markup","color":"#663399","ext":[".css"]},{"name":"CSV","type":"data","color":"#237346","ext":[".csv"]},{"name":"CUE","type":"programming","color":"#5886E1","ext":[".cue"]},{"name":"CWeb","type":"programming","color":"#00007a","ext":[".w"]},{"name":"Cabal Config","type":"data","color":"#483465","ext":[".cabal"],"files":["cabal.config","cabal.project"],"aliases":["Cabal"]},{"name":"Caddyfile","type":"data","color":"#22b638","ext":[".caddyfile"],"files":["Caddyfile"],"aliases":["Caddy"]},{"name":"Cadence","type":"programming","color":"#00ef8b","ext":[".cdc"]},{"name":"Cairo","type":"programming","color":"#ff4a48","group":"Cairo","ext":[".cairo"]},{"name":"Cairo Zero","type":"programming","color":"#ff4a48","group":"Cairo","ext":[".cairo"]},{"name":"CameLIGO","type":"programming","color":"#3be133","group":"LigoLANG","ext":[".mligo"]},{"name":"Cangjie","type":"programming","color":"#00868B","ext":[".cj"]},{"name":"Cap'n Proto","type":"programming","color":"#c42727","ext":[".capnp"]},{"name":"Carbon","type":"programming","color":"#222222","ext":[".carbon"]},{"name":"CartoCSS","type":"programming","ext":[".mss"],"aliases":["Carto"]},{"name":"Ceylon","type":"programming","color":"#dfa535","ext":[".ceylon"]},{"name":"Chapel","type":"programming","color":"#8dc63f","ext":[".chpl"],"aliases":["chpl"]},{"name":"Charity","type":"programming","ext":[".ch"]},{"name":"Checksums","type":"data","ext":[".crc32",".md2",".md4",".md5",".sha1",".sha2",".sha224",".sha256",".sha256sum",".sha3",".sha384",".sha512"],"files":["MD5SUMS","SHA1SUMS","SHA256SUMS","SHA256SUMS.txt","SHA512SUMS","checksums.txt","cksums","md5sum.txt"],"aliases":["checksum","hash","hashes","sum","sums"]},{"name":"ChucK","type":"programming","color":"#3f8000","ext":[".ck"]},{"name":"Circom","type":"programming","color":"#707575","ext":[".circom"]},{"name":"Cirru","type":"programming","color":"#ccccff","ext":[".cirru"]},{"name":"Clarion","type":"programming","color":"#db901e","ext":[".clw"]},{"name":"Clarity","type":"programming","color":"#5546ff","ext":[".clar"]},{"name":"Classic ASP","type":"programming","color":"#6a40fd","ext":[".asp"],"aliases":["asp"]},{"name":"Clean","type":"programming","color":"#3F85AF","ext":[".icl",".dcl"]},{"name":"Click","type":"programming","color":"#E4E6F3","ext":[".click"]},{"name":"Clojure","type":"programming","color":"#db5855","ext":[".clj",".bb",".boot",".cl2",".cljc",".cljs",".cljs.hl",".cljscm",".cljx",".hic"],"files":["riemann.config"],"interp":["bb"]},{"name":"Closure Templates","type":"markup","color":"#0d948f","ext":[".soy"],"aliases":["soy"]},{"name":"Cloud Firestore Security Rules","type":"data","color":"#FFA000","files":["firestore.rules"]},{"name":"Clue","type":"programming","color":"#0009b5","ext":[".clue"]},{"name":"CoNLL-U","type":"data","ext":[".conllu",".conll"],"aliases":["CoNLL","CoNLL-X"]},{"name":"CodeQL","type":"programming","color":"#140f46","ext":[".ql",".qll"],"aliases":["ql"]},{"name":"CoffeeScript","type":"programming","color":"#244776","ext":[".coffee","._coffee",".cake",".cjsx",".coffee.erb",".iced"],"files":["Cakefile"],"interp":["coffee"],"aliases":["coffee","coffee-script"]},{"name":"ColdFusion","type":"programming","color":"#ed2cd6","ext":[".cfm",".cfml"],"aliases":["cfm","cfml","coldfusion html"]},{"name":"ColdFusion CFC","type":"programming","color":"#ed2cd6","group":"ColdFusion","ext":[".cfc"],"aliases":["cfc"]},{"name":"Common Lisp","type":"programming","color":"#3fb68b","ext":[".lisp",".asd",".cl",".l",".lsp",".ny",".podsl",".sexp"],"interp":["lisp","sbcl","ccl","clisp","ecl"],"aliases":["lisp"]},{"name":"Common Workflow Language","type":"programming","color":"#B5314C","ext":[".cwl"],"interp":["cwl-runner"],"aliases":["cwl"]},{"name":"Component Pascal","type":"programming","color":"#B0CE4E","ext":[".cp",".cps"]},{"name":"Cooklang","type":"markup","color":"#E15A29","ext":[".cook"]},{"name":"Cool","type":"programming","ext":[".cl"]},{"name":"Cpp-ObjDump","type":"data","ext":[".cppobjdump",".c++-objdump",".c++objdump",".cpp-objdump",".cxx-objdump"],"aliases":["c++-objdump"]},{"name":"Creole","type":"prose","ext":[".creole"]},{"name":"Crystal","type":"programming","color":"#000100","ext":[".cr"],"interp":["crystal"]},{"name":"Csound","type":"programming","color":"#1a1a1a","ext":[".orc",".udo"],"aliases":["csound-orc"]},{"name":"Csound Document","type":"programming","color":"#1a1a1a","ext":[".csd"],"aliases":["csound-csd"]},{"name":"Csound Score","type":"programming","color":"#1a1a1a","ext":[".sco"],"aliases":["csound-sco"]},{"name":"Cuda","type":"programming","color":"#3A4E3A","ext":[".cu",".cuh"]},{"name":"Cue Sheet","type":"data","ext":[".cue"]},{"name":"Curry","type":"programming","color":"#531242","ext":[".curry"]},{"name":"Cycript","type":"programming","ext":[".cy"]},{"name":"Cylc","type":"data","color":"#00b3fd","group":"INI","ext":[".cylc"],"files":["suite.rc"]},{"name":"Cypher","type":"programming","color":"#34c0eb","ext":[".cyp",".cypher"]},{"name":"Cython","type":"programming","color":"#fedf5b","ext":[".pyx",".pxd",".pxi"],"aliases":["pyrex"]},{"name":"D","type":"programming","color":"#ba595e","ext":[".d",".di"],"aliases":["Dlang"]},{"name":"D-ObjDump","type":"data","ext":[".d-objdump"]},{"name":"D2","type":"markup","color":"#526ee8","ext":[".d2"],"aliases":["d2lang"]},{"name":"DIGITAL Command Language","type":"programming","ext":[".com"],"aliases":["dcl"]},{"name":"DM","type":"programming","color":"#447265","ext":[".dm"],"aliases":["byond"]},{"name":"DNS Zone","type":"data","ext":[".zone",".arpa"]},{"name":"DTrace","type":"programming","ext":[".d"],"interp":["dtrace"],"aliases":["dtrace-script"]},{"name":"Dafny","type":"programming","color":"#FFEC25","ext":[".dfy"],"interp":["dafny"]},{"name":"Darcs Patch","type":"data","color":"#8eff23","ext":[".darcspatch",".dpatch"],"aliases":["dpatch"]},{"name":"Dart","type":"programming","color":"#00B4AB","ext":[".dart"],"interp":["dart"]},{"name":"Daslang","type":"programming","color":"#d3d3d3","ext":[".das"]},{"name":"DataWeave","type":"programming","color":"#003a52","ext":[".dwl"]},{"name":"Debian Package Control File","type":"data","color":"#D70751","ext":[".dsc"]},{"name":"DenizenScript","type":"programming","color":"#FBEE96","ext":[".dsc"]},{"name":"Dhall","type":"programming","color":"#dfafff","ext":[".dhall"]},{"name":"Diff","type":"data","ext":[".diff",".patch"],"aliases":["udiff"]},{"name":"DirectX 3D File","type":"data","color":"#aace60","ext":[".x"]},{"name":"Dockerfile","type":"programming","color":"#384d54","ext":[".dockerfile",".containerfile"],"files":["Containerfile","Dockerfile"],"aliases":["Containerfile"]},{"name":"Dogescript","type":"programming","color":"#cca760","ext":[".djs"]},{"name":"Dotenv","type":"data","color":"#e5d559","ext":[".env"],"files":[".env",".env.ci",".env.dev",".env.development",".env.development.local",".env.example",".env.local",".env.prod",".env.production",".env.sample",".env.staging",".env.template",".env.test",".env.testing"]},{"name":"Dune","type":"programming","color":"#89421e","files":["dune-project"]},{"name":"Dylan","type":"programming","color":"#6c616e","ext":[".dylan",".dyl",".intr",".lid"]},{"name":"E","type":"programming","color":"#ccce35","ext":[".e"],"interp":["rune"]},{"name":"E-mail","type":"data","ext":[".eml",".mbox"],"aliases":["email","eml","mail","mbox"]},{"name":"EBNF","type":"data","ext":[".ebnf"]},{"name":"ECL","type":"programming","color":"#8a1267","ext":[".ecl",".eclxml"]},{"name":"ECLiPSe","type":"programming","color":"#001d9d","group":"Prolog","ext":[".ecl"]},{"name":"EJS","type":"markup","color":"#a91e50","ext":[".ejs",".ect",".ejs.t",".jst"]},{"name":"EQ","type":"programming","color":"#a78649","ext":[".eq"]},{"name":"Eagle","type":"data","ext":[".sch",".brd"]},{"name":"Earthly","type":"programming","color":"#2af0ff","files":["Earthfile"],"aliases":["Earthfile"]},{"name":"Easybuild","type":"data","color":"#069406","group":"Python","ext":[".eb"]},{"name":"Ecere Projects","type":"data","color":"#913960","group":"JavaScript","ext":[".epj"]},{"name":"Ecmarkup","type":"markup","color":"#eb8131","group":"HTML","ext":[".html"],"aliases":["ecmarkdown"]},{"name":"Edge","type":"markup","color":"#0dffe0","ext":[".edge"]},{"name":"EdgeQL","type":"programming","color":"#31A7FF","ext":[".edgeql",".esdl"],"aliases":["esdl"]},{"name":"EditorConfig","type":"data","color":"#fff1f2","group":"INI","ext":[".editorconfig"],"files":[".editorconfig"],"aliases":["editor-config"]},{"name":"Edje Data Collection","type":"data","ext":[".edc"]},{"name":"Eiffel","type":"programming","color":"#4d6977","ext":[".e"]},{"name":"Elixir","type":"programming","color":"#6e4a7e","ext":[".ex",".exs"],"files":["mix.lock"],"interp":["elixir"]},{"name":"Elm","type":"programming","color":"#60B5CC","ext":[".elm"]},{"name":"Elvish","type":"programming","color":"#55BB55","ext":[".elv"],"interp":["elvish"]},{"name":"Elvish Transcript","type":"programming","color":"#55BB55","group":"Elvish"},{"name":"Emacs Lisp","type":"programming","color":"#c065db","ext":[".el",".emacs",".emacs.desktop"],"files":[".abbrev_defs",".emacs",".emacs.desktop",".gnus",".spacemacs",".viper","Cask","Eask","Project.ede","_emacs","abbrev_defs"],"aliases":["cask","eask","elisp","emacs"]},{"name":"EmberScript","type":"programming","color":"#FFF4F3","ext":[".em",".emberscript"]},{"name":"Erlang","type":"programming","color":"#B83998","ext":[".erl",".app",".app.src",".es",".escript",".hrl",".xrl",".yrl"],"files":["Emakefile","rebar.config","rebar.config.lock","rebar.lock"],"interp":["escript"]},{"name":"Euphoria","type":"programming","color":"#FF790B","ext":[".e",".ex"],"interp":["eui","euiw"]},{"name":"F#","type":"programming","color":"#b845fc","ext":[".fs",".fsi",".fsx"],"aliases":["fsharp"]},{"name":"F*","type":"programming","color":"#572e30","ext":[".fst",".fsti"],"aliases":["fstar"]},{"name":"FIGlet Font","type":"data","color":"#FFDDBB","ext":[".flf"],"aliases":["FIGfont"]},{"name":"FIRRTL","type":"programming","color":"#2f632f","ext":[".fir"]},{"name":"FLUX","type":"programming","color":"#88ccff","ext":[".fx",".flux"]},{"name":"FPP","type":"programming","color":"#d37327","ext":[".fpp",".fppi"]},{"name":"Factor","type":"programming","color":"#636746","ext":[".factor"],"files":[".factor-boot-rc",".factor-rc"]},{"name":"Fancy","type":"programming","color":"#7b9db4","ext":[".fy",".fancypack"],"files":["Fakefile"]},{"name":"Fantom","type":"programming","color":"#14253c","ext":[".fan"]},{"name":"Faust","type":"programming","color":"#c37240","ext":[".dsp"]},{"name":"Fennel","type":"programming","color":"#fff3d7","ext":[".fnl"],"interp":["fennel"]},{"name":"Filebench WML","type":"programming","color":"#F6B900","ext":[".f"]},{"name":"Filterscript","type":"programming","group":"RenderScript","ext":[".fs"]},{"name":"FlatBuffers","type":"data","color":"#ed284a","ext":[".fbs"]},{"name":"Flix","type":"programming","color":"#d44a45","ext":[".flix"]},{"name":"Fluent","type":"programming","color":"#ffcc33","ext":[".ftl"]},{"name":"Formatted","type":"data","ext":[".for",".eam.fs"]},{"name":"Forth","type":"programming","color":"#341708","ext":[".fth",".4th",".f",".for",".forth",".fr",".frt",".fs"]},{"name":"Fortran","type":"programming","color":"#4d41b1","group":"Fortran","ext":[".f",".f77",".for",".fpp"]},{"name":"Fortran Free Form","type":"programming","color":"#4d41b1","group":"Fortran","ext":[".f90",".f03",".f08",".f95"]},{"name":"FreeBASIC","type":"programming","color":"#141AC9","ext":[".bi",".bas"],"aliases":["fb"]},{"name":"FreeMarker","type":"programming","color":"#0050b2","ext":[".ftl",".ftlh"],"aliases":["ftl"]},{"name":"Frege","type":"programming","color":"#00cafe","ext":[".fr"]},{"name":"Futhark","type":"programming","color":"#5f021f","ext":[".fut"]},{"name":"G-code","type":"programming","color":"#D08CF2","ext":[".g",".cnc",".gco",".gcode"]},{"name":"GAML","type":"programming","color":"#FFC766","ext":[".gaml"]},{"name":"GAMS","type":"programming","color":"#f49a22","ext":[".gms"]},{"name":"GAP","type":"programming","color":"#0000cc","ext":[".g",".gap",".gd",".gi",".tst"]},{"name":"GCC Machine Description","type":"programming","color":"#FFCFAB","ext":[".md"]},{"name":"GDB","type":"programming","ext":[".gdb",".gdbinit"]},{"name":"GDScript","type":"programming","color":"#355570","ext":[".gd"]},{"name":"GDShader","type":"programming","color":"#478CBF","ext":[".gdshader",".gdshaderinc"]},{"name":"GEDCOM","type":"data","color":"#003058","ext":[".ged"]},{"name":"GLSL","type":"programming","color":"#5686a5","ext":[".glsl",".fp",".frag",".frg",".fs",".fsh",".fshader",".geo",".geom",".glslf",".glslv",".gs",".gshader",".rchit",".rmiss",".shader",".tesc",".tese",".vert",".vrx",".vs",".vsh",".vshader"]},{"name":"GN","type":"data","ext":[".gn",".gni"],"files":[".gn"],"interp":["gn"]},{"name":"GSC","type":"programming","color":"#FF6800","ext":[".gsc",".csc",".gsh"]},{"name":"Game Maker Language","type":"programming","color":"#71b417","ext":[".gml"]},{"name":"Gemfile.lock","type":"data","color":"#701516","files":["Gemfile.lock"]},{"name":"Gemini","type":"prose","color":"#ff6900","ext":[".gmi"],"aliases":["gemtext"]},{"name":"Genero 4gl","type":"programming","color":"#63408e","ext":[".4gl"]},{"name":"Genero per","type":"markup","color":"#d8df39","ext":[".per"]},{"name":"Genie","type":"programming","color":"#fb855d","ext":[".gs"]},{"name":"Genshi","type":"programming","color":"#951531","ext":[".kid"],"aliases":["xml+genshi","xml+kid"]},{"name":"Gentoo Ebuild","type":"programming","color":"#9400ff","group":"Shell","ext":[".ebuild"]},{"name":"Gentoo Eclass","type":"programming","color":"#9400ff","group":"Shell","ext":[".eclass"]},{"name":"Gerber Image","type":"data","color":"#d20b00","ext":[".gbr",".cmp",".gbl",".gbo",".gbp",".gbs",".gko",".gml",".gpb",".gpt",".gtl",".gto",".gtp",".gts",".ncl",".sol"],"interp":["gerbv","gerbview"],"aliases":["rs-274x"]},{"name":"Gettext Catalog","type":"prose","ext":[".po",".pot"],"aliases":["pot"]},{"name":"Gherkin","type":"programming","color":"#5B2063","ext":[".feature",".story"],"aliases":["cucumber"]},{"name":"Git Attributes","type":"data","color":"#F44D27","files":[".gitattributes"],"aliases":["gitattributes"]},{"name":"Git Commit","type":"data","color":"#F44D27","files":[".gitmessage","COMMIT_EDITMSG"],"aliases":["commit"]},{"name":"Git Config","type":"data","color":"#F44D27","group":"INI","ext":[".gitconfig"],"files":[".gitconfig",".gitmodules",".tgitconfig","config.worktree"],"aliases":["gitconfig","gitmodules"]},{"name":"Git Revision List","type":"data","color":"#F44D27","files":[".git-blame-ignore-revs"],"aliases":["Git Blame Ignore Revs"]},{"name":"Gleam","type":"programming","color":"#ffaff3","ext":[".gleam"]},{"name":"Glimmer JS","type":"programming","color":"#F5835F","group":"JavaScript","ext":[".gjs"],"aliases":["gjs"]},{"name":"Glimmer TS","type":"programming","color":"#3178c6","group":"TypeScript","ext":[".gts"],"aliases":["gts"]},{"name":"Glyph","type":"programming","color":"#c1ac7f","ext":[".glf"]},{"name":"Glyph Bitmap Distribution Format","type":"data","ext":[".bdf"]},{"name":"Gno","type":"programming","color":"#226c57","ext":[".gno"],"aliases":["gnolang"]},{"name":"Gnuplot","type":"programming","color":"#f0a9f0","ext":[".gp",".gnu",".gnuplot",".p",".plot",".plt"],"interp":["gnuplot"]},{"name":"Go","type":"programming","color":"#00ADD8","ext":[".go"],"aliases":["golang"]},{"name":"Go Checksums","type":"data","color":"#00ADD8","files":["go.sum","go.work.sum"],"aliases":["go.sum","go sum","go.work.sum","go work sum"]},{"name":"Go Module","type":"data","color":"#00ADD8","files":["go.mod"],"aliases":["go.mod","go mod"]},{"name":"Go Template","type":"markup","color":"#00ADD8","ext":[".gohtml",".gotmpl",".html.tmpl",".tmpl",".tpl"],"files":["_helpers.tpl"],"aliases":["gotmpl"]},{"name":"Go Workspace","type":"data","color":"#00ADD8","files":["go.work"],"aliases":["go.work","go work"]},{"name":"Godot Resource","type":"data","color":"#355570","ext":[".gdnlib",".gdns",".tres",".tscn"],"files":["project.godot"]},{"name":"Golo","type":"programming","color":"#88562A","ext":[".golo"]},{"name":"Gosu","type":"programming","color":"#82937f","ext":[".gs",".gst",".gsx",".vark"]},{"name":"Grace","type":"programming","color":"#615f8b","ext":[".grace"]},{"name":"Gradle","type":"data","color":"#02303a","ext":[".gradle"]},{"name":"Gradle Kotlin DSL","type":"data","color":"#02303a","group":"Gradle","ext":[".gradle.kts"]},{"name":"Grammatical Framework","type":"programming","color":"#ff0000","ext":[".gf"],"aliases":["gf"]},{"name":"Graph Modeling Language","type":"data","ext":[".gml"]},{"name":"GraphQL","type":"data","color":"#e10098","ext":[".graphql",".gql",".graphqls"]},{"name":"Graphviz (DOT)","type":"data","color":"#2596be","ext":[".dot",".gv"]},{"name":"Groovy","type":"programming","color":"#4298b8","ext":[".groovy",".grt",".gtpl",".gvy"],"files":["Jenkinsfile"],"interp":["groovy"]},{"name":"Groovy Server Pages","type":"programming","color":"#4298b8","group":"Groovy","ext":[".gsp"],"aliases":["gsp","java server page"]},{"name":"GtkRC","type":"data","color":"#7fe719","ext":[".gtkrc"],"files":["gtkrc","gtkrc-2.0"],"aliases":["gtk","gtk 1","gtk 2"]},{"name":"HAProxy","type":"data","color":"#106da9","ext":[".cfg"],"files":["haproxy.cfg"]},{"name":"HCL","type":"programming","color":"#844FBA","ext":[".hcl",".nomad",".tf",".tfvars",".tofu",".workflow"],"aliases":["HashiCorp Configuration Language","opentofu","terraform"]},{"name":"HIP","type":"programming","color":"#4F3A4F","ext":[".hip"]},{"name":"HLSL","type":"programming","color":"#aace60","ext":[".hlsl",".cginc",".fx",".fxh",".hlsli"]},{"name":"HOCON","type":"data","color":"#9ff8ee","ext":[".hocon"],"files":[".scalafix.conf",".scalafmt.conf"]},{"name":"HTML","type":"markup","color":"#e34c26","ext":[".html",".hta",".htm",".html.hl",".inc",".xht",".xhtml"],"aliases":["xhtml"]},{"name":"HTML+ECR","type":"markup","color":"#2e1052","group":"HTML","ext":[".ecr"],"aliases":["ecr"]},{"name":"HTML+EEX","type":"markup","color":"#6e4a7e","group":"HTML","ext":[".html.eex",".heex",".leex"],"aliases":["eex","heex","leex"]},{"name":"HTML+ERB","type":"markup","color":"#701516","group":"HTML","ext":[".erb",".erb.deface",".rhtml"],"aliases":["erb","rhtml","html+ruby"]},{"name":"HTML+PHP","type":"markup","color":"#4f5d95","group":"HTML","ext":[".phtml"]},{"name":"HTML+Razor","type":"markup","color":"#512be4","group":"HTML","ext":[".cshtml",".razor"],"aliases":["razor"]},{"name":"HTTP","type":"data","color":"#005C9C","ext":[".http"]},{"name":"HXML","type":"data","color":"#f68712","ext":[".hxml"]},{"name":"Hack","type":"programming","color":"#878787","ext":[".hack",".hh",".hhi",".php"]},{"name":"Haml","type":"markup","color":"#ece2a9","ext":[".haml",".haml.deface"]},{"name":"Handlebars","type":"markup","color":"#f7931e","ext":[".handlebars",".hbs"],"aliases":["hbs","htmlbars"]},{"name":"Harbour","type":"programming","color":"#0e60e3","ext":[".hb"]},{"name":"Hare","type":"programming","color":"#9d7424","ext":[".ha"]},{"name":"Haskell","type":"programming","color":"#5e5086","ext":[".hs",".hs-boot",".hsc"],"interp":["runghc","runhaskell","runhugs"]},{"name":"Haxe","type":"programming","color":"#df7900","ext":[".hx",".hxsl"]},{"name":"HiveQL","type":"programming","color":"#dce200","ext":[".q",".hql"]},{"name":"HolyC","type":"programming","color":"#ffefaf","ext":[".hc"]},{"name":"Hosts File","type":"data","color":"#308888","files":["HOSTS","hosts","hosts.txt"],"aliases":["hosts"]},{"name":"Hurl","type":"programming","color":"#FF0288","ext":[".hurl"]},{"name":"Hy","type":"programming","color":"#7790B2","ext":[".hy"],"interp":["hy"],"aliases":["hylang"]},{"name":"HyPhy","type":"programming","ext":[".bf"]},{"name":"IDL","type":"programming","color":"#a3522f","ext":[".pro",".dlm"]},{"name":"IGOR Pro","type":"programming","color":"#0000cc","ext":[".ipf"],"aliases":["igor","igorpro"]},{"name":"IL Assembly","type":"programming","color":"#512BD4","ext":[".il"],"aliases":["ilasm","msil"]},{"name":"INI","type":"data","color":"#d1dbe0","ext":[".ini",".cfg",".cnf",".container",".dof",".frm",".lektorproject",".mount",".network",".prefs",".pro",".properties",".service",".socket",".target",".timer",".url"],"files":[".buckconfig",".coveragerc",".flake8",".gitreview",".pylintrc","HOSTS","buildozer.spec","hosts","pylintrc","vlcrc"],"aliases":["conf","dosini"]},{"name":"IRC log","type":"data","ext":[".irclog",".weechatlog"],"aliases":["irc","irc logs"]},{"name":"ISPC","type":"programming","color":"#2D68B1","ext":[".ispc"]},{"name":"Idris","type":"programming","color":"#b30000","ext":[".idr",".lidr"]},{"name":"Ignore List","type":"data","color":"#000000","ext":[".gitignore"],"files":[".agignore",".atomignore",".babelignore",".bzrignore",".ckignore",".coffeelintignore",".cvsignore",".dockerignore",".easignore",".eleventyignore",".eslint-ignore",".eslintignore",".gitignore",".ignore",".markdownlintignore",".nodemonignore",".npmignore",".prettierignore",".rgignore",".stylelintignore",".vercelignore",".vscodeignore","gitignore-global","gitignore_global"],"aliases":["ignore","gitignore","git-ignore"]},{"name":"ImHex Pattern Language","type":"programming","color":"#3a6be0","ext":[".hexpat"],"aliases":["ImHex","ImHexPatternLanguage","imhexpl"]},{"name":"ImageJ Macro","type":"programming","color":"#99AAFF","ext":[".ijm"],"aliases":["ijm"]},{"name":"Imba","type":"programming","color":"#16cec6","ext":[".imba"]},{"name":"Inform 7","type":"programming","ext":[".ni",".i7x"],"aliases":["i7","inform7"]},{"name":"Ink","type":"programming","ext":[".ink"]},{"name":"Inno Setup","type":"programming","color":"#264b99","ext":[".iss",".isl"]},{"name":"Io","type":"programming","color":"#a9188d","ext":[".io"],"interp":["io"]},{"name":"Ioke","type":"programming","color":"#078193","ext":[".ik"],"interp":["ioke"]},{"name":"Isabelle","type":"programming","color":"#FEFE00","ext":[".thy"]},{"name":"Isabelle ROOT","type":"programming","color":"#FEFE00","group":"Isabelle","files":["ROOT"]},{"name":"J","type":"programming","color":"#9EEDFF","ext":[".ijs"],"interp":["jconsole"]},{"name":"JAR Manifest","type":"data","color":"#b07219","files":["MANIFEST.MF"]},{"name":"JASS","type":"programming","color":"#FF0303","ext":[".j"],"aliases":["jass2"]},{"name":"JCL","type":"programming","color":"#d90e09","ext":[".jcl"]},{"name":"JFlex","type":"programming","color":"#DBCA00","group":"Lex","ext":[".flex",".jflex"]},{"name":"JSON","type":"data","color":"#292929","ext":[".json",".4DForm",".4DProject",".avsc",".geojson",".gltf",".har",".ice",".JSON-tmLanguage",".json.example",".jsonl",".mcmeta",".sarif",".slnlaunch",".tact",".tfstate",".tfstate.backup",".topojson",".webapp",".webmanifest",".yy",".yyp"],"files":[".all-contributorsrc",".arcconfig",".auto-changelog",".c8rc",".htmlhintrc",".imgbotconfig",".nycrc",".releaserc",".secrets.baseline",".tern-config",".tern-project",".watchmanconfig","MODULE.bazel.lock","Package.resolved","Pipfile.lock","bun.lock","composer.lock","deno.lock","flake.lock","mcmod.info"],"aliases":["geojson","jsonl","sarif","topojson"]},{"name":"JSON with Comments","type":"data","color":"#292929","group":"JSON","ext":[".jsonc",".code-snippets",".code-workspace",".hujson",".sublime-build",".sublime-color-scheme",".sublime-commands",".sublime-completions",".sublime-keymap",".sublime-macro",".sublime-menu",".sublime-mousemap",".sublime-project",".sublime-settings",".sublime-theme",".sublime-workspace",".sublime_metrics",".sublime_session",".tsconfig.json"],"files":[".babelrc",".devcontainer.json",".eslintrc",".eslintrc.json",".jscsrc",".jshintrc",".jslintrc",".oxlintrc.json",".swcrc","api-extractor.json","devcontainer.json","jsconfig.json","language-configuration.json","tsconfig.json","tslint.json"],"aliases":["jsonc"]},{"name":"JSON5","type":"data","color":"#267CB9","ext":[".json5"]},{"name":"JSONLD","type":"data","color":"#0c479c","ext":[".jsonld"]},{"name":"JSONiq","type":"programming","color":"#40d47e","ext":[".jq"]},{"name":"Jac","type":"programming","color":"#FC792D","ext":[".jac"]},{"name":"Jai","type":"programming","color":"#ab8b4b","ext":[".jai"]},{"name":"Janet","type":"programming","color":"#0886a5","ext":[".janet"],"interp":["janet"]},{"name":"Jasmin","type":"programming","color":"#d03600","ext":[".j"]},{"name":"Java","type":"programming","color":"#b07219","ext":[".java",".jav",".jsh"]},{"name":"Java Properties","type":"data","color":"#2A6277","ext":[".properties"]},{"name":"Java Server Pages","type":"programming","color":"#2A6277","group":"Java","ext":[".jsp",".tag"],"aliases":["jsp"]},{"name":"Java Template Engine","type":"programming","color":"#2A6277","group":"Java","ext":[".jte"],"aliases":["jte"]},{"name":"JavaScript","type":"programming","color":"#f1e05a","ext":[".js","._js",".bones",".cjs",".es",".es6",".frag",".gs",".jake",".javascript",".jsb",".jscad",".jsfl",".jslib",".jsm",".jspre",".jss",".jsx",".mjs",".njs",".pac",".sjs",".ssjs",".xsjs",".xsjslib"],"files":["Jakefile"],"interp":["bun","chakra","d8","deno","gjs","js","node","nodejs","qjs","rhino","v8","v8-shell"],"aliases":["js","node"]},{"name":"JavaScript+ERB","type":"programming","color":"#f1e05a","group":"JavaScript","ext":[".js.erb"]},{"name":"Jest Snapshot","type":"data","color":"#15c213","ext":[".snap"]},{"name":"JetBrains MPS","type":"programming","color":"#21D789","ext":[".mps",".mpl",".msd"],"aliases":["mps"]},{"name":"Jinja","type":"markup","color":"#a52a22","ext":[".jinja",".j2",".jinja2"],"aliases":["django","html+django","html+jinja","htmldjango"]},{"name":"Jison","type":"programming","color":"#56b3cb","group":"Yacc","ext":[".jison"]},{"name":"Jison Lex","type":"programming","color":"#56b3cb","group":"Lex","ext":[".jisonlex"]},{"name":"Jolie","type":"programming","color":"#843179","ext":[".ol",".iol"],"interp":["jolie"]},{"name":"Jsonnet","type":"programming","color":"#0064bd","ext":[".jsonnet",".libsonnet"]},{"name":"Julia","type":"programming","color":"#a270ba","ext":[".jl"],"interp":["julia"]},{"name":"Julia REPL","type":"programming","color":"#a270ba","group":"Julia"},{"name":"Jupyter Notebook","type":"markup","color":"#DA5B0B","ext":[".ipynb"],"files":["Notebook"],"aliases":["IPython Notebook"]},{"name":"Just","type":"programming","color":"#384d54","ext":[".just"],"files":[".JUSTFILE",".Justfile",".justfile","JUSTFILE","Justfile","justfile"],"aliases":["Justfile"]},{"name":"KCL","type":"programming","color":"#7ABABF","ext":[".k"],"files":["kcl.mod","kcl.mod.lock"]},{"name":"KDL","type":"data","color":"#ffb3b3","ext":[".kdl"]},{"name":"KFramework","type":"programming","color":"#4195c5","ext":[".k"]},{"name":"KRL","type":"programming","color":"#28430A","ext":[".krl"]},{"name":"Kaitai Struct","type":"programming","color":"#773b37","ext":[".ksy"],"aliases":["ksy"]},{"name":"KakouneScript","type":"programming","color":"#6f8042","ext":[".kak"],"files":["kakrc"],"aliases":["kak","kakscript"]},{"name":"KerboScript","type":"programming","color":"#41adf0","ext":[".ks"]},{"name":"KiCad Layout","type":"data","color":"#2f4aab","ext":[".kicad_pcb",".kicad_mod",".kicad_wks"],"files":["fp-lib-table"],"aliases":["pcbnew"]},{"name":"KiCad Legacy Layout","type":"data","color":"#2f4aab","ext":[".brd"]},{"name":"KiCad Schematic","type":"data","color":"#2f4aab","ext":[".kicad_sch",".kicad_sym",".sch"],"aliases":["eeschema schematic"]},{"name":"Kickstart","type":"data","ext":[".ks"]},{"name":"Kit","type":"markup","ext":[".kit"]},{"name":"KoLmafia ASH","type":"programming","color":"#B9D9B9","ext":[".ash"]},{"name":"Koka","type":"programming","color":"#215166","ext":[".kk"],"interp":["koka"]},{"name":"Kotlin","type":"programming","color":"#A97BFF","ext":[".kt",".ktm",".kts"]},{"name":"Kusto","type":"data","ext":[".csl",".kql"]},{"name":"LFE","type":"programming","color":"#4C3023","ext":[".lfe"]},{"name":"LLVM","type":"programming","color":"#185619","ext":[".ll"]},{"name":"LOLCODE","type":"programming","color":"#cc9900","ext":[".lol"]},{"name":"LSL","type":"programming","color":"#3d9970","ext":[".lsl",".lslp"],"interp":["lsl"]},{"name":"LTspice Symbol","type":"data","ext":[".asy"]},{"name":"LabVIEW","type":"programming","color":"#fede06","ext":[".lvproj",".lvclass",".lvlib"]},{"name":"Lambdapi","type":"programming","color":"#8027a3","ext":[".lp"]},{"name":"Langium","type":"programming","color":"#2c8c87","ext":[".langium"]},{"name":"Lark","type":"data","color":"#2980B9","ext":[".lark"]},{"name":"Lasso","type":"programming","color":"#999999","ext":[".lasso",".las",".lasso8",".lasso9"],"aliases":["lassoscript"]},{"name":"Latte","type":"markup","color":"#f2a542","ext":[".latte"]},{"name":"Lean","type":"programming","ext":[".lean",".hlean"]},{"name":"Lean 4","type":"programming","group":"Lean","ext":[".lean"],"aliases":["lean4"]},{"name":"Leo","type":"programming","color":"#C4FFC2","ext":[".leo"]},{"name":"Less","type":"markup","color":"#1d365d","ext":[".less"],"aliases":["less-css"]},{"name":"Lex","type":"programming","color":"#DBCA00","ext":[".l",".lex"],"files":["Lexer.x","lexer.x"],"aliases":["flex"]},{"name":"LigoLANG","type":"programming","color":"#0e74ff","group":"LigoLANG","ext":[".ligo"]},{"name":"LilyPond","type":"programming","color":"#9ccc7c","ext":[".ly",".ily"]},{"name":"Limbo","type":"programming","ext":[".b",".m"]},{"name":"Linear Programming","type":"programming","ext":[".lp"]},{"name":"Linker Script","type":"programming","ext":[".ld",".lds",".x"],"files":["ld.script"]},{"name":"Linux Kernel Module","type":"data","ext":[".mod"]},{"name":"Liquid","type":"markup","color":"#67b8de","ext":[".liquid"]},{"name":"Liquidsoap","type":"programming","color":"#990066","ext":[".liq"]},{"name":"Literate Agda","type":"programming","color":"#315665","group":"Agda","ext":[".lagda"]},{"name":"Literate CoffeeScript","type":"programming","color":"#244776","group":"CoffeeScript","ext":[".litcoffee",".coffee.md"],"aliases":["litcoffee"]},{"name":"Literate Haskell","type":"programming","color":"#5e5086","group":"Haskell","ext":[".lhs"],"aliases":["lhaskell","lhs"]},{"name":"LiveCode Script","type":"programming","color":"#0c5ba5","ext":[".livecodescript"]},{"name":"LiveScript","type":"programming","color":"#499886","ext":[".ls","._ls"],"files":["Slakefile"],"aliases":["live-script","ls"]},{"name":"Lobster","type":"programming","color":"#f95428","ext":[".lobster"]},{"name":"Logos","type":"programming","ext":[".xm",".x",".xi"]},{"name":"Logtalk","type":"programming","color":"#295b9a","ext":[".lgt",".logtalk"]},{"name":"LookML","type":"programming","color":"#652B81","ext":[".lkml",".lookml"]},{"name":"LoomScript","type":"programming","ext":[".ls"]},{"name":"Lua","type":"programming","color":"#000080","ext":[".lua",".fcgi",".nse",".p8",".pd_lua",".rbxs",".rockspec",".wlua"],"files":[".luacheckrc"],"interp":["lua","luajit"]},{"name":"Luau","type":"programming","color":"#00A2FF","ext":[".luau"],"interp":["luau"]},{"name":"M","type":"programming","ext":[".mumps",".m"],"aliases":["mumps"]},{"name":"M3U","type":"data","color":"#179C7D","ext":[".m3u",".m3u8"],"aliases":["hls playlist","m3u playlist"]},{"name":"M4","type":"programming","ext":[".m4",".mc"]},{"name":"M4Sugar","type":"programming","group":"M4","ext":[".m4"],"files":["configure.ac"],"aliases":["autoconf"]},{"name":"MATLAB","type":"programming","color":"#e16737","ext":[".matlab",".m"],"aliases":["octave"]},{"name":"MAXScript","type":"programming","color":"#00a6a6","ext":[".ms",".mcr"]},{"name":"MDX","type":"markup","color":"#fcb32c","ext":[".mdx"]},{"name":"MLIR","type":"programming","color":"#5EC8DB","ext":[".mlir"]},{"name":"MQL4","type":"programming","color":"#62A8D6","ext":[".mq4",".mqh"]},{"name":"MQL5","type":"programming","color":"#4A76B8","ext":[".mq5",".mqh"]},{"name":"MTML","type":"markup","color":"#b7e1f4","ext":[".mtml"]},{"name":"MUF","type":"programming","group":"Forth","ext":[".muf",".m"]},{"name":"Macaulay2","type":"programming","color":"#d8ffff","ext":[".m2"],"interp":["M2"],"aliases":["m2"]},{"name":"Makefile","type":"programming","color":"#427819","ext":[".mak",".d",".make",".makefile",".mk",".mkfile"],"files":["BSDmakefile","GNUmakefile","Kbuild","Makefile","Makefile.am","Makefile.boot","Makefile.frag","Makefile.in","Makefile.inc","Makefile.pc","Makefile.wat","makefile","makefile.sco","mkfile"],"interp":["make"],"aliases":["bsdmake","make","mf"]},{"name":"Mako","type":"programming","color":"#7e858d","ext":[".mako",".mao"]},{"name":"Markdown","type":"prose","color":"#083fa1","ext":[".md",".livemd",".markdown",".mdown",".mdwn",".mkd",".mkdn",".mkdown",".ronn",".scd",".workbook"],"files":["contents.lr"],"aliases":["md","pandoc"]},{"name":"Marko","type":"markup","color":"#42bff2","ext":[".marko"],"aliases":["markojs"]},{"name":"Mask","type":"markup","color":"#f97732","ext":[".mask"]},{"name":"Mathematical Programming System","type":"programming","color":"#0530ad","ext":[".mps"]},{"name":"Maven POM","type":"data","group":"XML","files":["pom.xml"]},{"name":"Max","type":"programming","color":"#c4a79c","ext":[".maxpat",".maxhelp",".maxproj",".mxt",".pat"],"aliases":["max/msp","maxmsp"]},{"name":"MeTTa","type":"programming","color":"#6a5acd","ext":[".metta"]},{"name":"Mercury","type":"programming","color":"#ff2b2b","ext":[".m",".moo"],"interp":["mmi"]},{"name":"Mermaid","type":"markup","color":"#ff3670","ext":[".mmd",".mermaid"],"aliases":["mermaid example"]},{"name":"Meson","type":"programming","color":"#007800","files":["meson.build","meson_options.txt"]},{"name":"Metal","type":"programming","color":"#8f14e9","ext":[".metal"]},{"name":"Microsoft Developer Studio Project","type":"data","ext":[".dsp"]},{"name":"Microsoft Visual Studio Solution","type":"data","ext":[".sln"]},{"name":"MiniD","type":"programming","ext":[".minid"]},{"name":"MiniScript","type":"programming","color":"#4B4A56","ext":[".ms"]},{"name":"MiniYAML","type":"data","color":"#ff1111","ext":[".yaml",".yml"]},{"name":"MiniZinc","type":"programming","color":"#06a9e6","ext":[".mzn"]},{"name":"MiniZinc Data","type":"data","ext":[".dzn"]},{"name":"Mint","type":"programming","color":"#02b046","ext":[".mint"]},{"name":"Mirah","type":"programming","color":"#c7a938","ext":[".druby",".duby",".mirah"]},{"name":"Modelica","type":"programming","color":"#de1d31","ext":[".mo"]},{"name":"Modula-2","type":"programming","color":"#10253f","ext":[".mod"]},{"name":"Modula-3","type":"programming","color":"#223388","ext":[".i3",".ig",".m3",".mg"]},{"name":"Module Management System","type":"programming","ext":[".mms",".mmk"],"files":["descrip.mmk","descrip.mms"]},{"name":"Mojo","type":"programming","color":"#ff4c1f","ext":[".mojo"]},{"name":"Monkey","type":"programming","ext":[".monkey",".monkey2"]},{"name":"Monkey C","type":"programming","color":"#8D6747","ext":[".mc"]},{"name":"Moocode","type":"programming","ext":[".moo"]},{"name":"MoonBit","type":"programming","color":"#b92381","ext":[".mbt"]},{"name":"MoonScript","type":"programming","color":"#ff4585","ext":[".moon"],"interp":["moon"]},{"name":"Motoko","type":"programming","color":"#fbb03b","ext":[".mo"]},{"name":"Motorola 68K Assembly","type":"programming","color":"#005daa","group":"Assembly","ext":[".asm",".i",".inc",".s",".x68"],"aliases":["m68k"]},{"name":"Move","type":"programming","color":"#4a137a","ext":[".move"]},{"name":"Muse","type":"prose","ext":[".muse"],"aliases":["amusewiki","emacs muse"]},{"name":"Mustache","type":"markup","color":"#724b3b","ext":[".mustache"]},{"name":"Myghty","type":"programming","ext":[".myt"]},{"name":"NASL","type":"programming","ext":[".nasl",".inc"]},{"name":"NCL","type":"programming","color":"#28431f","ext":[".ncl"]},{"name":"NEON","type":"data","ext":[".neon"],"aliases":["nette object notation","ne-on"]},{"name":"NL","type":"data","ext":[".nl"]},{"name":"NMODL","type":"programming","color":"#00356B","ext":[".mod"]},{"name":"NPM Config","type":"data","color":"#cb3837","group":"INI","files":[".npmrc"],"aliases":["npmrc"]},{"name":"NSIS","type":"programming","ext":[".nsi",".nsh"]},{"name":"NWScript","type":"programming","color":"#111522","ext":[".nss"]},{"name":"Nasal","type":"programming","color":"#1d2c4e","ext":[".nas"]},{"name":"Nearley","type":"programming","color":"#990000","ext":[".ne",".nearley"]},{"name":"Nemerle","type":"programming","color":"#3d3c6e","ext":[".n"]},{"name":"NetLinx","type":"programming","color":"#0aa0ff","ext":[".axs",".axi"]},{"name":"NetLinx+ERB","type":"programming","color":"#747faa","ext":[".axs.erb",".axi.erb"]},{"name":"NetLogo","type":"programming","color":"#ff6375","ext":[".nlogo"]},{"name":"NewLisp","type":"programming","color":"#87AED7","ext":[".nl",".lisp",".lsp"],"interp":["newlisp"]},{"name":"Nextflow","type":"programming","color":"#3ac486","ext":[".nf"],"files":["nextflow.config"],"interp":["nextflow"]},{"name":"Nginx","type":"data","color":"#009639","ext":[".nginx",".nginxconf",".vhost"],"files":["nginx.conf"],"aliases":["nginx configuration file"]},{"name":"Nickel","type":"programming","color":"#E0C3FC","ext":[".ncl"]},{"name":"Nim","type":"programming","color":"#ffc200","ext":[".nim",".nim.cfg",".nimble",".nimrod",".nims"],"files":["nim.cfg"]},{"name":"Ninja","type":"data","ext":[".ninja"]},{"name":"Nit","type":"programming","color":"#009917","ext":[".nit"]},{"name":"Nix","type":"programming","color":"#7e7eff","ext":[".nix"],"aliases":["nixos"]},{"name":"Noir","type":"programming","color":"#2f1f49","ext":[".nr"],"aliases":["nargo"]},{"name":"Nu","type":"programming","color":"#c9df40","ext":[".nu"],"files":["Nukefile"],"interp":["nush"],"aliases":["nush"]},{"name":"NumPy","type":"programming","color":"#9C8AF9","group":"Python","ext":[".numpy",".numpyw",".numsc"]},{"name":"Nunjucks","type":"markup","color":"#3d8137","ext":[".njk"],"aliases":["njk"]},{"name":"Nushell","type":"programming","color":"#4E9906","ext":[".nu"],"interp":["nu"],"aliases":["nu-script","nushell-script"]},{"name":"OASv2-json","type":"data","color":"#85ea2d","group":"OpenAPI Specification v2","ext":[".json"]},{"name":"OASv2-yaml","type":"data","color":"#85ea2d","group":"OpenAPI Specification v2","ext":[".yaml",".yml"]},{"name":"OASv3-json","type":"data","color":"#85ea2d","group":"OpenAPI Specification v3","ext":[".json"]},{"name":"OASv3-yaml","type":"data","color":"#85ea2d","group":"OpenAPI Specification v3","ext":[".yaml",".yml"]},{"name":"OCaml","type":"programming","color":"#ef7a08","ext":[".ml",".eliom",".eliomi",".ml4",".mli",".mll",".mly"],"interp":["ocaml","ocamlrun","ocamlscript"]},{"name":"OMNeT++ MSG","type":"programming","color":"#a0e0a0","ext":[".msg"],"aliases":["omnetpp-msg"]},{"name":"OMNeT++ NED","type":"programming","color":"#08607c","ext":[".ned"],"aliases":["omnetpp-ned"]},{"name":"Oberon","type":"programming","ext":[".ob2"]},{"name":"ObjDump","type":"data","ext":[".objdump"]},{"name":"Object Data Instance Notation","type":"data","ext":[".odin"]},{"name":"ObjectScript","type":"programming","color":"#424893","ext":[".cls"]},{"name":"Objective-C","type":"programming","color":"#438eff","ext":[".m",".h"],"aliases":["obj-c","objc","objectivec"]},{"name":"Objective-C++","type":"programming","color":"#6866fb","ext":[".mm"],"aliases":["obj-c++","objc++","objectivec++"]},{"name":"Objective-J","type":"programming","color":"#ff0c5a","ext":[".j",".sj"],"aliases":["obj-j","objectivej","objj"]},{"name":"Odin","type":"programming","color":"#60AFFE","ext":[".odin"],"aliases":["odinlang","odin-lang"]},{"name":"Omgrofl","type":"programming","color":"#cabbff","ext":[".omgrofl"]},{"name":"Opa","type":"programming","ext":[".opa"]},{"name":"Opal","type":"programming","color":"#f7ede0","ext":[".opal"]},{"name":"Open Policy Agent","type":"programming","color":"#7d9199","ext":[".rego"]},{"name":"OpenAPI Specification v2","type":"data","color":"#85ea2d","aliases":["oasv2"]},{"name":"OpenAPI Specification v3","type":"data","color":"#85ea2d","aliases":["oasv3"]},{"name":"OpenCL","type":"programming","color":"#ed2e2d","group":"C","ext":[".cl",".opencl"]},{"name":"OpenEdge ABL","type":"programming","color":"#5ce600","ext":[".p",".cls",".w"],"aliases":["progress","openedge","abl"]},{"name":"OpenQASM","type":"programming","color":"#AA70FF","ext":[".qasm"]},{"name":"OpenRC runscript","type":"programming","group":"Shell","interp":["openrc-run"],"aliases":["openrc"]},{"name":"OpenSCAD","type":"programming","color":"#e5cd45","ext":[".scad"]},{"name":"OpenStep Property List","type":"data","ext":[".plist",".glyphs"]},{"name":"OpenType Feature File","type":"data","ext":[".fea"],"aliases":["AFDKO"]},{"name":"Option List","type":"data","color":"#476732","files":[".ackrc",".rspec",".yardopts","ackrc","mocha.opts"],"aliases":["opts","ackrc"]},{"name":"Org","type":"prose","color":"#77aa99","ext":[".org"]},{"name":"OverPy","type":"programming","color":"#78b355","ext":[".opy"],"aliases":["opy"]},{"name":"OverpassQL","type":"programming","color":"#cce2aa","ext":[".overpassql"]},{"name":"Ox","type":"programming","ext":[".ox",".oxh",".oxo"]},{"name":"Oxygene","type":"programming","color":"#cdd0e3","ext":[".oxygene"]},{"name":"Oz","type":"programming","color":"#fab738","ext":[".oz"]},{"name":"P4","type":"programming","color":"#7055b5","ext":[".p4"]},{"name":"PDDL","type":"programming","color":"#0d00ff","ext":[".pddl"]},{"name":"PEG.js","type":"programming","color":"#234d6b","ext":[".pegjs",".peggy"]},{"name":"PHP","type":"programming","color":"#4F5D95","ext":[".php",".aw",".ctp",".fcgi",".inc",".php3",".php4",".php5",".phps",".phpt"],"files":[".php",".php_cs",".php_cs.dist","Phakefile"],"interp":["php"],"aliases":["inc"]},{"name":"PLSQL","type":"programming","color":"#dad8d8","ext":[".pls",".bdy",".ddl",".fnc",".pck",".pkb",".pks",".plb",".plsql",".prc",".spc",".sql",".tpb",".tps",".trg",".vw"]},{"name":"PLpgSQL","type":"programming","color":"#336790","ext":[".pgsql",".sql"]},{"name":"POV-Ray SDL","type":"programming","color":"#6bac65","ext":[".pov",".inc"],"aliases":["pov-ray","povray"]},{"name":"Pact","type":"programming","color":"#F7A8B8","ext":[".pact"]},{"name":"Pan","type":"programming","color":"#cc0000","ext":[".pan"]},{"name":"Papyrus","type":"programming","color":"#6600cc","ext":[".psc"]},{"name":"Parrot","type":"programming","color":"#f3ca0a","ext":[".parrot"]},{"name":"Parrot Assembly","type":"programming","group":"Parrot","ext":[".pasm"],"interp":["parrot"],"aliases":["pasm"]},{"name":"Parrot Internal Representation","type":"programming","group":"Parrot","ext":[".pir"],"interp":["parrot"],"aliases":["pir"]},{"name":"Pascal","type":"programming","color":"#E3F171","ext":[".pas",".dfm",".dpr",".inc",".lpr",".pascal",".pp"],"interp":["instantfpc"],"aliases":["delphi","objectpascal"]},{"name":"Pawn","type":"programming","color":"#dbb284","ext":[".pwn",".inc",".sma"]},{"name":"Pep8","type":"programming","color":"#C76F5B","ext":[".pep"]},{"name":"Perl","type":"programming","color":"#0298c3","ext":[".pl",".al",".cgi",".fcgi",".perl",".ph",".plx",".pm",".psgi",".t"],"files":[".latexmkrc","Makefile.PL","Rexfile","ack","cpanfile","latexmkrc"],"interp":["cperl","perl"],"aliases":["cperl"]},{"name":"Pic","type":"markup","group":"Roff","ext":[".pic",".chem"],"aliases":["pikchr"]},{"name":"Pickle","type":"data","ext":[".pkl"]},{"name":"PicoLisp","type":"programming","color":"#6067af","ext":[".l"],"interp":["picolisp","pil"]},{"name":"PigLatin","type":"programming","color":"#fcd7de","ext":[".pig"]},{"name":"Pike","type":"programming","color":"#005390","ext":[".pike",".pmod"],"interp":["pike"]},{"name":"Pip Requirements","type":"data","color":"#FFD343","files":["dev-requirements.txt","requirements-dev.txt","requirements.lock.txt","requirements.txt"]},{"name":"Pkl","type":"programming","color":"#6b9543","ext":[".pkl"],"interp":["pkl"]},{"name":"PlantUML","type":"data","color":"#fbbd16","ext":[".puml",".iuml",".plantuml"]},{"name":"Pod","type":"prose","ext":[".pod"],"interp":["perl"]},{"name":"Pod 6","type":"prose","ext":[".pod",".pod6"],"interp":["perl6"]},{"name":"PogoScript","type":"programming","color":"#d80074","ext":[".pogo"]},{"name":"Polar","type":"programming","color":"#ae81ff","ext":[".polar"]},{"name":"Pony","type":"programming","ext":[".pony"]},{"name":"Portugol","type":"programming","color":"#f8bd00","ext":[".por"]},{"name":"PostCSS","type":"markup","color":"#dc3a0c","group":"CSS","ext":[".pcss",".postcss"]},{"name":"PostScript","type":"markup","color":"#da291c","ext":[".ps",".eps",".epsi",".pfa"],"aliases":["postscr"]},{"name":"Power Query","type":"programming","color":"#d38e0d","ext":[".pq"],"aliases":["powerquery"]},{"name":"PowerBuilder","type":"programming","color":"#8f0f8d","ext":[".pbt",".sra",".sru",".srw"]},{"name":"PowerShell","type":"programming","color":"#012456","ext":[".ps1",".psd1",".psm1"],"interp":["pwsh"],"aliases":["posh","pwsh"]},{"name":"Praat","type":"programming","color":"#c8506d","ext":[".praat"]},{"name":"Prisma","type":"data","color":"#0c344b","ext":[".prisma"]},{"name":"Pro*C","type":"programming","color":"#bb8368","ext":[".pc"]},{"name":"Processing","type":"programming","color":"#0096D8","ext":[".pde"]},{"name":"Procfile","type":"programming","color":"#3B2F63","files":["Procfile"]},{"name":"Proguard","type":"data","ext":[".pro"]},{"name":"Prolog","type":"programming","color":"#74283c","ext":[".pl",".plt",".pro",".prolog",".yap"],"interp":["swipl","yap"]},{"name":"Promela","type":"programming","color":"#de0000","ext":[".pml"]},{"name":"Propeller Spin","type":"programming","color":"#7fa2a7","ext":[".spin"]},{"name":"Protocol Buffer","type":"data","ext":[".proto"],"aliases":["proto","protobuf","Protocol Buffers"]},{"name":"Protocol Buffer Text Format","type":"data","ext":[".textproto",".pbt",".pbtxt",".txtpb"],"aliases":["text proto","protobuf text format"]},{"name":"Public Key","type":"data","ext":[".asc",".pub"]},{"name":"Pug","type":"markup","color":"#a86454","ext":[".jade",".pug"]},{"name":"Puppet","type":"programming","color":"#302B6D","ext":[".pp"],"files":["Modulefile"]},{"name":"Pure Data","type":"data","ext":[".pd"]},{"name":"PureBasic","type":"programming","color":"#5a6986","ext":[".pb",".pbi"]},{"name":"PureScript","type":"programming","color":"#1D222D","ext":[".purs"]},{"name":"Pyret","type":"programming","color":"#ee1e10","ext":[".arr"]},{"name":"Python","type":"programming","color":"#3572A5","ext":[".py",".cgi",".fcgi",".gyp",".gypi",".lmi",".py3",".pyde",".pyi",".pyp",".pyt",".pyw",".rpy",".spec",".tac",".wsgi",".xpy"],"files":[".gclient","DEPS","SConscript","SConstruct","wscript"],"interp":["python","python2","python3","py","pypy","pypy3","uv"],"aliases":["py","py3","python3","rusthon"]},{"name":"Python console","type":"programming","color":"#3572A5","group":"Python","aliases":["pycon"]},{"name":"Python traceback","type":"data","color":"#3572A5","group":"Python","ext":[".pytb"]},{"name":"Q#","type":"programming","color":"#fed659","ext":[".qs"],"aliases":["qsharp"]},{"name":"QML","type":"programming","color":"#44a51c","ext":[".qml",".qbs"]},{"name":"QMake","type":"programming","ext":[".pro",".pri"],"interp":["qmake"]},{"name":"Qt Script","type":"programming","color":"#00b841","ext":[".qs"],"files":["installscript.qs","toolchain_installscript.qs"]},{"name":"Quake","type":"programming","color":"#882233","files":["m3makefile","m3overrides"]},{"name":"QuakeC","type":"programming","color":"#975777","ext":[".qc"]},{"name":"Quartus Simulation IP","type":"data","color":"#58C42E","ext":[".sip"]},{"name":"QuickBASIC","type":"programming","color":"#008080","ext":[".bas",".bi"],"aliases":["qb","qbasic","qb64","classic qbasic","classic quickbasic"]},{"name":"Quint","type":"programming","color":"#9d6ce5","ext":[".qnt"]},{"name":"R","type":"programming","color":"#198CE7","ext":[".r",".rd",".rhistory",".rsx"],"files":[".Rapp.history",".Rhistory",".Rprofile","expr-dist"],"interp":["Rscript"],"aliases":["Rscript","splus"]},{"name":"RAML","type":"markup","color":"#77d9fb","ext":[".raml"]},{"name":"RAScript","type":"programming","color":"#2C97FA","ext":[".rascript"]},{"name":"RBS","type":"data","color":"#701516","group":"Ruby","ext":[".rbs"]},{"name":"RDoc","type":"prose","color":"#701516","ext":[".rdoc"]},{"name":"REALbasic","type":"programming","ext":[".rbbas",".rbfrm",".rbmnu",".rbres",".rbtbar",".rbuistate"]},{"name":"REXX","type":"programming","color":"#d90e09","ext":[".rexx",".pprx",".rex"],"interp":["regina","rexx"],"aliases":["arexx"]},{"name":"RMarkdown","type":"prose","color":"#198ce7","ext":[".qmd",".rmd"]},{"name":"RON","type":"data","color":"#a62c00","ext":[".ron"]},{"name":"ROS Interface","type":"data","color":"#22314e","ext":[".msg",".action",".srv"],"aliases":["rosmsg"]},{"name":"RPC","type":"programming","ext":[".x"],"aliases":["rpcgen","oncrpc","xdr"]},{"name":"RPGLE","type":"programming","color":"#2BDE21","ext":[".rpgle",".sqlrpgle"],"aliases":["ile rpg","sqlrpgle"]},{"name":"RPM Spec","type":"data","ext":[".spec"],"aliases":["specfile"]},{"name":"RUNOFF","type":"markup","color":"#665a4e","ext":[".rnh",".rno"]},{"name":"Racket","type":"programming","color":"#3c5caa","ext":[".rkt",".rktd",".rktl",".scrbl"],"interp":["racket"]},{"name":"Ragel","type":"programming","color":"#9d5200","ext":[".rl"],"aliases":["ragel-rb","ragel-ruby"]},{"name":"Raku","type":"programming","color":"#0000fb","ext":[".6pl",".6pm",".nqp",".p6",".p6l",".p6m",".pl",".pl6",".pm",".pm6",".raku",".rakumod",".t"],"interp":["perl6","raku","rakudo"],"aliases":["perl6","perl-6"]},{"name":"Rascal","type":"programming","color":"#fffaa0","ext":[".rsc"]},{"name":"Raw token data","type":"data","ext":[".raw"],"aliases":["raw"]},{"name":"ReScript","type":"programming","color":"#ed5051","ext":[".res",".resi"],"interp":["ocaml"]},{"name":"Readline Config","type":"data","group":"INI","files":[".inputrc","inputrc"],"aliases":["inputrc","readline"]},{"name":"Reason","type":"programming","color":"#ff5847","ext":[".re",".rei"]},{"name":"ReasonLIGO","type":"programming","color":"#ff5847","group":"LigoLANG","ext":[".religo"]},{"name":"Rebol","type":"programming","color":"#358a5b","ext":[".reb",".r",".r2",".r3",".rebol"]},{"name":"Record Jar","type":"data","color":"#0673ba","files":["language-subtag-registry.txt"]},{"name":"Red","type":"programming","color":"#f50000","ext":[".red",".reds"],"aliases":["red/system"]},{"name":"Redcode","type":"programming","ext":[".cw"]},{"name":"Redirect Rules","type":"data","files":["_redirects"],"aliases":["redirects"]},{"name":"Redscript","type":"programming","color":"#f44336","ext":[".reds"]},{"name":"Regular Expression","type":"data","color":"#009a00","ext":[".regexp",".regex"],"aliases":["regexp","regex"]},{"name":"Ren'Py","type":"programming","color":"#ff7f7f","ext":[".rpy"],"aliases":["renpy"]},{"name":"RenderScript","type":"programming","ext":[".rs",".rsh"]},{"name":"Rez","type":"programming","color":"#FFDAB3","ext":[".r"]},{"name":"Rhai","type":"programming","color":"#FBA63B","ext":[".rhai"]},{"name":"Rich Text Format","type":"markup","ext":[".rtf"]},{"name":"Ring","type":"programming","color":"#2D54CB","ext":[".ring"]},{"name":"Riot","type":"markup","color":"#A71E49","ext":[".riot"]},{"name":"RobotFramework","type":"programming","color":"#00c0b5","ext":[".robot",".resource"]},{"name":"Robots Exclusion Rules","type":"data","files":["robots.txt"],"aliases":["robots","robots txt","robotx.txt"]},{"name":"Roc","type":"programming","color":"#7c38f5","ext":[".roc"]},{"name":"Rocq Prover","type":"programming","color":"#d0b68c","ext":[".v",".coq"],"aliases":["coq","rocq"]},{"name":"Roff","type":"markup","color":"#ecdebe","ext":[".roff",".1",".1in",".1m",".1x",".2",".3",".3in",".3m",".3p",".3pm",".3qt",".3x",".4",".5",".6",".7",".8",".9",".l",".man",".mdoc",".me",".ms",".n",".nr",".rno",".tmac"],"files":["eqnrc","mmn","mmt","troffrc","troffrc-end"],"aliases":["groff","man","manpage","man page","man-page","mdoc","nroff","troff"]},{"name":"Roff Manpage","type":"markup","color":"#ecdebe","group":"Roff","ext":[".1",".1in",".1m",".1x",".2",".3",".3in",".3m",".3p",".3pm",".3qt",".3x",".4",".5",".6",".7",".8",".9",".man",".mdoc"]},{"name":"Rouge","type":"programming","color":"#cc0088","ext":[".rg"]},{"name":"RouterOS Script","type":"programming","color":"#DE3941","ext":[".rsc"],"interp":["RouterOS"]},{"name":"Ruby","type":"programming","color":"#701516","ext":[".rb",".builder",".eye",".fcgi",".gemspec",".god",".jbuilder",".mspec",".pluginspec",".podspec",".prawn",".rabl",".rake",".rbi",".rbuild",".rbw",".rbx",".ru",".ruby",".spec",".thor",".watchr"],"files":[".irbrc",".pryrc",".simplecov","Appraisals","Berksfile","Brewfile","Buildfile","Capfile","Dangerfile","Deliverfile","Fastfile","Gemfile","Guardfile","Jarfile","Mavenfile","Podfile","Puppetfile","Rakefile","Snapfile","Steepfile","Thorfile","Vagrantfile","buildfile"],"interp":["ruby","macruby","rake","jruby","rbx"],"aliases":["jruby","macruby","rake","rb","rbx"]},{"name":"Rust","type":"programming","color":"#dea584","ext":[".rs",".rs.in"],"interp":["rust-script"],"aliases":["rs"]},{"name":"SAS","type":"programming","color":"#B34936","ext":[".sas"]},{"name":"SCSS","type":"markup","color":"#c6538c","ext":[".scss"]},{"name":"SELinux Policy","type":"data","ext":[".te"],"files":["file_contexts","genfs_contexts","initial_sids","port_contexts","security_classes"],"aliases":["SELinux Kernel Policy Language","sepolicy"]},{"name":"SIP","type":"programming","color":"#4E8D83","ext":[".sip"]},{"name":"SMT","type":"programming","ext":[".smt2",".smt",".z3"],"interp":["boolector","cvc4","mathsat5","opensmt","smtinterpol","smt-rat","stp","verit","yices2","z3"]},{"name":"SPARQL","type":"data","color":"#0C4597","ext":[".sparql",".rq"]},{"name":"SQF","type":"programming","color":"#3F3F3F","ext":[".sqf",".hqf"]},{"name":"SQL","type":"data","color":"#e38c00","ext":[".sql",".ddl",".inc",".mysql",".prc",".tab",".udf",".viw"]},{"name":"SQLPL","type":"programming","color":"#e38c00","ext":[".sql",".db2"]},{"name":"SRecode Template","type":"markup","color":"#348a34","ext":[".srt"]},{"name":"SSH Config","type":"data","group":"INI","files":["ssh-config","ssh_config","sshconfig","sshconfig.snip","sshd-config","sshd_config"],"aliases":["sshconfig","sshdconfig","ssh_config","sshd_config"]},{"name":"STAR","type":"data","ext":[".star"]},{"name":"STL","type":"data","color":"#373b5e","ext":[".stl"],"aliases":["ascii stl","stla"]},{"name":"STON","type":"data","group":"Smalltalk","ext":[".ston"]},{"name":"SVG","type":"data","color":"#ff9900","ext":[".svg"]},{"name":"SWIG","type":"programming","ext":[".i",".swg",".swig"]},{"name":"Sage","type":"programming","ext":[".sage",".sagews"]},{"name":"Sail","type":"programming","color":"#259dd5","ext":[".sail"]},{"name":"Salt","type":"programming","color":"#57bcad","ext":[".sls"],"aliases":["saltstack","saltstate"]},{"name":"Sass","type":"markup","color":"#a53b70","ext":[".sass"]},{"name":"Scala","type":"programming","color":"#c22d40","ext":[".scala",".kojo",".sbt",".sc"],"interp":["scala"]},{"name":"Scaml","type":"markup","color":"#bd181a","ext":[".scaml"]},{"name":"Scenic","type":"programming","color":"#fdc700","ext":[".scenic"],"interp":["scenic"]},{"name":"Scheme","type":"programming","color":"#1e4aec","ext":[".scm",".sch",".sld",".sls",".sps",".ss"],"interp":["scheme","guile","bigloo","chicken","csi","gosh","r6rs"]},{"name":"Scilab","type":"programming","color":"#ca0f21","ext":[".sci",".sce",".tst"]},{"name":"Self","type":"programming","color":"#0579aa","ext":[".self"]},{"name":"ShaderLab","type":"programming","color":"#222c37","ext":[".shader"]},{"name":"Shell","type":"programming","color":"#89e051","ext":[".sh",".bash",".bats",".cgi",".command",".fcgi",".ksh",".pacscript",".sbatch",".sh.in",".slurm",".tmux",".tool",".trigger",".zsh",".zsh-theme"],"files":[".bash_aliases",".bash_functions",".bash_history",".bash_logout",".bash_profile",".bashrc",".cshrc",".envrc",".flaskenv",".kshrc",".login",".profile",".tmux.conf",".xinitrc",".xsession",".zlogin",".zlogout",".zprofile",".zshenv",".zshrc","9fs","PKGBUILD","bash_aliases","bash_logout","bash_profile","bashrc","cshrc","gradlew","kshrc","login","man","mvnw","profile","tmux.conf","xinitrc","xsession","zlogin","zlogout","zprofile","zshenv","zshrc"],"interp":["ash","bash","dash","ksh","mksh","pdksh","rc","sh","zsh"],"aliases":["sh","shell-script","bash","zsh","envrc"]},{"name":"ShellCheck Config","type":"data","color":"#cecfcb","files":[".shellcheckrc"],"aliases":["shellcheckrc"]},{"name":"ShellSession","type":"programming","ext":[".sh-session"],"aliases":["bash session","console"]},{"name":"Shen","type":"programming","color":"#120F14","ext":[".shen"]},{"name":"Sieve","type":"programming","ext":[".sieve"]},{"name":"Simple File Verification","type":"data","color":"#C9BFED","group":"Checksums","ext":[".sfv"],"aliases":["sfv"]},{"name":"Singularity","type":"programming","color":"#64E6AD","files":["Singularity"]},{"name":"Slang","type":"programming","color":"#1fbec9","ext":[".slang"]},{"name":"Slash","type":"programming","color":"#007eff","ext":[".sl"]},{"name":"Slice","type":"programming","color":"#003fa2","ext":[".ice"]},{"name":"Slim","type":"markup","color":"#2b2b2b","ext":[".slim"]},{"name":"Slint","type":"markup","color":"#2379F4","ext":[".slint"]},{"name":"SmPL","type":"programming","color":"#c94949","ext":[".cocci"],"aliases":["coccinelle"]},{"name":"Smali","type":"programming","ext":[".smali"]},{"name":"Smalltalk","type":"programming","color":"#596706","ext":[".st",".cs"],"aliases":["squeak"]},{"name":"Smarty","type":"programming","color":"#f0c040","ext":[".tpl"]},{"name":"Smithy","type":"programming","color":"#c44536","ext":[".smithy"]},{"name":"Snakemake","type":"programming","color":"#419179","group":"Python","ext":[".smk",".snakefile"],"files":["Snakefile"],"aliases":["snakefile"]},{"name":"Solidity","type":"programming","color":"#AA6746","ext":[".sol"]},{"name":"Soong","type":"data","files":["Android.bp"]},{"name":"SourcePawn","type":"programming","color":"#f69e1d","ext":[".sp",".inc"],"aliases":["sourcemod"]},{"name":"SpiceDB Schema","type":"data","color":"#a5318a","ext":[".zed"]},{"name":"Spline Font Database","type":"data","ext":[".sfd"]},{"name":"Squirrel","type":"programming","color":"#800000","ext":[".nut"]},{"name":"Stan","type":"programming","color":"#b2011d","ext":[".stan"]},{"name":"Standard ML","type":"programming","color":"#dc566d","ext":[".ml",".fun",".sig",".sml"],"aliases":["sml"]},{"name":"Starlark","type":"programming","color":"#76d275","ext":[".bzl",".star"],"files":["BUCK","BUILD","BUILD.bazel","MODULE.bazel","Tiltfile","WORKSPACE","WORKSPACE.bazel","WORKSPACE.bzlmod"],"aliases":["bazel","bzl"]},{"name":"Stata","type":"programming","color":"#1a5f91","ext":[".do",".ado",".doh",".ihlp",".mata",".matah",".sthlp"]},{"name":"StringTemplate","type":"markup","color":"#3fb34f","ext":[".st"]},{"name":"Stylus","type":"markup","color":"#ff6347","ext":[".styl"]},{"name":"SubRip Text","type":"data","color":"#9e0101","ext":[".srt"]},{"name":"SugarSS","type":"markup","color":"#2fcc9f","ext":[".sss"]},{"name":"SuperCollider","type":"programming","color":"#46390b","ext":[".sc",".scd"],"interp":["sclang","scsynth"]},{"name":"SurrealQL","type":"programming","color":"#ff00a0","ext":[".surql"],"aliases":["surql"]},{"name":"Survex data","type":"data","color":"#ffcc99","ext":[".svx"]},{"name":"Svelte","type":"markup","color":"#ff3e00","ext":[".svelte"]},{"name":"Sway","type":"programming","color":"#00F58C","ext":[".sw"]},{"name":"Sweave","type":"prose","color":"#198ce7","ext":[".rnw"]},{"name":"Swift","type":"programming","color":"#F05138","ext":[".swift"]},{"name":"SystemVerilog","type":"programming","color":"#DAE1C2","ext":[".sv",".svh",".vh"]},{"name":"TI Program","type":"programming","color":"#A0AA87","ext":[".8xp",".8xp.txt"]},{"name":"TL-Verilog","type":"programming","color":"#C40023","ext":[".tlv"]},{"name":"TLA","type":"programming","color":"#4b0079","ext":[".tla"]},{"name":"TMDL","type":"data","color":"#f0c913","ext":[".tmdl"],"aliases":["Tabular Model Definition Language"]},{"name":"TOML","type":"data","color":"#9c4221","ext":[".toml",".toml.example"],"files":["Cargo.lock","Cargo.toml.orig","Gopkg.lock","Pipfile","mise.local.lock","mise.lock","pdm.lock","poetry.lock","uv.lock"]},{"name":"TSPLIB data","type":"data","ext":[".tsp"],"aliases":["travelling salesman problem","traveling salesman problem"]},{"name":"TSQL","type":"programming","color":"#e38c00","ext":[".sql"]},{"name":"TSV","type":"data","color":"#237346","ext":[".tsv",".vcf"],"aliases":["tab-seperated values"]},{"name":"TSX","type":"programming","color":"#3178c6","group":"TypeScript","ext":[".tsx"],"aliases":["typescriptreact"]},{"name":"TXL","type":"programming","color":"#0178b8","ext":[".txl"]},{"name":"Tact","type":"programming","color":"#48b5ff","ext":[".tact"]},{"name":"Talon","type":"programming","color":"#333333","ext":[".talon"]},{"name":"Tape","type":"programming","ext":[".tape"],"aliases":["vhs"]},{"name":"Tcl","type":"programming","color":"#e4cc98","ext":[".tcl",".adp",".sdc",".tcl.in",".tm",".xdc"],"files":["owh","starfield"],"interp":["tclsh","wish"],"aliases":["sdc","xdc"]},{"name":"Tcsh","type":"programming","group":"Shell","ext":[".tcsh",".csh"],"interp":["tcsh","csh"]},{"name":"TeX","type":"markup","color":"#3D6117","ext":[".tex",".aux",".bbx",".cbx",".cls",".dtx",".ins",".lbx",".ltx",".mkii",".mkiv",".mkvi",".sty",".toc"],"aliases":["latex"]},{"name":"Tea","type":"markup","ext":[".tea"]},{"name":"Teal","type":"programming","color":"#00B1BC","ext":[".tl"],"interp":["tl"]},{"name":"Terra","type":"programming","color":"#00004c","ext":[".t"],"interp":["lua"]},{"name":"Terraform Template","type":"markup","color":"#7b42bb","group":"HCL","ext":[".tftpl"]},{"name":"Texinfo","type":"prose","ext":[".texinfo",".texi",".txi"],"interp":["makeinfo"]},{"name":"Text","type":"prose","ext":[".txt",".fr",".nb",".ncl",".no"],"files":["CITATION","CITATIONS","COPYING","COPYING.regex","COPYRIGHT.regex","FONTLOG","INSTALL","INSTALL.mysql","LICENSE","LICENSE.mysql","NEWS","README.me","README.mysql","README.nss","README.pc","click.me","delete.me","keep.me","package.mask","package.use.mask","package.use.stable.mask","read.me","readme.1st","test.me","use.mask","use.stable.mask"],"aliases":["fundamental","plain text"]},{"name":"TextGrid","type":"data","color":"#c8506d","ext":[".TextGrid"]},{"name":"TextMate Properties","type":"data","color":"#df66e4","files":[".tm_properties"],"aliases":["tm-properties"]},{"name":"Textile","type":"prose","color":"#ffe7ac","ext":[".textile"]},{"name":"Thrift","type":"programming","color":"#D12127","ext":[".thrift"]},{"name":"Toit","type":"programming","color":"#c2c9fb","ext":[".toit"]},{"name":"Tolk","type":"programming","color":"#30A1F5","ext":[".tolk"]},{"name":"Tor Config","type":"data","color":"#59316b","files":["torrc"],"aliases":["torrc"]},{"name":"Tree-sitter Query","type":"programming","color":"#8ea64c","ext":[".scm"],"aliases":["tsq"]},{"name":"Turing","type":"programming","color":"#cf142b","ext":[".t",".tu"]},{"name":"Turtle","type":"data","ext":[".ttl"]},{"name":"Twig","type":"markup","color":"#c1d026","ext":[".twig"]},{"name":"Type Language","type":"data","ext":[".tl"],"aliases":["tl"]},{"name":"TypeScript","type":"programming","color":"#3178c6","ext":[".ts",".cts",".mts"],"interp":["bun","deno","ts-node","tsx","node"],"aliases":["ts"]},{"name":"TypeSpec","type":"programming","color":"#4A3665","ext":[".tsp"],"aliases":["tsp"]},{"name":"Typst","type":"programming","color":"#239dad","ext":[".typ"],"aliases":["typ"]},{"name":"Unified Parallel C","type":"programming","color":"#4e3617","group":"C","ext":[".upc"]},{"name":"Unity3D Asset","type":"data","color":"#222c37","ext":[".anim",".asset",".mask",".mat",".meta",".prefab",".unity"]},{"name":"Unix Assembly","type":"programming","group":"Assembly","ext":[".s",".ms"],"aliases":["gas","gnu asm","unix asm"]},{"name":"Uno","type":"programming","color":"#9933cc","ext":[".uno"]},{"name":"UnrealScript","type":"programming","color":"#a54c4d","ext":[".uc"]},{"name":"Untyped Plutus Core","type":"programming","color":"#36adbd","ext":[".uplc"]},{"name":"UrWeb","type":"programming","color":"#ccccee","ext":[".ur",".urs"],"aliases":["Ur/Web","Ur"]},{"name":"V","type":"programming","color":"#4f87c4","ext":[".v"],"aliases":["vlang"]},{"name":"VBA","type":"programming","color":"#867db1","ext":[".bas",".cls",".frm",".vba"],"aliases":["visual basic for applications"]},{"name":"VBScript","type":"programming","color":"#15dcdc","ext":[".vbs"]},{"name":"VCL","type":"programming","color":"#148AA8","ext":[".vcl"]},{"name":"VHDL","type":"programming","color":"#adb2cb","ext":[".vhdl",".vhd",".vhf",".vhi",".vho",".vhs",".vht",".vhw"]},{"name":"Vala","type":"programming","color":"#a56de2","ext":[".vala",".vapi"]},{"name":"Valve Data Format","type":"data","color":"#f26025","ext":[".vdf",".vmf"],"aliases":["keyvalues","vdf"]},{"name":"Velocity Template Language","type":"markup","color":"#507cff","ext":[".vtl"],"aliases":["vtl","velocity"]},{"name":"Vento","type":"markup","color":"#ff0080","ext":[".vto"]},{"name":"Verilog","type":"programming","color":"#b2b7f8","ext":[".v",".veo"]},{"name":"Verse","type":"programming","color":"#518ef8","ext":[".verse"]},{"name":"Vespa Schema Definition","type":"data","color":"#61D790","ext":[".sd"],"aliases":["vespa"]},{"name":"Vim Help File","type":"prose","color":"#199f4b","ext":[".txt"],"aliases":["help","vimhelp"]},{"name":"Vim Snippet","type":"markup","color":"#199f4b","ext":[".snip",".snippet",".snippets"],"aliases":["SnipMate","UltiSnip","UltiSnips","NeoSnippet"]},{"name":"Vim script","type":"programming","color":"#199f4b","ext":[".vim",".vba",".vimrc",".vmb"],"files":[".exrc",".gvimrc",".nvimrc",".vimrc","_vimrc","gvimrc","nvimrc","vimrc"],"aliases":["vim","viml","nvim","vimscript"]},{"name":"Visual Basic .NET","type":"programming","color":"#945db7","ext":[".vb",".vbhtml"],"aliases":["visual basic","vbnet","vb .net","vb.net"]},{"name":"Visual Basic 6.0","type":"programming","color":"#2c6353","ext":[".bas",".cls",".ctl",".Dsr",".frm"],"aliases":["vb6","vb 6","visual basic 6","visual basic classic","classic visual basic"]},{"name":"Volt","type":"programming","color":"#1F1F1F","ext":[".volt"]},{"name":"Vue","type":"markup","color":"#41b883","ext":[".vue"]},{"name":"Vyper","type":"programming","color":"#9F4CF2","ext":[".vy"]},{"name":"WDL","type":"programming","color":"#42f1f4","ext":[".wdl"],"aliases":["Workflow Description Language"]},{"name":"WGSL","type":"programming","color":"#1a5e9a","ext":[".wgsl"]},{"name":"Wavefront Material","type":"data","ext":[".mtl"]},{"name":"Wavefront Object","type":"data","ext":[".obj"]},{"name":"Web Ontology Language","type":"data","color":"#5b70bd","ext":[".owl"]},{"name":"WebAssembly","type":"programming","color":"#04133b","ext":[".wast",".wat"],"aliases":["wast","wasm"]},{"name":"WebAssembly Interface Type","type":"data","color":"#6250e7","ext":[".wit"],"aliases":["wit"]},{"name":"WebIDL","type":"programming","ext":[".webidl"]},{"name":"WebVTT","type":"data","ext":[".vtt"],"aliases":["vtt"]},{"name":"Wget Config","type":"data","group":"INI","files":[".wgetrc"],"aliases":["wgetrc"]},{"name":"Whiley","type":"programming","color":"#d5c397","ext":[".whiley"]},{"name":"Wikitext","type":"prose","color":"#fc5757","ext":[".mediawiki",".wiki",".wikitext"],"aliases":["mediawiki","wiki"]},{"name":"Win32 Message File","type":"data","ext":[".mc"]},{"name":"Windows Registry Entries","type":"data","color":"#52d5ff","ext":[".reg"]},{"name":"Witcher Script","type":"programming","color":"#ff0000","ext":[".ws"]},{"name":"Wolfram Language","type":"programming","color":"#dd1100","ext":[".mathematica",".cdf",".m",".ma",".mt",".nb",".nbp",".wl",".wls",".wlt"],"interp":["wolfram","WolframKernel","wolframscript","math","MathKernel","MathematicaScript","WolframNB","Mathematica"],"aliases":["mathematica","mma","wolfram","wolfram lang","wl"]},{"name":"Wollok","type":"programming","color":"#a23738","ext":[".wlk"]},{"name":"World of Warcraft Addon Data","type":"data","color":"#f7e43f","ext":[".toc"]},{"name":"Wren","type":"programming","color":"#383838","ext":[".wren"],"aliases":["wrenlang"]},{"name":"X BitMap","type":"data","group":"C","ext":[".xbm"],"aliases":["xbm"]},{"name":"X Font Directory Index","type":"data","files":["encodings.dir","fonts.alias","fonts.dir","fonts.scale"]},{"name":"X PixMap","type":"data","group":"C","ext":[".xpm",".pm"],"aliases":["xpm"]},{"name":"X10","type":"programming","color":"#4B6BEF","ext":[".x10"],"aliases":["xten"]},{"name":"XC","type":"programming","color":"#99DA07","ext":[".xc"]},{"name":"XCompose","type":"data","files":[".XCompose","XCompose","xcompose"]},{"name":"XML","type":"data","color":"#0060ac","ext":[".xml",".adml",".admx",".ant",".axaml",".axml",".builds",".ccproj",".ccxml",".clixml",".cproject",".cscfg",".csdef",".csl",".csproj",".ct",".depproj",".dita",".ditamap",".ditaval",".dll.config",".dotsettings",".filters",".fsproj",".fxml",".glade",".gml",".gmx",".gpx",".grxml",".gst",".hzp",".icls",".iml",".ivy",".jelly",".jsproj",".kml",".launch",".mdpolicy",".meta4",".mjml",".mm",".mod",".mojo",".mxml",".natvis",".ncl",".ndproj",".nproj",".nuspec",".odd",".osm",".pkgproj",".pluginspec",".proj",".props",".ps1xml",".psc1",".pt",".pubxml",".qhelp",".rbxmx",".rdf",".res",".resx",".rs",".rss",".sch",".scxml",".sfproj",".shproj",".slnx",".srdf",".storyboard",".sublime-snippet",".sw",".targets",".tml",".ts",".tsx",".typ",".ui",".urdf",".ux",".vbproj",".vcxproj",".vsixmanifest",".vssettings",".vstemplate",".vxml",".wixproj",".workflow",".wsdl",".wsf",".wxi",".wxl",".wxs",".x3d",".xacro",".xaml",".xib",".xlf",".xliff",".xmi",".xml.dist",".xmp",".xproj",".xsd",".xspec",".xul",".zcml"],"files":[".classpath",".cproject",".project","App.config","NuGet.config","Settings.StyleCop","Web.Debug.config","Web.Release.config","Web.config","packages.config"],"aliases":["rss","xsd","wsdl"]},{"name":"XML Property List","type":"data","color":"#0060ac","group":"XML","ext":[".plist",".stTheme",".tmCommand",".tmLanguage",".tmPreferences",".tmSnippet",".tmTheme"]},{"name":"XPages","type":"data","ext":[".xsp-config",".xsp.metadata"]},{"name":"XProc","type":"programming","ext":[".xpl",".xproc"]},{"name":"XQuery","type":"programming","color":"#5232e7","ext":[".xquery",".xq",".xql",".xqm",".xqy"]},{"name":"XS","type":"programming","ext":[".xs"]},{"name":"XSLT","type":"programming","color":"#EB8CEB","ext":[".xslt",".xsl"],"aliases":["xsl"]},{"name":"Xmake","type":"programming","color":"#22a079","files":["xmake.lua"]},{"name":"Xojo","type":"programming","color":"#81bd41","ext":[".xojo_code",".xojo_menu",".xojo_report",".xojo_script",".xojo_toolbar",".xojo_window"]},{"name":"Xonsh","type":"programming","color":"#285EEF","ext":[".xsh"]},{"name":"Xtend","type":"programming","color":"#24255d","ext":[".xtend"]},{"name":"YAML","type":"data","color":"#cb171e","ext":[".yml",".mir",".reek",".rviz",".sublime-syntax",".syntax",".yaml",".yaml-tmlanguage",".yaml.sed",".yml.mysql"],"files":[".clang-format",".clang-tidy",".clangd",".eslintrc",".gemrc",".releaserc","CITATION.cff","glide.lock","pixi.lock","yarn.lock"],"aliases":["yml"]},{"name":"YANG","type":"data","ext":[".yang"]},{"name":"YARA","type":"programming","color":"#220000","ext":[".yar",".yara"]},{"name":"YASnippet","type":"markup","color":"#32AB90","ext":[".yasnippet"],"aliases":["snippet","yas"]},{"name":"Yacc","type":"programming","color":"#4B6C4B","ext":[".y",".yacc",".yy"]},{"name":"Yul","type":"programming","color":"#794932","ext":[".yul"]},{"name":"ZAP","type":"programming","color":"#0d665e","ext":[".zap",".xzap"]},{"name":"ZIL","type":"programming","color":"#dc75e5","ext":[".zil",".mud"]},{"name":"Zeek","type":"programming","ext":[".zeek",".bro"],"aliases":["bro"]},{"name":"ZenScript","type":"programming","color":"#00BCD1","ext":[".zs"]},{"name":"Zephir","type":"programming","color":"#118f9e","ext":[".zep"]},{"name":"Zig","type":"programming","color":"#ec915c","ext":[".zig",".zig.zon"]},{"name":"Zimpl","type":"programming","color":"#d67711","ext":[".zimpl",".zmpl",".zpl"]},{"name":"Zmodel","type":"data","color":"#ff7100","ext":[".zmodel"]},{"name":"cURL Config","type":"data","group":"INI","files":[".curlrc","_curlrc"],"aliases":["curlrc"]},{"name":"crontab","type":"data","color":"#ead7ac","files":["crontab"],"aliases":["cron","cron table"]},{"name":"desktop","type":"data","group":"INI","ext":[".desktop",".desktop.in"]},{"name":"dircolors","type":"data","ext":[".dircolors"],"files":[".dir_colors",".dircolors","DIR_COLORS","_dir_colors","_dircolors","dir_colors"]},{"name":"eC","type":"programming","color":"#913960","ext":[".ec",".eh"]},{"name":"edn","type":"data","ext":[".edn"]},{"name":"fish","type":"programming","color":"#4aae47","group":"Shell","ext":[".fish"],"interp":["fish"]},{"name":"hoon","type":"programming","color":"#00b171","ext":[".hoon"]},{"name":"iCalendar","type":"data","color":"#ec564c","ext":[".ics",".ical"],"aliases":["iCal"]},{"name":"jq","type":"programming","color":"#c7254e","ext":[".jq"],"interp":["gojq","jaq","jq","jqjq","jqq","query-json"]},{"name":"kvlang","type":"markup","color":"#1da6e0","ext":[".kv"]},{"name":"mIRC Script","type":"programming","color":"#3d57c3","ext":[".mrc"]},{"name":"mcfunction","type":"programming","color":"#E22837","ext":[".mcfunction"]},{"name":"mdsvex","type":"markup","color":"#5f9ea0","ext":[".svx"]},{"name":"mupad","type":"programming","color":"#244963","ext":[".mu"]},{"name":"nanorc","type":"data","color":"#2d004d","group":"INI","ext":[".nanorc"],"files":[".nanorc","nanorc"]},{"name":"nesC","type":"programming","color":"#94B0C7","ext":[".nc"]},{"name":"ooc","type":"programming","color":"#b0b77e","ext":[".ooc"]},{"name":"pkg-config","type":"data","color":"#2b5e82","ext":[".pc",".pc.in"],"aliases":["pkgconf"]},{"name":"q","type":"programming","color":"#0040cd","ext":[".q"]},{"name":"reStructuredText","type":"prose","color":"#141414","ext":[".rst",".rest",".rest.txt",".rst.txt"],"aliases":["rst"]},{"name":"sed","type":"programming","color":"#64b970","ext":[".sed"],"interp":["gsed","minised","sed","ssed"]},{"name":"templ","type":"markup","color":"#66D0DD","ext":[".templ"]},{"name":"ucode","type":"programming","color":"#00b8d4","ext":[".uc"],"interp":["ucode"]},{"name":"vCard","type":"data","color":"#ee2647","ext":[".vcf"],"aliases":["virtual contact file","electronic business card"]},{"name":"wisp","type":"programming","color":"#7582D1","ext":[".wisp"]},{"name":"xBase","type":"programming","color":"#403a40","ext":[".prg",".ch",".prw"],"aliases":["advpl","clipper","foxpro"]}],"vendor":["(^|/)cache/","^[Dd]ependencies/","(^|/)dist/","^deps/","(^|/)configure$","(^|/)config\\.guess$","(^|/)config\\.sub$","(^|/)aclocal\\.m4","(^|/)libtool\\.m4","(^|/)ltoptions\\.m4","(^|/)ltsugar\\.m4","(^|/)ltversion\\.m4","(^|/)lt~obsolete\\.m4","(^|/)dotnet-install\\.(ps1|sh)$","(^|/)cpplint\\.py","(^|/)node_modules/","(^|/)\\.yarn/releases/","(^|/)\\.yarn/plugins/","(^|/)\\.yarn/sdks/","(^|/)\\.yarn/versions/","(^|/)\\.yarn/unplugged/","(^|/)_esy$","(^|/)bower_components/","^rebar$","(^|/)erlang\\.mk","(^|/)Godeps/_workspace/","(^|/)testdata/","(^|/)\\.indent\\.pro","(\\.|-)min\\.(js|css)$","([^\\s]*)import\\.(css|less|scss|styl)$","(^|/)bootstrap([^/.]*)(\\..*)?\\.(js|css|less|scss|styl)$","(^|/)custom\\.bootstrap([^\\s]*)(js|css|less|scss|styl)$","(^|/)font-?awesome\\.(css|less|scss|styl)$","(^|/)font-?awesome/.*\\.(css|less|scss|styl)$","(^|/)foundation\\.(css|less|scss|styl)$","(^|/)normalize\\.(css|less|scss|styl)$","(^|/)skeleton\\.(css|less|scss|styl)$","(^|/)[Bb]ourbon/.*\\.(css|less|scss|styl)$","(^|/)animate\\.(css|less|scss|styl)$","(^|/)materialize\\.(css|less|scss|styl|js)$","(^|/)select2/.*\\.(css|scss|js)$","(^|/)bulma\\.(css|sass|scss)$","(3rd|[Tt]hird)[-_]?[Pp]arty/","(^|/)vendors?/","(^|/)[Ee]xtern(als?)?/","(^|/)[Vv]+endor/","^debian/","(^|/)run\\.n$","(^|/)bootstrap-datepicker/","(^|/)jquery([^.]*)\\.js$","(^|/)jquery\\-\\d\\.\\d+(\\.\\d+)?\\.js$","(^|/)jquery\\-ui(\\-\\d\\.\\d+(\\.\\d+)?)?(\\.\\w+)?\\.(js|css)$","(^|/)jquery\\.(ui|effects)\\.([^.]*)\\.(js|css)$","(^|/)jquery\\.fn\\.gantt\\.js","(^|/)jquery\\.fancybox\\.(js|css)","(^|/)fuelux\\.js","(^|/)jquery\\.fileupload(-\\w+)?\\.js$","(^|/)jquery\\.dataTables\\.js","(^|/)bootbox\\.js","(^|/)pdf\\.worker\\.js","(^|/)slick\\.\\w+.js$","(^|/)Leaflet\\.Coordinates-\\d+\\.\\d+\\.\\d+\\.src\\.js$","(^|/)leaflet\\.draw-src\\.js","(^|/)leaflet\\.draw\\.css","(^|/)Control\\.FullScreen\\.css","(^|/)Control\\.FullScreen\\.js","(^|/)leaflet\\.spin\\.js","(^|/)wicket-leaflet\\.js","(^|/)\\.sublime-project","(^|/)\\.sublime-workspace","(^|/)\\.vscode/","(^|/)prototype(.*)\\.js$","(^|/)effects\\.js$","(^|/)controls\\.js$","(^|/)dragdrop\\.js$","(.*?)\\.d\\.ts$","(^|/)mootools([^.]*)\\d+\\.\\d+.\\d+([^.]*)\\.js$","(^|/)dojo\\.js$","(^|/)MochiKit\\.js$","(^|/)yahoo-([^.]*)\\.js$","(^|/)yui([^.]*)\\.js$","(^|/)ckeditor\\.js$","(^|/)tiny_mce([^.]*)\\.js$","(^|/)tiny_mce/(langs|plugins|themes|utils)","(^|/)ace-builds/","(^|/)fontello(.*?)\\.css$","(^|/)MathJax/","(^|/)Chart\\.js$","(^|/)[Cc]ode[Mm]irror/(\\d+\\.\\d+/)?(lib|mode|theme|addon|keymap|demo)","(^|/)shBrush([^.]*)\\.js$","(^|/)shCore\\.js$","(^|/)shLegacy\\.js$","(^|/)angular([^.]*)\\.js$","(^|\\/)d3(\\.v\\d+)?([^.]*)\\.js$","(^|/)react(-[^.]*)?\\.js$","(^|/)flow-typed/.*\\.js$","(^|/)modernizr\\-\\d\\.\\d+(\\.\\d+)?\\.js$","(^|/)modernizr\\.custom\\.\\d+\\.js$","(^|/)knockout-(\\d+\\.){3}(debug\\.)?js$","(^|/)docs?/_?(build|themes?|templates?|static)/","(^|/)admin_media/","(^|/)env/","(^|/)fabfile\\.py$","(^|/)waf$","(^|/)\\.osx$","\\.xctemplate/","\\.imageset/","(^|/)Carthage/","(^|/)Sparkle/","(^|/)Crashlytics\\.framework/","(^|/)Fabric\\.framework/","(^|/)BuddyBuildSDK\\.framework/","(^|/)Realm\\.framework","(^|/)RealmSwift\\.framework","(^|/)\\.gitattributes$","(^|/)\\.gitignore$","(^|/)\\.gitmodules$","(^|/)gradlew$","(^|/)gradlew\\.bat$","(^|/)gradle/wrapper/","(^|/)mvnw$","(^|/)mvnw\\.cmd$","(^|/)\\.mvn/wrapper/","-vsdoc\\.js$","\\.intellisense\\.js$","(^|/)jquery([^.]*)\\.validate(\\.unobtrusive)?\\.js$","(^|/)jquery([^.]*)\\.unobtrusive\\-ajax\\.js$","(^|/)[Mm]icrosoft([Mm]vc)?([Aa]jax|[Vv]alidation)(\\.debug)?\\.js$","(^|/)[Pp]ackages\\/.+\\.\\d+\\/","(^|/)extjs/.*?\\.js$","(^|/)extjs/.*?\\.xml$","(^|/)extjs/.*?\\.txt$","(^|/)extjs/.*?\\.html$","(^|/)extjs/.*?\\.properties$","(^|/)extjs/\\.sencha/","(^|/)extjs/docs/","(^|/)extjs/builds/","(^|/)extjs/cmd/","(^|/)extjs/examples/","(^|/)extjs/locale/","(^|/)extjs/packages/","(^|/)extjs/plugins/","(^|/)extjs/resources/","(^|/)extjs/src/","(^|/)extjs/welcome/","(^|/)html5shiv\\.js$","(^|/)[Tt]ests?/fixtures/","(^|/)[Ss]pecs?/fixtures/","(^|/)cordova([^.]*)\\.js$","(^|/)cordova\\-\\d\\.\\d(\\.\\d)?\\.js$","(^|/)foundation(\\..*)?\\.js$","(^|/)Vagrantfile$","(^|/)\\.[Dd][Ss]_[Ss]tore$","(^|/)inst/extdata/","(^|/)octicons\\.css","(^|/)sprockets-octicons\\.scss","(^|/)activator$","(^|/)activator\\.bat$","(^|/)proguard\\.pro$","(^|/)proguard-rules\\.pro$","(^|/)puphpet/","(^|/)\\.google_apis/","(^|/)Jenkinsfile$","(^|/)\\.gitpod\\.Dockerfile$","(^|/)\\.github/","(^|/)\\.obsidian/","(^|/)\\.teamcity/","(^|/)xvba_modules/"],"documentation":["^[Dd]ocs?/","(^|/)[Dd]ocumentation/","(^|/)[Gg]roovydoc/","(^|/)[Jj]avadoc/","^[Mm]an/","^[Ee]xamples/","^[Dd]emos?/","(^|/)inst/doc/","(^|/)CITATION(\\.cff|(S)?(\\.(bib|md))?)$","(^|/)CHANGE(S|LOG)?(\\.|$)","(^|/)CONTRIBUTING(\\.|$)","(^|/)COPYING(\\.|$)","(^|/)INSTALL(\\.|$)","(^|/)LICEN[CS]E(\\.|$)","(^|/)[Ll]icen[cs]e(\\.|$)","(^|/)README(\\.|$)","(^|/)[Rr]eadme(\\.|$)","^[Ss]amples?/"],"generic":[".1",".2",".3",".4",".5",".6",".7",".8",".9",".action",".alg",".app",".cmp",".msg",".network",".resource",".sd",".sol",".srv",".stl",".tag",".target",".url"],"heuristics":[{"ext":[".1",".2",".3",".4",".5",".6",".7",".8",".9"],"rules":[{"languages":["Roff Manpage"],"all":[{"re":"(?m)^[.'][ \\t]*Dd +(?:[^\"\\s]+|\"[^\"]+\")"},{"re":"(?m)^[.'][ \\t]*Dt +(?:[^\"\\s]+|\"[^\"]+\") +\"?(?:[1-9]|@[^\\s@]+@)"},{"re":"(?m)^[.'][ \\t]*Sh +(?:[^\"\\s]|\"[^\"]+\")"}]},{"languages":["Roff Manpage"],"all":[{"re":"(?m)^[.'][ \\t]*TH +(?:[^\"\\s]+|\"[^\"]+\") +\"?(?:[1-9]|@[^\\s@]+@)"},{"re":"(?m)^[.'][ \\t]*SH +(?:[^\"\\s]+|\"[^\"\\s]+)"}]},{"languages":["Roff"],"all":[{"re":"(?m)^\\.(?:[A-Za-z]{2}(?:\\s|$)|\\\\\")"}]},{"languages":["Text"],"all":[]}]},{"ext":[".1in",".1m",".1x",".3in",".3m",".3p",".3pm",".3qt",".3x",".man",".mdoc"],"rules":[{"languages":["Roff Manpage"],"all":[{"re":"(?m)^[.'][ \\t]*Dd +(?:[^\"\\s]+|\"[^\"]+\")"},{"re":"(?m)^[.'][ \\t]*Dt +(?:[^\"\\s]+|\"[^\"]+\") +\"?(?:[1-9]|@[^\\s@]+@)"},{"re":"(?m)^[.'][ \\t]*Sh +(?:[^\"\\s]|\"[^\"]+\")"}]},{"languages":["Roff Manpage"],"all":[{"re":"(?m)^[.'][ \\t]*TH +(?:[^\"\\s]+|\"[^\"]+\") +\"?(?:[1-9]|@[^\\s@]+@)"},{"re":"(?m)^[.'][ \\t]*SH +(?:[^\"\\s]+|\"[^\"\\s]+)"}]},{"languages":["Roff"],"all":[]}]},{"ext":[".action"],"rules":[{"languages":["ROS Interface"],"all":[{"re":"(?m)(?i)^[a-z]\\w*(?:\\/[a-z]\\w*)?(?:<=\\d+)?(?:\\[(?:<=\\d+)?\\])?\\s+\\w+\\b"},{"re":"(?m)^---\\s*$"},{"re":"(?m)^\\s*[^\\s@#\\w-]","neg":true}]}]},{"ext":[".al"],"rules":[{"languages":["AL"],"all":[{"re":"(?:(?m)(?:\\A\\W{0,3}|^)\\s*(?i:(CODEUNIT|PAGE|PAGEEXTENSION|PAGECUSTOMIZATION|ENUM|ENUMEXTENSION|QUERY|REPORT|REPORTEXTENSION|TABLE|TABLEEXTENSION|XMLPORT|PROFILE|PROFILEEXTENSION|CONTROLADDIN|INTERFACE|PERMISSIONSET|PERMISSIONSETEXTENSION|ENTITLEMENT))\\b\\s+(?:\\d|\"|[A-Z]))|(?:(?m)(?:\\A\\W{0,3}|^)\\s*(?i:DOTNET)\\s*$)|(?:(?m)(?:\\A\\W{0,3}|^)\\s*(?i:(NAMESPACE|USING))\\s+[\\w.]+\\s*;)"}]},{"languages":["Perl"],"all":[]}]},{"ext":[".alg"],"rules":[{"languages":["ALGOL"],"all":[{"re":"(?:(?m)(?i)^comment\\b.*(;|comment))|(?:(?m)^#[^#\\r\\n]+#$)|(?:(?m)(?i)\\b(integer|real|boolean|string)\\b\\s+\\w+\\s*:=\\s*)|(?:(?m)(?i)^begin[\\s\\S]*^end)"}]}]},{"ext":[".app"],"rules":[{"languages":["Erlang"],"all":[{"re":"(?m)^\\{\\s*(?:application|'application')\\s*,\\s*(?:[a-z]+[\\w@]*|'[^']+')\\s*,\\s*\\[(?:.|[\\r\\n])*\\]\\s*\\}\\.[ \\t]*$"}]}]},{"ext":[".as"],"rules":[{"languages":["ActionScript"],"all":[{"re":"(?m)^\\s*(?:package(?:\\s+[\\w.]+)?\\s+(?:\\{|$)|import\\s+[\\w.*]+\\s*;|(?:intrinsic\\s+)class\\s+[\\w<>.]+|\\s+class\\s+extends\\s+[\\w<>.]+|(?:(?:public|protected|private|static)\\s+)*(?:(?:var|const|local)\\s+\\w+\\s*:\\s*[\\w<>.]+(?:\\s*=.*)?\\s*;|function\\s+\\w+\\s*\\((?:\\s*\\w+\\s*:\\s*[\\w<>.]+\\s*(,\\s*\\w+\\s*:\\s*[\\w<>.]+\\s*)*)?\\)))"}]}]},{"ext":[".asc"],"rules":[{"languages":["Public Key"],"all":[{"re":"(?m)^(----[- ]BEGIN|ssh-(rsa|dss)) "}]},{"languages":["AsciiDoc"],"all":[{"re":"(?m)^[=-]+\\s|\\{\\{[A-Za-z]"}]},{"languages":["AGS Script"],"all":[{"re":"(?m)^(\\/\\/.+|((import|export)\\s+)?(function|int|float|char)\\s+((room|repeatedly|on|game)_)?([A-Za-z]+[A-Za-z_0-9]+)\\s*[;\\(])"}]}]},{"ext":[".asm"],"rules":[{"languages":["Assembly"],"all":[{"re":"(?m)(?i)mov\\s+[^\\s]{2,},"}]},{"languages":["Motorola 68K Assembly"],"all":[{"re":"(?:(?m)(?is)\\bmoveq(?:\\.l)?\\s+#(?:\\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\\s*d[0-7]\\b)|(?:(?m)(?is)^\\s*move(?:\\.[bwl])?\\s+(?:sr|usp),\\s*[^\\s]+)|(?:(?m)(?is)^\\s*move\\.[bwl]\\s+.*\\b[ad]\\d)|(?:(?m)(?is)^\\s*movem\\.[bwl]\\b)|(?:(?m)(?is)^\\s*move[mp](?:\\.[wl])?\\b)|(?:(?m)(?is)^\\s*btst\\b)|(?:(?m)(?is)^\\s*dbra\\b)"}]}]},{"ext":[".asy"],"rules":[{"languages":["LTspice Symbol"],"all":[{"re":"(?m)^SymbolType[ \\t]"}]},{"languages":["Asymptote"],"all":[]}]},{"ext":[".b"],"rules":[{"languages":["B"],"all":[{"re":"(?m)^\\s*(?:extrn|auto|goto)\\b"}]},{"languages":["Brainfuck"],"all":[{"re":"(?m)(>\\+>|>\\+<)"}]},{"languages":["Limbo"],"all":[{"re":"(?m)(?:^\\w+\\s*:\\s*module\\s*\\{)|(implement \\w+;)"}]}]},{"ext":[".bas"],"rules":[{"languages":["B4X"],"all":[{"re":"(?m)\\A\\W{0,3}(?:.*(?:\\r?\\n|\\r)){0,9}B4(?:J|A|R|i)=true"}]},{"languages":["FreeBASIC"],"all":[{"re":"(?:(?m)(?i)^[ \\t]*#(?:define|endif|endmacro|ifn?def|include|lang|macro|pragma)(?:$|\\s))|(?:(?m)(?i)^[ \\t]*dim( shared)? [a-z_][a-z0-9_]* as [a-z_][a-z0-9_]* ptr)"}]},{"languages":["FreeBASIC"],"all":[{"re":"(?m)(?i)^[ \\t]*return "},{"re":"(?m)(?i)[ \\t]*gosub ","neg":true}]},{"languages":["BASIC"],"all":[{"re":"(?m)\\A\\s*\\d"}]},{"languages":["QuickBASIC"],"all":[{"re":"(?:(?m)^[ ]*(CONST|DIM|REDIM|DEFINT|PRINT|DECLARE (SUB|FUNCTION)|FUNCTION|SUB) )|(?:(?m)(#|$)lang:?\\s*\"?qb\"?)|(?:(?m)(?i)'\\$INCLUDE:)|(?:(?m)(?i)^[ ]*CLS[ ]*('|:|\\r|\\n))|(?:(?m)(?i)^[ ]*OPTION _EXPLICIT)|(?:(?m)(?i)^[ ]*DIM SHARED )|(?:(?m)(?i)^[ ]*PRINT \")|(?:(?m)(?i) As _(Byte|Offset|MEM))|(?:(?m)(?i)^[ ]*_(DISPLAY|DEST|CONSOLE|SOURCE|FREEIMAGE|PALETTECOLOR|PRINTSTRING|LOADFONT|PUTIMAGE))|(?:(?m)(?i)^[ ]*_(TITLE|PLAYMOD) \")|(?:(?m)(?i)^[ ]*_(LIMIT|SCREEN|DELAY) \\.?\\d+)|(?:(?m)(?i)\\b_(MOUSEBUTTON|NEWIMAGE|KEYDOWN|WIDTH|HEIGHT)\\()|(?:(?m)(?i)^[ ]*\\$(CONSOLE|CHECKING):)|(?:(?m)(?i)^[ ]*\\$(FULLSCREEN|RESIZE|STATIC|DYNAMIC|NOPREFIX|SCREENSHOW|SCREENHIDE|EXEICON)\\b)"}]},{"languages":["VBA"],"all":[{"re":"(?:(?m)\\b(?:VBA|[vV]ba)(?:\\b|[0-9A-Z_]))|(?:(?m)^[ ]*(?:Public|Private)? Declare PtrSafe (?:Sub|Function)\\b)|(?:(?m)^[ ]*#If Win64\\b)|(?:(?m)^[ ]*(?:Dim|Const) [0-9a-zA-Z_]*[ ]*As Long(?:Ptr|Long)\\b)|(?:(?m)^[ ]*Option (?:Private Module|Compare Database)\\b)|(?:(?m)(?: |\\()(?:Access|Excel|Outlook|PowerPoint|Visio|Word|VBIDE)\\.\\w)|(?:(?m)\\b(?:(?:Active)?VBProjects?|VBComponents?|Application\\.(?:VBE|ScreenUpdating))\\b)|(?:(?m)\\b(?:ThisDrawing|AcadObject|Active(?:Explorer|Inspector|Window\\.Presentation|Presentation|Document)|Selection\\.(?:Document|Find|Paragraphs|Range))\\b)|(?:(?m)\\b(?:(?:This|Active)?Workbooks?|Worksheets?|Active(?:Sheet|Chart|Cell)|WorksheetFunction)\\b)|(?:(?m)\\b(?:Range\\(\".*|Cells\\([0-9a-zA-Z_]*, (?:[0-9a-zA-Z_]*|\"[a-zA-Z]{1,3}\"))\\))"}]},{"languages":["Visual Basic 6.0"],"all":[{"re":"(?m)^[ ]*Attribute VB_Name = "}]}]},{"ext":[".bb"],"rules":[{"languages":["BlitzBasic"],"all":[{"re":"(?m)(<^\\s*; |End Function)"}]},{"languages":["BitBake"],"all":[{"re":"(?m)^(# |include|require|inherit)\\b"}]},{"languages":["Clojure"],"all":[{"re":"(?m)\\((def|defn|defmacro|let)\\s"}]}]},{"ext":[".bf"],"rules":[{"languages":["Beef"],"all":[{"re":"(?m)(?-s)^\\s*using\\s+(System|Beefy)(\\.(.*))?;\\s*$"}]},{"languages":["HyPhy"],"all":[{"re":"(?:(?m)(?-s)^\\s*#include\\s+\".*\";\\s*$)|(?:(?m)\\sfprintf\\s*\\()"}]},{"languages":["Brainfuck"],"all":[{"re":"(?m)(>\\+>|>\\+<)"}]}]},{"ext":[".bi"],"rules":[{"languages":["FreeBASIC"],"all":[{"re":"(?:(?m)(?i)^[ \\t]*#(?:define|endif|endmacro|ifn?def|include|lang|macro|pragma)(?:$|\\s))|(?:(?m)(?i)^[ \\t]*dim( shared)? [a-z_][a-z0-9_]* as [a-z_][a-z0-9_]* ptr)"}]},{"languages":["FreeBASIC"],"all":[{"re":"(?m)(?i)^[ \\t]*return "},{"re":"(?m)(?i)[ \\t]*gosub ","neg":true}]},{"languages":["QuickBASIC"],"all":[{"re":"(?:(?m)^[ ]*(CONST|DIM|REDIM|DEFINT|PRINT|DECLARE (SUB|FUNCTION)|FUNCTION|SUB) )|(?:(?m)(#|$)lang:?\\s*\"?qb\"?)|(?:(?m)(?i)'\\$INCLUDE:)|(?:(?m)(?i)^[ ]*CLS[ ]*('|:|\\r|\\n))|(?:(?m)(?i)^[ ]*OPTION _EXPLICIT)|(?:(?m)(?i)^[ ]*DIM SHARED )|(?:(?m)(?i)^[ ]*PRINT \")|(?:(?m)(?i) As _(Byte|Offset|MEM))|(?:(?m)(?i)^[ ]*_(DISPLAY|DEST|CONSOLE|SOURCE|FREEIMAGE|PALETTECOLOR|PRINTSTRING|LOADFONT|PUTIMAGE))|(?:(?m)(?i)^[ ]*_(TITLE|PLAYMOD) \")|(?:(?m)(?i)^[ ]*_(LIMIT|SCREEN|DELAY) \\.?\\d+)|(?:(?m)(?i)\\b_(MOUSEBUTTON|NEWIMAGE|KEYDOWN|WIDTH|HEIGHT)\\()|(?:(?m)(?i)^[ ]*\\$(CONSOLE|CHECKING):)|(?:(?m)(?i)^[ ]*\\$(FULLSCREEN|RESIZE|STATIC|DYNAMIC|NOPREFIX|SCREENSHOW|SCREENHIDE|EXEICON)\\b)"}]}]},{"ext":[".bs"],"rules":[{"languages":["Bikeshed"],"all":[{"re":"(?m)^(?i:<pre\\s+class)\\s*=\\s*('|\\\"|\\b)metadata\\b('|\\\"|\\b)[^>\\r\\n]*>"}]},{"languages":["BrighterScript"],"all":[{"re":"(?:(?m)(?i:^\\s*(?:sub\\s*\\w+\\(.*?\\))|(?::\\s*sub\\(.*?\\))$))|(?:(?m)(?i:^\\s*(end\\ssub)$))|(?:(?m)(?i:^\\s*(?:function\\s*\\w+\\(.*?\\)\\s*as\\s*\\w*)|(?::\\s*function\\(.*?\\)\\s*as\\s*\\w*)$))|(?:(?m)(?i:^\\s*(end\\sfunction)$))"}]},{"languages":["Bluespec BH"],"all":[{"re":"(?m)^package\\s+[A-Za-z_][A-Za-z0-9_']*(?:\\s*\\(|\\s+where)"}]}]},{"ext":[".bst"],"rules":[{"languages":["BibTeX Style"],"all":[{"re":"(?m)ENTRY\\s*\\{"}]},{"languages":["BuildStream"],"all":[]}]},{"ext":[".builds"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)^(\\s*)(?i:<Project|<Import|<Property|<?xml|xmlns)"}]}]},{"ext":[".cairo"],"rules":[{"languages":["Cairo Zero"],"all":[{"re":"(?m)(^(\\s*)%lang(\\s+)([A-Za-z0-9_]+))|(^(\\s*)%builtins(\\s+)([A-Za-z0-9_]+\\s*)*$)|(^(\\s*)from(\\s+)starkware\\.(cairo|starknet)\\.([A-Za-z0-9_.\\s]+?)import)|(,\\s*ap\\+\\+;$)|(;\\s*ap\\+\\+$)"}]},{"languages":["Cairo"],"all":[]}]},{"ext":[".ch"],"rules":[{"languages":["xBase"],"all":[{"re":"(?m)^\\s*#\\s*(?i:if|ifdef|ifndef|define|command|xcommand|translate|xtranslate|include|pragma|undef)\\b"}]}]},{"ext":[".cl"],"rules":[{"languages":["Common Lisp"],"all":[{"re":"(?m)^\\s*\\((?i:defun|in-package|defpackage) "}]},{"languages":["Cool"],"all":[{"re":"(?m)^class"}]},{"languages":["OpenCL"],"all":[{"re":"(?m)\\/\\* |\\/\\/ |^\\}"}]}]},{"ext":[".cls"],"rules":[{"languages":["Visual Basic 6.0"],"all":[{"re":"(?m)^[ ]*VERSION [0-9]\\.[0-9] CLASS"},{"re":"(?m)^\\s*BEGIN(?:\\r?\\n|\\r)\\s*MultiUse\\s*=.*(?:\\r?\\n|\\r)\\s*Persistable\\s*="}]},{"languages":["VBA"],"all":[{"re":"(?m)^[ ]*VERSION [0-9]\\.[0-9] CLASS"}]},{"languages":["TeX"],"all":[{"re":"(?m)^\\s*\\\\(?:NeedsTeXFormat|ProvidesClass)\\{"}]},{"languages":["ObjectScript"],"all":[{"re":"(?m)^Class\\s"}]},{"languages":["OpenEdge ABL"],"all":[{"re":"(?m)(?i)^\\s*(?:end\\s+(?:class|interface)\\.|(?:class|interface)\\s+[a-z_][a-z0-9_#$%&-]*:)"}]}]},{"ext":[".cmp"],"rules":[{"languages":["Gerber Image"],"all":[{"re":"(?m)^[DGMT][0-9]{2}\\*(?:\\r?\\n|\\r)"}]}]},{"ext":[".container",".mount",".network",".service",".socket",".target",".timer"],"rules":[{"languages":["INI"],"all":[{"re":"(?m)^[#\\s]*\\[[^\\]]+\\]\\s*$"},{"re":"(?m)^[^#!;][^=]*="},{"re":"(?m)^[^#;=\\[]+[^ \\r\\n=]$","neg":true}]}]},{"ext":[".cs"],"rules":[{"languages":["Smalltalk"],"all":[{"re":"(?m)![\\w\\s]+methodsFor: "}]},{"languages":["C#"],"all":[{"re":"(?m)^\\s*(using\\s+[A-Z][\\s\\w.]+;|namespace\\s*[\\w\\.]+\\s*(\\{|;)|\\/\\/)"}]}]},{"ext":[".csc"],"rules":[{"languages":["GSC"],"all":[{"re":"(?:(?m)^\\s*#\\s*(?:using|insert|include|define|namespace)[ \\t]+\\w)|(?:(?m)^\\s*((?:autoexec|private)\\s+){0,2}function\\s+((?:autoexec|private)\\s+){0,2}\\w+\\s*\\()|(?:(?m)\\b(?:level|self)[ \\t]+thread[ \\t]+(?:\\[\\[[ \\t]*(\\w+\\.)+[ \\t]*\\]\\]|\\w+)[ \\t]*\\([^\\r\\n\\)]*\\)[ \\t]*;)|(?:(?m)^[ \\t]*#[ \\t]*(?:precache|using_animtree)[ \\t]*\\()"}]}]},{"ext":[".csl"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)(?i:^\\s*(<\\?xml|xmlns))"}]},{"languages":["Kusto"],"all":[{"re":"(?m)(^\\|\\s*(where|extend|project|limit|summarize))|(^\\.\\w+)"}]}]},{"ext":[".d"],"rules":[{"languages":["D"],"all":[{"re":"(?m)^module\\s+[\\w.]*\\s*;|import\\s+[\\w\\s,.:]*;|\\w+\\s+\\w+\\s*\\(.*\\)(?:\\(.*\\))?\\s*\\{[^}]*\\}|unittest\\s*(?:\\(.*\\))?\\s*\\{[^}]*\\}"}]},{"languages":["DTrace"],"all":[{"re":"(?m)^(\\w+:\\w*:\\w*:\\w*|BEGIN|END|provider\\s+|(tick|profile)-\\w+\\s+\\{[^}]*\\}|#pragma\\s+D\\s+(option|attributes|depends_on)\\s|#pragma\\s+ident\\s)"}]},{"languages":["Makefile"],"all":[{"re":"(?m)([\\/\\\\].*:\\s+.*\\s\\\\$|: \\\\$|^[ %]:|^[\\w\\s\\/\\\\.]+\\w+\\.\\w+\\s*:\\s+[\\w\\s\\/\\\\.]+\\w+\\.\\w+)"}]}]},{"ext":[".dsp"],"rules":[{"languages":["Microsoft Developer Studio Project"],"all":[{"re":"(?m)# Microsoft Developer Studio Generated Build File"}]},{"languages":["Faust"],"all":[{"re":"(?m)\\bprocess\\s*[(=]|\\b(library|import)\\s*\\(\\s*\"|\\bdeclare\\s+(name|version|author|copyright|license)\\s+\""}]}]},{"ext":[".e"],"rules":[{"languages":["E"],"all":[{"re":"(?:(?m)^\\s*(def|var)\\s+(.+):=)|(?:(?m)^\\s*(def|to)\\s+(\\w+)(\\(.+\\))?\\s+\\{)|(?:(?m)^\\s*(when)\\s+(\\(.+\\))\\s+->\\s+\\{)"}]},{"languages":["Eiffel"],"all":[{"re":"(?:(?m)^\\s*\\w+\\s*(?:,\\s*\\w+)*[:]\\s*\\w+\\s)|(?:(?m)^\\s*\\w+\\s*(?:\\(\\s*\\w+[:][^)]+\\))?(?:[:]\\s*\\w+)?(?:--.+\\s+)*\\s+(?:do|local)\\s)|(?:(?m)^\\s*(?:across|deferred|elseif|ensure|feature|from|inherit|inspect|invariant|note|once|require|undefine|variant|when)\\s*$)"}]},{"languages":["Euphoria"],"all":[{"re":"(?:(?m)^\\s*namespace\\s)|(?:(?m)^\\s*(?:public\\s+)?include\\s)|(?:(?m)^\\s*(?:(?:public|export|global)\\s+)?(?:atom|constant|enum|function|integer|object|procedure|sequence|type)\\s)"}]}]},{"ext":[".ecl"],"rules":[{"languages":["ECLiPSe"],"all":[{"re":"(?m)^[^#]+:-"}]},{"languages":["ECL"],"all":[{"re":"(?m):="}]}]},{"ext":[".es"],"rules":[{"languages":["Erlang"],"all":[{"re":"(?m)^\\s*(?:%%|main\\s*\\(.*?\\)\\s*->)"}]},{"languages":["JavaScript"],"all":[{"re":"(?m)\\/\\/|[\"']use strict[\"']|export\\s+default\\s|\\/\\*(?:.|[\\r\\n])*?\\*\\/"}]}]},{"ext":[".eslintrc"],"rules":[{"languages":["YAML"],"all":[{"re":"(?m)^[ \\t]*(?:#|(?:[^/\"\\s:\\{}\\[\\]]|\\/[^/*:\\n])[^:\\n]*:)"}]},{"languages":["JSON with Comments"],"all":[]}]},{"ext":[".ex"],"rules":[{"languages":["Elixir"],"all":[{"re":"(?:(?m)^\\s*@moduledoc\\s)|(?:(?m)^\\s*(?:cond|import|quote|unless)\\s)|(?:(?m)^\\s*def(?:exception|impl|macro|module|protocol)[(\\s])"}]},{"languages":["Euphoria"],"all":[{"re":"(?:(?m)^\\s*namespace\\s)|(?:(?m)^\\s*(?:public\\s+)?include\\s)|(?:(?m)^\\s*(?:(?:public|export|global)\\s+)?(?:atom|constant|enum|function|integer|object|procedure|sequence|type)\\s)"}]}]},{"ext":[".f"],"rules":[{"languages":["Forth"],"all":[{"re":"(?m)^: "}]},{"languages":["Filebench WML"],"all":[{"re":"(?m)flowop"}]},{"languages":["Fortran"],"all":[{"re":"(?m)^(?i:[c*][^abd-z]| (subroutine|program|end|data)\\s|\\s*!)"}]}]},{"ext":[".for"],"rules":[{"languages":["Forth"],"all":[{"re":"(?m)^: "}]},{"languages":["Fortran"],"all":[{"re":"(?m)^(?i:[c*][^abd-z]| (subroutine|program|end|data)\\s|\\s*!)"}]}]},{"ext":[".fpp"],"rules":[{"languages":["FPP"],"all":[{"re":"(?:(?m)^\\s*(active|passive|queued)\\s+component\\s)|(?:(?m)^\\s*(module|topology|struct|enum|port|interface)\\s+[A-Za-z_$][A-Za-z0-9_]*\\s*\\{)|(?:(?m)^\\s*instance\\s+[A-Za-z_$][A-Za-z0-9_]*\\s*:)"}]},{"languages":["Fortran"],"all":[{"re":"(?m)^(?i:[c*][^abd-z]| (subroutine|program|end|data)\\s|\\s*!)"}]}]},{"ext":[".fr"],"rules":[{"languages":["Forth"],"all":[{"re":"(?m)^(: |also |new-device|previous )"}]},{"languages":["Frege"],"all":[{"re":"(?m)^\\s*(import|module|package|data|type) "}]},{"languages":["Text"],"all":[]}]},{"ext":[".frm"],"rules":[{"languages":["VBA"],"all":[{"re":"(?m)^[ ]*VERSION [0-9]\\.[0-9]{2}"},{"re":"(?m)^\\s*Begin\\s+\\{[0-9A-Z\\-]*\\}\\s?"}]},{"languages":["Visual Basic 6.0"],"all":[{"re":"(?m)^[ ]*VERSION [0-9]\\.[0-9]{2}"},{"re":"(?m)^\\s*Begin\\s+VB\\.Form\\s+"}]},{"languages":["INI"],"all":[{"re":"(?m)\\ATYPE=VIEW"}]}]},{"ext":[".fs"],"rules":[{"languages":["Forth"],"all":[{"re":"(?m)^(: |new-device)"}]},{"languages":["F#"],"all":[{"re":"(?m)^\\s*(#light|import|let|module|namespace|open|type)"}]},{"languages":["GLSL"],"all":[{"re":"(?m)^\\s*(#version|precision|uniform|varying|vec[234])"}]},{"languages":["Filterscript"],"all":[{"re":"(?m)#include|#pragma\\s+(rs|version)|__attribute__"}]}]},{"ext":[".ftl"],"rules":[{"languages":["FreeMarker"],"all":[{"re":"(?m)^(?:<|[a-zA-Z-][a-zA-Z0-9_-]+[ \\t]+\\w)|\\$\\{\\w+[^\\r\\n]*?\\}|^[ \\t]*(?:<#--.*?-->|<#(?:[a-z]+)\\s[^>]*>.*?</#(?:[a-z]+)>|\\[#--.*?--\\]|\\[#(?:[a-z]+)\\s[^\\]]*\\].*?\\[#(?:[a-z]+)\\])"}]},{"languages":["Fluent"],"all":[{"re":"(?m)^-?[a-zA-Z][a-zA-Z0-9_-]* *=|\\{\\$-?[a-zA-Z][-\\w]*(?:\\.[a-zA-Z][-\\w]*)?\\}"}]}]},{"ext":[".g"],"rules":[{"languages":["GAP"],"all":[{"re":"(?m)\\s*(Declare|BindGlobal|KeyDependentOperation|Install(Method|GlobalFunction)|SetPackageInfo)"}]},{"languages":["G-code"],"all":[{"re":"(?m)^[MG][0-9]+(?:\\r?\\n|\\r)"}]}]},{"ext":[".gd"],"rules":[{"languages":["GAP"],"all":[{"re":"(?m)\\s*(Declare|BindGlobal|KeyDependentOperation)"}]},{"languages":["GDScript"],"all":[{"re":"(?m)\\s*(extends|var|const|enum|func|class|signal|tool|yield|assert|onready)"}]}]},{"ext":[".gml"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)(?i:^\\s*(<\\?xml|xmlns))"}]},{"languages":["Graph Modeling Language"],"all":[{"re":"(?m)(?i:^\\s*(graph|node)\\s+\\[$)"}]},{"languages":["Gerber Image"],"all":[{"re":"(?m)^[DGMT][0-9]{2}\\*$"}]},{"languages":["Game Maker Language"],"all":[]}]},{"ext":[".gs"],"rules":[{"languages":["GLSL"],"all":[{"re":"(?m)^#version\\s+[0-9]+\\b"}]},{"languages":["Gosu"],"all":[{"re":"(?m)^uses (java|gw)\\."}]},{"languages":["Genie"],"all":[{"re":"(?m)^\\[indent=[0-9]+\\]"}]}]},{"ext":[".gsc"],"rules":[{"languages":["GSC"],"all":[{"re":"(?:(?m)^\\s*#\\s*(?:using|insert|include|define|namespace)[ \\t]+\\w)|(?:(?m)^\\s*((?:autoexec|private)\\s+){0,2}function\\s+((?:autoexec|private)\\s+){0,2}\\w+\\s*\\()|(?:(?m)\\b(?:level|self)[ \\t]+thread[ \\t]+(?:\\[\\[[ \\t]*(\\w+\\.)+[ \\t]*\\]\\]|\\w+)[ \\t]*\\([^\\r\\n\\)]*\\)[ \\t]*;)|(?:(?m)^[ \\t]*#[ \\t]*(?:precache|using_animtree)[ \\t]*\\()"}]}]},{"ext":[".gsh"],"rules":[{"languages":["GSC"],"all":[{"re":"(?:(?m)^\\s*#\\s*(?:using|insert|include|define|namespace)[ \\t]+\\w)|(?:(?m)^\\s*((?:autoexec|private)\\s+){0,2}function\\s+((?:autoexec|private)\\s+){0,2}\\w+\\s*\\()|(?:(?m)\\b(?:level|self)[ \\t]+thread[ \\t]+(?:\\[\\[[ \\t]*(\\w+\\.)+[ \\t]*\\]\\]|\\w+)[ \\t]*\\([^\\r\\n\\)]*\\)[ \\t]*;)|(?:(?m)^[ \\t]*#[ \\t]*(?:precache|using_animtree)[ \\t]*\\()"}]}]},{"ext":[".gts"],"rules":[{"languages":["Gerber Image"],"all":[{"re":"(?m)^G0."}]},{"languages":["Glimmer TS"],"all":[{"re":"(?m)^G0.","neg":true}]}]},{"ext":[".h"],"rules":[{"languages":["Objective-C"],"all":[{"re":"(?m)^\\s*(@(interface|class|protocol|property|end|synchronised|selector|implementation)\\b|#import\\s+.+\\.h[\">])"}]},{"languages":["C++"],"all":[{"re":"(?:(?m)^\\s*#\\s*include <(cstdint|string|vector|map|list|array|bitset|queue|stack|forward_list|unordered_map|unordered_set|(i|o|io)stream)>)|(?:(?m)^\\s*template\\s*<)|(?:(?m)^[ \\t]*(try|constexpr))|(?:(?m)^[ \\t]*catch\\s*\\()|(?:(?m)^[ \\t]*(class|(using[ \\t]+)?namespace)\\s+\\w+)|(?:(?m)^[ \\t]*(private|public|protected):$)|(?:(?m)__has_cpp_attribute|__cplusplus >)|(?:(?m)std::\\w+)"}]},{"languages":["C"],"all":[]}]},{"ext":[".hh"],"rules":[{"languages":["Hack"],"all":[{"re":"(?m)<\\?hh"}]}]},{"ext":[".html"],"rules":[{"languages":["Ecmarkup"],"all":[{"re":"(?m)<emu-(?:alg|annex|biblio|clause|eqn|example|figure|gann|gmod|gprose|grammar|intro|not-ref|note|nt|prodref|production|rhs|table|t|xref)(?:$|\\s|>)"}]},{"languages":["HTML"],"all":[]}]},{"ext":[".i"],"rules":[{"languages":["Motorola 68K Assembly"],"all":[{"re":"(?:(?m)(?is)\\bmoveq(?:\\.l)?\\s+#(?:\\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\\s*d[0-7]\\b)|(?:(?m)(?is)^\\s*move(?:\\.[bwl])?\\s+(?:sr|usp),\\s*[^\\s]+)|(?:(?m)(?is)^\\s*move\\.[bwl]\\s+.*\\b[ad]\\d)|(?:(?m)(?is)^\\s*movem\\.[bwl]\\b)|(?:(?m)(?is)^\\s*move[mp](?:\\.[wl])?\\b)|(?:(?m)(?is)^\\s*btst\\b)|(?:(?m)(?is)^\\s*dbra\\b)"}]},{"languages":["SWIG"],"all":[{"re":"(?m)^[ \\t]*%[a-z_]+\\b|^%[\\{}]$"}]},{"languages":["Assembly"],"all":[{"re":"(?:(?m)(?i)mov\\s+[^\\s]+,)|(?:(?m)^\\s+(i?)db\\s+[a-z\\d])"}]}]},{"ext":[".ice"],"rules":[{"languages":["JSON"],"all":[{"re":"(?m)\\A\\s*[\\{\\[]"}]},{"languages":["Slice"],"all":[]}]},{"ext":[".inc"],"rules":[{"languages":["Motorola 68K Assembly"],"all":[{"re":"(?:(?m)(?is)\\bmoveq(?:\\.l)?\\s+#(?:\\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\\s*d[0-7]\\b)|(?:(?m)(?is)^\\s*move(?:\\.[bwl])?\\s+(?:sr|usp),\\s*[^\\s]+)|(?:(?m)(?is)^\\s*move\\.[bwl]\\s+.*\\b[ad]\\d)|(?:(?m)(?is)^\\s*movem\\.[bwl]\\b)|(?:(?m)(?is)^\\s*move[mp](?:\\.[wl])?\\b)|(?:(?m)(?is)^\\s*btst\\b)|(?:(?m)(?is)^\\s*dbra\\b)"}]},{"languages":["PHP"],"all":[{"re":"(?m)^<\\?(?:php)?"}]},{"languages":["SourcePawn"],"all":[{"re":"(?:(?m)^public\\s+(?:SharedPlugin(?:\\s+|:)__pl_\\w+\\s*=(?:\\s*\\{)?|(?:void\\s+)?__pl_\\w+_SetNTVOptional\\(\\)(?:\\s*\\{)?))|(?:(?m)^methodmap\\s+\\w+\\s+<\\s+\\w+)|(?:(?m)^\\s*MarkNativeAsOptional\\s*\\()"}]},{"languages":["NASL"],"all":[{"re":"(?:(?m)^\\s*include\\s*\\(\\s*(?:\"|')[\\\\/\\w\\-\\.:\\s]+\\.(?:nasl|inc)\\s*(?:\"|')\\s*\\)\\s*;)|(?:(?m)^\\s*(?:global|local)_var\\s+(?:\\w+(?:\\s*=\\s*[\\w\\-\"']+)?\\s*)(?:,\\s*\\w+(?:\\s*=\\s*[\\w\\-\"']+)?\\s*)*\\s*;)|(?:(?m)^\\s*namespace\\s+\\w+\\s*\\{)|(?:(?m)^\\s*object\\s+\\w+\\s*(?:extends\\s+\\w+(?:::\\w+)?)?\\s*\\{)|(?:(?m)^\\s*(?:public\\s+|private\\s+|\\s*)function\\s+\\w+\\s*\\([\\w\\s,]*\\)\\s*\\{)"}]},{"languages":["POV-Ray SDL"],"all":[{"re":"(?m)^\\s*#(declare|local|macro|while)\\s"}]},{"languages":["Pascal"],"all":[{"re":"(?:(?m)(?i:^\\s*\\{\\$(?:mode|ifdef|undef|define)[ ]+[a-z0-9_]+\\}))|(?:(?m)^\\s*end[.;]\\s*$)"}]},{"languages":["BitBake"],"all":[{"re":"(?m)^inherit(\\s+[\\w.-]+)+\\s*$"}]},{"languages":["Assembly"],"all":[{"re":"(?:(?m)^(?i)[^\"m]*mov\\s+[^\\s]+,)|(?:(?m)^\\s+(?i)db\\s+[a-z\\d])"}]}]},{"ext":[".j"],"rules":[{"languages":["Jasmin"],"all":[{"re":"(?m)^\\s*\\.(?:class|super|method|end\\s+method|limit|field|source|bytecode)\\b"}]},{"languages":["Objective-J"],"all":[{"re":"(?m)^\\s*@(?:import|implementation|protocol|interface|typedef|end)\\b"}]},{"languages":["JASS"],"all":[{"re":"(?:(?m)^\\s*(?:constant\\s+)?(?:function|native)\\s+\\w+\\s+takes\\b)|(?:(?m)^\\s*end(?:function|globals|loop)\\s*$)|(?:(?m)^\\s*(?:call|set|local|exitwhen)\\s+\\S)"}]}]},{"ext":[".json"],"rules":[{"languages":["OASv2-json"],"all":[{"re":"(?m)\"swagger\":\\s?\"2.[0-9.]+\""}]},{"languages":["OASv3-json"],"all":[{"re":"(?m)\"openapi\":\\s?\"3.[0-9.]+\""}]},{"languages":["JSON"],"all":[]}]},{"ext":[".k"],"rules":[{"languages":["KCL"],"all":[{"re":"(?:(?m)^schema [A-Za-z0-9_-]+)|(?:(?m)^\\}$)|(?:(?m)\\s*\\}\\s*)"}]},{"languages":["KFramework"],"all":[{"re":"(?:(?m)^requires\\s+\"[^\"]+\"$)|(?:(?m)^syntax\\s+\\w+\\s+::=.*)|(?:(?m)^endmodule$)"}]}]},{"ext":[".l"],"rules":[{"languages":["Common Lisp"],"all":[{"re":"(?m)\\(def(un|macro)\\s"}]},{"languages":["Lex"],"all":[{"re":"(?m)^(%[%\\{}]xs|<.*>)"}]},{"languages":["Roff"],"all":[{"re":"(?m)^\\.[A-Za-z]{2}(\\s|$)"}]},{"languages":["PicoLisp"],"all":[{"re":"(?m)^\\((de|class|rel|code|data|must)\\s"}]}]},{"ext":[".lean"],"rules":[{"languages":["Lean"],"all":[{"re":"(?m)^import [a-z]"}]},{"languages":["Lean 4"],"all":[{"re":"(?m)^import [A-Z]"}]}]},{"ext":[".lp"],"rules":[{"languages":["Lambdapi"],"all":[{"re":"(?m)^\\s*(?:opaque\\s+symbol|symbol|rule|notation|constant|injective|require(?:\\s+open)?|open|type|assert(?:not)?|compute)\\b"}]},{"languages":["Linear Programming"],"all":[{"re":"(?m)(?i)^\\s*(?:minimize|minimum|min|maximize|maximum|max)(?:\\s+multi-objectives)?\\s*$"}]},{"languages":["Answer Set Programming"],"all":[{"re":"(?:(?m)^\\s*#(?:include|const|program|script|show|minimize|maximize|heuristic|project|edge|theory|defined)\\b)|(?:(?m)^\\s*(?:[A-Za-z_]\\w*(?:\\([^)]*\\))?\\s*:-|:-)\\s[^.]*\\.)|(?:(?m)^\\s*[a-z_][a-zA-Z0-9_]*(?:\\([^)]*\\))?\\s*\\.\\s*$)"}]}]},{"ext":[".ls"],"rules":[{"languages":["LoomScript"],"all":[{"re":"(?m)^\\s*package\\s*[\\w\\.\\/\\*\\s]*\\s*\\{"}]},{"languages":["LiveScript"],"all":[]}]},{"ext":[".lsp",".lisp"],"rules":[{"languages":["Common Lisp"],"all":[{"re":"(?m)^\\s*\\((?i:defun|in-package|defpackage) "}]},{"languages":["NewLisp"],"all":[{"re":"(?m)^\\s*\\(define "}]}]},{"ext":[".m"],"rules":[{"languages":["Objective-C"],"all":[{"re":"(?m)^\\s*(@(interface|class|protocol|property|end|synchronised|selector|implementation)\\b|#import\\s+.+\\.h[\">])"}]},{"languages":["Mercury"],"all":[{"re":"(?m):- module"}]},{"languages":["MUF"],"all":[{"re":"(?m)^: "}]},{"languages":["M"],"all":[{"re":"(?m)^\\s*;"}]},{"languages":["Wolfram Language"],"all":[{"re":"(?m)\\(\\*"},{"re":"(?m)\\*\\)$"}]},{"languages":["MATLAB"],"all":[{"re":"(?m)^\\s*%"}]},{"languages":["Limbo"],"all":[{"re":"(?m)^\\w+\\s*:\\s*module\\s*\\{"}]}]},{"ext":[".m4"],"rules":[{"languages":["M4Sugar"],"all":[{"re":"(?:(?m)AC_DEFUN|AC_PREREQ|AC_INIT)|(?:(?m)^_?m4_)"}]},{"languages":["M4"],"all":[]}]},{"ext":[".mask"],"rules":[{"languages":["Unity3D Asset"],"all":[{"re":"(?m)tag:unity3d.com"}]}]},{"ext":[".mc"],"rules":[{"languages":["Win32 Message File"],"all":[{"re":"(?m)(?i)^[ \\t]*(\\/\\*\\s*)?MessageId=|^\\.$"}]},{"languages":["M4"],"all":[{"re":"(?m)^dnl|^divert\\((?:-?\\d+)?\\)|^\\w+\\(`[^\\r\\n]*?'[),]"}]},{"languages":["Monkey C"],"all":[{"re":"(?m)\\b(?:using|module|function|class|var)\\s+\\w"}]}]},{"ext":[".md"],"rules":[{"languages":["Markdown"],"all":[{"re":"(?:(?m)(^[-A-Za-z0-9=#!\\*\\[|>])|<\\/)|(?:(?m)\\A\\z)"}]},{"languages":["GCC Machine Description"],"all":[{"re":"(?m)^(;;|\\(define_)"}]},{"languages":["Markdown"],"all":[]}]},{"ext":[".ml"],"rules":[{"languages":["OCaml"],"all":[{"re":"(?m)(^\\s*module)|let rec |match\\s+(\\S+\\s)+with"}]},{"languages":["Standard ML"],"all":[{"re":"(?m)=> |case\\s+(\\S+\\s)+of"}]}]},{"ext":[".mod"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)<!ENTITY "}]},{"languages":["NMODL"],"all":[{"re":"(?m)\\b(NEURON|INITIAL|UNITS)\\b"}]},{"languages":["Modula-2"],"all":[{"re":"(?m)^\\s*(?i:MODULE|END) [\\w\\.]+;"}]},{"languages":["Linux Kernel Module","AMPL"],"all":[]}]},{"ext":[".mojo"],"rules":[{"languages":["Mojo"],"all":[{"re":"(?m)^\\s*(alias|def|from|fn|import|struct|trait)\\s"}]},{"languages":["XML"],"all":[{"re":"(?m)^\\s*<\\?xml"}]}]},{"ext":[".mps"],"rules":[{"languages":["JetBrains MPS"],"all":[{"re":"(?m)<persistence"}]},{"languages":["Mathematical Programming System"],"all":[{"re":"(?m)(?i)^\\s*(ROWS|COLUMNS)\\s*$"}]}]},{"ext":[".ms"],"rules":[{"languages":["Roff"],"all":[{"re":"(?m)^[.'][A-Za-z]{2}(\\s|$)"}]},{"languages":["Unix Assembly"],"all":[{"re":"(?m)/\\*","neg":true},{"re":"(?m)^\\s*\\.(?:include\\s|globa?l\\s|[A-Za-z][_A-Za-z0-9]*:)"}]},{"languages":["MiniScript"],"all":[{"re":"(?m)^\\s*\\bend (?:function|while|if|for)\\b|\\s\\b(?:isa)\\b\\s"}]},{"languages":["MAXScript"],"all":[]}]},{"ext":[".msg"],"rules":[{"languages":["OMNeT++ MSG"],"all":[{"re":"(?m)^cplusplus\\(?[\\S]*\\)?[\\s]*\\{?\\{?|^namespace[\\s]+([^.\\s]*\\.)*[^.\\s]*;|^struct[\\s]+[\\S]+|^message[\\s]+[\\S]+(extends )?[\\S]*[\\s]*|^packet[\\s]+[\\S]+|^class[\\s]+[\\S]+(extends )?[\\S]*[\\s]*|^enum[\\s]+[\\S]+|^import ([^.\\s]*\\.)*[^.\\s]*;"}]},{"languages":["ROS Interface"],"all":[{"re":"(?m)(?i)^[a-z]\\w*(?:\\/[a-z]\\w*)?(?:<=\\d+)?(?:\\[(?:<=\\d+)?\\])?\\s+\\w+\\b"},{"re":"(?m)^\\s*[^\\s@#\\w-]","neg":true}]}]},{"ext":[".n"],"rules":[{"languages":["Roff"],"all":[{"re":"(?m)^[.']"}]},{"languages":["Nemerle"],"all":[{"re":"(?m)^(module|namespace|using)\\s"}]}]},{"ext":[".ncl"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)^\\s*<\\?xml\\s+version"}]},{"languages":["Gerber Image"],"all":[{"re":"(?m)^[DGMT][0-9]{2}\\*(?:\\r?\\n|\\r)"}]},{"languages":["Nickel"],"all":[{"re":"(?:(?m)^let(?:\\srec)?(?:\\s[a-zA-Z_][a-zA-Z0-9_]*)?)|(?:(?m)^import\\s\"[^\"]+\"\\s+as\\s)|(?:(?m)std\\.[a-zA-Z_][a-zA-Z0-9_]*\\.)"}]},{"languages":["Text"],"all":[{"re":"(?m)THE_TITLE"}]},{"languages":["NCL"],"all":[{"re":"(?:(?m)^load \")|(?:(?m)^begin$)|(?:(?m)[0-9]\\.$)|(?:(?m)^;)"}]}]},{"ext":[".nl"],"rules":[{"languages":["NL"],"all":[{"re":"(?m)^(b|g)[0-9]+ "}]},{"languages":["NewLisp"],"all":[]}]},{"ext":[".nr"],"rules":[{"languages":["Roff"],"all":[{"re":"(?m)^\\."}]},{"languages":["Noir"],"all":[]}]},{"ext":[".nu"],"rules":[{"languages":["Nushell"],"all":[{"re":"(?m)^\\s*(import|export|module|def|let|let-env) "}]},{"languages":["Nu"],"all":[]}]},{"ext":[".odin"],"rules":[{"languages":["Object Data Instance Notation"],"all":[{"re":"(?m)(?:^|<)\\s*[A-Za-z0-9_]+\\s*=\\s*<"}]},{"languages":["Odin"],"all":[{"re":"(?m)package\\s+\\w+|\\b(?:im|ex)port\\s*\"[\\w:./]+\"|\\w+\\s*::\\s*(?:proc|struct)\\s*\\(|^\\s*//\\s"}]}]},{"ext":[".p"],"rules":[{"languages":["Gnuplot"],"all":[{"re":"(?:(?m)^s?plot\\b)|(?:(?m)^set\\s+(term|terminal|out|output|[xy]tics|[xy]label|[xy]range|style)\\b)"}]},{"languages":["OpenEdge ABL"],"all":[{"re":"(?:(?m)(?i)\\bDEFINE\\s+(?:VARIABLE|TEMP-TABLE|BUFFER|QUERY|INPUT\\s+PARAMETER|OUTPUT\\s+PARAMETER)\\b)|(?:(?m)(?i)\\bEND(?:\\s+(?:PROCEDURE|FUNCTION|DO|FOR\\s+EACH))?\\.)"}]}]},{"ext":[".pc"],"rules":[{"languages":["Pro*C"],"all":[{"re":"(?m)^\\s*EXEC\\s+SQL\\s"}]},{"languages":["pkg-config"],"all":[{"re":"(?m)^Name:[ \\t]+\\S"},{"re":"(?m)^Description:[ \\t]+\\S"},{"re":"(?m)^Version:[ \\t]+\\S"}]}]},{"ext":[".php"],"rules":[{"languages":["Hack"],"all":[{"re":"(?m)<\\?hh"}]},{"languages":["PHP"],"all":[{"re":"(?m)<\\?[^h]"}]}]},{"ext":[".pkl"],"rules":[{"languages":["Pkl"],"all":[{"re":"(?:(?m)^\\s*(module|import|amends|extends|local|const|fixed|abstract|open|class|typealias|@\\w+)\\b)|(?:(?m)^\\s*[a-zA-Z0-9_$]+\\s*(=|\\{|:)|^\\s*`[^`]+`\\s*(=|\\{|:)|for\\s*\\(|when\\s*\\()"}]},{"languages":["Pickle"],"all":[]}]},{"ext":[".pl"],"rules":[{"languages":["Prolog"],"all":[{"re":"(?m)^[^#]*:-"}]},{"languages":["Perl"],"all":[{"re":"(?m)^\\s*use\\s+v6\\b","neg":true},{"re":"(?:(?m)\\buse\\s+(?:strict\\b|v?5\\b))|(?:(?m)^\\s*use\\s+(?:constant|overload)\\b)|(?:(?m)^\\s*(?:\\*|(?:our\\s*)?@)EXPORT\\s*=)|(?:(?m)^\\s*package\\s+[^\\W\\d]\\w*(?:::\\w+)*\\s*(?:[;\\{]|\\sv?\\d))|(?:(?m)[\\s$][^\\W\\d]\\w*(?::\\w+)*->[a-zA-Z_\\[(\\{])"}]},{"languages":["Raku"],"all":[{"re":"(?m)^\\s*(?:use\\s+v6\\b|\\bmodule\\b|\\b(?:my\\s+)?class\\b)"}]}]},{"ext":[".plist"],"rules":[{"languages":["XML Property List"],"all":[{"re":"(?m)^\\s*(?:<\\?xml\\s|<!DOCTYPE\\s+plist|<plist(?:\\s+version\\s*=\\s*[\"']\\d+(?:\\.\\d+)?[\"'])?\\s*>\\s*$)"}]},{"languages":["OpenStep Property List"],"all":[]}]},{"ext":[".plt"],"rules":[{"languages":["Prolog"],"all":[{"re":"(?m)^\\s*:-"}]}]},{"ext":[".pm"],"rules":[{"languages":["Perl"],"all":[{"re":"(?m)^\\s*use\\s+v6\\b","neg":true},{"re":"(?:(?m)\\buse\\s+(?:strict\\b|v?5\\b))|(?:(?m)^\\s*use\\s+(?:constant|overload)\\b)|(?:(?m)^\\s*(?:\\*|(?:our\\s*)?@)EXPORT\\s*=)|(?:(?m)^\\s*package\\s+[^\\W\\d]\\w*(?:::\\w+)*\\s*(?:[;\\{]|\\sv?\\d))|(?:(?m)[\\s$][^\\W\\d]\\w*(?::\\w+)*->[a-zA-Z_\\[(\\{])"}]},{"languages":["Raku"],"all":[{"re":"(?m)^\\s*(?:use\\s+v6\\b|\\bmodule\\b|\\b(?:my\\s+)?class\\b)"}]},{"languages":["X PixMap"],"all":[{"re":"(?m)^\\s*\\/\\* XPM \\*\\/"}]}]},{"ext":[".pod"],"rules":[{"languages":["Pod 6"],"all":[{"re":"(?m)^[\\s&&[^\\r\\n]]*=(comment|begin pod|begin para|item\\d+)"}]},{"languages":["Pod"],"all":[]}]},{"ext":[".pp"],"rules":[{"languages":["Pascal"],"all":[{"re":"(?m)^\\s*end[.;]"}]},{"languages":["Puppet"],"all":[{"re":"(?m)^\\s+\\w+\\s+=>\\s"}]}]},{"ext":[".pro"],"rules":[{"languages":["Proguard"],"all":[{"re":"(?m)^-(include\\b.*\\.pro$|keep\\b|keepclassmembers\\b|keepattributes\\b)"}]},{"languages":["Prolog"],"all":[{"re":"(?m)^[^\\[#]+:-"}]},{"languages":["INI"],"all":[{"re":"(?m)last_client="}]},{"languages":["QMake"],"all":[{"re":"(?m)HEADERS"},{"re":"(?m)SOURCES"}]},{"languages":["IDL"],"all":[{"re":"(?m)^\\s*(?i:function|pro|compile_opt) \\w[ \\w,:]*$"}]}]},{"ext":[".properties"],"rules":[{"languages":["INI"],"all":[{"re":"(?m)^[^#!;][^=]*="},{"re":"(?m)^[;\\[]"}]},{"languages":["Java Properties"],"all":[{"re":"(?m)^[^#!;][^=]*="},{"re":"(?m)^[#!]"}]},{"languages":["INI"],"all":[{"re":"(?m)^[^#!;][^=]*="}]},{"languages":["Java Properties"],"all":[{"re":"(?m)^[^#!][^:]*:"}]}]},{"ext":[".q"],"rules":[{"languages":["q"],"all":[{"re":"(?m)((?i:[A-Z.][\\w.]*:\\{)|^\\\\(cd?|d|l|p|ts?) )"}]},{"languages":["HiveQL"],"all":[{"re":"(?m)(?i:SELECT\\s+[\\w*,]+\\s+FROM|(CREATE|ALTER|DROP)\\s(DATABASE|SCHEMA|TABLE))"}]}]},{"ext":[".qs"],"rules":[{"languages":["Q#"],"all":[{"re":"(?m)^((\\/{2,3})?\\s*(namespace|operation)\\b)"}]},{"languages":["Qt Script"],"all":[{"re":"(?m)(\\w+\\.prototype\\.\\w+|===|\\bvar\\b)"}]}]},{"ext":[".r"],"rules":[{"languages":["Rebol"],"all":[{"re":"(?m)(?i:\\bRebol\\b)"}]},{"languages":["Rez"],"all":[{"re":"(?m)(#include\\s+[\"<](Types\\.r|Carbon\\/Carbon\\.r)[\">])|((resource|data|type)\\s+'[A-Za-z0-9]{4}'\\s+((\\(.*\\)\\s+){0,1})\\{)"}]},{"languages":["R"],"all":[{"re":"(?m)<-|^\\s*#"}]}]},{"ext":[".re"],"rules":[{"languages":["Reason"],"all":[{"re":"(?:(?m)^\\s*module\\s+type\\s)|(?:(?m)^\\s*(?:include|open)\\s+\\w+\\s*;\\s*$)|(?:(?m)^\\s*let\\s+(?:module\\s\\w+\\s*=\\s*\\{|\\w+:\\s+.*=.*;\\s*$))"}]},{"languages":["C++"],"all":[{"re":"(?:(?m)^\\s*#(?:(?:if|ifdef|define|pragma)\\s+\\w|\\s*include\\s+<[^>]+>))|(?:(?m)^\\s*template\\s*<)"}]}]},{"ext":[".releaserc"],"rules":[{"languages":["YAML"],"all":[{"re":"(?m)^[ \\t]*(?:#|(?:[^/\"\\s:\\{}\\[\\]]|\\/[^/*:\\n])[^:\\n]*:)"}]},{"languages":["JSON"],"all":[]}]},{"ext":[".res"],"rules":[{"languages":["ReScript"],"all":[{"re":"(?:(?m)^\\s*(let|module|type)\\s+\\w*\\s+=\\s+)|(?:(?m)^\\s*(?:include|open)\\s+\\w+\\s*$)"}]}]},{"ext":[".resource"],"rules":[{"languages":["RobotFramework"],"all":[{"re":"(?m)^\\*{3} (Settings|Variables|Keywords) \\*{3}$"}]}]},{"ext":[".rno"],"rules":[{"languages":["RUNOFF"],"all":[{"re":"(?m)(?i:^\\.!|^\\f|\\f$|^\\.end lit(?:eral)?\\b|^\\.[a-zA-Z].*?;\\.[a-zA-Z](?:[; \\t])|\\^\\*[^\\s*][^*]*\\\\\\*(?:$|\\s)|^\\.c;[ \\t]*\\w+)"}]},{"languages":["Roff"],"all":[{"re":"(?m)^\\.\\\\\" "}]}]},{"ext":[".rpy"],"rules":[{"languages":["Python"],"all":[{"re":"(?m)^(import|from|class|def)\\s"}]},{"languages":["Ren'Py"],"all":[]}]},{"ext":[".rs"],"rules":[{"languages":["Rust"],"all":[{"re":"(?m)^(use |fn |mod |pub |macro_rules|impl|#!?\\[)"}]},{"languages":["RenderScript"],"all":[{"re":"(?m)#include|#pragma\\s+(rs|version)|__attribute__"}]},{"languages":["XML"],"all":[{"re":"(?m)^\\s*<\\?xml"}]}]},{"ext":[".s"],"rules":[{"languages":["Unix Assembly"],"all":[{"re":"(?m)(?i:mov[lq]?)\\s+[%\\$]"}]},{"languages":["Assembly"],"all":[{"re":"(?m)(?i:mov)\\s+[^\\s%]{2,},"}]},{"languages":["Motorola 68K Assembly"],"all":[{"re":"(?:(?m)(?is)\\bmoveq(?:\\.l)?\\s+#(?:\\$-?[0-9a-f]{1,3}|%[0-1]{1,8}|-?[0-9]{1,3}),\\s*d[0-7]\\b)|(?:(?m)(?is)^\\s*move(?:\\.[bwl])?\\s+(?:sr|usp),\\s*[^\\s]+)|(?:(?m)(?is)^\\s*move\\.[bwl]\\s+.*\\b[ad]\\d)|(?:(?m)(?is)^\\s*movem\\.[bwl]\\b)|(?:(?m)(?is)^\\s*move[mp](?:\\.[wl])?\\b)|(?:(?m)(?is)^\\s*btst\\b)|(?:(?m)(?is)^\\s*dbra\\b)"}]}]},{"ext":[".sc"],"rules":[{"languages":["SuperCollider"],"all":[{"re":"(?m)(?i:\\^(this|super)\\.|^\\s*~\\w+\\s*=\\.)"}]},{"languages":["Scala"],"all":[{"re":"(?m)(^\\s*import (scala|java)\\.|^\\s*class\\b)"}]}]},{"ext":[".scd"],"rules":[{"languages":["SuperCollider"],"all":[{"re":"(?m)(?i:\\^(this|super)\\.|^\\s*(~\\w+\\s*=\\.|SynthDef\\b))"}]},{"languages":["Markdown"],"all":[{"re":"(?m)^#+\\s+(NAME|SYNOPSIS|DESCRIPTION)"}]}]},{"ext":[".scm"],"rules":[{"languages":["Tree-sitter Query"],"all":[{"re":"(?:(?m)\\(#[\\w-]+[!\\?])|(?:(?m)(?:[\\)\\]]\\s*[\\*\\+\\?](?:\\s|$)))|(?:(?m)(?:^\\s*\\w+:\\s*[\\(\\[\\\"]))|(?:(?m)\\(#(?:set!|(?:not-)?(?:any-of|match)\\?))|(?:(?m)@[\\w.-]+(?:\\)\\s|$))"}]},{"languages":["Scheme"],"all":[{"re":"(?:(?m)(?:'[\\(\\*#]|\\w->\\w|\\.\\.\\.[\\s\\)]|\\([+\\-:<>\\/=~\\)]|~>|[#`]\\(|#:\\w))|(?:(?m)^\\s*\\((?:define\\*?|import|library|lambda))"}]}]},{"ext":[".sd"],"rules":[{"languages":["Vespa Schema Definition"],"all":[{"re":"(?m)^[ \\t]*(?:schema|search|document)[ \\t]+[\\w.\\/-]+[ \\t]*(?:inherits[ \\t][^\\{\\n]*)?\\{"}]}]},{"ext":[".sip"],"rules":[{"languages":["Quartus Simulation IP"],"all":[{"re":"(?m)^[ \\t]*set_global_assignment\\b"}]},{"languages":["SIP"],"all":[]}]},{"ext":[".sol"],"rules":[{"languages":["Solidity"],"all":[{"re":"(?m)\\bpragma\\s+solidity\\b|\\b(?:abstract\\s+)?contract\\s+[a-zA-Z$_][a-zA-Z0-9$_]*(?:\\s+is\\s+(?:[a-zA-Z0-9$_][^\\{]*?)?)?\\s*\\{"}]},{"languages":["Gerber Image"],"all":[{"re":"(?m)^[DGMT][0-9]{2}\\*(?:\\r?\\n|\\r)"}]}]},{"ext":[".sql"],"rules":[{"languages":["PLpgSQL"],"all":[{"re":"(?m)(?i:^\\\\i\\b|^\\s*DO\\s+\\$|LANGUAGE\\s+'?plpgsql'?|BEGIN(\\s+WORK)?\\s*;)"}]},{"languages":["SQLPL"],"all":[{"re":"(?m)(?i:ALTER\\s+MODULE|MODE\\s+DB2SQL|\\bSYS(CAT|PROC)\\.|ASSOCIATE\\s+RESULT\\s+SET|\\bEND!\\s*$)"}]},{"languages":["PLSQL"],"all":[{"re":"(?m)(?i:\\$\\$PLSQL_|XMLTYPE|systimestamp|\\.nextval|CONNECT\\s+BY|AUTHID\\s+(DEFINER|CURRENT_USER)|constructor\\W+function)"}]},{"languages":["TSQL"],"all":[{"re":"(?m)(?i:^\\s*GO\\b|BEGIN(\\s+TRY|\\s+CATCH)|OUTPUT\\s+INSERTED|DECLARE\\s+@|\\[dbo\\])"}]},{"languages":["SQL"],"all":[]}]},{"ext":[".srt"],"rules":[{"languages":["SubRip Text"],"all":[{"re":"(?m)^(\\d{2}:\\d{2}:\\d{2},\\d{3})\\s*(-->)\\s*(\\d{2}:\\d{2}:\\d{2},\\d{3})$"}]}]},{"ext":[".srv"],"rules":[{"languages":["ROS Interface"],"all":[{"re":"(?m)(?i)^[a-z]\\w*(?:\\/[a-z]\\w*)?(?:<=\\d+)?(?:\\[(?:<=\\d+)?\\])?\\s+\\w+\\b"},{"re":"(?m)^---\\s*$"},{"re":"(?m)^\\s*[^\\s@#\\w-]","neg":true}]}]},{"ext":[".st"],"rules":[{"languages":["StringTemplate"],"all":[{"re":"(?m)\\$\\w+[($]|.!\\s*.+?\\s*!.|<!\\s*.+?\\s*!>|\\[!\\s*.+?\\s*!\\]|\\{!\\s*.+?\\s*!\\}"}]},{"languages":["Smalltalk"],"all":[{"re":"(?m)\\A\\s*[\\[\\{(^\"'\\w#]|[a-zA-Z_]\\w*\\s*:=\\s*[a-zA-Z_]\\w*|class\\s*>>\\s*[a-zA-Z_]\\w*|^[a-zA-Z_]\\w*\\s+[a-zA-Z_]\\w*:|^Class\\s*\\{|if(?:True|False):\\s*\\["}]}]},{"ext":[".star"],"rules":[{"languages":["STAR"],"all":[{"re":"(?m)^loop_\\s*$"}]},{"languages":["Starlark"],"all":[]}]},{"ext":[".stl"],"rules":[{"languages":["STL"],"all":[{"re":"(?m)\\A\\s*solid(?:$|\\s)[\\s\\S]*^endsolid(?:$|\\s)"}]}]},{"ext":[".svx"],"rules":[{"languages":["Survex data"],"all":[{"re":"(?m)\\A(;|\\*[^*]+$)"}]},{"languages":["mdsvex"],"all":[]}]},{"ext":[".sw"],"rules":[{"languages":["Sway"],"all":[{"re":"(?m)^\\s*(?:(?:abi|dep|fn|impl|mod|pub|trait)\\s|#\\[)"}]},{"languages":["XML"],"all":[{"re":"(?m)^\\s*<\\?xml\\s+version"}]}]},{"ext":[".t"],"rules":[{"languages":["Perl"],"all":[{"re":"(?m)^\\s*use\\s+v6\\b","neg":true},{"re":"(?:(?m)\\buse\\s+(?:strict\\b|v?5\\b))|(?:(?m)^\\s*use\\s+(?:constant|overload)\\b)|(?:(?m)^\\s*(?:\\*|(?:our\\s*)?@)EXPORT\\s*=)|(?:(?m)^\\s*package\\s+[^\\W\\d]\\w*(?:::\\w+)*\\s*(?:[;\\{]|\\sv?\\d))|(?:(?m)[\\s$][^\\W\\d]\\w*(?::\\w+)*->[a-zA-Z_\\[(\\{])"}]},{"languages":["Raku"],"all":[{"re":"(?m)^\\s*(?:use\\s+v6\\b|\\bmodule\\b|\\bmy\\s+class\\b)"}]},{"languages":["Turing"],"all":[{"re":"(?m)^\\s*%[ \\t]+|^\\s*var\\s+\\w+(\\s*:\\s*\\w+)?\\s*:=\\s*\\w+"}]}]},{"ext":[".tact"],"rules":[{"languages":["JSON"],"all":[{"re":"(?m)\\A\\s*\\{\\\""}]},{"languages":["Tact"],"all":[]}]},{"ext":[".tag"],"rules":[{"languages":["Java Server Pages"],"all":[{"re":"(?m)<%[@!=\\s]?\\s*(taglib|tag|include|attribute|variable)\\s"}]}]},{"ext":[".tl"],"rules":[{"languages":["Teal"],"all":[{"re":"(?m)--.*"},{"re":"(?m)\\b(local|function|end|record|interface|enum)\\b"}]},{"languages":["Type Language"],"all":[]}]},{"ext":[".tlv"],"rules":[{"languages":["TL-Verilog"],"all":[{"re":"(?m)^\\\\.{0,10}TLV_version"}]}]},{"ext":[".toc"],"rules":[{"languages":["World of Warcraft Addon Data"],"all":[{"re":"(?m)^## |@no-lib-strip@"}]},{"languages":["TeX"],"all":[{"re":"(?m)^\\\\(contentsline|defcounter|beamer|boolfalse)"}]}]},{"ext":[".tpl"],"rules":[{"languages":["Go Template"],"all":[{"re":"(?m)\\{\\{-?\\s*(\\`?\\\"\\\\?\\\"?|\\/\\*\\s|\\w*)\\b"}]},{"languages":["Smarty"],"all":[{"re":"(?m)(?:^|[^{])\\{(\\*\\s|\\$|\\/)?\\w*\\b"}]}]},{"ext":[".ts"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)<TS\\b"}]},{"languages":["TypeScript"],"all":[]}]},{"ext":[".tsp"],"rules":[{"languages":["TypeSpec"],"all":[{"re":"(?m)^(import|using|namespace|interface|op|model|scalar|alias|union|enum)\\s"}]},{"languages":["TSPLIB data"],"all":[{"re":"(?m)^(NAME|TYPE|DIMENSION|EDGE_WEIGHT_TYPE|EDGE_WEIGHT_FORMAT)\\s*:"}]}]},{"ext":[".tst"],"rules":[{"languages":["GAP"],"all":[{"re":"(?m)gap> "}]},{"languages":["Scilab"],"all":[]}]},{"ext":[".tsx"],"rules":[{"languages":["XML"],"all":[{"re":"(?m)(?i:^\\s*<\\?xml\\s+version)"}]},{"languages":["TSX"],"all":[]}]},{"ext":[".txt"],"rules":[{"languages":["Vim Help File"],"all":[{"re":"(?m)(?:(?:^|[ \\t])(?:vi|Vi(?=m))(?:m[<=>]?[0-9]+|m)?|[ \\t]ex)(?=:(?=[ \\t]*set?[ \\t][^\\r\\n:]+:)|:(?![ \\t]*set?[ \\t]))(?:(?:[ \\t]*:[ \\t]*|[ \\t])\\w*(?:[ \\t]*=(?:[^\\\\\\s]|\\\\.)*)?)*[ \\t:](?:filetype|ft|syntax)[ \\t]*=(help)(?=$|\\s|:)"}]},{"languages":["Hosts File"],"all":[{"re":"(?m)(?xi) ^\n\n# IPv4 address\n(?<ipv4>\n (?!\\.)\n (?:\\.?\n (?: 25[0-5] # 250-255\n | 2[0-4]\\d # 200-249\n | 1\\d\\d # 100-199\n | [1-9]?\\d # 0-99\n )\\b\n){4})\n\n# CIDR notation: /[0-32]\n(?<cidr>/(3[0-2]|[12]?\\d)\\b)?\n\n# Domains list\n(?<domains>\n [ \\t]+\n \\w[-\\w]* (?:\\.\\w[-\\w]*)*\n (?<!-)\\b\n)*+\n\n(?:$|\\s)"}]},{"languages":["Adblock Filter List"],"all":[{"re":"(?m)(?x)\\A\n\\[\n(?<version>\n (?:\n [Aa]d[Bb]lock\n (?:[ \\t][Pp]lus)?\n |\n u[Bb]lock\n (?:[ \\t][Oo]rigin)?\n |\n [Aa]d[Gg]uard\n )\n (?:[ \\t] \\d+(?:\\.\\d+)*+)?\n)\n(?:\n [ \\t]?;[ \\t]?\n \\g<version>\n)*+\n\\]"}]},{"languages":["Text"],"all":[]}]},{"ext":[".typ"],"rules":[{"languages":["Typst"],"all":[{"re":"(?m)^#(import|show|let|set)"}]},{"languages":["XML"],"all":[]}]},{"ext":[".uc"],"rules":[{"languages":["ucode"],"all":[{"re":"(?:(?m)^#!.*\\bucode\\b)|(?:(?m)^\\s*['\"]use strict['\"];)|(?:(?m)^[ \\t]*(?:export|import)\\b)|(?:(?m)\\b(?:require|include)\\s*\\(\\s*['\"])|(?:(?m)^\\s*let\\s+\\w+)|(?:(?m)\\b(?:printf|sprintf)\\s*\\()"}]},{"languages":["UnrealScript"],"all":[]}]},{"ext":[".url"],"rules":[{"languages":["INI"],"all":[{"re":"(?m)^\\[InternetShortcut\\](?:\\r?\\n|\\r)([^\\s\\[][^\\r\\n]*(?:\\r?\\n|\\r)){0,20}URL="}]}]},{"ext":[".v"],"rules":[{"languages":["Rocq Prover"],"all":[{"re":"(?m)(?:^|\\s)(?:Proof|Qed)\\.(?:$|\\s)|(?:^|\\s)Require[ \\t]+(Import|Export)\\s"}]},{"languages":["Verilog"],"all":[{"re":"(?m)^[ \\t]*module\\s+[^\\s()]+\\s*\\#?\\(|^[ \\t]*`(?:define|ifdef|ifndef|include|timescale|pragma)|^[ \\t]*always[ \\t]*@|^[ \\t]*initial[ \\t]*(begin|@)"}]},{"languages":["V"],"all":[{"re":"(?m)\\$(?:if|else)[ \\t]|^[ \\t]*fn\\s+[^\\s()]+\\(.*?\\).*?\\{|^[ \\t]*for\\s*\\{"}]}]},{"ext":[".vba"],"rules":[{"languages":["Vim script"],"all":[{"re":"(?m)^UseVimball"}]},{"languages":["VBA"],"all":[]}]},{"ext":[".vcf"],"rules":[{"languages":["TSV"],"all":[{"re":"(?m)\\A##fileformat=VCF"}]},{"languages":["vCard"],"all":[{"re":"(?m)\\ABEGIN:VCARD"}]}]},{"ext":[".w"],"rules":[{"languages":["OpenEdge ABL"],"all":[{"re":"(?m)&ANALYZE-SUSPEND _UIB-CODE-BLOCK _CUSTOM _DEFINITIONS"}]},{"languages":["CWeb"],"all":[{"re":"(?m)^@(<|\\w+\\.)"}]}]},{"ext":[".x"],"rules":[{"languages":["DirectX 3D File"],"all":[{"re":"(?m)^xof 030(2|3)(?:txt|bin|tzip|bzip)\\b"}]},{"languages":["RPC"],"all":[{"re":"(?m)\\b(program|version)\\s+\\w+\\s*\\{|\\bunion\\s+\\w+\\s+switch\\s*\\("}]},{"languages":["Logos"],"all":[{"re":"(?m)^%(end|ctor|hook|group)\\b"}]},{"languages":["Linker Script"],"all":[{"re":"(?m)OUTPUT_ARCH\\(|OUTPUT_FORMAT\\(|SECTIONS"}]}]},{"ext":[".yaml",".yml"],"rules":[{"languages":["MiniYAML"],"all":[{"re":"(?m)^\\t+.*?[^\\s:].*?:"}]},{"languages":["OASv2-yaml"],"all":[{"re":"(?m)swagger:\\s?'?\"?2.[0-9.]+'?\"?"}]},{"languages":["OASv3-yaml"],"all":[{"re":"(?m)openapi:\\s?'?\"?3.[0-9.]+'?\"?"}]},{"languages":["YAML"],"all":[]}]},{"ext":[".yy"],"rules":[{"languages":["JSON"],"all":[{"re":"(?m)\\A\\s*[\\{\\[]"}]},{"languages":["Yacc"],"all":[]}]}]} |
+792-0backend/src/languages/linguist.rs
| @@ -0,0 +1,792 @@ | ||
| 1 | +//! GitHub Linguist's file classification, driven by Linguist's own data | |
| 2 | +//! (`linguist.json`, built by `scripts/gen-linguist.py`): which language a | |
| 3 | +//! file is written in, and whether it is vendored, documentation or | |
| 4 | +//! generated. | |
| 5 | +//! | |
| 6 | +//! Detection runs Linguist's strategies in its order: modeline, filename, | |
| 7 | +//! shebang, extension, XML, heuristics. Each narrows the candidates and the | |
| 8 | +//! first to leave exactly one decides. Where Linguist would finally ask its | |
| 9 | +//! Bayesian classifier, the candidate whose primary extension matches wins, | |
| 10 | +//! then the first by name. | |
| 11 | + | |
| 12 | +use std::{ | |
| 13 | + collections::HashMap, | |
| 14 | + sync::{LazyLock, OnceLock}, | |
| 15 | +}; | |
| 16 | + | |
| 17 | +use regex::bytes::{Regex, RegexSet}; | |
| 18 | +use serde::Deserialize; | |
| 19 | + | |
| 20 | +/// Linguist reads at most this much of a blob (`LazyBlob::MAX_SIZE`); every | |
| 21 | +/// content rule sees only this prefix. | |
| 22 | +pub const MAX_CONTENT: usize = 128 * 1024; | |
| 23 | +/// Heuristics look at the first 50 KiB (`HEURISTICS_CONSIDER_BYTES`). | |
| 24 | +const HEURISTIC_BYTES: usize = 50 * 1024; | |
| 25 | + | |
| 26 | +#[derive(Deserialize)] | |
| 27 | +struct Data { | |
| 28 | + source: String, | |
| 29 | + languages: Vec<RawLanguage>, | |
| 30 | + vendor: Vec<String>, | |
| 31 | + documentation: Vec<String>, | |
| 32 | + generic: Vec<String>, | |
| 33 | + heuristics: Vec<RawHeuristic>, | |
| 34 | +} | |
| 35 | + | |
| 36 | +#[derive(Deserialize)] | |
| 37 | +struct RawLanguage { | |
| 38 | + name: String, | |
| 39 | + #[serde(rename = "type")] | |
| 40 | + kind: Kind, | |
| 41 | + color: Option<String>, | |
| 42 | + group: Option<String>, | |
| 43 | + #[serde(default)] | |
| 44 | + ext: Vec<String>, | |
| 45 | + #[serde(default)] | |
| 46 | + files: Vec<String>, | |
| 47 | + #[serde(default)] | |
| 48 | + interp: Vec<String>, | |
| 49 | + #[serde(default)] | |
| 50 | + aliases: Vec<String>, | |
| 51 | +} | |
| 52 | + | |
| 53 | +#[derive(Deserialize)] | |
| 54 | +struct RawHeuristic { | |
| 55 | + ext: Vec<String>, | |
| 56 | + rules: Vec<RawRule>, | |
| 57 | +} | |
| 58 | + | |
| 59 | +#[derive(Deserialize)] | |
| 60 | +struct RawRule { | |
| 61 | + languages: Vec<String>, | |
| 62 | + all: Vec<RawPattern>, | |
| 63 | +} | |
| 64 | + | |
| 65 | +#[derive(Deserialize)] | |
| 66 | +struct RawPattern { | |
| 67 | + re: String, | |
| 68 | + #[serde(default)] | |
| 69 | + neg: bool, | |
| 70 | +} | |
| 71 | + | |
| 72 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] | |
| 73 | +#[serde(rename_all = "lowercase")] | |
| 74 | +pub enum Kind { | |
| 75 | + Programming, | |
| 76 | + Markup, | |
| 77 | + Data, | |
| 78 | + Prose, | |
| 79 | +} | |
| 80 | + | |
| 81 | +/// A language, as an index into the table. | |
| 82 | +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
| 83 | +pub struct Lang(u16); | |
| 84 | + | |
| 85 | +struct Language { | |
| 86 | + name: String, | |
| 87 | + kind: Kind, | |
| 88 | + color: Option<String>, | |
| 89 | + group: Lang, | |
| 90 | + primary_extension: Option<String>, | |
| 91 | +} | |
| 92 | + | |
| 93 | +struct Heuristic { | |
| 94 | + extensions: Vec<String>, | |
| 95 | + rules: Vec<Rule>, | |
| 96 | +} | |
| 97 | + | |
| 98 | +struct Rule { | |
| 99 | + languages: Vec<Lang>, | |
| 100 | + all: Vec<Pattern>, | |
| 101 | +} | |
| 102 | + | |
| 103 | +struct Pattern { | |
| 104 | + source: String, | |
| 105 | + negated: bool, | |
| 106 | + compiled: OnceLock<Option<Regex>>, | |
| 107 | +} | |
| 108 | + | |
| 109 | +impl Pattern { | |
| 110 | + /// Compiled on first use; a pattern Rust cannot compile never matches | |
| 111 | + /// (a test keeps that list empty). | |
| 112 | + fn regex(&self) -> Option<&Regex> { | |
| 113 | + self.compiled | |
| 114 | + .get_or_init(|| match regex::bytes::RegexBuilder::new(&self.source).size_limit(64 << 20).build() { | |
| 115 | + Ok(regex) => Some(regex), | |
| 116 | + Err(error) => { | |
| 117 | + tracing::warn!(pattern = %self.source, %error, "linguist heuristic does not compile"); | |
| 118 | + None | |
| 119 | + } | |
| 120 | + }) | |
| 121 | + .as_ref() | |
| 122 | + } | |
| 123 | + | |
| 124 | + fn matches(&self, data: &[u8]) -> bool { | |
| 125 | + let found = self.regex().is_some_and(|r| r.is_match(data)); | |
| 126 | + found != self.negated | |
| 127 | + } | |
| 128 | +} | |
| 129 | + | |
| 130 | +pub struct Linguist { | |
| 131 | + pub source: String, | |
| 132 | + languages: Vec<Language>, | |
| 133 | + by_name: HashMap<String, Lang>, | |
| 134 | + by_alias: HashMap<String, Lang>, | |
| 135 | + by_extension: HashMap<String, Vec<Lang>>, | |
| 136 | + by_filename: HashMap<String, Vec<Lang>>, | |
| 137 | + by_interpreter: HashMap<String, Vec<Lang>>, | |
| 138 | + generic: Vec<String>, | |
| 139 | + vendor: RegexSet, | |
| 140 | + documentation: RegexSet, | |
| 141 | + generated_paths: RegexSet, | |
| 142 | + heuristics: Vec<Heuristic>, | |
| 143 | +} | |
| 144 | + | |
| 145 | +pub static LINGUIST: LazyLock<Linguist> = LazyLock::new(|| Linguist::load(include_str!("linguist.json"))); | |
| 146 | + | |
| 147 | +/// Path rules from Linguist's `generated.rb` (lock files, IDE state, vendored | |
| 148 | +/// Go modules, generated-code directories). | |
| 149 | +const GENERATED_PATHS: &[&str] = &[ | |
| 150 | + r"\.(?:nib|xcworkspacedata|xcuserstate)$", | |
| 151 | + r"(?:^|/)\.idea/", | |
| 152 | + r"(?:^Pods|/Pods)/", | |
| 153 | + r"(?:^|/)Carthage/Build/", | |
| 154 | + r"__generated__/", | |
| 155 | + r"(?i)\.designer\.(?:cs|vb)$", | |
| 156 | + r"(?i)\.feature\.cs$", | |
| 157 | + r"composer\.lock", | |
| 158 | + r"Cargo\.lock", | |
| 159 | + r"Cargo\.toml\.orig", | |
| 160 | + r"deno\.lock", | |
| 161 | + r"(?:^|/)flake\.lock$", | |
| 162 | + r"(?:^|/)MODULE\.bazel\.lock$", | |
| 163 | + r"node_modules/", | |
| 164 | + // vendor/<host>.<tld>: Linguist's lookarounds spelled out. | |
| 165 | + r"vendor/(?:(?:[0-9A-Za-z]|[0-9A-Za-z][-0-9A-Za-z]*[0-9A-Za-z])\.)+(?:com|edu|gov|in|me|net|org|fm|io)", | |
| 166 | + r"(?:Gopkg|glide)\.lock", | |
| 167 | + r"Package\.resolved", | |
| 168 | + r"poetry\.lock", | |
| 169 | + r"pdm\.lock", | |
| 170 | + r"uv\.lock", | |
| 171 | + r"pixi\.lock", | |
| 172 | + r"(?:^|/)(?:\w+\.)?esy.lock$", | |
| 173 | + r"npm-shrinkwrap\.json", | |
| 174 | + r"package-lock\.json", | |
| 175 | + r"pnpm-lock\.yaml", | |
| 176 | + r"(?:^|/)bun\.lockb?$", | |
| 177 | + r"(?:^|/)\.terraform\.lock\.hcl$", | |
| 178 | + r"(?:^|/)\.pnp\..*$", | |
| 179 | + r"Godeps/", | |
| 180 | + r".\.zep\.(?:c|h|php)$", | |
| 181 | + r"(?:^|/)htmlcov/", | |
| 182 | + r"(?i)(?:^|/)gradlew(?:\.bat)?$", | |
| 183 | + r"(?i)(?:^|/)mvnw(?:\.cmd)?$", | |
| 184 | + r"(?:^|/)mise(?:\.[^/]+)?\.lock$", | |
| 185 | + r"(?:^|/)\.secrets\.baseline$", | |
| 186 | + r"(?:^|/)(?:Julia)?Manifest(?:-v\d+\.\d+)?\.toml$", | |
| 187 | + r"Pipfile\.lock", | |
| 188 | + r"(?:^|/)\.sqlx/query-[a-f\d]{64}\.json$", | |
| 189 | + r"(?i)_tlb\.pas$", | |
| 190 | +]; | |
| 191 | + | |
| 192 | +fn regex_set(patterns: &[String]) -> RegexSet { | |
| 193 | + regex::bytes::RegexSetBuilder::new(patterns).size_limit(64 << 20).build().expect("linguist path patterns compile") | |
| 194 | +} | |
| 195 | + | |
| 196 | +impl Linguist { | |
| 197 | + fn load(json: &str) -> Self { | |
| 198 | + let data: Data = serde_json::from_str(json).expect("linguist.json is valid"); | |
| 199 | + let mut by_name = HashMap::new(); | |
| 200 | + for (i, raw) in data.languages.iter().enumerate() { | |
| 201 | + by_name.insert(raw.name.to_lowercase(), Lang(i as u16)); | |
| 202 | + } | |
| 203 | + let mut languages = Vec::with_capacity(data.languages.len()); | |
| 204 | + let mut by_alias = HashMap::new(); | |
| 205 | + let mut by_extension: HashMap<String, Vec<Lang>> = HashMap::new(); | |
| 206 | + let mut by_filename: HashMap<String, Vec<Lang>> = HashMap::new(); | |
| 207 | + let mut by_interpreter: HashMap<String, Vec<Lang>> = HashMap::new(); | |
| 208 | + for (i, raw) in data.languages.iter().enumerate() { | |
| 209 | + let lang = Lang(i as u16); | |
| 210 | + let group = raw.group.as_ref().and_then(|g| by_name.get(&g.to_lowercase()).copied()).unwrap_or(lang); | |
| 211 | + // Linguist's default alias is the name, lowercased, spaces as dashes. | |
| 212 | + by_alias.insert(raw.name.to_lowercase().replace(char::is_whitespace, "-"), lang); | |
| 213 | + for alias in &raw.aliases { | |
| 214 | + by_alias.insert(alias.to_lowercase(), lang); | |
| 215 | + } | |
| 216 | + for extension in &raw.ext { | |
| 217 | + by_extension.entry(extension.to_lowercase()).or_default().push(lang); | |
| 218 | + } | |
| 219 | + for filename in &raw.files { | |
| 220 | + by_filename.entry(filename.clone()).or_default().push(lang); | |
| 221 | + } | |
| 222 | + for interpreter in &raw.interp { | |
| 223 | + by_interpreter.entry(interpreter.clone()).or_default().push(lang); | |
| 224 | + } | |
| 225 | + languages.push(Language { | |
| 226 | + name: raw.name.clone(), | |
| 227 | + kind: raw.kind, | |
| 228 | + color: raw.color.clone(), | |
| 229 | + group, | |
| 230 | + primary_extension: raw.ext.first().map(|e| e.to_lowercase()), | |
| 231 | + }); | |
| 232 | + } | |
| 233 | + let lookup = |name: &str| by_name.get(&name.to_lowercase()).copied(); | |
| 234 | + let heuristics = data | |
| 235 | + .heuristics | |
| 236 | + .iter() | |
| 237 | + .map(|h| Heuristic { | |
| 238 | + extensions: h.ext.iter().map(|e| e.to_lowercase()).collect(), | |
| 239 | + rules: h | |
| 240 | + .rules | |
| 241 | + .iter() | |
| 242 | + .map(|r| Rule { | |
| 243 | + languages: r.languages.iter().filter_map(|n| lookup(n)).collect(), | |
| 244 | + all: r | |
| 245 | + .all | |
| 246 | + .iter() | |
| 247 | + .map(|p| Pattern { source: p.re.clone(), negated: p.neg, compiled: OnceLock::new() }) | |
| 248 | + .collect(), | |
| 249 | + }) | |
| 250 | + .collect(), | |
| 251 | + }) | |
| 252 | + .collect(); | |
| 253 | + let generated: Vec<String> = GENERATED_PATHS.iter().map(|p| p.to_string()).collect(); | |
| 254 | + Self { | |
| 255 | + source: data.source, | |
| 256 | + languages, | |
| 257 | + by_name, | |
| 258 | + by_alias, | |
| 259 | + by_extension, | |
| 260 | + by_filename, | |
| 261 | + by_interpreter, | |
| 262 | + generic: data.generic, | |
| 263 | + vendor: regex_set(&data.vendor), | |
| 264 | + documentation: regex_set(&data.documentation), | |
| 265 | + generated_paths: regex_set(&generated), | |
| 266 | + heuristics, | |
| 267 | + } | |
| 268 | + } | |
| 269 | + | |
| 270 | + fn language(&self, lang: Lang) -> &Language { | |
| 271 | + &self.languages[lang.0 as usize] | |
| 272 | + } | |
| 273 | + | |
| 274 | + pub fn name(&self, lang: Lang) -> &str { | |
| 275 | + &self.language(lang).name | |
| 276 | + } | |
| 277 | + | |
| 278 | + pub fn kind(&self, lang: Lang) -> Kind { | |
| 279 | + self.language(lang).kind | |
| 280 | + } | |
| 281 | + | |
| 282 | + /// The language stats are reported under (TSX counts as TypeScript). | |
| 283 | + pub fn group(&self, lang: Lang) -> Lang { | |
| 284 | + self.language(lang).group | |
| 285 | + } | |
| 286 | + | |
| 287 | + pub fn color_of(&self, name: &str) -> Option<&str> { | |
| 288 | + let lang = self.by_name.get(&name.to_lowercase())?; | |
| 289 | + self.language(*lang).color.as_deref() | |
| 290 | + } | |
| 291 | + | |
| 292 | + /// `linguist-language=` values and modelines name a language by alias | |
| 293 | + /// (`js`, `vim`) or by name. | |
| 294 | + pub fn find(&self, name: &str) -> Option<Lang> { | |
| 295 | + let lower = name.trim().to_lowercase(); | |
| 296 | + self.by_alias.get(&lower).or_else(|| self.by_name.get(&lower)).or_else(|| self.by_alias.get(&lower.replace(' ', "-"))).copied() | |
| 297 | + } | |
| 298 | + | |
| 299 | + pub fn vendored(&self, path: &str) -> bool { | |
| 300 | + self.vendor.is_match(path.as_bytes()) | |
| 301 | + } | |
| 302 | + | |
| 303 | + pub fn documentation(&self, path: &str) -> bool { | |
| 304 | + self.documentation.is_match(path.as_bytes()) | |
| 305 | + } | |
| 306 | + | |
| 307 | + pub fn generated_by_path(&self, path: &str) -> bool { | |
| 308 | + self.generated_paths.is_match(path.as_bytes()) | |
| 309 | + } | |
| 310 | + | |
| 311 | + /// What the name alone says: the filename's languages, else the | |
| 312 | + /// extension's (unless the extension is too generic to mean anything). | |
| 313 | + pub fn path_candidates(&self, path: &str) -> Vec<Lang> { | |
| 314 | + let name = basename(path); | |
| 315 | + if let Some(found) = self.by_filename.get(name) { | |
| 316 | + return found.clone(); | |
| 317 | + } | |
| 318 | + self.extension_candidates(path) | |
| 319 | + } | |
| 320 | + | |
| 321 | + fn extension_candidates(&self, path: &str) -> Vec<Lang> { | |
| 322 | + let lower = path.to_lowercase(); | |
| 323 | + if self.generic.iter().any(|g| lower.ends_with(g.as_str())) { | |
| 324 | + return Vec::new(); | |
| 325 | + } | |
| 326 | + extensions(basename(&lower)).into_iter().find_map(|e| self.by_extension.get(&e).cloned()).unwrap_or_default() | |
| 327 | + } | |
| 328 | + | |
| 329 | + /// Whether reading the file could change its outcome: unknown names may | |
| 330 | + /// carry a shebang or modeline, and anything that might count needs the | |
| 331 | + /// binary, generated and heuristic checks. Names that can only be data or | |
| 332 | + /// prose are settled without reading. | |
| 333 | + pub fn wants_content(&self, path: &str) -> bool { | |
| 334 | + let candidates = self.path_candidates(path); | |
| 335 | + if candidates.is_empty() { | |
| 336 | + return !binary_extension(path); | |
| 337 | + } | |
| 338 | + candidates.iter().any(|l| matches!(self.kind(*l), Kind::Programming | Kind::Markup)) | |
| 339 | + } | |
| 340 | + | |
| 341 | + /// The language of a file from its path and, when read, its first | |
| 342 | + /// `MAX_CONTENT` bytes. | |
| 343 | + pub fn detect(&self, path: &str, data: Option<&[u8]>) -> Option<Lang> { | |
| 344 | + let name = basename(path); | |
| 345 | + if let Some(data) = data { | |
| 346 | + if data.is_empty() { | |
| 347 | + return None; | |
| 348 | + } | |
| 349 | + if let Some(lang) = modeline(data).and_then(|m| self.find(&m)) { | |
| 350 | + return Some(lang); | |
| 351 | + } | |
| 352 | + } | |
| 353 | + let mut candidates = Vec::new(); | |
| 354 | + let found = self.by_filename.get(name).cloned().unwrap_or_default(); | |
| 355 | + if let Some(done) = narrow(&mut candidates, found) { | |
| 356 | + return Some(done); | |
| 357 | + } | |
| 358 | + if let Some(interpreter) = data.and_then(shebang_interpreter) { | |
| 359 | + let found = self.by_interpreter.get(&interpreter).cloned().unwrap_or_default(); | |
| 360 | + if let Some(done) = narrow(&mut candidates, found) { | |
| 361 | + return Some(done); | |
| 362 | + } | |
| 363 | + } | |
| 364 | + let found = self.extension_candidates(path); | |
| 365 | + if let Some(done) = narrow(&mut candidates, found) { | |
| 366 | + return Some(done); | |
| 367 | + } | |
| 368 | + if candidates.is_empty() && data.is_some_and(|d| first_lines(d, 2).any(|l| contains(l, b"<?xml version="))) { | |
| 369 | + return self.by_name.get("xml").copied(); | |
| 370 | + } | |
| 371 | + // Unread files get the heuristic's default, as if they were empty. | |
| 372 | + { | |
| 373 | + let lower = path.to_lowercase(); | |
| 374 | + if let Some(heuristic) = self.heuristics.iter().find(|h| h.extensions.iter().any(|e| lower.ends_with(e.as_str()))) { | |
| 375 | + let head = data.map(|d| &d[..d.len().min(HEURISTIC_BYTES)]).unwrap_or(b""); | |
| 376 | + let found = heuristic | |
| 377 | + .rules | |
| 378 | + .iter() | |
| 379 | + .find(|rule| rule.all.iter().all(|p| p.matches(head))) | |
| 380 | + .map(|rule| rule.languages.clone()) | |
| 381 | + .unwrap_or_default(); | |
| 382 | + // Heuristics replace the candidates rather than narrowing them. | |
| 383 | + match found.len() { | |
| 384 | + 1 => return Some(found[0]), | |
| 385 | + 0 => {} | |
| 386 | + _ => candidates = found, | |
| 387 | + } | |
| 388 | + } | |
| 389 | + } | |
| 390 | + // Linguist's classifier would decide here; prefer the language this | |
| 391 | + // extension belongs to first. | |
| 392 | + let extension = extensions(&basename(path).to_lowercase()).into_iter().last(); | |
| 393 | + candidates | |
| 394 | + .iter() | |
| 395 | + .find(|l| extension.is_some() && self.language(**l).primary_extension == extension) | |
| 396 | + .or(candidates.first()) | |
| 397 | + .copied() | |
| 398 | + } | |
| 399 | + | |
| 400 | + /// Linguist's content rules for generated code (`generated.rb`), applied | |
| 401 | + /// to the first `MAX_CONTENT` bytes. | |
| 402 | + pub fn generated_by_content(&self, path: &str, data: &[u8]) -> bool { | |
| 403 | + generated_content(path, data) | |
| 404 | + } | |
| 405 | +} | |
| 406 | + | |
| 407 | +fn narrow(candidates: &mut Vec<Lang>, found: Vec<Lang>) -> Option<Lang> { | |
| 408 | + let found: Vec<Lang> = if candidates.is_empty() { found } else { candidates.iter().filter(|c| found.contains(c)).copied().collect() }; | |
| 409 | + match found.len() { | |
| 410 | + 0 => None, | |
| 411 | + 1 => Some(found[0]), | |
| 412 | + _ => { | |
| 413 | + *candidates = found; | |
| 414 | + None | |
| 415 | + } | |
| 416 | + } | |
| 417 | +} | |
| 418 | + | |
| 419 | +fn basename(path: &str) -> &str { | |
| 420 | + path.rsplit('/').next().unwrap_or(path) | |
| 421 | +} | |
| 422 | + | |
| 423 | +/// Linguist's extensions of a name, longest first: `a.d.ts` is `.d.ts`, `.ts`. | |
| 424 | +fn extensions(name: &str) -> Vec<String> { | |
| 425 | + let segments: Vec<&str> = name.split('.').skip(1).collect(); | |
| 426 | + (0..segments.len()).map(|i| format!(".{}", segments[i..].join("."))).collect() | |
| 427 | +} | |
| 428 | + | |
| 429 | +/// Ruby's `File.extname`: the last extension, none for dotfiles. | |
| 430 | +fn extname(path: &str) -> &str { | |
| 431 | + let name = basename(path); | |
| 432 | + match name.rfind('.') { | |
| 433 | + Some(0) | None => "", | |
| 434 | + Some(i) if i + 1 == name.len() => "", | |
| 435 | + Some(i) => &name[i..], | |
| 436 | + } | |
| 437 | +} | |
| 438 | + | |
| 439 | +fn binary_extension(path: &str) -> bool { | |
| 440 | + const BINARY: &[&str] = &[ | |
| 441 | + ".png", ".jpg", ".jpeg", ".gif", ".ico", ".icns", ".webp", ".bmp", ".tif", ".tiff", ".avif", ".heic", ".psd", ".mp4", ".mov", | |
| 442 | + ".webm", ".mkv", ".avi", ".mp3", ".wav", ".ogg", ".flac", ".m4a", ".zip", ".gz", ".tgz", ".bz2", ".xz", ".zst", ".7z", ".rar", | |
| 443 | + ".jar", ".war", ".pdf", ".woff", ".woff2", ".ttf", ".otf", ".eot", ".so", ".dylib", ".dll", ".exe", ".o", ".a", ".lib", ".class", | |
| 444 | + ".pyc", ".wasm", ".bin", ".db", ".sqlite", ".sqlite3", ".parquet", ".pkl", ".npy", ".npz", ".onnx", ".pt", ".safetensors", | |
| 445 | + ]; | |
| 446 | + let ext = extname(path).to_ascii_lowercase(); | |
| 447 | + BINARY.contains(&ext.as_str()) | |
| 448 | +} | |
| 449 | + | |
| 450 | +/// libgit2's binary test, which GitHub's diff applies before Linguist sees | |
| 451 | +/// a file: a NUL in the first 8000 bytes, or mostly unprintable bytes. | |
| 452 | +pub fn is_binary(data: &[u8]) -> bool { | |
| 453 | + let data = &data[..data.len().min(8000)]; | |
| 454 | + let data = data.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(data); | |
| 455 | + if data.starts_with(b"\xFF\xFE") || data.starts_with(b"\xFE\xFF") { | |
| 456 | + return true; | |
| 457 | + } | |
| 458 | + let (mut printable, mut nonprintable) = (0usize, 0usize); | |
| 459 | + for &c in data { | |
| 460 | + if (c > 0x1f && c != 0x7f) || c == 0x08 || c == 0x1b || c == 0x0c { | |
| 461 | + printable += 1; | |
| 462 | + } else if c == 0 { | |
| 463 | + return true; | |
| 464 | + } else if !matches!(c, b' ' | b'\t' | b'\n' | b'\x0b' | b'\r') { | |
| 465 | + nonprintable += 1; | |
| 466 | + } | |
| 467 | + } | |
| 468 | + (printable >> 7) < nonprintable | |
| 469 | +} | |
| 470 | + | |
| 471 | +fn lines(data: &[u8]) -> Vec<&[u8]> { | |
| 472 | + if data.is_empty() { | |
| 473 | + return Vec::new(); | |
| 474 | + } | |
| 475 | + data.split(|b| *b == b'\n').collect() | |
| 476 | +} | |
| 477 | + | |
| 478 | +fn first_lines(data: &[u8], n: usize) -> impl Iterator<Item = &[u8]> { | |
| 479 | + data.split(|b| *b == b'\n').take(n) | |
| 480 | +} | |
| 481 | + | |
| 482 | +fn contains(haystack: &[u8], needle: &[u8]) -> bool { | |
| 483 | + haystack.windows(needle.len()).any(|w| w == needle) | |
| 484 | +} | |
| 485 | + | |
| 486 | +/// The interpreter a `#!` line names (`/usr/bin/env -S python3.11 -u` is | |
| 487 | +/// `python3`), following Linguist's shebang strategy. | |
| 488 | +fn shebang_interpreter(data: &[u8]) -> Option<String> { | |
| 489 | + let first = data.split(|b| *b == b'\n').next()?; | |
| 490 | + let line = String::from_utf8_lossy(first); | |
| 491 | + let rest = line.strip_prefix("#!")?; | |
| 492 | + let mut words = rest.split_whitespace(); | |
| 493 | + let mut script = words.next()?.rsplit('/').next()?.to_string(); | |
| 494 | + if script == "env" { | |
| 495 | + script = words.find(|w| !w.starts_with('-') && !w.contains('='))?.to_string(); | |
| 496 | + } | |
| 497 | + if let Some((stem, version)) = script.rsplit_once('.') { | |
| 498 | + if !version.is_empty() && version.bytes().all(|b| b.is_ascii_digit()) { | |
| 499 | + script = stem.to_string(); | |
| 500 | + } | |
| 501 | + } | |
| 502 | + if script == "sh" { | |
| 503 | + static EXEC: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"exec (\w+)[\s'"]+\$0[\s'"]+\$@"#).unwrap()); | |
| 504 | + if let Some(capture) = first_lines(data, 5).find_map(|l| EXEC.captures(l)) { | |
| 505 | + script = String::from_utf8_lossy(&capture[1]).into_owned(); | |
| 506 | + } | |
| 507 | + } | |
| 508 | + if script == "osascript" && rest.contains("-l") { | |
| 509 | + return None; | |
| 510 | + } | |
| 511 | + Some(script.rsplit('/').next().unwrap_or(&script).to_string()) | |
| 512 | +} | |
| 513 | + | |
| 514 | +/// An Emacs (`-*- mode: ruby -*-`) or Vim (`vim: set ft=python:`) modeline | |
| 515 | +/// in the first or last five lines. | |
| 516 | +fn modeline(data: &[u8]) -> Option<String> { | |
| 517 | + static EMACS_SHORT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"-\*-[ \t]*([^:;\s]+)[ \t]*-\*-").unwrap()); | |
| 518 | + static EMACS_LONG: LazyLock<Regex> = | |
| 519 | + LazyLock::new(|| Regex::new(r"(?i)-\*-(?:.*?[ \t;])?[ \t]*mode[ \t]*:[ \t]*([^:;\s]+?)[ \t]*(?:[;\s]|-\*-)").unwrap()); | |
| 520 | + static VIM: LazyLock<Regex> = LazyLock::new(|| { | |
| 521 | + Regex::new(r"(?m)(?:^|[ \t])(?:vi|[Vv]im(?:[<=>]?[0-9]+)?|[ \t]ex):.*?[ \t:](?:filetype|ft|syntax)[ \t]*=(\w+)(?:$|\s|:)").unwrap() | |
| 522 | + }); | |
| 523 | + let all = lines(data); | |
| 524 | + let head = all.iter().take(5); | |
| 525 | + let tail = all.iter().rev().take(5); | |
| 526 | + for line in head.chain(tail) { | |
| 527 | + if !contains(line, b"-*-") && !contains(line, b"vi") && !contains(line, b"Vim") && !contains(line, b"ex:") { | |
| 528 | + continue; | |
| 529 | + } | |
| 530 | + for regex in [&*EMACS_SHORT, &*EMACS_LONG, &*VIM] { | |
| 531 | + if let Some(capture) = regex.captures(line) { | |
| 532 | + return Some(String::from_utf8_lossy(&capture[1]).into_owned()); | |
| 533 | + } | |
| 534 | + } | |
| 535 | + } | |
| 536 | + None | |
| 537 | +} | |
| 538 | + | |
| 539 | +fn generated_content(path: &str, data: &[u8]) -> bool { | |
| 540 | + let ext = extname(path); | |
| 541 | + let lower_ext = ext.to_ascii_lowercase(); | |
| 542 | + let lines = lines(data); | |
| 543 | + let line = |i: usize| lines.get(i).copied().unwrap_or(b""); | |
| 544 | + let first = |n: usize| lines.iter().take(n).copied(); | |
| 545 | + let starts = |i: usize, prefix: &str| line(i).starts_with(prefix.as_bytes()); | |
| 546 | + let has = |i: usize, needle: &str| contains(line(i), needle.as_bytes()); | |
| 547 | + let count = lines.len(); | |
| 548 | + | |
| 549 | + // Minified JS and CSS: lines averaging over 110 characters. | |
| 550 | + if (lower_ext == ".js" || lower_ext == ".css") && count > 0 { | |
| 551 | + let total: usize = lines.iter().map(|l| l.len()).sum(); | |
| 552 | + if total / count > 110 { | |
| 553 | + return true; | |
| 554 | + } | |
| 555 | + // A source-map reference in the last two lines. | |
| 556 | + static SOURCE_MAP: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^/[*/][#@] source(?:Mapping)?URL|sourceURL=").unwrap()); | |
| 557 | + if lines.iter().rev().take(2).any(|l| SOURCE_MAP.is_match(l)) { | |
| 558 | + return true; | |
| 559 | + } | |
| 560 | + } | |
| 561 | + match ext { | |
| 562 | + ".js" => { | |
| 563 | + // CoffeeScript output, a PEG.js parser, Jison, or protoc's JS. | |
| 564 | + if starts(0, "// Generated by ") { | |
| 565 | + return true; | |
| 566 | + } | |
| 567 | + if line(0) == b"(function() {" && count >= 2 && lines[count - 2] == b"}).call(this);" && lines[count - 1].is_empty() { | |
| 568 | + let mut score = 0; | |
| 569 | + for l in &lines { | |
| 570 | + if contains(l, b"var ") { | |
| 571 | + let text = String::from_utf8_lossy(l); | |
| 572 | + score += ["_fn", "_i", "_len", "_ref", "_results"].iter().map(|t| text.matches(t).count()).sum::<usize>(); | |
| 573 | + score += 3 * ["__bind", "__extends", "__hasProp", "__indexOf", "__slice"].iter().map(|t| text.matches(t).count()).sum::<usize>(); | |
| 574 | + } | |
| 575 | + } | |
| 576 | + if score >= 3 { | |
| 577 | + return true; | |
| 578 | + } | |
| 579 | + } | |
| 580 | + static PEG: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^(?:[^/]|/[^*])*/\*(?:[^*]|\*[^/])*Generated by PEG.js").unwrap()); | |
| 581 | + if PEG.is_match(&first(5).collect::<Vec<_>>().concat()) { | |
| 582 | + return true; | |
| 583 | + } | |
| 584 | + if count > 1 && (starts(0, "/* parser generated by jison ") || starts(0, "/* generated by jison-lex ")) { | |
| 585 | + return true; | |
| 586 | + } | |
| 587 | + if count > 6 && has(5, "GENERATED CODE -- DO NOT EDIT!") { | |
| 588 | + return true; | |
| 589 | + } | |
| 590 | + } | |
| 591 | + ".go" if count > 1 && first(40).any(|l| l.starts_with(b"// Code generated ")) => return true, | |
| 592 | + ".ts" if count > 4 && has(0, "Code generated by protoc-gen-ts_proto. DO NOT EDIT.") => return true, | |
| 593 | + ".rb" if count > 1 | |
| 594 | + && first(3).any(|l| contains(l, b"Code generated by protoc-gen-twirp_ruby") && contains(l, b"DO NOT EDIT.")) => | |
| 595 | + { | |
| 596 | + return true; | |
| 597 | + } | |
| 598 | + ".rb" if count > 2 && starts(2, "# This file is automatically generated by Racc") => return true, | |
| 599 | + ".h" if count > 2 && has(0, "/* DO NOT EDIT THIS FILE - it is machine generated */") && has(1, "#include <jni.h>") => return true, | |
| 600 | + ".g" if count > 2 && has(1, "generated by Xtest") => return true, | |
| 601 | + ".mod" if count > 1 && (has(0, "PCBNEW-LibModule-V") || has(0, "GFORTRAN module version '")) => return true, | |
| 602 | + ".meta" if count > 1 && has(0, "fileFormatVersion: ") => return true, | |
| 603 | + ".java" if count > 1 | |
| 604 | + && (starts(0, "/* The following code was generated by JFlex ") | |
| 605 | + || starts(0, "// This is a generated file. Not intended for manual editing.")) => | |
| 606 | + { | |
| 607 | + return true; | |
| 608 | + } | |
| 609 | + ".Rd" if count > 1 && has(0, "% Generated by roxygen2: do not edit by hand") => return true, | |
| 610 | + ".dart" if count > 1 => { | |
| 611 | + static DART: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)generated code\W{2,3}do not modify").unwrap()); | |
| 612 | + if first(3).any(|l| DART.is_match(l)) { | |
| 613 | + return true; | |
| 614 | + } | |
| 615 | + } | |
| 616 | + _ => {} | |
| 617 | + } | |
| 618 | + if [".py", ".java", ".h", ".cc", ".cpp", ".m", ".rb", ".php"].contains(&ext) | |
| 619 | + && count > 1 | |
| 620 | + && first(3).any(|l| contains(l, b"Generated by the protocol buffer compiler. DO NOT EDIT!")) | |
| 621 | + { | |
| 622 | + return true; | |
| 623 | + } | |
| 624 | + if [".rb", ".py", ".go", ".js", ".m", ".java", ".h", ".cc", ".cpp", ".php"].contains(&ext) | |
| 625 | + && first(6).any(|l| contains(l, b"Autogenerated by Thrift Compiler")) | |
| 626 | + { | |
| 627 | + return true; | |
| 628 | + } | |
| 629 | + if [".c", ".cpp"].contains(&ext) && count > 1 && has(0, "Generated by Cython") { | |
| 630 | + return true; | |
| 631 | + } | |
| 632 | + if [".cpp", ".hpp", ".h", ".cc"].contains(&ext) && count > 1 && starts(0, "// Generated by the gRPC") { | |
| 633 | + return true; | |
| 634 | + } | |
| 635 | + if path.ends_with("ppport.h") && count > 10 && has(8, "Automatically created by Devel::PPPort") { | |
| 636 | + return true; | |
| 637 | + } | |
| 638 | + if [".c", ".h"].contains(&ext) && count > 0 { | |
| 639 | + static GIMP: LazyLock<Regex> = LazyLock::new(|| { | |
| 640 | + Regex::new(r"^/\* GIMP [a-zA-Z0-9\- ]+ C\-Source image dump \(.+?\.c\) \*/|^/\* GIMP header image file format \([a-zA-Z0-9\- ]+\): .+?\.h \*/").unwrap() | |
| 641 | + }); | |
| 642 | + if GIMP.is_match(line(0)) { | |
| 643 | + return true; | |
| 644 | + } | |
| 645 | + } | |
| 646 | + if [".js", ".py", ".lua", ".cpp", ".h", ".java", ".cs", ".php"].contains(&ext) && first(3).any(|l| contains(l, b"Generated by Haxe")) { | |
| 647 | + return true; | |
| 648 | + } | |
| 649 | + match lower_ext.as_str() { | |
| 650 | + ".java" if first(2).any(|l| contains(l, b"This file is generated by jOOQ.")) => return true, | |
| 651 | + ".dsp" if first(3).any(|l| contains(l, b"# Microsoft Developer Studio Generated Build File")) => return true, | |
| 652 | + ".frm" if has(0, "TYPE=VIEW") => return true, | |
| 653 | + ".rbi" if count >= 5 => { | |
| 654 | + static TAPIOCA: LazyLock<Regex> = | |
| 655 | + LazyLock::new(|| Regex::new(r"^# Please (run|instead update this file by running) `bin/tapioca").unwrap()); | |
| 656 | + if starts(0, "# typed:") && has(2, "DO NOT EDIT MANUALLY") && TAPIOCA.is_match(line(4)) { | |
| 657 | + return true; | |
| 658 | + } | |
| 659 | + } | |
| 660 | + ".html" | ".htm" | ".xhtml" if count > 1 => return generated_html(&lines), | |
| 661 | + _ => {} | |
| 662 | + } | |
| 663 | + if (ext == ".yy" || ext == ".yyp") && count > 3 { | |
| 664 | + static GMS_JSON: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\s*[\{\[]").unwrap()); | |
| 665 | + static GMS_VERSION: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\d\.\d\.\d.+\|\{").unwrap()); | |
| 666 | + if GMS_JSON.is_match(&first(3).collect::<Vec<_>>().concat()) || GMS_VERSION.is_match(line(0)) { | |
| 667 | + return true; | |
| 668 | + } | |
| 669 | + } | |
| 670 | + false | |
| 671 | +} | |
| 672 | + | |
| 673 | +/// HTML written by documentation tools: pkgdown, mandoc, Doxygen, or a | |
| 674 | +/// `<meta name="generator">` naming org-mode, latex2html, groff and friends. | |
| 675 | +fn generated_html(lines: &[&[u8]]) -> bool { | |
| 676 | + if lines.iter().take(2).any(|l| contains(l, b"<!-- Generated by pkgdown: do not edit by hand -->")) { | |
| 677 | + return true; | |
| 678 | + } | |
| 679 | + if lines.len() > 2 && lines[2].starts_with(b"<!-- This is an automatically generated file.") { | |
| 680 | + return true; | |
| 681 | + } | |
| 682 | + static DOXYGEN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)<!--\s+Generated by Doxygen\s+[.0-9]+\s*-->").unwrap()); | |
| 683 | + if lines.iter().take(31).any(|l| DOXYGEN.is_match(l)) { | |
| 684 | + return true; | |
| 685 | + } | |
| 686 | + static META: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?i)<meta(\s+[^>]+)>").unwrap()); | |
| 687 | + static ATTRIBUTE: LazyLock<Regex> = | |
| 688 | + LazyLock::new(|| Regex::new(r#"(?i)(?:^|\s)(name|content|value)\s*=\s*("[^"]+"|'[^']+'|[^\s"']+)"#).unwrap()); | |
| 689 | + static TOOLS: LazyLock<Regex> = | |
| 690 | + LazyLock::new(|| Regex::new(r"(?i)^(?:org\s+mode|j?latex2html|groff|makeinfo|texi2html|ronn)\b").unwrap()); | |
| 691 | + let head = lines.iter().take(31).copied().collect::<Vec<_>>().join(&b' '); | |
| 692 | + META.captures_iter(&head).any(|meta| { | |
| 693 | + let mut attributes: HashMap<String, Vec<u8>> = HashMap::new(); | |
| 694 | + for attribute in ATTRIBUTE.captures_iter(&meta[1]) { | |
| 695 | + let value = attribute[2].strip_prefix(b"\"").or_else(|| attribute[2].strip_prefix(b"'")).unwrap_or(&attribute[2]); | |
| 696 | + let value = value.strip_suffix(b"\"").or_else(|| value.strip_suffix(b"'")).unwrap_or(value); | |
| 697 | + attributes.insert(String::from_utf8_lossy(&attribute[1]).to_lowercase(), value.to_vec()); | |
| 698 | + } | |
| 699 | + attributes.get("name").is_some_and(|n| n.eq_ignore_ascii_case(b"generator")) | |
| 700 | + && ["content", "value"].iter().any(|k| attributes.get(*k).is_some_and(|v| TOOLS.is_match(v))) | |
| 701 | + }) | |
| 702 | +} | |
| 703 | + | |
| 704 | +/// Heuristic patterns Rust cannot compile, in heuristics where the outcome | |
| 705 | +/// matters: ones that can pick a language that counts. | |
| 706 | +#[cfg(test)] | |
| 707 | +fn broken_heuristics() -> Vec<String> { | |
| 708 | + let l = &*LINGUIST; | |
| 709 | + l.heuristics | |
| 710 | + .iter() | |
| 711 | + .filter(|h| h.rules.iter().flat_map(|r| r.languages.iter()).any(|lang| matches!(l.kind(*lang), Kind::Programming | Kind::Markup))) | |
| 712 | + .flat_map(|h| h.rules.iter()) | |
| 713 | + .flat_map(|r| r.all.iter()) | |
| 714 | + .filter(|p| p.regex().is_none()) | |
| 715 | + .map(|p| p.source.clone()) | |
| 716 | + .collect() | |
| 717 | +} | |
| 718 | + | |
| 719 | +#[cfg(test)] | |
| 720 | +mod tests { | |
| 721 | + use super::*; | |
| 722 | + | |
| 723 | + fn detect(path: &str, data: Option<&str>) -> Option<String> { | |
| 724 | + let l = &*LINGUIST; | |
| 725 | + l.detect(path, data.map(str::as_bytes)).map(|lang| l.name(lang).to_string()) | |
| 726 | + } | |
| 727 | + | |
| 728 | + #[test] | |
| 729 | + fn all_heuristics_compile() { | |
| 730 | + assert_eq!(broken_heuristics(), Vec::<String>::new()); | |
| 731 | + } | |
| 732 | + | |
| 733 | + #[test] | |
| 734 | + fn strategies() { | |
| 735 | + assert_eq!(detect("src/main.rs", Some("fn main() {}\n")).as_deref(), Some("Rust")); | |
| 736 | + assert_eq!(detect("web/App.tsx", None).as_deref(), Some("TSX")); | |
| 737 | + assert_eq!(detect("Dockerfile", None).as_deref(), Some("Dockerfile")); | |
| 738 | + assert_eq!(detect("tmux.conf", None).as_deref(), Some("Shell")); | |
| 739 | + assert_eq!(detect("bin/deploy", Some("#!/usr/bin/env bash\necho hi\n")).as_deref(), Some("Shell")); | |
| 740 | + assert_eq!(detect("bin/tool", Some("#!/usr/bin/env -S python3.11 -u\nprint(1)\n")).as_deref(), Some("Python")); | |
| 741 | + assert_eq!(detect("notes", Some("plain words\n")), None); | |
| 742 | + assert_eq!(detect("lib/x.h", Some("#include <vector>\nstd::vector<int> v;\n")).as_deref(), Some("C++")); | |
| 743 | + assert_eq!(detect("lib/x.h", Some("int main(void);\n")).as_deref(), Some("C")); | |
| 744 | + assert_eq!(detect("db/0001.sql", Some("create function f() returns void as $$ begin end; $$ language plpgsql;\n")).as_deref(), Some("PLpgSQL")); | |
| 745 | + assert_eq!(detect("db/0002.sql", Some("create table t (id int);\n")).as_deref(), Some("SQL")); | |
| 746 | + assert_eq!(detect("script.txt", Some("# vim: set ft=python:\nprint(1)\n")).as_deref(), Some("Python")); | |
| 747 | + assert_eq!(detect("README.md", None).as_deref(), Some("Markdown")); | |
| 748 | + } | |
| 749 | + | |
| 750 | + #[test] | |
| 751 | + fn groups() { | |
| 752 | + let l = &*LINGUIST; | |
| 753 | + let tsx = l.detect("a.tsx", None).unwrap(); | |
| 754 | + assert_eq!(l.name(l.group(tsx)), "TypeScript"); | |
| 755 | + assert_eq!(l.find("vim").map(|v| l.name(v).to_string()).as_deref(), Some("Vim script")); | |
| 756 | + assert_eq!(l.find("JavaScript").map(|v| l.name(v).to_string()).as_deref(), Some("JavaScript")); | |
| 757 | + } | |
| 758 | + | |
| 759 | + #[test] | |
| 760 | + fn excluded_paths() { | |
| 761 | + let l = &*LINGUIST; | |
| 762 | + assert!(l.vendored("node_modules/react/index.js")); | |
| 763 | + assert!(l.vendored("public/css/normalize.css")); | |
| 764 | + assert!(l.vendored("tests/fixtures/page.html")); | |
| 765 | + assert!(l.vendored("src/server/worker-configuration.d.ts")); | |
| 766 | + assert!(!l.vendored("src/lib.rs")); | |
| 767 | + assert!(l.documentation("docs/conf.py")); | |
| 768 | + assert!(l.documentation("samples/main.rs")); | |
| 769 | + assert!(!l.documentation("src/docs/page.rs")); | |
| 770 | + assert!(l.generated_by_path("Cargo.lock")); | |
| 771 | + assert!(l.generated_by_path("vendor/github.com/x/y.go")); | |
| 772 | + } | |
| 773 | + | |
| 774 | + #[test] | |
| 775 | + fn generated_content_rules() { | |
| 776 | + let l = &*LINGUIST; | |
| 777 | + assert!(l.generated_by_content("x/pdqsort.go", b"// Code generated by gen_sort_variants.go; DO NOT EDIT.\n\npackage x\n")); | |
| 778 | + assert!(!l.generated_by_content("x/sort.go", b"// Package sort sorts.\npackage sort\n")); | |
| 779 | + let minified = format!("{}\n", "a".repeat(500)); | |
| 780 | + assert!(l.generated_by_content("static/app.js", minified.as_bytes())); | |
| 781 | + assert!(l.generated_by_content("out/index.js", b"\"use strict\";\nexports.x = 1;\n//# sourceMappingURL=index.js.map")); | |
| 782 | + assert!(!l.generated_by_content("src/index.js", b"export const x = 1;\n")); | |
| 783 | + assert!(l.generated_by_content("pb/a_pb2.py", b"# -*- coding: utf-8 -*-\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n")); | |
| 784 | + assert!(l.generated_by_content("doc/x.html", b"<html>\n<head><meta name=\"generator\" content=\"groff 1.22\"></head>\n")); | |
| 785 | + } | |
| 786 | + | |
| 787 | + #[test] | |
| 788 | + fn binary() { | |
| 789 | + assert!(is_binary(b"\x7fELF\x02\x01\x01\0\0\0")); | |
| 790 | + assert!(!is_binary(b"#!/bin/sh\necho hi\n")); | |
| 791 | + } | |
| 792 | +} |
+419-0backend/src/languages/mod.rs
| @@ -0,0 +1,419 @@ | ||
| 1 | +//! Language breakdown of a repository, the way GitHub computes it: bytes per | |
| 2 | +//! language across the default branch, classified by Linguist's own rules | |
| 3 | +//! (see `linguist`). Vendored, generated and documentation files do not | |
| 4 | +//! count, nor do binaries or data and prose formats (JSON, YAML, Markdown). | |
| 5 | +//! A repo's `.gitattributes` can override any of it with `linguist-vendored`, | |
| 6 | +//! `linguist-generated`, `linguist-documentation`, `linguist-detectable` and | |
| 7 | +//! `linguist-language=<Name>`, resolved by `git check-attr` exactly as git | |
| 8 | +//! would. | |
| 9 | +//! | |
| 10 | +//! Results are cached per commit in `repo_languages`, so a page view costs a | |
| 11 | +//! lookup. Recomputing lists the tree with `git ls-tree -r -l` and reads the | |
| 12 | +//! first 128 KiB of each candidate file in one `git cat-file --batch`. | |
| 13 | + | |
| 14 | +mod linguist; | |
| 15 | + | |
| 16 | +use std::{collections::HashMap, process::Stdio}; | |
| 17 | + | |
| 18 | +use serde::{Deserialize, Serialize}; | |
| 19 | + | |
| 20 | +use crate::{git::Git, models::Repo, state::AppState}; | |
| 21 | +pub use linguist::{Kind, LINGUIST}; | |
| 22 | + | |
| 23 | +/// Total bytes streamed out of git per computation. Past it, the remaining | |
| 24 | +/// files are classified by name alone (and logged). | |
| 25 | +const CONTENT_BUDGET: u64 = 512 * 1024 * 1024; | |
| 26 | + | |
| 27 | +/// GitHub's color for a language, or a neutral grey for unknown names. | |
| 28 | +pub fn color(name: &str) -> &'static str { | |
| 29 | + LINGUIST.color_of(name).unwrap_or("#8b949e") | |
| 30 | +} | |
| 31 | + | |
| 32 | +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | |
| 33 | +pub struct LanguageBytes { | |
| 34 | + pub name: String, | |
| 35 | + pub bytes: u64, | |
| 36 | +} | |
| 37 | + | |
| 38 | +/// Why a file does or does not count. | |
| 39 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| 40 | +pub enum Verdict { | |
| 41 | + Counted, | |
| 42 | + Vendored, | |
| 43 | + Documentation, | |
| 44 | + Generated, | |
| 45 | + Binary, | |
| 46 | + /// Detected, but data or prose (JSON, Markdown) and not marked detectable. | |
| 47 | + NotDetectable, | |
| 48 | + /// No language at all. | |
| 49 | + Unknown, | |
| 50 | +} | |
| 51 | + | |
| 52 | +impl Verdict { | |
| 53 | + pub fn label(self) -> &'static str { | |
| 54 | + match self { | |
| 55 | + Verdict::Counted => "counted", | |
| 56 | + Verdict::Vendored => "vendored", | |
| 57 | + Verdict::Documentation => "documentation", | |
| 58 | + Verdict::Generated => "generated", | |
| 59 | + Verdict::Binary => "binary", | |
| 60 | + Verdict::NotDetectable => "not detectable", | |
| 61 | + Verdict::Unknown => "unknown", | |
| 62 | + } | |
| 63 | + } | |
| 64 | +} | |
| 65 | + | |
| 66 | +#[derive(Debug, Clone)] | |
| 67 | +pub struct FileReport { | |
| 68 | + pub path: String, | |
| 69 | + pub bytes: u64, | |
| 70 | + /// The language stats are reported under (its group), when detected. | |
| 71 | + pub language: Option<String>, | |
| 72 | + pub verdict: Verdict, | |
| 73 | + /// Whether the contents were read, or the name alone decided. | |
| 74 | + pub read: bool, | |
| 75 | +} | |
| 76 | + | |
| 77 | +/// `.gitattributes` answers for one path; `None` when unspecified. | |
| 78 | +#[derive(Debug, Default, Clone)] | |
| 79 | +struct Attributes { | |
| 80 | + vendored: Option<bool>, | |
| 81 | + generated: Option<bool>, | |
| 82 | + documentation: Option<bool>, | |
| 83 | + detectable: Option<bool>, | |
| 84 | + language: Option<String>, | |
| 85 | +} | |
| 86 | + | |
| 87 | +struct Entry { | |
| 88 | + path: String, | |
| 89 | + sha: String, | |
| 90 | + size: u64, | |
| 91 | +} | |
| 92 | + | |
| 93 | +/// Bytes per language in `commit`, largest first. | |
| 94 | +pub async fn compute(git: &Git, commit: &str) -> anyhow::Result<Vec<LanguageBytes>> { | |
| 95 | + Ok(totals(&classify(git, commit).await?)) | |
| 96 | +} | |
| 97 | + | |
| 98 | +pub fn totals(reports: &[FileReport]) -> Vec<LanguageBytes> { | |
| 99 | + let mut totals: HashMap<&str, u64> = HashMap::new(); | |
| 100 | + for report in reports.iter().filter(|r| r.verdict == Verdict::Counted) { | |
| 101 | + if let Some(language) = &report.language { | |
| 102 | + *totals.entry(language).or_default() += report.bytes; | |
| 103 | + } | |
| 104 | + } | |
| 105 | + let mut out: Vec<LanguageBytes> = | |
| 106 | + totals.into_iter().filter(|(_, b)| *b > 0).map(|(name, bytes)| LanguageBytes { name: name.to_string(), bytes }).collect(); | |
| 107 | + out.sort_by(|a, b| b.bytes.cmp(&a.bytes).then_with(|| a.name.cmp(&b.name))); | |
| 108 | + out | |
| 109 | +} | |
| 110 | + | |
| 111 | +/// Every blob in `commit` with the reason it does or does not count. | |
| 112 | +pub async fn classify(git: &Git, commit: &str) -> anyhow::Result<Vec<FileReport>> { | |
| 113 | + let started = std::time::Instant::now(); | |
| 114 | + let entries = list_blobs(git, commit).await?; | |
| 115 | + let has_attributes = entries.iter().any(|e| e.path == ".gitattributes" || e.path.ends_with("/.gitattributes")); | |
| 116 | + let attributes = if has_attributes { | |
| 117 | + match check_attr(git, commit, &entries).await { | |
| 118 | + Ok(attributes) => attributes, | |
| 119 | + Err(error) => { | |
| 120 | + tracing::warn!(?error, "git check-attr failed; ignoring .gitattributes"); | |
| 121 | + HashMap::new() | |
| 122 | + } | |
| 123 | + } | |
| 124 | + } else { | |
| 125 | + HashMap::new() | |
| 126 | + }; | |
| 127 | + let linguist = &*LINGUIST; | |
| 128 | + let none = Attributes::default(); | |
| 129 | + let mut reports: Vec<Option<FileReport>> = Vec::with_capacity(entries.len()); | |
| 130 | + let mut wanted: Vec<usize> = Vec::new(); | |
| 131 | + for (i, entry) in entries.iter().enumerate() { | |
| 132 | + let attrs = attributes.get(&entry.path).unwrap_or(&none); | |
| 133 | + let excluded = if attrs.vendored.unwrap_or_else(|| linguist.vendored(&entry.path)) { | |
| 134 | + Some(Verdict::Vendored) | |
| 135 | + } else if attrs.documentation.unwrap_or_else(|| linguist.documentation(&entry.path)) { | |
| 136 | + Some(Verdict::Documentation) | |
| 137 | + } else if attrs.generated.unwrap_or_else(|| linguist.generated_by_path(&entry.path)) { | |
| 138 | + Some(Verdict::Generated) | |
| 139 | + } else { | |
| 140 | + None | |
| 141 | + }; | |
| 142 | + if let Some(verdict) = excluded { | |
| 143 | + reports.push(Some(FileReport { path: entry.path.clone(), bytes: entry.size, language: None, verdict, read: false })); | |
| 144 | + } else if entry.size == 0 { | |
| 145 | + reports.push(Some(FileReport { path: entry.path.clone(), bytes: 0, language: None, verdict: Verdict::Unknown, read: false })); | |
| 146 | + } else if attrs.language.is_some() || linguist.wants_content(&entry.path) { | |
| 147 | + reports.push(None); | |
| 148 | + wanted.push(i); | |
| 149 | + } else { | |
| 150 | + reports.push(Some(decide(&entry.path, entry.size, attrs, None))); | |
| 151 | + } | |
| 152 | + } | |
| 153 | + | |
| 154 | + let mut streamed = 0u64; | |
| 155 | + let mut budget_hit = false; | |
| 156 | + let shas: Vec<&str> = wanted | |
| 157 | + .iter() | |
| 158 | + .map(|i| &entries[*i]) | |
| 159 | + .take_while(|entry| { | |
| 160 | + streamed += entry.size; | |
| 161 | + budget_hit = streamed > CONTENT_BUDGET; | |
| 162 | + !budget_hit | |
| 163 | + }) | |
| 164 | + .map(|entry| entry.sha.as_str()) | |
| 165 | + .collect(); | |
| 166 | + let mut heads = read_heads(git, &shas).await.unwrap_or_else(|error| { | |
| 167 | + tracing::warn!(?error, "reading file contents for language stats failed; using names only"); | |
| 168 | + Vec::new() | |
| 169 | + }); | |
| 170 | + heads.resize(wanted.len(), None); | |
| 171 | + for (slot, head) in wanted.iter().zip(heads) { | |
| 172 | + let entry = &entries[*slot]; | |
| 173 | + let attrs = attributes.get(&entry.path).unwrap_or(&none); | |
| 174 | + reports[*slot] = Some(decide(&entry.path, entry.size, attrs, head.as_deref())); | |
| 175 | + } | |
| 176 | + if budget_hit { | |
| 177 | + tracing::warn!(files = entries.len(), read = shas.len(), budget_mb = CONTENT_BUDGET >> 20, "language stats read budget reached"); | |
| 178 | + } | |
| 179 | + tracing::debug!(files = entries.len(), read = shas.len(), ms = started.elapsed().as_millis() as u64, "classified files"); | |
| 180 | + Ok(reports.into_iter().flatten().collect()) | |
| 181 | +} | |
| 182 | + | |
| 183 | +/// The outcome for a file that no path rule excluded, from its name and, if | |
| 184 | +/// it was read, its first 128 KiB. | |
| 185 | +fn decide(path: &str, size: u64, attrs: &Attributes, data: Option<&[u8]>) -> FileReport { | |
| 186 | + let linguist = &*LINGUIST; | |
| 187 | + let report = |language: Option<String>, verdict| FileReport { path: path.to_string(), bytes: size, language, verdict, read: data.is_some() }; | |
| 188 | + if data.is_some_and(linguist::is_binary) { | |
| 189 | + return report(None, Verdict::Binary); | |
| 190 | + } | |
| 191 | + let detected = match &attrs.language { | |
| 192 | + Some(name) => linguist.find(name), | |
| 193 | + None => linguist.detect(path, data), | |
| 194 | + }; | |
| 195 | + let Some(lang) = detected else { return report(None, Verdict::Unknown) }; | |
| 196 | + let group = linguist.group(lang); | |
| 197 | + let name = Some(linguist.name(group).to_string()); | |
| 198 | + if attrs.generated.is_none() && data.is_some_and(|d| linguist.generated_by_content(path, d)) { | |
| 199 | + return report(name, Verdict::Generated); | |
| 200 | + } | |
| 201 | + let detectable = attrs.detectable.unwrap_or(matches!(linguist.kind(lang), Kind::Programming | Kind::Markup)); | |
| 202 | + report(name, if detectable { Verdict::Counted } else { Verdict::NotDetectable }) | |
| 203 | +} | |
| 204 | + | |
| 205 | +/// Regular files in the tree: no symlinks, no submodules. | |
| 206 | +async fn list_blobs(git: &Git, commit: &str) -> anyhow::Result<Vec<Entry>> { | |
| 207 | + let output = git | |
| 208 | + .command() | |
| 209 | + .args(["ls-tree", "-r", "-l", "-z", "--end-of-options", commit]) | |
| 210 | + .stdout(Stdio::piped()) | |
| 211 | + .stderr(Stdio::null()) | |
| 212 | + .output() | |
| 213 | + .await?; | |
| 214 | + anyhow::ensure!(output.status.success(), "git ls-tree failed"); | |
| 215 | + let mut entries = Vec::new(); | |
| 216 | + for record in output.stdout.split(|b| *b == 0).filter(|r| !r.is_empty()) { | |
| 217 | + let record = String::from_utf8_lossy(record); | |
| 218 | + let Some((meta, path)) = record.split_once('\t') else { continue }; | |
| 219 | + let mut fields = meta.split_whitespace(); | |
| 220 | + let (Some(mode), Some(kind), Some(sha), Some(size)) = (fields.next(), fields.next(), fields.next(), fields.next()) else { continue }; | |
| 221 | + if kind != "blob" || mode == "120000" { | |
| 222 | + continue; | |
| 223 | + } | |
| 224 | + let Ok(size) = size.parse::<u64>() else { continue }; | |
| 225 | + entries.push(Entry { path: path.to_string(), sha: sha.to_string(), size }); | |
| 226 | + } | |
| 227 | + Ok(entries) | |
| 228 | +} | |
| 229 | + | |
| 230 | +/// The linguist attributes of every path, read from the commit's own | |
| 231 | +/// `.gitattributes` files. | |
| 232 | +async fn check_attr(git: &Git, commit: &str, entries: &[Entry]) -> anyhow::Result<HashMap<String, Attributes>> { | |
| 233 | + use tokio::io::{AsyncReadExt, AsyncWriteExt}; | |
| 234 | + let mut child = git | |
| 235 | + .command() | |
| 236 | + .args(["check-attr", "-z", "--stdin", "--source", commit]) | |
| 237 | + .args(["linguist-vendored", "linguist-generated", "linguist-documentation", "linguist-detectable", "linguist-language"]) | |
| 238 | + .stdin(Stdio::piped()) | |
| 239 | + .stdout(Stdio::piped()) | |
| 240 | + .stderr(Stdio::null()) | |
| 241 | + .spawn()?; | |
| 242 | + let mut stdin = child.stdin.take().ok_or_else(|| anyhow::anyhow!("stdin"))?; | |
| 243 | + let mut input = Vec::new(); | |
| 244 | + for entry in entries { | |
| 245 | + input.extend_from_slice(entry.path.as_bytes()); | |
| 246 | + input.push(0); | |
| 247 | + } | |
| 248 | + let writer = tokio::spawn(async move { | |
| 249 | + let _ = stdin.write_all(&input).await; | |
| 250 | + }); | |
| 251 | + let mut output = Vec::new(); | |
| 252 | + child.stdout.take().ok_or_else(|| anyhow::anyhow!("stdout"))?.read_to_end(&mut output).await?; | |
| 253 | + let _ = writer.await; | |
| 254 | + anyhow::ensure!(child.wait().await?.success(), "git check-attr failed"); | |
| 255 | + let mut out: HashMap<String, Attributes> = HashMap::new(); | |
| 256 | + let fields: Vec<String> = output.split(|b| *b == 0).map(|f| String::from_utf8_lossy(f).into_owned()).collect(); | |
| 257 | + for triple in fields.chunks_exact(3) { | |
| 258 | + let (path, attribute, value) = (&triple[0], triple[1].as_str(), triple[2].as_str()); | |
| 259 | + if value == "unspecified" { | |
| 260 | + continue; | |
| 261 | + } | |
| 262 | + // Linguist's boolean_attribute: anything but unset or "false" is on. | |
| 263 | + let on = value != "unset" && value != "false"; | |
| 264 | + let attrs = out.entry(path.clone()).or_default(); | |
| 265 | + match attribute { | |
| 266 | + "linguist-vendored" => attrs.vendored = Some(on), | |
| 267 | + "linguist-generated" => attrs.generated = Some(on), | |
| 268 | + "linguist-documentation" => attrs.documentation = Some(on), | |
| 269 | + "linguist-detectable" => attrs.detectable = Some(on), | |
| 270 | + "linguist-language" if on && value != "set" => attrs.language = Some(value.to_string()), | |
| 271 | + _ => {} | |
| 272 | + } | |
| 273 | + } | |
| 274 | + Ok(out) | |
| 275 | +} | |
| 276 | + | |
| 277 | +/// The first `MAX_CONTENT` bytes of each blob, in order, streamed through one | |
| 278 | +/// `git cat-file --batch` (the rest of each blob is read and dropped). | |
| 279 | +async fn read_heads(git: &Git, shas: &[&str]) -> anyhow::Result<Vec<Option<Vec<u8>>>> { | |
| 280 | + use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; | |
| 281 | + if shas.is_empty() { | |
| 282 | + return Ok(Vec::new()); | |
| 283 | + } | |
| 284 | + let mut child = git.command().args(["cat-file", "--batch"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn()?; | |
| 285 | + let mut stdin = child.stdin.take().ok_or_else(|| anyhow::anyhow!("stdin"))?; | |
| 286 | + let input: Vec<u8> = shas.iter().flat_map(|sha| format!("{sha}\n").into_bytes()).collect(); | |
| 287 | + let writer = tokio::spawn(async move { | |
| 288 | + let _ = stdin.write_all(&input).await; | |
| 289 | + }); | |
| 290 | + let mut reader = BufReader::with_capacity(256 * 1024, child.stdout.take().ok_or_else(|| anyhow::anyhow!("stdout"))?); | |
| 291 | + let mut heads = Vec::with_capacity(shas.len()); | |
| 292 | + let mut header = String::new(); | |
| 293 | + for _ in shas { | |
| 294 | + header.clear(); | |
| 295 | + if reader.read_line(&mut header).await? == 0 { | |
| 296 | + break; | |
| 297 | + } | |
| 298 | + // "<sha> missing" carries no body. | |
| 299 | + let Some(size) = header.split_whitespace().nth(2).and_then(|s| s.parse::<u64>().ok()) else { | |
| 300 | + heads.push(None); | |
| 301 | + continue; | |
| 302 | + }; | |
| 303 | + let keep = size.min(linguist::MAX_CONTENT as u64); | |
| 304 | + let mut head = vec![0u8; keep as usize]; | |
| 305 | + reader.read_exact(&mut head).await?; | |
| 306 | + // The rest of the blob plus the trailing newline. | |
| 307 | + tokio::io::copy(&mut (&mut reader).take(size - keep + 1), &mut tokio::io::sink()).await?; | |
| 308 | + heads.push(Some(head)); | |
| 309 | + } | |
| 310 | + let _ = writer.await; | |
| 311 | + let _ = child.wait().await; | |
| 312 | + Ok(heads) | |
| 313 | +} | |
| 314 | + | |
| 315 | +/// The cached breakdown for the repo's default branch, recomputed when the | |
| 316 | +/// branch has moved since. Also keeps `repos.primary_language` current. | |
| 317 | +pub async fn for_repo(state: &AppState, repo: &Repo) -> anyhow::Result<Vec<LanguageBytes>> { | |
| 318 | + let git = Git::new(repo.disk_path(&state.config)); | |
| 319 | + let Some(head) = git.resolve_commit(&format!("refs/heads/{}", repo.default_branch)).await? else { | |
| 320 | + return Ok(Vec::new()); | |
| 321 | + }; | |
| 322 | + let cached: Option<(String, sqlx::types::Json<Vec<LanguageBytes>>)> = | |
| 323 | + sqlx::query_as("select commit_sha, stats from repo_languages where repo_id = $1").bind(repo.id).fetch_optional(&state.db).await?; | |
| 324 | + if let Some((sha, stats)) = cached { | |
| 325 | + if sha == head { | |
| 326 | + return Ok(stats.0); | |
| 327 | + } | |
| 328 | + } | |
| 329 | + let started = std::time::Instant::now(); | |
| 330 | + let stats = compute(&git, &head).await?; | |
| 331 | + let primary = stats.first().map(|l| l.name.clone()); | |
| 332 | + sqlx::query( | |
| 333 | + "insert into repo_languages (repo_id, commit_sha, stats) values ($1, $2, $3) | |
| 334 | + on conflict (repo_id) do update set commit_sha = excluded.commit_sha, stats = excluded.stats, computed_at = now()", | |
| 335 | + ) | |
| 336 | + .bind(repo.id) | |
| 337 | + .bind(&head) | |
| 338 | + .bind(sqlx::types::Json(&stats)) | |
| 339 | + .execute(&state.db) | |
| 340 | + .await?; | |
| 341 | + sqlx::query("update repos set primary_language = $2 where id = $1").bind(repo.id).bind(&primary).execute(&state.db).await?; | |
| 342 | + tracing::debug!(repo = %repo.full_name(), languages = stats.len(), ms = started.elapsed().as_millis() as u64, "language stats computed"); | |
| 343 | + Ok(stats) | |
| 344 | +} | |
| 345 | + | |
| 346 | +/// Computes stats for repos that have none yet (after an upgrade), quietly in | |
| 347 | +/// the background, one at a time. | |
| 348 | +pub fn spawn_backfill(state: AppState) { | |
| 349 | + tokio::spawn(async move { | |
| 350 | + let repos: Vec<Repo> = match sqlx::query_as(concat!( | |
| 351 | + crate::repo_select!(), | |
| 352 | + " where not r.is_empty and not exists (select 1 from repo_languages l where l.repo_id = r.id) order by r.id" | |
| 353 | + )) | |
| 354 | + .fetch_all(&state.db) | |
| 355 | + .await | |
| 356 | + { | |
| 357 | + Ok(repos) => repos, | |
| 358 | + Err(error) => { | |
| 359 | + tracing::warn!(%error, "language backfill query failed"); | |
| 360 | + return; | |
| 361 | + } | |
| 362 | + }; | |
| 363 | + if repos.is_empty() { | |
| 364 | + return; | |
| 365 | + } | |
| 366 | + tracing::info!(repos = repos.len(), "computing language stats for existing repositories"); | |
| 367 | + for repo in repos { | |
| 368 | + if let Err(error) = for_repo(&state, &repo).await { | |
| 369 | + tracing::warn!(repo = %repo.full_name(), ?error, "language stats failed"); | |
| 370 | + } | |
| 371 | + } | |
| 372 | + }); | |
| 373 | +} | |
| 374 | + | |
| 375 | +/// Percentages for display: languages under 0.1% are folded into "Other". | |
| 376 | +pub fn percentages(stats: &[LanguageBytes]) -> Vec<(String, f64)> { | |
| 377 | + let total: u64 = stats.iter().map(|l| l.bytes).sum(); | |
| 378 | + if total == 0 { | |
| 379 | + return Vec::new(); | |
| 380 | + } | |
| 381 | + let mut out = Vec::new(); | |
| 382 | + let mut other = 0.0; | |
| 383 | + for language in stats { | |
| 384 | + let pct = language.bytes as f64 * 100.0 / total as f64; | |
| 385 | + if pct < 0.1 || out.len() >= 8 { | |
| 386 | + other += pct; | |
| 387 | + } else { | |
| 388 | + out.push((language.name.clone(), pct)); | |
| 389 | + } | |
| 390 | + } | |
| 391 | + if other >= 0.05 { | |
| 392 | + out.push(("Other".to_string(), other)); | |
| 393 | + } | |
| 394 | + out | |
| 395 | +} | |
| 396 | + | |
| 397 | +#[cfg(test)] | |
| 398 | +mod tests { | |
| 399 | + use super::*; | |
| 400 | + | |
| 401 | + #[test] | |
| 402 | + fn small_languages_fold_into_other() { | |
| 403 | + let stats = vec![ | |
| 404 | + LanguageBytes { name: "Rust".into(), bytes: 9000 }, | |
| 405 | + LanguageBytes { name: "Shell".into(), bytes: 995 }, | |
| 406 | + LanguageBytes { name: "Nix".into(), bytes: 5 }, | |
| 407 | + ]; | |
| 408 | + let pct = percentages(&stats); | |
| 409 | + assert_eq!(pct[0].0, "Rust"); | |
| 410 | + assert!((pct[0].1 - 90.0).abs() < 0.01); | |
| 411 | + assert_eq!(pct.last().unwrap().0, "Other"); | |
| 412 | + } | |
| 413 | + | |
| 414 | + #[test] | |
| 415 | + fn colors() { | |
| 416 | + assert_eq!(color("Rust"), "#dea584"); | |
| 417 | + assert_eq!(color("Nope"), "#8b949e"); | |
| 418 | + } | |
| 419 | +} |
+48-0backend/src/main.rs
| @@ -83,6 +83,18 @@ enum AdminCommand { | ||
| 83 | 83 | TestEmail { to: String }, |
| 84 | 84 | /// Recount every repository's heatmap contributions from git history. |
| 85 | 85 | RebuildContributions, |
| 86 | + /// Show how a repository's languages are counted, the way the profile | |
| 87 | + /// and repo pages see them. Takes owner/repo, or a path to a git directory | |
| 88 | + /// (no database needed then). | |
| 89 | + Languages { | |
| 90 | + repo: String, | |
| 91 | + /// List every file with its language and why it does or does not count. | |
| 92 | + #[arg(long)] | |
| 93 | + files: bool, | |
| 94 | + /// Commit or branch to classify (default: the default branch). | |
| 95 | + #[arg(long)] | |
| 96 | + rev: Option<String>, | |
| 97 | + }, | |
| 86 | 98 | /// Publish a CLI build to R2 so `ig upgrade` and install.sh serve it. |
| 87 | 99 | PublishCli { |
| 88 | 100 | /// Path to the built `ig` binary. |
| @@ -174,6 +186,11 @@ async fn shutdown_signal() { | ||
| 174 | 186 | } |
| 175 | 187 | |
| 176 | 188 | async fn admin(command: AdminCommand) -> anyhow::Result<()> { |
| 189 | + if let AdminCommand::Languages { repo, files, rev } = &command { | |
| 190 | + if std::path::Path::new(repo).is_dir() { | |
| 191 | + return languages_report(&git::Git::new(repo), rev.as_deref().unwrap_or("HEAD"), *files).await; | |
| 192 | + } | |
| 193 | + } | |
| 177 | 194 | let config = Config::from_env()?; |
| 178 | 195 | let db = connect(&config).await?; |
| 179 | 196 | match command { |
| @@ -210,6 +227,12 @@ async fn admin(command: AdminCommand) -> anyhow::Result<()> { | ||
| 210 | 227 | println!("sent a test email to {to}"); |
| 211 | 228 | Ok(()) |
| 212 | 229 | } |
| 230 | + AdminCommand::Languages { repo, files, rev } => { | |
| 231 | + let (owner, name) = repo.split_once('/').context("expected owner/repo, or a path to a git directory")?; | |
| 232 | + let repo = models::Repo::by_path(&db, owner, name).await?.with_context(|| format!("no repository {owner}/{name}"))?; | |
| 233 | + let rev = rev.unwrap_or_else(|| format!("refs/heads/{}", repo.default_branch)); | |
| 234 | + languages_report(&git::Git::new(repo.disk_path(&config)), &rev, files).await | |
| 235 | + } | |
| 213 | 236 | AdminCommand::PublishCli { path, version, target } => { |
| 214 | 237 | let storage = Storage::new(&config.r2)?; |
| 215 | 238 | api::release::publish(&db, &storage, &path, &version, &target).await |
| @@ -246,3 +269,28 @@ async fn set_admin(db: &sqlx::PgPool, username: &str, admin: bool) -> anyhow::Re | ||
| 246 | 269 | println!("{username} is {}a site admin", if admin { "now " } else { "no longer " }); |
| 247 | 270 | Ok(()) |
| 248 | 271 | } |
| 272 | + | |
| 273 | +async fn languages_report(git: &git::Git, rev: &str, files: bool) -> anyhow::Result<()> { | |
| 274 | + let commit = git.resolve_commit(rev).await?.with_context(|| format!("no commit at {rev}"))?; | |
| 275 | + let reports = languages::classify(git, &commit).await?; | |
| 276 | + if files { | |
| 277 | + for report in &reports { | |
| 278 | + println!( | |
| 279 | + "{:>10} {:<14} {:<20} {}{}", | |
| 280 | + report.bytes, | |
| 281 | + report.verdict.label(), | |
| 282 | + report.language.as_deref().unwrap_or("-"), | |
| 283 | + report.path, | |
| 284 | + if report.read { "" } else { " (name only)" } | |
| 285 | + ); | |
| 286 | + } | |
| 287 | + println!(); | |
| 288 | + } | |
| 289 | + let stats = languages::totals(&reports); | |
| 290 | + let total: u64 = stats.iter().map(|l| l.bytes).sum(); | |
| 291 | + for language in &stats { | |
| 292 | + println!("{:>6.2}% {:>10} {}", language.bytes as f64 * 100.0 / total.max(1) as f64, language.bytes, language.name); | |
| 293 | + } | |
| 294 | + println!("{} files at {}, {} bytes counted ({})", reports.len(), &commit[..12], total, languages::LINGUIST.source); | |
| 295 | + Ok(()) | |
| 296 | +} |
+121-0scripts/gen-linguist.py
| @@ -0,0 +1,121 @@ | ||
| 1 | +#!/usr/bin/env python3 | |
| 2 | +"""Build backend/src/languages/linguist.json from GitHub Linguist's data files. | |
| 3 | + | |
| 4 | +irongit detects languages the way GitHub does, with Linguist's own tables: | |
| 5 | +languages.yml (names, colors, groups, extensions, filenames, interpreters, | |
| 6 | +aliases), vendor.yml and documentation.yml (excluded paths), generic.yml | |
| 7 | +(extensions too vague to decide on), and heuristics.yml (content rules for | |
| 8 | +shared extensions such as .h, .m, .sql or .pl). This script downloads them for | |
| 9 | +one Linguist release and writes a compact JSON the server embeds. | |
| 10 | + | |
| 11 | + python3 scripts/gen-linguist.py # the release pinned below | |
| 12 | + python3 scripts/gen-linguist.py v9.8.0 # another release | |
| 13 | + | |
| 14 | +Heuristic patterns are Ruby regexes; they are rewritten for Rust's regex crate | |
| 15 | +here (Ruby's ^ and $ always match at line breaks, Ruby's m flag is Rust's s, | |
| 16 | +a stray { is a literal). A few use lookarounds or subroutines Rust lacks; a | |
| 17 | +server test checks that those only choose between data and prose formats, | |
| 18 | +which never count, so a pattern that never matches costs nothing. | |
| 19 | + | |
| 20 | +Linguist is MIT licensed: https://github.com/github-linguist/linguist | |
| 21 | +""" | |
| 22 | + | |
| 23 | +import json | |
| 24 | +import re | |
| 25 | +import sys | |
| 26 | +import urllib.request | |
| 27 | +from pathlib import Path | |
| 28 | + | |
| 29 | +import yaml | |
| 30 | + | |
| 31 | +TAG = sys.argv[1] if len(sys.argv) > 1 else "v9.7.0" | |
| 32 | +BASE = f"https://raw.githubusercontent.com/github-linguist/linguist/{TAG}/lib/linguist" | |
| 33 | +OUT = Path(__file__).resolve().parent.parent / "backend/src/languages/linguist.json" | |
| 34 | + | |
| 35 | + | |
| 36 | +def fetch(name): | |
| 37 | + with urllib.request.urlopen(f"{BASE}/{name}", timeout=30) as response: | |
| 38 | + return yaml.safe_load(response.read()) | |
| 39 | + | |
| 40 | + | |
| 41 | +def ruby_to_rust(pattern): | |
| 42 | + """One Ruby regex as a Rust one with the same meaning.""" | |
| 43 | + # Ruby's inline m flag is dot-matches-newline, which Rust calls s. | |
| 44 | + def flags(match): | |
| 45 | + on, sep = match.group(1), match.group(2) | |
| 46 | + return "(?" + on.replace("m", "s") + sep | |
| 47 | + pattern = re.sub(r"\(\?([imx-]+)([:)])", flags, pattern) | |
| 48 | + # {,n} is Ruby shorthand for {0,n}. | |
| 49 | + pattern = re.sub(r"\{,(\d+)\}", r"{0,\1}", pattern) | |
| 50 | + # Ruby reads a { that starts no repetition as a literal; Rust wants \{. | |
| 51 | + pattern = re.sub(r"(?<!\\)\{(?!\d+(?:,\d*)?\})", r"\\{", pattern) | |
| 52 | + # Rust has no lookbehind; "not after {" is spelled out. | |
| 53 | + pattern = pattern.replace("(?<!\\{)", "(?:^|[^{])") | |
| 54 | + # Ruby's ^ and $ are always line anchors. | |
| 55 | + return "(?m)" + pattern | |
| 56 | + | |
| 57 | + | |
| 58 | +def union(value): | |
| 59 | + patterns = value if isinstance(value, list) else [value] | |
| 60 | + if len(patterns) == 1: | |
| 61 | + return ruby_to_rust(patterns[0]) | |
| 62 | + return "|".join(f"(?:{ruby_to_rust(p)})" for p in patterns) | |
| 63 | + | |
| 64 | + | |
| 65 | +def main(): | |
| 66 | + languages = fetch("languages.yml") | |
| 67 | + vendor = fetch("vendor.yml") | |
| 68 | + documentation = fetch("documentation.yml") | |
| 69 | + generic = fetch("generic.yml") | |
| 70 | + heuristics = fetch("heuristics.yml") | |
| 71 | + | |
| 72 | + kinds = {"programming": "programming", "markup": "markup", "data": "data", "prose": "prose"} | |
| 73 | + out_languages = [] | |
| 74 | + for name, spec in languages.items(): | |
| 75 | + entry = {"name": name, "type": kinds[spec["type"]]} | |
| 76 | + if spec.get("color"): | |
| 77 | + entry["color"] = spec["color"] | |
| 78 | + if spec.get("group"): | |
| 79 | + entry["group"] = spec["group"] | |
| 80 | + for key, short in (("extensions", "ext"), ("filenames", "files"), ("interpreters", "interp"), ("aliases", "aliases")): | |
| 81 | + if spec.get(key): | |
| 82 | + entry[short] = spec[key] | |
| 83 | + out_languages.append(entry) | |
| 84 | + | |
| 85 | + named = {key: union(value) for key, value in heuristics["named_patterns"].items()} | |
| 86 | + | |
| 87 | + def conjuncts(rule): | |
| 88 | + if rule.get("and") is not None: | |
| 89 | + return [c for block in rule["and"] for c in conjuncts(block)] | |
| 90 | + if rule.get("pattern") is not None: | |
| 91 | + return [{"re": union(rule["pattern"])}] | |
| 92 | + if rule.get("negative_pattern") is not None: | |
| 93 | + return [{"re": union(rule["negative_pattern"]), "neg": True}] | |
| 94 | + if rule.get("named_pattern") is not None: | |
| 95 | + return [{"re": named[rule["named_pattern"]]}] | |
| 96 | + return [] | |
| 97 | + | |
| 98 | + out_heuristics = [] | |
| 99 | + for block in heuristics["disambiguations"]: | |
| 100 | + rules = [] | |
| 101 | + for rule in block["rules"]: | |
| 102 | + names = rule["language"] if isinstance(rule["language"], list) else [rule["language"]] | |
| 103 | + rules.append({"languages": names, "all": conjuncts(rule)}) | |
| 104 | + out_heuristics.append({"ext": block["extensions"], "rules": rules}) | |
| 105 | + | |
| 106 | + data = { | |
| 107 | + "source": f"github-linguist/linguist {TAG}", | |
| 108 | + "languages": out_languages, | |
| 109 | + "vendor": vendor, | |
| 110 | + "documentation": documentation, | |
| 111 | + "generic": generic["extensions"], | |
| 112 | + "heuristics": out_heuristics, | |
| 113 | + } | |
| 114 | + OUT.parent.mkdir(parents=True, exist_ok=True) | |
| 115 | + OUT.write_text(json.dumps(data, separators=(",", ":"), ensure_ascii=False) + "\n") | |
| 116 | + print(f"wrote {OUT} ({OUT.stat().st_size // 1024} KiB): {len(out_languages)} languages, " | |
| 117 | + f"{len(vendor)} vendor and {len(documentation)} documentation paths, {len(out_heuristics)} heuristics") | |
| 118 | + | |
| 119 | + | |
| 120 | +if __name__ == "__main__": | |
| 121 | + main() |