diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1181d07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/* +build diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..9b3741f --- /dev/null +++ b/build.ts @@ -0,0 +1,13 @@ +import * as everwild from "./schemes/everwild.ts"; +import * as emacs from "./programs/emacs.ts"; +import * as kitty from "./programs/kitty.ts"; + +const build = async () => { + console.info("Building..."); + await emacs.writeTheme(everwild.emacsDark, "build/emacs/everwild-dark.el"); + await kitty.writeConf(everwild.kittyDark, "build/kitty/everwild-dark.conf"); + + console.info("Done"); +}; + +await build(); diff --git a/deps.ts b/deps.ts new file mode 100644 index 0000000..e69de29 diff --git a/programs/emacs.ts b/programs/emacs.ts new file mode 100644 index 0000000..d06bbc5 --- /dev/null +++ b/programs/emacs.ts @@ -0,0 +1,118 @@ +import { Face, Color, } from "../util/colors.ts" +import { Syntax } from "../util/syntax.ts"; +import { ensureDir } from "../util/fs.ts"; + +export interface Theme { + name: string, + faces: Faces, +} + +const ts = (s: string) => `tree-sitter-hl-face:${s}`; +const fl = (s: string) => `font-lock-${s}-face`; +const syntaxToFaceName: Record = { + attribute: ts("attribute"), + argumentType: ts("type.argument"), + builtinConstant: ts("constant.builtin"), + builtinFunction: ts("function.builtin"), + builtinType: ts("type.builtin"), + builtinVariable: ts("variable.builtin"), + comment: fl("comment"), + constant: fl("constant"), + constructor_: ts("constructor"), + documentaton: fl("doc"), + embedded: ts("embedded"), + escape: ts("escape"), + function: fl("function-name"), + functionCall: ts("function.call"), + keyword: fl("keyword"), + label: ts("label"), + macro: ts("macro"), + method: ts("method"), + methodCall: ts("method.call"), + number: ts("number"), + operator: ts("operator"), + parameter: ts("parameter"), + property: ts("property"), + propertyDefinition: ts("property.definition"), + punctuation: ts("punctuation"), + punctuationBracket: ts("punctuation.bracket"), + punctuationSpecial: ts("punctuation.special"), + punctuationDelimiter: ts("punctuation.delimiter"), + specialFunction: ts("function.special"), + specialString: ts("string.special"), + specialVariable: ts("variable.special"), + string: fl("string"), + super: ts("super"), + tag: ts("tag"), + type: fl("type"), + typeParameter: ts("type.parameter"), + variable: fl("variable-name"), +}; +export const createTheme = ( + name: string, + fi: FacesInput, + s: Partial, +) => { + const faces = Object.entries(syntaxToFaceName).reduce((faces, [sn, fn]) => { + const sn_ = sn as keyof Syntax; + const c = s[sn_]; + if (!c) { + faces[fn] = fi.default; + return faces; + } + + faces[fn] = c; + return faces; + }, {} as Record); + Object.assign(faces, fi); + + return { + name, + faces, + }; +} + +export type Faces = Record; +export type FacesInput = Faces & { + default: Face, +}; + +const themeToString = (theme: Theme) => { + let s = ` +(deftheme ${theme.name} "${theme.name}") +(custom-theme-set-faces + '${theme.name} +`; + for (const [name, face] of Object.entries(theme.faces)) { + s += ` \`(${name} ((t (`; + s += faceToString(face); + s += `))))\n`; + } + s += `)\n`; + + return s; +} + +const faceToString = (face: Face) => { + const t = (name: string, v: string) => `:${name} ${v} `; + const y = (f: (v: T) => string) => { + return (n: string, v: T | null) => { + return v ? t(n, f(v)) : ''; + } + }; + const c = y((v: Color) => `"${v}"`); + + let o = ''; + o += c("background", face.bg); + o += c("foreground", face.fg); + + return o; +}; + +export const writeTheme = async (theme: Theme, path: string) => { + await ensureDir(path); + + const s = themeToString(theme); + + return Deno.writeTextFile(path, s); +}; diff --git a/programs/kitty.ts b/programs/kitty.ts new file mode 100644 index 0000000..6439eb7 --- /dev/null +++ b/programs/kitty.ts @@ -0,0 +1,60 @@ +import { Color } from "../util/colors.ts"; +import { ensureDir } from "../util/fs.ts"; + +export interface ColorPair { + regular: Color, + bright: Color, +} +export const pair = (regular: Color, bright: Color) => ({ regular, bright }); +export interface Colors { + black: ColorPair, + red: ColorPair, + green: ColorPair, + yellow: ColorPair, + blue: ColorPair, + magenta: ColorPair, + cyan: ColorPair, + white: ColorPair, +} +export interface Conf { + foreground: Color, + background: Color, + selectionForeground: Color, + selectionBackground: Color, + cursor: Color, + cursorTextColor: Color | "background", + colors: Colors, +} + +export const writeConf = async (r: Conf, path: string) => { + await ensureDir(path); + + let s = ''; + const l = (n: string, v: string) => `${n} ${v}\n`; + const c = (c: Color) => c; + + s += l('foreground', c(r.foreground)); + s += l('background', c(r.background)); + s += l('selection_foreground', c(r.selectionForeground)); + s += l('selection_background', c(r.selectionBackground)); + s += l('cursor', c(r.foreground)); + s += l('cursor_text_color', 'background'); + s += l('color0', r.colors.black.regular); + s += l('color8', r.colors.black.bright); + s += l('color1', r.colors.red.regular); + s += l('color9', r.colors.red.bright); + s += l('color2', r.colors.green.regular); + s += l('color10', r.colors.green.bright); + s += l('color3', r.colors.yellow.regular); + s += l('color11', r.colors.yellow.bright); + s += l('color4', r.colors.blue.regular); + s += l('color12', r.colors.blue.bright); + s += l('color5', r.colors.magenta.regular); + s += l('color13', r.colors.magenta.bright); + s += l('color6', r.colors.cyan.regular); + s += l('color14', r.colors.cyan.bright); + s += l('color7', r.colors.white.regular); + s += l('color15', r.colors.white.bright); + + return Deno.writeTextFile(path, s); +} diff --git a/schemes/everwild.ts b/schemes/everwild.ts new file mode 100644 index 0000000..6e267eb --- /dev/null +++ b/schemes/everwild.ts @@ -0,0 +1,84 @@ +import { defaultSyntax } from "../util/syntax.ts"; +import { color, fg, fgBg } from "../util/colors.ts"; +import { resolve, ref } from "../util/ouro.ts"; + +import { createTheme } from "../programs/emacs.ts"; +import { pair, Conf } from "../programs/kitty.ts"; + + +// This is a comment +export const palette = { + gray: { + 0: color("#262726"), + 10: color("#EAEBEA"), + }, + green: { + 3: color("#214521"), + 4: color("#285328"), + 6: color("#356E35"), + 8: color("#91CA91"), + 10: color("#C0E1C0"), + }, + yellow: { + 8: color("#D4BF88"), + }, + blue: { + 7: color("#9EBEE6"), + 10: color("#9EBEE6"), + }, + cyan: { + 7: color("#7CDFC5"), + 10: color("#D0F4EA"), + }, + orange: { + 7: color("#DFA77C"), + }, + pink: { + 9: color("#E7B6C3"), + }, + purple: { + 8: color("#D4B6E7"), + 10: color("#E4D0F0"), + }, +}; +const p = palette; + +export const emacsDark = createTheme( + "everwild-dark", + resolve({ + default: fgBg(p.gray[10], p.gray[0]), + "mode-line": fgBg(p.gray[10], p.green[4]), + region: ref("mode-line"), + "vertico-current": ref("region"), + "corfu-current": ref("region"), + }), + resolve({ + ...defaultSyntax, + comment: fg(p.cyan[7]), + type: fg(p.blue[7]), + function: fg(p.green[8]), + constant: fg(p.purple[8]), + string: fg(p.yellow[8]), + variable: fg(p.orange[7]), + keyword: fg(p.pink[9]), + }), +); + +export const kittyDark: Conf = resolve({ + background: p.gray[0], + foreground: p.gray[10], + selectionBackground: p.green[4], + selectionForeground: ref("foreground"), + cursor: ref("foreground"), + cursorTextColor: "background", + colors: { + white: pair(p.gray[10], color("#FFFFFF")), + black: pair(color("#000000"), p.gray[0]), + blue: pair(p.blue[7], p.blue[10]), + cyan: pair(p.cyan[7], p.cyan[10]), + green: pair(p.green[8], p.green[10]), + magenta: pair(p.purple[8], p.purple[10]), + red: pair(p.pink[9], p.pink[9]), + yellow: pair(p.yellow[8], p.yellow[8]), + }, +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..51e0a6c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "noEmit": true, + "strict": true, + "moduleResolution": "node", + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true + } +} diff --git a/util/colors.ts b/util/colors.ts new file mode 100644 index 0000000..15283a4 --- /dev/null +++ b/util/colors.ts @@ -0,0 +1,13 @@ +export type Color = string & { readonly __color: unique symbol }; +export const color = (hex: string) => hex as Color; + +export interface Face { + fg: Color | null, + bg: Color | null, +} +export const createFace = ( + fg: Face["fg"], + bg: Face["bg"], +): Face => ({ fg, bg }); +export const fgBg = createFace; +export const fg = (fg: Face["fg"]) => createFace(fg, null); diff --git a/util/fs.ts b/util/fs.ts new file mode 100644 index 0000000..a477107 --- /dev/null +++ b/util/fs.ts @@ -0,0 +1,12 @@ +const dirname = (path: string) => path.split("/").slice(0, -1).join("/"); + +export const ensureDir = async (path: string) => { + const dir = dirname(path); + try { + await Deno.mkdir(dir, { recursive: true }); + } catch (e) { + if (!(e instanceof Deno.errors.AlreadyExists)) { + throw e; + } + } +} diff --git a/util/ouro.ts b/util/ouro.ts new file mode 100644 index 0000000..87e997e --- /dev/null +++ b/util/ouro.ts @@ -0,0 +1,51 @@ +export type Ref = { __ref: keyof T }; +export const ref = (k: keyof T) => ({ __ref: k }); +const isRef = (t: unknown): t is Ref => + t !== null && typeof t === "object" && "__ref" in t; + +export type Ouro = { + [K in keyof T]: T[K] | Ref +}; + +type Follow = + T[K] extends Ref + ? Follow + : T[K]; + +export type Boros = { + [K in keyof T]: Follow +}; + +export const resolve = (obj: Ouro): Boros => { + const newObj = {} as Partial>; + const keysToProcess = new Set(Object.keys(obj) as (keyof typeof obj)[]); + while (true) { + if (keysToProcess.size === 0) { + break; + } + + const keysToAssign = new Set(); + + let k = [...keysToProcess][0]; + while (true) { + if (keysToAssign.has(k)) { + throw new Error("Infinite loop detected"); + } + + keysToProcess.delete(k); + keysToAssign.add(k); + + const v = newObj[k] ?? obj[k]; + if (!isRef(v)) { + for (const k of keysToAssign) { + newObj[k] = v as Boros[typeof k]; + } + break; + } + + k = v.__ref; + } + } + + return newObj as Boros; +}; diff --git a/util/syntax.ts b/util/syntax.ts new file mode 100644 index 0000000..2771e28 --- /dev/null +++ b/util/syntax.ts @@ -0,0 +1,91 @@ +import { Face } from "./colors.ts"; +import { resolve, ref, Ouro } from "./ouro.ts"; + +export type Syn = Face | null; + +export type BasicSyntax = { + comment: Syn, + documentaton: Syn, + string: Syn, + keyword: Syn, + operator: Syn, + constant: Syn, + function: Syn, + variable: Syn, + punctuation: Syn, + type: Syn, + label: Syn, +}; +export const defaultBasicSyntax = { + comment: null, + documentaton: null, + string: null, + keyword: null, + operator: null, + constant: null, + function: null, + variable: null, + punctuation: null, + type: null, + label: null, +} as Ouro; + +export interface ExtendedSyntax { + tag: Syn, + number: Syn, + method: Syn, + methodCall: Syn, + functionCall: Syn, + property: Syn, + attribute: Syn, + constructor_: Syn, + punctuationSpecial: Syn, + punctuationBracket: Syn, + punctuationDelimiter: Syn, + super: Syn, + builtinType: Syn, + argumentType: Syn, + specialString: Syn, + macro: Syn, + typeParameter: Syn, + specialFunction: Syn, + specialVariable: Syn, + builtinConstant: Syn, + builtinFunction: Syn, + builtinVariable: Syn, + parameter: Syn, + propertyDefinition: Syn, + escape: Syn, + embedded: Syn, +} +export const defaultSyntax: Ouro = { + ...defaultBasicSyntax, + tag: null, + number: ref("constant"), + functionCall: ref("function"), + method: ref("function"), + methodCall: ref("method"), + property: ref("variable"), + attribute: ref("property"), + constructor_: ref("type"), + punctuationSpecial: ref("punctuation"), + punctuationBracket: ref("punctuation"), + punctuationDelimiter: ref("punctuation"), + super: ref("keyword"), + builtinType: ref("type"), + builtinConstant: ref("constant"), + builtinFunction: ref("function"), + builtinVariable: ref("variable"), + argumentType: ref("type"), + embedded: null, + escape: null, + macro: ref("function"), + parameter: ref("variable"), + propertyDefinition: ref("variable"), + specialFunction: ref("function"), + specialString: ref("string"), + specialVariable: ref("variable"), + typeParameter: ref("type"), +}; + +export type Syntax = BasicSyntax & ExtendedSyntax;