irongit

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

huncholanehuncholaneauthored
parent 426b72bcommit e584fdfce6808bca97c1bdad4233134fb2489252Browse files

10 files changed, +1405 -627

+0-24Cargo.lock
@@ -440,16 +440,6 @@ dependencies = [
440440 "syn 3.0.6",
441441 ]
442442
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-
453443 [[package]]
454444 name = "bumpalo"
455445 version = "3.20.3"
@@ -1502,19 +1492,6 @@ dependencies = [
15021492 "zeroize",
15031493 ]
15041494
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-
15181495 [[package]]
15191496 name = "group"
15201497 version = "0.14.0"
@@ -1997,7 +1974,6 @@ dependencies = [
19971974 "dotenvy",
19981975 "flate2",
19991976 "futures",
2000- "globset",
20011977 "hex",
20021978 "hmac",
20031979 "irongit-shared",
+21-0README.md
@@ -84,6 +84,26 @@ commits count on your heatmap. It runs as a background job on the server
8484 import tokens are revoked when the job ends. Sign in with GitHub and "Connect
8585 GitHub" need `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` (see secrets.md).
8686
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+
87107 ## Tests
88108
89109 ```sh
@@ -106,6 +126,7 @@ backend/
106126 src/transport.rs spawning upload-pack/receive-pack with hooks and limits
107127 src/hook.rs pre-receive size and quota checks
108128 src/push.rs post-push: events, default branch, heatmap contributions
129+ src/languages/ language stats (Linguist data and rules)
109130 src/ssh.rs built-in SSH server
110131 src/lfs.rs Git LFS batch API
111132 src/registry/ OCI distribution API (docker push/pull)
+0-1backend/Cargo.toml
@@ -51,4 +51,3 @@ clap = { version = "4.6.7", features = ["derive", "env"] }
5151 lettre = { version = "0.11.23", default-features = false, features = ["tokio1-rustls-tls", "smtp-transport", "builder", "hostname"] }
5252 axum-extra = { version = "0.12.6", features = ["cookie", "typed-header"] }
5353 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 {
8383 TestEmail { to: String },
8484 /// Recount every repository's heatmap contributions from git history.
8585 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+ },
8698 /// Publish a CLI build to R2 so `ig upgrade` and install.sh serve it.
8799 PublishCli {
88100 /// Path to the built `ig` binary.
@@ -174,6 +186,11 @@ async fn shutdown_signal() {
174186 }
175187
176188 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+ }
177194 let config = Config::from_env()?;
178195 let db = connect(&config).await?;
179196 match command {
@@ -210,6 +227,12 @@ async fn admin(command: AdminCommand) -> anyhow::Result<()> {
210227 println!("sent a test email to {to}");
211228 Ok(())
212229 }
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+ }
213236 AdminCommand::PublishCli { path, version, target } => {
214237 let storage = Storage::new(&config.r2)?;
215238 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
246269 println!("{username} is {}a site admin", if admin { "now " } else { "no longer " });
247270 Ok(())
248271 }
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()