[pritty] Initial commit

This commit is contained in:
soup 2025-01-17 11:41:35 -05:00
parent 4f589a07af
commit 116dc232a8
Signed by: soup
SSH key fingerprint: SHA256:GYxje8eQkJ6HZKzVWDdyOUF1TyDiprruGhE0Ym8qYDY
6 changed files with 651 additions and 0 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
**/build/*
**/target/*
pritty/outputs/*

245
pritty/colors/flexoki.toml Normal file
View file

@ -0,0 +1,245 @@
name = "flexoki"
[palette]
black = "#100F0F"
paper = "#FFFCF0"
base-50 = "#F2F0E5"
base-100 = "#E6E4D9"
base-150 = "#DAD8CE"
base-200 = "#CECDC3"
base-300 = "#B7B5AC"
base-400 = "#9F9D96"
base-500 = "#878580"
base-600 = "#6F6E69"
base-700 = "#575653"
base-800 = "#403E3C"
base-850 = "#343331"
base-900 = "#282726"
base-950 = "#1C1B1A"
red-50 = "#FFE1D5"
red-100 = "#FFCABB"
red-150 = "#FDB2A2"
red-200 = "#F89A8A"
red-300 = "#E8705F"
red-400 = "#D14D41"
red-500 = "#C03E35"
red-600 = "#AF3029"
red-700 = "#942822"
red-800 = "#6C201C"
red-850 = "#551B18"
red-900 = "#3E1715"
red-950 = "#261312"
orange-50 = "#FFE7CE"
orange-100 = "#FED3AF"
orange-150 = "#FCC192"
orange-200 = "#F9AE77"
orange-300 = "#EC8B49"
orange-400 = "#DA702C"
orange-500 = "#CB6120"
orange-600 = "#BC5215"
orange-700 = "#9D4310"
orange-800 = "#71320D"
orange-850 = "#59290D"
orange-900 = "#40200D"
orange-950 = "#27180E"
yellow-50 = "#FAEEC6"
yellow-100 = "#F6E2A0"
yellow-150 = "#F1D67E"
yellow-200 = "#ECCB60"
yellow-300 = "#DFB431"
yellow-400 = "#D0A215"
yellow-500 = "#BE9207"
yellow-600 = "#AD8301"
yellow-700 = "#8E6B01"
yellow-800 = "#664D01"
yellow-850 = "#503D02"
yellow-900 = "#3A2D04"
yellow-950 = "#241E08"
green-50 = "#EDEECF"
green-100 = "#DDE2B2"
green-150 = "#CDD597"
green-200 = "#BEC97E"
green-300 = "#A0AF54"
green-400 = "#879A39"
green-500 = "#768D21"
green-600 = "#66800B"
green-700 = "#536907"
green-800 = "#3D4C07"
green-850 = "#313D07"
green-900 = "#252D09"
green-950 = "#1A1E0C"
cyan-50 = "#DDF1E4"
cyan-100 = "#BFE8D9"
cyan-150 = "#A2DECE"
cyan-200 = "#87D3C3"
cyan-300 = "#5ABDAC"
cyan-400 = "#3AA99F"
cyan-500 = "#2F968D"
cyan-600 = "#24837B"
cyan-700 = "#1C6C66"
cyan-800 = "#164F4A"
cyan-850 = "#143F3C"
cyan-900 = "#122F2C"
cyan-950 = "#101F1D"
blue-50 = "#E1ECEB"
blue-100 = "#C6DDE8"
blue-150 = "#ABCFE2"
blue-200 = "#92BFDB"
blue-300 = "#66A0C8"
blue-400 = "#4385BE"
blue-500 = "#3171B2"
blue-600 = "#205EA6"
blue-700 = "#1A4F8C"
blue-800 = "#163B66"
blue-850 = "#133051"
blue-900 = "#12253B"
blue-950 = "#101A24"
purple-50 = "#F0EAEC"
purple-100 = "#E2D9E9"
purple-150 = "#D3CAE6"
purple-200 = "#C4B9E0"
purple-300 = "#A699D0"
purple-400 = "#8B7EC8"
purple-500 = "#735EB5"
purple-600 = "#5E409D"
purple-700 = "#4F3685"
purple-800 = "#3C2A62"
purple-850 = "#31234E"
purple-900 = "#261C39"
purple-950 = "#1A1623"
magenta-50 = "#FEE4E5"
magenta-100 = "#FCCFDA"
magenta-150 = "#F9B9CF"
magenta-200 = "#F4A4C2"
magenta-300 = "#E47DA8"
magenta-400 = "#CE5D97"
magenta-500 = "#B74583"
magenta-600 = "#A02F6F"
magenta-700 = "#87285E"
magenta-800 = "#641F46"
magenta-850 = "#4F1B39"
magenta-900 = "#39172B"
magenta-950 = "#24131D"
[dark.backgrounds]
primary = "@black"
secondary = "@base-950"
selection = "@base-800"
status-bar-active = "@base-900"
status-bar-inactive = "@black"
cursor = "@base-200"
[dark.borders]
default = "@base-900"
hovered = "@base-850"
active = "@base-800"
[dark.text]
primary = "@base-200"
muted = "@base-500"
faint = "@base-700"
error = "@red-400"
warning = "@orange-400"
success = "@green-400"
link = "@cyan-400"
selection = "@base-200"
cursor = "@black"
[dark.syntax]
operator = "@base-500"
import = "@red-400"
function = "@orange-400"
constant = "@yellow-400"
keyword = "@green-400"
string = "@cyan-400"
identifier = "@blue-400"
number = "@purple-400"
comment = "@base-700"
macro = "@magenta-400"
other = "@magenta-400"
[dark.terminal-16]
bright-black = "@base-600"
bright-red = "@red-400"
bright-green = "@green-400"
bright-yellow = "@yellow-400"
bright-blue = "@blue-400"
bright-magenta = "@magenta-400"
bright-cyan = "@cyan-400"
bright-white = "@base-200"
black = "@black"
red = "@red-600"
green = "@green-600"
yellow = "@yellow-600"
blue = "@blue-600"
magenta = "@magenta-600"
cyan = "@cyan-600"
white = "@base-500"
[light.backgrounds]
primary = "@paper"
secondary = "@base-50"
selection = "@base-200"
status-bar-active = "@base-100"
status-bar-inactive = "@paper"
cursor = "@black"
[light.borders]
default = "@base-900"
hovered = "@base-850"
active = "@base-800"
[light.text]
primary = "@black"
muted = "@base-600"
faint = "@base-300"
error = "@red-600"
warning = "@orange-600"
success = "@green-600"
link = "@cyan-600"
selection = "@black"
cursor = "@black"
[light.syntax]
operator = "@base-500"
import = "@red-600"
function = "@orange-600"
constant = "@yellow-600"
keyword = "@green-600"
string = "@cyan-600"
identifier = "@blue-600"
number = "@purple-600"
comment = "@base-300"
macro = "@magenta-600"
other = "@magenta-600"
[light.terminal-16]
bright-black = "@base-600"
red = "@red-400"
green = "@green-400"
yellow = "@yellow-400"
blue = "@blue-400"
magenta = "@magenta-400"
cyan = "@cyan-400"
bright-white = "@base-200"
black = "@black"
bright-red = "@red-600"
bright-green = "@green-600"
bright-yellow = "@yellow-600"
bright-blue = "@blue-600"
bright-magenta = "@magenta-600"
bright-cyan = "@cyan-600"
white = "@base-500"

24
pritty/deno.jsonc Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"checkJs": true,
"lib": [
"esnext",
"dom",
"dom.iterable",
"deno.window",
"deno.unstable"
]
},
"tasks": {
"check": "deno check src/main.ts"
},
"imports": {
"@std/toml": "jsr:@std/toml@^1.0.2"
},
"fmt": {
"semiColons": true,
"singleQuote": true,
"useTabs": true,
"lineWidth": 90
}
}

23
pritty/deno.lock Normal file
View file

@ -0,0 +1,23 @@
{
"version": "4",
"specifiers": {
"jsr:@std/collections@^1.0.9": "1.0.9",
"jsr:@std/toml@^1.0.2": "1.0.2"
},
"jsr": {
"@std/collections@1.0.9": {
"integrity": "4f58104ead08a04a2199374247f07befe50ba01d9cca8cbb23ab9a0419921e71"
},
"@std/toml@1.0.2": {
"integrity": "5892ba489c5b512265a384238a8fe8dddbbb9498b4b210ef1b9f0336a423a39b",
"dependencies": [
"jsr:@std/collections"
]
}
},
"workspace": {
"dependencies": [
"jsr:@std/toml@^1.0.2"
]
}
}

32
pritty/scripts/colortest-16.sh Executable file
View file

@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Default 16 ANSI color names
color_names=(
"Black"
"Red"
"Green"
"Yellow"
"Blue"
"Magenta"
"Cyan"
"White"
"Bright Black"
"Bright Red"
"Bright Green"
"Bright Yellow"
"Bright Blue"
"Bright Magenta"
"Bright Cyan"
"Bright White"
)
echo "16-color palette with indices & names:"
for c in {0..15}; do
# Print the color block
printf "\033[38;5;%sm%2d: %-13s\033[0m " "$c" "$c" "${color_names[$c]}"
# New line every 4 colors
if (( (c+1) % 4 == 0 )); then
echo
fi
done
echo

View file

@ -0,0 +1,326 @@
import * as toml from '@std/toml';
type OutputType = 'ghostty' | 'emacs';
type Output = {
outputType: OutputType;
dir: string;
};
const USAGE = `
pritty <input_path> <output_type:output_dir>...
`;
function printUsageAndDie() {
console.log(USAGE);
Deno.exit(1);
}
type RawPalette = {
[k: string]: string | RawPalette;
};
type Palette = Record<string, string>;
function flattenPalette(
input: RawPalette,
prefix: string = '',
output: Record<string, string> = {},
) {
for (const key in input) {
const value = input[key];
if (typeof value === 'object') {
flattenPalette(value, `${prefix}${key}.`, output);
} else {
output[`${prefix}${key}`] = value;
}
}
return output;
}
type TableEntryColor = `#${string}`;
type TableEntryRef = `@${string}`;
type TableEntry = TableEntryColor | TableEntryRef;
type Tables = Partial<{
backgrounds: Partial<{
primary: TableEntry;
secondary: TableEntry;
'status-bar-active': TableEntry;
'status-bar-inactive': TableEntry;
}>;
borders: Partial<{
default: TableEntry;
hovered: TableEntry;
active: TableEntry;
}>;
text: Partial<{
primary: TableEntry;
muted: TableEntry;
faint: TableEntry;
error: TableEntry;
warning: TableEntry;
success: TableEntry;
link: TableEntry;
}>;
syntax: Partial<{
operator: TableEntry;
import: TableEntry;
function: TableEntry;
constant: TableEntry;
keyword: TableEntry;
string: TableEntry;
identifier: TableEntry;
number: TableEntry;
macro: TableEntry;
other: TableEntry;
}>;
'terminal-16': Partial<{
black: TableEntry;
red: TableEntry;
green: TableEntry;
yellow: TableEntry;
blue: TableEntry;
magenta: TableEntry;
cyan: TableEntry;
white: TableEntry;
'bright-black': TableEntry;
'bright-red': TableEntry;
'bright-green': TableEntry;
'bright-yellow': TableEntry;
'bright-blue': TableEntry;
'bright-magenta': TableEntry;
'bright-cyan': TableEntry;
'bright-white': TableEntry;
}>;
}>;
function get(entry: TableEntry | undefined, palette: Palette) {
if (entry === undefined) {
return undefined;
}
if (entry.startsWith('@')) {
const c = palette[entry.substring(1)];
if (c === undefined) {
throw new Error(`Undefined reference ${entry}`);
}
return c;
}
return entry;
}
function bindGet(palette: Palette) {
return (entry: TableEntry | undefined) => get(entry, palette);
}
async function generateGhostty(
dir: string,
name: string,
variantName: string,
tables: Tables,
palette: Palette,
) {
let output = '';
const get = bindGet(palette);
const w = (prop: string, v: string | undefined) => {
if (v !== undefined) {
output += `${prop} = ${v}\n`;
}
};
const p = (index: number, v: string | undefined) => {
if (v !== undefined) {
output += `palette = ${index}=${v}\n`;
}
};
w('background', get(tables['backgrounds']?.['primary']));
w('foreground', get(tables['text']?.['primary']));
w('selection-background', get(tables['backgrounds']?.['selection']));
w('selection-foreground', get(tables['text']?.['selection']));
p(0, get(tables['terminal-16']?.['black']));
p(1, get(tables['terminal-16']?.['red']));
p(2, get(tables['terminal-16']?.['green']));
p(3, get(tables['terminal-16']?.['yellow']));
p(4, get(tables['terminal-16']?.['blue']));
p(5, get(tables['terminal-16']?.['magenta']));
p(6, get(tables['terminal-16']?.['cyan']));
p(7, get(tables['terminal-16']?.['white']));
p(8, get(tables['terminal-16']?.['bright-black']));
p(9, get(tables['terminal-16']?.['bright-red']));
p(10, get(tables['terminal-16']?.['bright-green']));
p(11, get(tables['terminal-16']?.['bright-yellow']));
p(12, get(tables['terminal-16']?.['bright-blue']));
p(13, get(tables['terminal-16']?.['bright-magenta']));
p(14, get(tables['terminal-16']?.['bright-cyan']));
p(15, get(tables['terminal-16']?.['bright-white']));
const path = `${dir}/${name}-${variantName}.ghostty`;
await Deno.writeTextFile(path, output);
}
async function generateEmacs(
dir: string,
name: string,
variantName: string,
tables: Tables,
palette: Palette,
) {
let output = `(deftheme ${name}-${variantName})\n`;
output += `(custom-theme-set-faces '${name}-${variantName}\n`;
const get = bindGet(palette);
const f = (faceName: string, props: Record<string, boolean | string | undefined>) => {
let out = '';
for (const [prop, value] of Object.entries(props)) {
if (value !== undefined) {
if (out === '') {
out += ` \`(${faceName} ((t (`;
}
if (typeof value === 'string') {
out += `:${prop} "${value}" `;
} else if (typeof value === 'boolean') {
const v = value ? 't' : 'nil';
out += `:${prop} ${v} `;
}
}
}
if (out !== '') {
out += '))))\n';
}
output += out;
};
f('default', {
foreground: get(tables['text']?.['primary']),
background: get(tables['backgrounds']?.['primary']),
});
f('mode-line-active', {
foreground: get(tables['text']?.['primary']),
background: get(tables['backgrounds']?.['status-bar-active']),
box: true,
});
f('mode-line-inactive', {
foreground: get(tables['text']?.['primary']),
background: get(tables['backgrounds']?.['status-bar-inactive']),
box: true,
});
f('cursor', {
foreground: get(tables['text']?.['cursor']),
background: get(tables['backgrounds']?.['cursor']),
});
f('region', {
foreground: get(tables['text']?.['selection']),
background: get(tables['backgrounds']?.['selection']),
});
f('vertico-current', {
foreground: get(tables['text']?.['selection']),
background: get(tables['backgrounds']?.['selection']),
});
f('whitespace-tab', { foreground: get(tables['text']?.['faint']) });
const s = (faceName: string, syntaxEntry: string) => {
const v = get(tables['syntax']?.[syntaxEntry]) ?? get(tables['text']?.[syntaxEntry]);
f(faceName, { foreground: v });
};
const syntaxMappings = [
['font-lock-type-face', 'identifier'],
['font-lock-constant-face', 'constant'],
['font-lock-builtin-face', 'other'],
['font-lock-preprocessor-face', 'macro'],
['font-lock-doc-face', 'comment'],
['font-lock-function-name-face', 'function'],
['font-lock-keyword-face', 'keyword'],
['font-lock-variable-name-face', 'identifier'],
['font-lock-string-face', 'string'],
['font-lock-variable-use-face', 'identifier'],
['font-lock-negation-char-face', 'operator'],
['font-lock-property-use-face', 'identifier'],
['font-lock-punctuation-face', 'operator'],
['font-lock-doc-markup-face', 'comment'],
['font-lock-comment-delimiter-face', 'comment'],
['font-lock-delimiter-face', 'operator'],
['font-lock-number-face', 'constant'],
['font-lock-function-call-face', 'function'],
['font-lock-comment-face', 'comment'],
['font-lock-operator-face', 'operator'],
['font-lock-property-name-face', 'identifier'],
['font-lock-misc-punctuation-face', 'operator'],
['font-lock-escape-face', 'other'],
['font-lock-warning-face', 'warning'],
['font-lock-error-face', 'error'],
] as const;
for (const [faceName, syntaxEntry] of syntaxMappings) {
s(faceName, syntaxEntry);
}
output += `)\n`;
const path = `${dir}/${name}-${variantName}-theme.el`;
await Deno.writeTextFile(path, output);
}
async function generateOutput(
output: Output,
name: string,
variants: Record<string, Tables>,
palette: Palette,
) {
const { outputType, dir } = output;
await Deno.mkdir(dir, { recursive: true });
const genFunctions = {
'ghostty': generateGhostty,
'emacs': generateEmacs,
};
const genFunction = genFunctions[outputType];
if (genFunction === undefined) {
throw new Error(`Unsupported output type "${outputType}"`);
}
for (const [variantName, tables] of Object.entries(variants)) {
await genFunction(dir, name, variantName, tables, palette);
}
}
async function main() {
const inputPath = Deno.args[1];
if (inputPath === undefined) {
printUsageAndDie();
}
const outputs_ = Deno.args.slice(2);
if (outputs_.length === 0) {
printUsageAndDie();
}
const outputs: Output[] = outputs_.map((o) => {
const [outputType, dir] = o.split(':');
return { outputType: outputType as OutputType, dir };
});
const input = await Deno.readTextFile(inputPath);
const parsed = toml.parse(input);
const { name, palette: palette_, ...variants } = parsed;
const palette = flattenPalette(palette_ as RawPalette);
for (const output of outputs) {
await generateOutput(
output,
name as string,
variants as Record<string, Tables>,
palette,
);
}
}
await main();