247 lines
7.8 KiB
JavaScript
247 lines
7.8 KiB
JavaScript
/**
|
|
* Named UI themes: REPL ANSI tokens + LS_COLORS strings for Bare OS.
|
|
*/
|
|
|
|
import {
|
|
bareDefaultLsColorsString,
|
|
bareParseDircolorsDatabase,
|
|
bareSerializeLsColors
|
|
} from 'bare-os-lscolors'
|
|
|
|
/** @typedef {{ colors: Record<string, string>, lsColors?: string }} BareOsThemePreset */
|
|
|
|
/** @type {Record<string, BareOsThemePreset>} */
|
|
export const BARE_OS_THEME_PRESETS = {
|
|
default: {
|
|
colors: {
|
|
prompt: '\x1b[32m',
|
|
command: '\x1b[36m',
|
|
path: '\x1b[33m',
|
|
envset: '\x1b[35m',
|
|
envunset: '\x1b[31m',
|
|
ghost: '\x1b[90m',
|
|
search: '\x1b[36m'
|
|
}
|
|
},
|
|
dracula: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;80;250;123m',
|
|
command: '\x1b[38;2;139;233;253m',
|
|
path: '\x1b[38;2;241;250;140m',
|
|
envset: '\x1b[38;2;189;147;249m',
|
|
envunset: '\x1b[38;2;255;85;85m',
|
|
ghost: '\x1b[38;2;98;114;164m',
|
|
search: '\x1b[38;2;139;233;253m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;36:ln=01;35:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;33'
|
|
},
|
|
nord: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;163;190;140m',
|
|
command: '\x1b[38;2;136;192;208m',
|
|
path: '\x1b[38;2;235;203;139m',
|
|
envset: '\x1b[38;2;180;142;173m',
|
|
envunset: '\x1b[38;2;191;97;106m',
|
|
ghost: '\x1b[38;2;76;86;106m',
|
|
search: '\x1b[38;2;136;192;208m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
|
|
},
|
|
solarized_dark: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;133;153;0m',
|
|
command: '\x1b[38;2;38;139;210m',
|
|
path: '\x1b[38;2;181;137;0m',
|
|
envset: '\x1b[38;2;108;113;196m',
|
|
envunset: '\x1b[38;2;220;50;47m',
|
|
ghost: '\x1b[38;2;88;110;117m',
|
|
search: '\x1b[38;2;38;139;210m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
|
|
},
|
|
gruvbox_dark: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;184;187;38m',
|
|
command: '\x1b[38;2;131;165;152m',
|
|
path: '\x1b[38;2;250;189;47m',
|
|
envset: '\x1b[38;2;211;134;155m',
|
|
envunset: '\x1b[38;2;251;73;52m',
|
|
ghost: '\x1b[38;2;102;92;84m',
|
|
search: '\x1b[38;2;131;165;152m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
|
|
},
|
|
catppuccin_mocha: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;166;227;161m',
|
|
command: '\x1b[38;2;137;180;250m',
|
|
path: '\x1b[38;2;249;226;175m',
|
|
envset: '\x1b[38;2;203;166;247m',
|
|
envunset: '\x1b[38;2;243;139;168m',
|
|
ghost: '\x1b[38;2;108;112;134m',
|
|
search: '\x1b[38;2;137;180;250m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;36:ln=01;35:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;33'
|
|
},
|
|
tokyo_night: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;158;206;106m',
|
|
command: '\x1b[38;2;122;162;247m',
|
|
path: '\x1b[38;2;224;163;82m',
|
|
envset: '\x1b[38;2;187;154;247m',
|
|
envunset: '\x1b[38;2;247;118;142m',
|
|
ghost: '\x1b[38;2;86;95;137m',
|
|
search: '\x1b[38;2;122;162;247m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
|
|
},
|
|
github_dark: {
|
|
colors: {
|
|
prompt: '\x1b[38;2;63;185;80m',
|
|
command: '\x1b[38;2;121;192;255m',
|
|
path: '\x1b[38;2;210;153;34m',
|
|
envset: '\x1b[38;2;188;140;255m',
|
|
envunset: '\x1b[38;2;248;81;73m',
|
|
ghost: '\x1b[38;2;110;118;129m',
|
|
search: '\x1b[38;2;121;192;255m'
|
|
},
|
|
lsColors:
|
|
'no=00:fi=00:di=01;34:ln=01;36:pi=33:so=01;35:bd=33;01:cd=33;01:or=31;01:mi=00:su=37;41:sg=30;43:tw=30;42:ow=34;43:st=37;44:ex=01;32'
|
|
}
|
|
}
|
|
|
|
BARE_OS_THEME_PRESETS.default.lsColors = bareDefaultLsColorsString()
|
|
|
|
/**
|
|
* Downgrade truecolor REPL sequences in BARE_OS_COLOR_* for limited terminals.
|
|
* @param {Record<string, unknown>} env
|
|
*/
|
|
export function bareOsDowngradeReplColorsForDepth(env) {
|
|
if (!env || typeof env !== 'object') return
|
|
const depth = String(env.BARE_OS_COLOR_DEPTH ?? 'truecolor')
|
|
.toLowerCase()
|
|
.trim()
|
|
if (
|
|
depth === '' ||
|
|
depth === 'truecolor' ||
|
|
depth === '24bit' ||
|
|
depth === 'rgb'
|
|
)
|
|
return
|
|
|
|
const keys = Object.keys(env).filter((k) => k.startsWith('BARE_OS_COLOR_'))
|
|
for (const k of keys) {
|
|
const raw = env[k]
|
|
if (raw == null) continue
|
|
let v = String(raw)
|
|
if (depth === '256' || depth === '8bit') {
|
|
v = v.replace(
|
|
/\x1b\[38;2;(\d+);(\d+);(\d+)m/g,
|
|
(_, rs, gs, bs) => {
|
|
const r = Math.min(255, Math.max(0, Number(rs)))
|
|
const g = Math.min(255, Math.max(0, Number(gs)))
|
|
const b = Math.min(255, Math.max(0, Number(bs)))
|
|
const ri = Math.min(5, Math.round((r / 255) * 5))
|
|
const gi = Math.min(5, Math.round((g / 255) * 5))
|
|
const bi = Math.min(5, Math.round((b / 255) * 5))
|
|
const idx = 16 + 36 * ri + 6 * gi + bi
|
|
return '\x1b[38;5;' + idx + 'm'
|
|
}
|
|
)
|
|
} else if (depth === '16' || depth === '8' || depth === 'ansi') {
|
|
v = v.replace(
|
|
/\x1b\[38;2;(\d+);(\d+);(\d+)m/g,
|
|
(_, rs, gs, bs) => {
|
|
const r = Number(rs)
|
|
const g = Number(gs)
|
|
const b = Number(bs)
|
|
const lum = 0.299 * r + 0.587 * g + 0.114 * b
|
|
const bold = lum > 195 ? '1;' : ''
|
|
const max = Math.max(r, g, b)
|
|
if (max < 48) return '\x1b[90m'
|
|
if (r > g * 1.25 && r > b * 1.25) return '\x1b[' + bold + '31m'
|
|
if (g > r * 1.25 && g > b * 1.25) return '\x1b[' + bold + '32m'
|
|
if (b > r * 1.25 && b > g * 1.25) return '\x1b[' + bold + '34m'
|
|
if (r > 200 && g > 200 && b < 120) return '\x1b[' + bold + '33m'
|
|
if (g > 180 && b > 180) return '\x1b[' + bold + '36m'
|
|
if (r > 180 && b > 180) return '\x1b[' + bold + '35m'
|
|
return '\x1b[' + bold + '37m'
|
|
}
|
|
)
|
|
}
|
|
env[k] = v
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns {string[]}
|
|
*/
|
|
export function bareOsListThemeNames() {
|
|
return Object.keys(BARE_OS_THEME_PRESETS).sort()
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {BareOsThemePreset | null}
|
|
*/
|
|
export function bareOsGetThemePreset(name) {
|
|
const k = String(name || 'default')
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/\s+/g, '_')
|
|
return BARE_OS_THEME_PRESETS[k] || null
|
|
}
|
|
|
|
/**
|
|
* Apply active theme to ctx.vfs.env (REPL colors + LS_COLORS when not user-locked).
|
|
* @param {Record<string, unknown>} ctx
|
|
*/
|
|
export async function applyBareOsThemeFromEnv(ctx) {
|
|
const vfs = ctx.vfs
|
|
const env = vfs && vfs.env
|
|
if (!env || typeof env !== 'object') return
|
|
|
|
const rawName = env.BARE_OS_THEME
|
|
const themeKey = String(rawName != null && rawName !== '' ? rawName : 'default')
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/\s+/g, '_')
|
|
|
|
const preset =
|
|
BARE_OS_THEME_PRESETS[themeKey] || BARE_OS_THEME_PRESETS.default
|
|
|
|
for (const [short, seq] of Object.entries(preset.colors || {})) {
|
|
env['BARE_OS_COLOR_' + short.toUpperCase()] = seq
|
|
}
|
|
|
|
const locked = env.BARE_OS_LS_COLORS_LOCKED === '1'
|
|
const hadLsColors =
|
|
env.LS_COLORS != null && String(env.LS_COLORS).trim() !== ''
|
|
|
|
const dcPath = env.BARE_OS_DIRCOLORS
|
|
if (dcPath && String(dcPath).trim() && !locked) {
|
|
try {
|
|
const buf = await vfs.readFile(dcPath)
|
|
if (buf && ctx.b4a) {
|
|
const text = ctx.b4a.toString(buf)
|
|
const term = env.TERM || ''
|
|
const db = bareParseDircolorsDatabase(text, term)
|
|
env.LS_COLORS = bareSerializeLsColors(db)
|
|
}
|
|
} catch {
|
|
/* keep existing LS_COLORS */
|
|
}
|
|
} else if (!hadLsColors && !locked) {
|
|
env.LS_COLORS =
|
|
preset.lsColors != null ? preset.lsColors : bareDefaultLsColorsString()
|
|
}
|
|
|
|
bareOsDowngradeReplColorsForDepth(env)
|
|
}
|