Skip to content

Commit

Permalink
Add code.server
Browse files Browse the repository at this point in the history
  • Loading branch information
pomber committed Sep 7, 2022
1 parent 3c650b9 commit a88fecf
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 2 deletions.
4 changes: 3 additions & 1 deletion packages/bright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
"name": "@code-hike/bright",
"dependencies": {
"import-meta-resolve": "^2.0.0",
"react": "experimental",
"unzipit": "^1.4.0",
"vscode-oniguruma": "^1.0.0",
"vscode-textmate": "^7.0.0"
},
"devDependencies": {
"react": "experimental"
}
}
75 changes: 75 additions & 0 deletions packages/bright/src/code.server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { Suspense } from "react";
import { toTokens } from "./bright";

Code.defaultTheme = "Dracula Soft";
Code.api = "https://bright.codehike.org/api";

async function highlight(code, lang, label) {
const themeResponse = await fetch(`${Code.api}/theme?label=${label}`);
const theme = await themeResponse.json();
const lines = await toTokens(code, lang, theme);

return {
fg: theme.fg,
bg: theme.bg,
lines,
};
}

function InnerCode({ children, lang, theme }) {
const result = useData(`code-${children}-${lang}-${theme}`, () =>
highlight(children, lang, theme)
);

if (result.error) {
return <pre>{result.error}</pre>;
}

const { lines, fg, bg } = result.data;

return (
<pre style={{ background: bg, color: fg }}>
<code>
{lines.map((line, lineIndex) => (
<span key={lineIndex}>
{line.map((token, tokenIndex) => (
<span style={token.style} key={tokenIndex}>
{token.content}
</span>
))}
<br />
</span>
))}
</code>
</pre>
);
}
function Code(props) {
return (
<Suspense fallback={"Loading..."}>
<InnerCode {...props} theme={props.theme || Code.defaultTheme} />
</Suspense>
);
}

export { Code };

const cache = {};
function useData(key, fetcher) {
if (!cache[key]) {
let data;
let error;
let promise;
cache[key] = () => {
if (error !== undefined || data !== undefined) return { data, error };
if (!promise) {
promise = fetcher()
.then((r) => (data = r))
// Convert all errors to plain string for serialization
.catch((e) => (error = e + ""));
}
throw promise;
};
}
return cache[key]();
}
70 changes: 70 additions & 0 deletions packages/bright/src/highlighter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Registry } from "vscode-textmate";
import grammars from "./all-grammars.js";
import * as vscodeOniguruma from "vscode-oniguruma";
import fs from "node:fs/promises";
import { resolve } from "import-meta-resolve";
import { parse } from "./parse.js";

export async function toTokens(code, lang, theme) {
const registry = await createRegistry(grammars, theme);
const scope = flagToScope(lang);
console.log(scope);

const grammar = registry._syncRegistry._grammars[scope];
if (!grammar) {
throw new Error("No grammar for `" + lang);
}
return parse(code, grammar, registry.getColorMap());
}
/** @type {Map<string, Grammar>} */
const registered = new Map();
/** @type {Map<string, string>} */
const names = new Map();
/** @type {Map<string, string>} */
const extensions = new Map();
async function createRegistry(grammars, theme) {
for (const grammar of grammars) {
const scope = grammar.scopeName;
// for (const d of grammar.extensions) extensions.set(d, scope);
for (const d of grammar.names) names.set(d, scope);
registered.set(scope, grammar);
}

const registry = new Registry({
onigLib: createOniguruma(),
async loadGrammar(scopeName) {
return registered.get(scopeName);
},
});
registry.setTheme(theme);

await Promise.all(
[...registered.keys()].map((d) => {
return registry.loadGrammar(d);
})
);

return registry;
}

function flagToScope(flag) {
if (typeof flag !== "string") {
throw new TypeError("Expected `string` for `flag`, got `" + flag + "`");
}

const normal = flag
.toLowerCase()
.replace(/^[ \t]+/, "")
.replace(/\/*[ \t]*$/g, "");

return (
names.get(normal) || extensions.get(normal) || extensions.get("." + normal)
);
}

async function createOniguruma() {
const pkgUrl = await resolve("vscode-oniguruma", import.meta.url);
const wasmBin = await fs.readFile(new URL("onig.wasm", pkgUrl));
await vscodeOniguruma.loadWASM(wasmBin);
return vscodeOniguruma;
}
84 changes: 84 additions & 0 deletions packages/bright/src/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// code from: https://github.com/wooorm/starry-night

// Source: <https://github.com/microsoft/vscode-textmate/blob/9157c7f/src/metadata.ts#L33-L35>
const FONT_STYLE_MASK = 0b0000_0000_0000_0000_0111_1000_0000_0000;
const FOREGROUND_MASK = 0b0000_0000_1111_1111_1000_0000_0000_0000;
const BACKGROUND_MASK = 0b1111_1111_0000_0000_0000_0000_0000_0000;

// Source: <https://github.com/microsoft/vscode-textmate/blob/9157c7f/src/metadata.ts#L37-L42>
const FONT_STYLE_OFFSET = 11;
const FOREGROUND_OFFSET = 15;
const BACKGROUND_OFFSET = 24;

export function parse(value, grammar, colors) {
const search = /\r?\n|\r/g;
/** @type {StackElement|null} */
let stack = null;
let start = 0;

const lines = [];

while (start < value.length) {
const match = search.exec(value);
const end = match ? match.index : value.length;
const line = [];
lines.push(line);

if (start !== end) {
const { tokens, ruleStack } = grammar.tokenizeLine2(
value.slice(start, end),
stack
);
let index = 0;

while (index < tokens.length) {
const tokenStart = start + tokens[index++];
const metadata = tokens[index++];
const tokenEnd = index < tokens.length ? start + tokens[index] : end;
// Source: <https://github.com/microsoft/vscode-textmate/blob/9157c7f/src/metadata.ts#L71-L93>
const fg = (metadata & FOREGROUND_MASK) >>> FOREGROUND_OFFSET;
const bg = (metadata & BACKGROUND_MASK) >>> BACKGROUND_OFFSET;
const fs = (metadata & FONT_STYLE_MASK) >>> FONT_STYLE_OFFSET;

const style = {
color: colors[fg],
// backgroundColor: colors[bg],
...fontStyle(fs),
};
line.push({ content: value.slice(tokenStart, tokenEnd), style });
}

stack = ruleStack;
}

start = end;

if (match) {
start += match[0].length;
}
}

return lines;
}

const FontStyle = {
NotSet: -1,
None: 0,
Italic: 1,
Bold: 2,
Underline: 4,
};

function fontStyle(fs) {
const style = {};
if (fs & FontStyle.Italic) {
style["fontStyle"] = "italic";
}
if (fs & FontStyle.Bold) {
style["fontWeight"] = "bold";
}
if (fs & FontStyle.Underline) {
style["textDecoration"] = "underline";
}
return style;
}
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
work in progress

> the future is bright
1 change: 0 additions & 1 deletion web/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export async function getStaticProps() {
const { labels } = require("../data/themes.json");
console.log(labels);
return {
props: {
labels: labels.map((x) => x.label),
Expand Down

0 comments on commit a88fecf

Please sign in to comment.