1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-10-08 22:52:04 +02:00

chore: add JS formatting with biome

This commit is contained in:
Alec Thomas
2025-07-01 10:16:32 +10:00
parent adeac8f5db
commit b69cd3d846
6 changed files with 152 additions and 115 deletions

View File

@@ -12,6 +12,10 @@ README.md: lexers/*/*.go
tokentype_string.go: types.go
go generate
.PHONY: format-js
format-js:
biome format --write cmd/chromad/static/{index.js,chroma.js}
.PHONY: chromad
chromad: build/chromad

1
bin/.biome-1.9.4.pkg Symbolic link
View File

@@ -0,0 +1 @@
hermit

1
bin/biome Symbolic link
View File

@@ -0,0 +1 @@
.biome-1.9.4.pkg

6
biome.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
"formatter": {
"indentStyle": "space"
}
}

View File

@@ -1,72 +1,79 @@
// chroma.js - TinyGo WASM runtime initialization for Chroma syntax highlighter
// Import wasm_exec.js so that it initialises the Go WASM runtime.
import './wasm_exec.js';
import "./wasm_exec.js";
class ChromaWASM {
constructor() {
this.go = null;
this.wasm = null;
this.ready = false;
this.readyPromise = this.init();
constructor() {
this.go = null;
this.wasm = null;
this.ready = false;
this.readyPromise = this.init();
}
async init() {
try {
// Create a new Go instance
this.go = new Go();
// Load the WASM module
const wasmResponse = await fetch("./static/chroma.wasm");
if (!wasmResponse.ok) {
throw new Error(`Failed to fetch chroma.wasm: ${wasmResponse.status}`);
}
const wasmBytes = await wasmResponse.arrayBuffer();
const wasmModule = await WebAssembly.instantiate(
wasmBytes,
this.go.importObject,
);
this.wasm = wasmModule.instance;
// Run the Go program
this.go.run(this.wasm);
this.ready = true;
console.log("Chroma WASM module initialized successfully");
} catch (error) {
console.error("Failed to initialize Chroma WASM module:", error);
throw error;
}
}
async waitForReady() {
await this.readyPromise;
if (!this.ready) {
throw new Error("Chroma WASM module failed to initialize");
}
}
async highlight(source, lexer, formatter, withClasses) {
await this.waitForReady();
if (typeof window.highlight !== "function") {
throw new Error("highlight function not available from WASM module");
}
async init() {
try {
// Create a new Go instance
this.go = new Go();
// Load the WASM module
const wasmResponse = await fetch('./static/chroma.wasm');
if (!wasmResponse.ok) {
throw new Error(`Failed to fetch chroma.wasm: ${wasmResponse.status}`);
}
const wasmBytes = await wasmResponse.arrayBuffer();
const wasmModule = await WebAssembly.instantiate(wasmBytes, this.go.importObject);
this.wasm = wasmModule.instance;
// Run the Go program
this.go.run(this.wasm);
this.ready = true;
console.log('Chroma WASM module initialized successfully');
} catch (error) {
console.error('Failed to initialize Chroma WASM module:', error);
throw error;
}
}
async waitForReady() {
await this.readyPromise;
if (!this.ready) {
throw new Error('Chroma WASM module failed to initialize');
}
}
async highlight(source, lexer, formatter, withClasses) {
await this.waitForReady();
if (typeof window.highlight !== 'function') {
throw new Error('highlight function not available from WASM module');
}
try {
return window.highlight(source, lexer, formatter, withClasses);
} catch (error) {
console.error('Error calling highlight function:', error);
throw error;
}
try {
return window.highlight(source, lexer, formatter, withClasses);
} catch (error) {
console.error("Error calling highlight function:", error);
throw error;
}
}
}
export function isWasmSupported() {
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
if (
typeof WebAssembly === "object" &&
typeof WebAssembly.instantiate === "function"
) {
// The smallest possible WebAssembly module (magic number + version)
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00),
);
if (module instanceof WebAssembly.Module) {
// Try to instantiate the module to ensure it's truly runnable
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
@@ -78,6 +85,5 @@ export function isWasmSupported() {
return false;
}
// Create global instance, null if WASM is not supported.
export const chroma = isWasmSupported() ? new ChromaWASM() : null;

View File

@@ -2,12 +2,14 @@ import * as Base64 from "./base64.js";
import { chroma } from "./chroma.js";
document.addEventListener("DOMContentLoaded", function () {
var darkMode = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
var style = document.createElement('style');
var ref = document.querySelector('script');
var darkMode =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
var style = document.createElement("style");
var ref = document.querySelector("script");
ref.parentNode.insertBefore(style, ref);
var form = document.getElementById('chroma');
var form = document.getElementById("chroma");
var textArea = form.elements["text"];
var styleSelect = form.elements["style"];
var languageSelect = form.elements["language"];
@@ -16,49 +18,62 @@ document.addEventListener("DOMContentLoaded", function () {
var output = document.getElementById("output");
var htmlCheckbox = document.getElementById("html");
(document.querySelectorAll('.notification .delete') || []).forEach((el) => {
(document.querySelectorAll(".notification .delete") || []).forEach((el) => {
const notification = el.parentNode;
el.addEventListener('click', () => {
el.addEventListener("click", () => {
notification.parentNode.removeChild(notification);
});
});
async function renderServer(formData) {
return (await fetch("api/render", {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json',
},
redirect: 'follow',
referrer: 'no-referrer',
body: JSON.stringify(formData),
})).json();
return (
await fetch("api/render", {
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"X-CSRF-Token": csrfToken,
"Content-Type": "application/json",
},
redirect: "follow",
referrer: "no-referrer",
body: JSON.stringify(formData),
})
).json();
}
async function renderWasm(formData) {
return await chroma.highlight(
formData.text,
formData.language,
formData.style,
formData.classes,
);
return await chroma.highlight(
formData.text,
formData.language,
formData.style,
formData.classes,
);
}
async function render(formData) {
return chroma !== null
? renderWasm(formData)
: renderServer(formData);
return chroma !== null ? renderWasm(formData) : renderServer(formData);
}
// https://stackoverflow.com/a/37697925/7980
function handleTab(e) {
var after, before, end, lastNewLine, changeLength, re, replace, selection, start, val;
if ((e.charCode === 9 || e.keyCode === 9) && !e.altKey && !e.ctrlKey && !e.metaKey) {
var after,
before,
end,
lastNewLine,
changeLength,
re,
replace,
selection,
start,
val;
if (
(e.charCode === 9 || e.keyCode === 9) &&
!e.altKey &&
!e.ctrlKey &&
!e.metaKey
) {
e.preventDefault();
start = this.selectionStart;
end = this.selectionEnd;
@@ -68,14 +83,14 @@ document.addEventListener("DOMContentLoaded", function () {
replace = true;
if (start !== end) {
selection = val.substring(start, end);
if (~selection.indexOf('\n')) {
if (~selection.indexOf("\n")) {
replace = false;
changeLength = 0;
lastNewLine = before.lastIndexOf('\n');
lastNewLine = before.lastIndexOf("\n");
if (!~lastNewLine) {
selection = before + selection;
changeLength = before.length;
before = '';
before = "";
} else {
selection = before.substring(lastNewLine) + selection;
changeLength = before.length - lastNewLine;
@@ -87,9 +102,9 @@ document.addEventListener("DOMContentLoaded", function () {
start--;
changeLength--;
}
selection = selection.replace(re, '$1');
selection = selection.replace(re, "$1");
} else {
selection = selection.replace(/(\n|^)/g, '$1\t');
selection = selection.replace(/(\n|^)/g, "$1\t");
start++;
changeLength++;
}
@@ -99,7 +114,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
}
if (replace && !e.shiftKey) {
this.value = before + '\t' + after;
this.value = before + "\t" + after;
this.selectionStart = this.selectionEnd = start + 1;
}
}
@@ -109,7 +124,8 @@ document.addEventListener("DOMContentLoaded", function () {
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this, args = arguments;
var context = this;
var args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
@@ -123,11 +139,11 @@ document.addEventListener("DOMContentLoaded", function () {
function getFormJSON() {
return {
"language": languageSelect.value,
"style": styleSelect.value,
"text": textArea.value,
"classes": htmlCheckbox.checked,
}
language: languageSelect.value,
style: styleSelect.value,
text: textArea.value,
classes: htmlCheckbox.checked,
};
}
async function update(event) {
@@ -145,12 +161,12 @@ document.addEventListener("DOMContentLoaded", function () {
output.innerHTML = value.html;
}
} catch (error) {
console.error('Error highlighting code:', error);
console.error("Error highlighting code:", error);
// Fallback: display plain text
if (htmlCheckbox.checked) {
output.innerText = textArea.value;
} else {
output.innerHTML = '<pre>' + textArea.value + '</pre>';
output.innerHTML = "<pre>" + textArea.value + "</pre>";
}
}
@@ -160,7 +176,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
function share(event) {
let data = JSON.stringify(getFormJSON())
let data = JSON.stringify(getFormJSON());
data = Base64.encodeURI(data);
location.hash = "#" + data;
try {
@@ -172,26 +188,29 @@ document.addEventListener("DOMContentLoaded", function () {
}
if (location.hash) {
let json = Base64.decode(location.hash.substring(1))
let json = Base64.decode(location.hash.substring(1));
json = JSON.parse(json);
textArea.value = json.text;
languageSelect.value = json.language;
styleSelect.value = json.style;
htmlCheckbox.checked = json.classes;
update(new Event('change'));
} else if (darkMode) {
styleSelect.value = "monokai";
update(new Event("change"));
update(new Event("change"));
} else if (darkMode) {
styleSelect.value = "monokai";
update(new Event("change"));
}
var eventHandler = (event) => update(event);
var debouncedEventHandler = debounce(eventHandler, chroma === null ? 250 : 100);
var debouncedEventHandler = debounce(
eventHandler,
chroma === null ? 250 : 100,
);
languageSelect.addEventListener('change', eventHandler);
styleSelect.addEventListener('change', eventHandler);
htmlCheckbox.addEventListener('change', eventHandler);
copyButton.addEventListener('click', share);
languageSelect.addEventListener("change", eventHandler);
styleSelect.addEventListener("change", eventHandler);
htmlCheckbox.addEventListener("change", eventHandler);
copyButton.addEventListener("click", share);
textArea.addEventListener('keydown', handleTab);
textArea.addEventListener('change', debouncedEventHandler);
textArea.addEventListener("keydown", handleTab);
textArea.addEventListener("change", debouncedEventHandler);
});