blob: 4e0ac705ed9949c6de82df42171d7d33b58e5820 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
/* cgit.js: javacript functions for cgit
 *
 * Copyright (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com>
 *
 * Licensed under GNU General Public License v2
 *   (see LICENSE.txt for full license text)
 */

(function () {

/* This follows the logic and suffixes used in ui-shared.c */

var age_classes = [ "age-mins", "age-hours", "age-days",    "age-weeks",    "age-months",    "age-years" ];
var age_suffix =  [ "min.",     "hours",     "days",        "weeks",        "months",        "years",         "years" ];
var age_next =    [ 60,         3600,        24 * 3600,     7 * 24 * 3600,  30 * 24 * 3600,  365 * 24 * 3600, 365 * 24 * 3600 ];
var age_limit =   [ 7200,       24 * 7200,   7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ];
var update_next = [ 10,         5 * 60,      1800,          24 * 3600,      24 * 3600,       24 * 3600,       24 * 3600 ];

function render_age(e, age) {
	var t, n;

	for (n = 0; n < age_classes.length; n++)
		if (age < age_limit[n])
			break;

	t = Math.round(age / age_next[n]) + " " + age_suffix[n];

	if (e.textContent != t) {
		e.textContent = t;
		if (n == age_classes.length)
			n--;
		if (e.className != age_classes[n])
			e.className = age_classes[n];
	}
}

function aging() {
	var n, next = 24 * 3600,
	    now_ut = Math.round((new Date().getTime() / 1000));

	for (n = 0; n < age_classes.length; n++) {
		var m, elems = document.getElementsByClassName(age_classes[n]);

		if (elems.length && update_next[n] < next)
			next = update_next[n];

		for (m = 0; m < elems.length; m++) {
			var age = now_ut - elems[m].getAttribute("data-ut");

			/* A commit dated ahead of the viewer's clock would
			 * otherwise render as a negative age; ui-shared.c
			 * clamps the same way. */
			if (age < 0)
				age = 0;

			render_age(elems[m], age);
		}
	}

	/*
	 * We only need to come back when the age might have changed.
	 * Eg, if everything is counted in hours already, once per
	 * 5 minutes is accurate enough.
	 */

	window.setTimeout(aging, next * 1000);
}

document.addEventListener("DOMContentLoaded", function() {
	/* we can do the aging on DOM content load since no layout dependency */
	aging();
}, false);

})();

/* Selects marked data-autosubmit reload the page with the new setting.
 * Wired here instead of inline onchange handlers, which a strict
 * Content-Security-Policy blocks. Without scripting the forms keep
 * their noscript reload button. */

(function () {

document.addEventListener("DOMContentLoaded", function () {
	var i, els = document.querySelectorAll("select[data-autosubmit]");

	for (i = 0; i < els.length; i++)
		els[i].addEventListener("change", function () {
			this.form.submit();
		});
}, false);

})();

/* Colour theme toggle: cycles auto -> light -> dark and remembers the
 * choice. "auto" leaves the page following the system preference via CSS. */

(function () {

var KEY = "cgit-theme";
var ORDER = [ "auto", "light", "dark" ];
var ICONS = {
	auto:  '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 0 0 18z" fill="currentColor" stroke="none"/></svg>',
	light: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v3M12 20v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M1 12h3M20 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/></svg>',
	dark:  '<svg viewBox="0 0 24 24"><path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z"/></svg>'
};

function saved() {
	try { return localStorage.getItem(KEY); } catch (e) { return null; }
}

function persist(value) {
	try { localStorage.setItem(KEY, value); } catch (e) { }
}

function apply(theme, root, btn) {
	if (theme === "auto")
		root.removeAttribute("data-theme");
	else
		root.setAttribute("data-theme", theme);
	/* Keep the standalone page background in sync with the choice. */
	document.documentElement.style.colorScheme = (theme === "auto") ? "" : theme;
	if (btn) {
		btn.innerHTML = ICONS[theme];
		btn.title = "Colour theme: " + theme;
	}
}

document.addEventListener("DOMContentLoaded", function () {
	var root = document.getElementById("cgit");
	if (!root)
		return;
	var btn = root.querySelector(".theme-toggle");
	var theme = saved();
	if (ORDER.indexOf(theme) < 0)
		theme = "auto";

	apply(theme, root, btn);
	if (!btn)
		return;
	btn.hidden = false;
	btn.addEventListener("click", function () {
		theme = ORDER[(ORDER.indexOf(theme) + 1) % ORDER.length];
		persist(theme);
		apply(theme, root, btn);
	});
}, false);

})();

/* Built-in Markdown rendering for the about page. When no about-filter is
 * configured, cgit escapes a markdown readme into a data-markdown container
 * (see cgit_print_repo_readme) and this renders a deliberately small, safe
 * subset client-side: headings, lists, blockquotes, rules, fenced and inline
 * code, pipe tables, links, images and emphasis. Every run of text is escaped
 * before any markup is added, and link and image URLs are restricted to http,
 * https, mailto and relative targets, so a hostile readme cannot inject markup
 * or scripts. Fenced code keeps its data-lang so the highlighter below styles
 * it. Without JavaScript the escaped source stays readable as plain text.
 *
 * This is intentionally a subset, not CommonMark: no reference links, raw HTML
 * passthrough, nested lists or setext headings. Configure an about-filter to
 * replace it, or set enable-markdown=0 to turn it off. */

(function () {

var MAX_BYTES = 400000;

function esc(s) {
	return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function escAttr(s) {
	return esc(s).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
}

/* Return the url if its scheme is safe, else "". Whitespace and control bytes
 * are stripped before the scheme is read because browsers ignore them when
 * resolving it, so "java\nscript:..." must still be caught as javascript. */
function safeUrl(url) {
	url = (url || "").replace(/[\u0000-\u0020]+/g, "");
	var scheme = /^([a-z][a-z0-9+.\-]*):/i.exec(url);
	if (scheme && !/^(https?|mailto)$/i.test(scheme[1]))
		return "";
	return url;
}

function link(text, url, image) {
	var u = safeUrl(url);
	if (!u)
		return image ? esc("![" + text + "]") : inline(text);
	if (image)
		return "<img src='" + escAttr(u) + "' alt='" + escAttr(text) + "'/>";
	return "<a href='" + escAttr(u) + "'>" + inline(text) + "</a>";
}

/* Inline rendering over one block of text. Scans to the next marker character
 * and bulk-escapes the plain text in between, so it stays roughly linear. */
function inline(s) {
	var out = "", i = 0, n = s.length, marker = /[`!\[*_]/g, m, rest;
	while (i < n) {
		marker.lastIndex = i;
		m = marker.exec(s);
		if (!m) { out += esc(s.slice(i)); break; }
		if (m.index > i) { out += esc(s.slice(i, m.index)); i = m.index; }
		rest = s.slice(i);
		if ((m = /^`([^`]+)`/.exec(rest)))
			out += "<code>" + esc(m[1]) + "</code>";
		else if ((m = /^!\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)/.exec(rest)))
			out += link(m[1], m[2], true);
		else if ((m = /^\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)/.exec(rest)))
			out += link(m[1], m[2], false);
		else if ((m = /^(\*\*|__)([\s\S]+?)\1/.exec(rest)))
			out += "<strong>" + inline(m[2]) + "</strong>";
		else if ((m = /^(\*|_)([^\s][\s\S]*?)\1/.exec(rest)))
			out += "<em>" + inline(m[2]) + "</em>";
		else { out += esc(s.charAt(i)); i++; continue; }
		i += m[0].length;
	}
	return out;
}

function cells(row) {
	return row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map(function (c) {
		return c.trim();
	});
}

function render(src) {
	var lines = src.replace(/\r\n?/g, "\n").split("\n");
	var out = "", i = 0, n = lines.length, line, m, k;
	while (i < n) {
		line = lines[i];
		if (/^\s*$/.test(line)) { i++; continue; }
		if ((m = /^\s*(`{3,}|~{3,})\s*([\w.+#-]*)/.exec(line))) {
			var fence = m[1].charAt(0) === "`" ? /^\s*`{3,}\s*$/ : /^\s*~{3,}\s*$/;
			var lang = m[2], code = "";
			for (i++; i < n && !fence.test(lines[i]); i++)
				code += lines[i] + "\n";
			i++;
			out += "<pre><code" + (lang ? " data-lang='" + escAttr(lang) + "'" : "") +
				">" + esc(code) + "</code></pre>";
			continue;
		}
		if ((m = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(line))) {
			k = m[1].length;
			out += "<h" + k + ">" + inline(m[2]) + "</h" + k + ">";
			i++; continue;
		}
		if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) { out += "<hr/>"; i++; continue; }
		if (/^\s*>/.test(line)) {
			var q = "";
			for (; i < n && /^\s*>/.test(lines[i]); i++)
				q += lines[i].replace(/^\s*>\s?/, "") + "\n";
			out += "<blockquote>" + render(q) + "</blockquote>";
			continue;
		}
		if (line.indexOf("|") >= 0 && i + 1 < n &&
		    /^\s*\|?(\s*:?-+:?\s*\|)+\s*:?-+:?\s*\|?\s*$/.test(lines[i + 1])) {
			var head = cells(line), t = "<table><thead><tr>";
			for (k = 0; k < head.length; k++)
				t += "<th>" + inline(head[k]) + "</th>";
			t += "</tr></thead><tbody>";
			for (i += 2; i < n && lines[i].indexOf("|") >= 0 && !/^\s*$/.test(lines[i]); i++) {
				var row = cells(lines[i]);
				t += "<tr>";
				for (k = 0; k < row.length; k++)
					t += "<td>" + inline(row[k]) + "</td>";
				t += "</tr>";
			}
			out += t + "</tbody></table>";
			continue;
		}
		if (/^\s*([-*+]|\d+[.)])\s+/.test(line)) {
			var ordered = /^\s*\d/.test(line), tag = ordered ? "ol" : "ul";
			out += "<" + tag + ">";
			for (; i < n && (m = /^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(lines[i])); i++) {
				if ((/\d/.test(m[1])) !== ordered) break;
				out += "<li>" + inline(m[2]) + "</li>";
			}
			out += "</" + tag + ">";
			continue;
		}
		/* Always consume the current line so i advances even when it
		 * matched none of the block branches above. */
		var para = lines[i++];
		for (; i < n && !/^\s*$/.test(lines[i]) &&
		       !/^\s*(#{1,6}\s|>|`{3,}|~{3,}|([-*+]|\d+[.)])\s)/.test(lines[i]); i++)
			para += "\n" + lines[i];
		out += "<p>" + inline(para).replace(/\n/g, "<br/>") + "</p>";
	}
	return out;
}

document.addEventListener("DOMContentLoaded", function () {
	var nodes = document.querySelectorAll("div#cgit [data-markdown]"), i, el, text;
	for (i = 0; i < nodes.length; i++) {
		el = nodes[i];
		text = el.textContent;
		if (!text || text.length > MAX_BYTES)
			continue;
		try {
			el.innerHTML = render(text);
		} catch (e) {
			/* leave the escaped source in place on any failure */
		}
	}
}, false);

})();

/* Built-in syntax highlighting for the blob view. When no server-side
 * source filter is configured, cgit tags the <code> element with
 * data-lang set to the file's extension (or bare name, so Makefile and
 * Dockerfile work too). We tokenise the plain text into comments,
 * strings, numbers and keywords. Every character is preserved so the
 * line-number gutter stays aligned, and any failure leaves the original
 * text untouched, so a blob is always readable with or without JS.
 *
 * To support another language: reuse or add a lexical profile in P, add
 * one LANGS entry with its keyword list, then map its extensions in EXT. */

(function () {

var MAX_BYTES = 400000;

/* Lexical profiles shared across language families. Fields:
 *   line  - line-comment prefix, or an array of prefixes
 *   block - [open, close] block-comment delimiters
 *   str   - characters that open a single-line quoted string
 *   tick  - subset of str whose strings may also span newlines
 *   tstr  - triple-quoted string delimiters (Python-style)
 *   ci    - keywords match case-insensitively (SQL) */
var P = {
	c:       { line: "//", block: [ "/*", "*/" ], str: "\"'" },
	tick:    { line: "//", block: [ "/*", "*/" ], str: "\"'`", tick: "`" },
	hash:    { line: "#", str: "\"'" },
	sql:     { line: "--", block: [ "/*", "*/" ], str: "\"'", ci: true },
	lua:     { line: "--", block: [ "--[[", "]]" ], str: "\"'" },
	haskell: { line: "--", block: [ "{-", "-}" ], str: "\"" },
	lisp:    { line: ";", str: "\"" },
	ini:     { line: [ ";", "#" ], str: "\"'" },
	css:     { block: [ "/*", "*/" ], str: "\"'" },
	scss:    { line: "//", block: [ "/*", "*/" ], str: "\"'" },
	json:    { str: "\"" },
	ps:      { line: "#", block: [ "<#", "#>" ], str: "\"'" },
	py:      { line: "#", str: "\"'", tstr: [ "\"\"\"", "'''" ] }
};

var KW = {
	c: "auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while _Bool bool complex size_t ssize_t ptrdiff_t intptr_t uintptr_t int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t true false NULL",
	cpp: "auto break case catch char class const consteval constexpr constinit continue co_await co_return co_yield decltype default delete do double dynamic_cast else enum explicit export extern false final float for friend goto if inline int long mutable namespace new noexcept nullptr operator override private protected public register reinterpret_cast return short signed sizeof static static_assert static_cast struct switch template this thread_local throw true try typedef typeid typename union unsigned using virtual void volatile wchar_t while and or not xor concept requires bool size_t std string vector map set",
	cs: "abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while var async await dynamic yield get set value record nameof when where",
	java: "abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while true false null var record sealed permits yield String Integer Boolean Object List Map",
	js: "async await break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return static super switch this throw try typeof var void while with yield null true false undefined of get set from as NaN Infinity console",
	ts: "async await break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return static super switch this throw try typeof var void while with yield null true false undefined of get set from as interface type enum namespace declare abstract implements private public protected readonly any unknown never string number boolean object symbol bigint keyof infer satisfies is",
	go: "break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var nil true false iota append cap close complex copy delete imag len make new panic print println real recover string bool byte rune error int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex64 complex128 any",
	rust: "as async await break const continue crate dyn else enum extern false fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait true type unsafe use where while union String str Vec Option Result Some None Ok Err Box Rc Arc bool char i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64",
	swift: "associatedtype actor async await class deinit enum extension fileprivate func import init inout internal let open operator private protocol public rethrows static struct subscript typealias var break case continue default defer do else fallthrough for guard if in repeat return switch where while as catch is super self Self throw throws try false true nil some Any Int Double String Bool Array Dictionary Optional",
	kotlin: "abstract actual annotation as break by catch class companion const constructor continue crossinline data delegate do dynamic else enum external false final finally for fun get if import in infix init inline inner interface internal is lateinit noinline null object open operator out override package private protected public reified return sealed set super suspend tailrec this throw true try typealias typeof val var vararg when where while Int Long Double Float String Boolean List Map",
	scala: "abstract case catch class def do else extends false final finally for forSome given if implicit import lazy match new null object override package private protected return sealed super this throw trait try true type using val var while with yield enum export then Int Long Double String Boolean List Map Option Some None",
	php: "abstract and array as break callable case catch class clone const continue declare default do echo else elseif empty enddeclare endfor endforeach endif endswitch endwhile enum extends final finally fn for foreach function global goto if implements include include_once instanceof insteadof interface isset list match namespace new or print private protected public readonly require require_once return static switch throw trait try unset use var while xor yield true false null self parent int float string bool void mixed object",
	dart: "abstract as assert async await break case catch class const continue covariant default deferred do dynamic else enum export extends extension external factory false final finally for get hide if implements import in interface is late library mixin new null on operator part required rethrow return set show static super switch sync this throw true try typedef var void while with yield int double String bool List Map Set Future Stream",
	objc: "auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while bool true false NULL id Class SEL IMP BOOL YES NO nil Nil self super instancetype nonatomic atomic strong weak copy assign retain readonly readwrite nonnull nullable NSString NSObject NSArray NSInteger NSUInteger",
	d: "abstract alias align asm assert auto body bool break byte case cast catch cdouble cent cfloat char class const continue creal dchar debug default delegate delete deprecated do double else enum export extern false final finally float for foreach function goto idouble if ifloat immutable import in inout int interface invariant ireal is lazy long macro mixin module new nothrow null out override package pragma private protected public pure real ref return scope shared short static struct super switch synchronized template this throw true try typeid typeof ubyte uint ulong union unittest ushort version void wchar while with string",
	zig: "addrspace align allowzero and anyframe anytype asm async await break callconv catch comptime const continue defer else enum errdefer error export extern fn for if inline noalias noinline nosuspend opaque or orelse packed pub resume return struct suspend switch test threadlocal try union unreachable usingnamespace var volatile while bool void type u8 u16 u32 u64 usize i8 i16 i32 i64 isize f16 f32 f64 f128 comptime_int comptime_float true false null undefined",
	py: "False None True and as assert async await break class continue def del elif else except finally for from global if import in is lambda match nonlocal not or pass raise return try while with yield self cls print range len int str float bool list dict set tuple bytes object Exception",
	sh: "if then elif else fi for while until do done case esac in function select return local export readonly declare typeset unset shift eval exec source break continue trap set getopts read echo printf test cd pushd popd",
	ruby: "alias and begin break case class def defined do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield require require_relative attr_accessor attr_reader attr_writer include extend private public protected new puts print lambda proc",
	perl: "if elsif else unless while until for foreach do sub return last next redo goto my our local use require package no and or not eq ne lt gt le ge cmp print printf say chomp chop split join map grep keys values each shift unshift push pop defined undef ref bless wantarray qw",
	r: "if else repeat while function for in next break TRUE FALSE NULL Inf NaN NA return library require",
	yaml: "true false null yes no on off True False Null Yes No On Off",
	toml: "true false",
	ini: "true false yes no on off",
	make: "ifeq ifneq ifdef ifndef else endif define endef include export unexport override vpath and or foreach filter wildcard patsubst subst shell call eval error warning info notdir dir basename suffix addprefix addsuffix",
	docker: "FROM RUN CMD LABEL MAINTAINER EXPOSE ENV ADD COPY ENTRYPOINT VOLUME USER WORKDIR ARG ONBUILD STOPSIGNAL HEALTHCHECK SHELL AS",
	lua: "and break do else elseif end false for function goto if in local nil not or repeat return then true until while self require print pairs ipairs tostring tonumber type setmetatable getmetatable pcall error assert table string math io os",
	sql: "select from where insert into values update set delete create table drop alter add column index view join inner outer left right full on as distinct group by order having limit offset union all and or not null is in like between exists case when then else end asc desc primary key foreign references default unique check constraint int integer varchar char text date datetime timestamp boolean float double decimal begin commit rollback transaction grant revoke with returning true false",
	haskell: "case class data default deriving do else foreign if import in infix infixl infixr instance let module newtype of then type where as qualified hiding forall family Int Integer Float Double Char String Bool Maybe Just Nothing Either Left Right IO map filter foldr foldl",
	lisp: "defun defvar defparameter defmacro defconstant let let* lambda if cond case when unless progn setq setf quote function list cons car cdr append mapcar reduce and or not nil t define set begin delay force define-syntax syntax-rules require provide ns defn def fn loop recur do var true false",
	scss: "if else for each while function return extend use import forward media content mixin include null true false and or not in from through to default important",
	json: "true false null",
	ps: "if else elseif switch foreach for while do until break continue function return param begin process end try catch finally throw class enum filter workflow in trap exit true false null"
};

var LANGS = {
	c: def(KW.c, P.c), cpp: def(KW.cpp, P.c), cs: def(KW.cs, P.c),
	java: def(KW.java, P.c), js: def(KW.js, P.tick), ts: def(KW.ts, P.tick),
	go: def(KW.go, P.c), rust: def(KW.rust, P.c), swift: def(KW.swift, P.c),
	kotlin: def(KW.kotlin, P.c), scala: def(KW.scala, P.c),
	php: def(KW.php, { line: [ "//", "#" ], block: [ "/*", "*/" ], str: "\"'" }),
	dart: def(KW.dart, P.c), objc: def(KW.objc, P.c), d: def(KW.d, P.c),
	zig: def(KW.zig, { line: "//", str: "\"" }),
	py: def(KW.py, P.py), sh: def(KW.sh, P.hash), ruby: def(KW.ruby, P.hash),
	perl: def(KW.perl, P.hash), r: def(KW.r, P.hash), yaml: def(KW.yaml, P.hash),
	toml: def(KW.toml, P.hash), ini: def(KW.ini, P.ini),
	make: def(KW.make, P.hash), docker: def(KW.docker, P.hash),
	lua: def(KW.lua, P.lua), sql: def(KW.sql, P.sql),
	haskell: def(KW.haskell, P.haskell), lisp: def(KW.lisp, P.lisp),
	css: def("", P.css), scss: def(KW.scss, P.scss), json: def(KW.json, P.json),
	ps: def(KW.ps, P.ps)
};

/* file extension (or bare filename), lowercased, -> language id */
var EXT = {
	c: "c", h: "c", cc: "cpp", cpp: "cpp", cxx: "cpp", hpp: "cpp", hh: "cpp",
	hxx: "cpp", ipp: "cpp", cs: "cs", java: "java",
	js: "js", mjs: "js", cjs: "js", jsx: "js",
	ts: "ts", tsx: "ts", mts: "ts", cts: "ts",
	go: "go", rs: "rust", swift: "swift", kt: "kotlin", kts: "kotlin",
	scala: "scala", sc: "scala", php: "php", phtml: "php",
	dart: "dart", m: "objc", mm: "objc", d: "d", di: "d", zig: "zig",
	py: "py", pyw: "py", pyi: "py",
	sh: "sh", bash: "sh", zsh: "sh", ksh: "sh", bashrc: "sh", zshrc: "sh",
	rb: "ruby", gemspec: "ruby", rake: "ruby",
	pl: "perl", pm: "perl", r: "r",
	yaml: "yaml", yml: "yaml", toml: "toml",
	ini: "ini", cfg: "ini", conf: "ini",
	makefile: "make", mk: "make", mak: "make", dockerfile: "docker",
	lua: "lua", sql: "sql", hs: "haskell",
	lisp: "lisp", cl: "lisp", el: "lisp", clj: "lisp", cljs: "lisp",
	cljc: "lisp", scm: "lisp", rkt: "lisp",
	css: "css", scss: "scss", sass: "scss", less: "scss",
	json: "json", jsonc: "json",
	ps1: "ps", psm1: "ps", psd1: "ps",
	/* language names as they appear in markdown code fences */
	python: "py", ruby: "ruby", rust: "rust", golang: "go", cpp: "cpp",
	"c++": "cpp", csharp: "cs", "c#": "cs", javascript: "js",
	typescript: "ts", kotlin: "kotlin", haskell: "haskell", shell: "sh"
};

function words(s, ci) {
	var d = Object.create(null), a = s.split(/\s+/), i, w;
	for (i = 0; i < a.length; i++) {
		w = a[i];
		if (w)
			d[ci ? w.toLowerCase() : w] = true;
	}
	return d;
}

function norm(v) {
	return v == null ? [] : (typeof v === "string" ? [ v ] : v);
}

function def(kw, p) {
	return {
		kw: words(kw || "", p.ci), ci: !!p.ci,
		line: norm(p.line), bs: p.block ? p.block[0] : null,
		be: p.block ? p.block[1] : null, str: p.str || "",
		tick: p.tick || "", tstr: norm(p.tstr)
	};
}

var reWord = /[A-Za-z_$][\w$]*/y;
var reNum = /(?:0[xX][0-9a-fA-F]+|0[bB][01]+|\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?)[fFlLuU]*/y;

function esc(s) {
	return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function span(cls, s) {
	return "<span class='hl-" + cls + "'>" + esc(s) + "</span>";
}

function tokenize(text, cfg) {
	var out = "", i = 0, n = text.length, c, j, k, m, t, multi;
	while (i < n) {
		c = text.charAt(i);
		if (cfg.bs && text.startsWith(cfg.bs, i)) {
			j = text.indexOf(cfg.be, i + cfg.bs.length);
			j = j < 0 ? n : j + cfg.be.length;
			out += span("comment", text.slice(i, j)); i = j; continue;
		}
		for (k = 0; k < cfg.line.length; k++)
			if (text.startsWith(cfg.line[k], i)) {
				j = text.indexOf("\n", i); if (j < 0) j = n;
				out += span("comment", text.slice(i, j)); i = j; break;
			}
		if (k < cfg.line.length) continue;
		for (k = 0; k < cfg.tstr.length; k++) {
			t = cfg.tstr[k];
			if (text.startsWith(t, i)) {
				j = text.indexOf(t, i + t.length);
				j = j < 0 ? n : j + t.length;
				out += span("string", text.slice(i, j)); i = j; break;
			}
		}
		if (k < cfg.tstr.length) continue;
		if (cfg.str.indexOf(c) >= 0) {
			multi = cfg.tick.indexOf(c) >= 0;
			j = i + 1;
			while (j < n) {
				if (text.charAt(j) === "\\") { j += 2; continue; }
				if (text.charAt(j) === c) { j++; break; }
				if (text.charAt(j) === "\n" && !multi) break;
				j++;
			}
			out += span("string", text.slice(i, j)); i = j; continue;
		}
		if (c >= "0" && c <= "9") {
			reNum.lastIndex = i;
			m = reNum.exec(text);
			if (m) { out += span("number", m[0]); i += m[0].length; continue; }
		}
		reWord.lastIndex = i;
		m = reWord.exec(text);
		if (m) {
			t = cfg.ci ? m[0].toLowerCase() : m[0];
			out += cfg.kw[t] ? span("keyword", m[0]) : esc(m[0]);
			i += m[0].length; continue;
		}
		out += esc(c); i++;
	}
	return out;
}

document.addEventListener("DOMContentLoaded", function () {
	var nodes = document.querySelectorAll("div#cgit code[data-lang]"), i, el, cfg, text;
	for (i = 0; i < nodes.length; i++) {
		el = nodes[i];
		cfg = LANGS[EXT[(el.getAttribute("data-lang") || "").toLowerCase()]];
		if (!cfg)
			continue;
		text = el.textContent;
		if (!text || text.length > MAX_BYTES)
			continue;
		try {
			el.innerHTML = tokenize(text, cfg);
		} catch (e) {
			/* leave the plain text untouched on any failure */
		}
	}
}, false);

})();