opencode/packages/tui/src/theme/system.ts

144 lines
4.6 KiB
TypeScript

import { RGBA, type TerminalColors } from "@opentui/core"
import { ansiToRgba, tint } from "./color"
export function terminalMode(colors: TerminalColors): "dark" | "light" | undefined {
const bg = colors.defaultBackground
if (!bg) return
const { r, g, b } = RGBA.fromHex(bg)
return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? "light" : "dark"
}
export function generateSystem(colors: TerminalColors, mode: "dark" | "light") {
const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
const transparent = RGBA.fromValues(bg.r, bg.g, bg.b, 0)
const isDark = mode === "dark"
const col = (index: number) => {
const value = colors.palette[index]
if (value) return RGBA.fromHex(value)
return ansiToRgba(index)
}
const grays = generateGrayScale(bg, isDark)
const textMuted = generateMutedTextColor(bg, isDark)
const ansiColors = {
red: col(1),
green: col(2),
yellow: col(3),
blue: col(4),
magenta: col(5),
cyan: col(6),
redBright: col(9),
greenBright: col(10),
}
const diffAlpha = isDark ? 0.22 : 0.14
const diffAddedBg = tint(bg, ansiColors.green, diffAlpha)
const diffRemovedBg = tint(bg, ansiColors.red, diffAlpha)
const diffContextBg = grays[2]
const diffAddedLineNumberBg = tint(diffContextBg, ansiColors.green, diffAlpha)
const diffRemovedLineNumberBg = tint(diffContextBg, ansiColors.red, diffAlpha)
return {
theme: {
primary: ansiColors.cyan,
secondary: ansiColors.magenta,
accent: ansiColors.cyan,
error: ansiColors.red,
warning: ansiColors.yellow,
success: ansiColors.green,
info: ansiColors.cyan,
text: fg,
textMuted,
selectedListItemText: bg,
background: transparent,
backgroundPanel: grays[2],
backgroundElement: grays[3],
backgroundMenu: grays[3],
borderSubtle: grays[6],
border: grays[7],
borderActive: grays[8],
diffAdded: ansiColors.green,
diffRemoved: ansiColors.red,
diffContext: grays[7],
diffHunkHeader: grays[7],
diffHighlightAdded: ansiColors.greenBright,
diffHighlightRemoved: ansiColors.redBright,
diffAddedBg,
diffRemovedBg,
diffContextBg,
diffLineNumber: textMuted,
diffAddedLineNumberBg,
diffRemovedLineNumberBg,
markdownText: fg,
markdownHeading: fg,
markdownLink: ansiColors.blue,
markdownLinkText: ansiColors.cyan,
markdownCode: ansiColors.green,
markdownBlockQuote: ansiColors.yellow,
markdownEmph: ansiColors.yellow,
markdownStrong: fg,
markdownHorizontalRule: grays[7],
markdownListItem: ansiColors.blue,
markdownListEnumeration: ansiColors.cyan,
markdownImage: ansiColors.blue,
markdownImageText: ansiColors.cyan,
markdownCodeBlock: fg,
syntaxComment: textMuted,
syntaxKeyword: ansiColors.magenta,
syntaxFunction: ansiColors.blue,
syntaxVariable: fg,
syntaxString: ansiColors.green,
syntaxNumber: ansiColors.yellow,
syntaxType: ansiColors.cyan,
syntaxOperator: ansiColors.cyan,
syntaxPunctuation: fg,
},
}
}
function generateGrayScale(bg: RGBA, isDark: boolean): Record<number, RGBA> {
const grays: Record<number, RGBA> = {}
const bgR = bg.r * 255
const bgG = bg.g * 255
const bgB = bg.b * 255
const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
for (let i = 1; i <= 12; i++) {
const factor = i / 12
if (isDark && luminance < 10) {
const gray = Math.floor(factor * 0.4 * 255)
grays[i] = RGBA.fromInts(gray, gray, gray)
continue
}
if (!isDark && luminance > 245) {
const gray = Math.floor(255 - factor * 0.4 * 255)
grays[i] = RGBA.fromInts(gray, gray, gray)
continue
}
const next = isDark ? luminance + (255 - luminance) * factor * 0.4 : luminance * (1 - factor * 0.4)
const ratio = next / luminance
grays[i] = RGBA.fromInts(
Math.floor(Math.min(Math.max(bgR * ratio, 0), 255)),
Math.floor(Math.min(Math.max(bgG * ratio, 0), 255)),
Math.floor(Math.min(Math.max(bgB * ratio, 0), 255)),
)
}
return grays
}
function generateMutedTextColor(bg: RGBA, isDark: boolean): RGBA {
const luminance = 0.299 * bg.r * 255 + 0.587 * bg.g * 255 + 0.114 * bg.b * 255
if (isDark) {
const gray = luminance < 10 ? 180 : Math.min(Math.floor(160 + luminance * 0.3), 200)
return RGBA.fromInts(gray, gray, gray)
}
const gray = luminance > 245 ? 75 : Math.max(Math.floor(100 - (255 - luminance) * 0.2), 60)
return RGBA.fromInts(gray, gray, gray)
}