diff --git a/packages/tui/src/context/theme.tsx b/packages/tui/src/context/theme.tsx index aff085e969e..16ffdeaccbe 100644 --- a/packages/tui/src/context/theme.tsx +++ b/packages/tui/src/context/theme.tsx @@ -4,10 +4,8 @@ import { DEFAULT_THEMES, addTheme, allThemes, - generateSyntax, hasTheme, isTheme, - resolveTheme, selectedForeground, setCustomThemes, setSystemTheme, @@ -16,6 +14,7 @@ import { type Theme, type ThemeJson, } from "../theme" +import { generateSyntax } from "../theme/v2/syntax" import { generateSystem, terminalMode } from "../theme/system" import { discoverThemes, themeDirectories } from "../theme/discovery" import { createComponentTheme, type ComponentTheme } from "../theme/v2/component" @@ -279,7 +278,6 @@ const themeContext = createSimpleContext({ if (supported.includes(store.mode)) return store.mode return supported[0] ?? store.mode } - const legacySyntaxTheme = createMemo(() => resolveTheme(source(), mode())) const valuesV2 = createMemo(() => resolveThemeFile(file(), mode(), sourceName())) valuesV2() themePerformance.set("Init", `${(performance.now() - initStarted).toFixed(2)} ms`) @@ -299,7 +297,7 @@ const themeContext = createSimpleContext({ createEffect(() => renderer.setBackgroundColor(valuesV2().background.default)) - const syntax = createSyntaxStyleMemo(() => generateSyntax(legacySyntaxTheme())) + const syntax = createSyntaxStyleMemo(() => generateSyntax(valuesV2(), mode())) function contextual(context: ContextName) { return contextualServices[context] } diff --git a/packages/tui/src/theme/v2/syntax.ts b/packages/tui/src/theme/v2/syntax.ts new file mode 100644 index 00000000000..4a8917f089d --- /dev/null +++ b/packages/tui/src/theme/v2/syntax.ts @@ -0,0 +1,93 @@ +import { SyntaxStyle, type RGBA, type ThemeTokenStyle } from "@opentui/core" +import type { Mode, ResolvedThemeView } from "./index" + +export function generateSyntax(theme: ResolvedThemeView, mode: Mode) { + const step = mode === "light" ? 800 : 200 + const syntax = theme.syntax + const markdown = theme.markdown + const feedback = theme.text.feedback + + return SyntaxStyle.fromTheme([ + rule(["default"], theme.text.default), + rule(["prompt"], theme.hue.accent[step]), + rule(["extmark.file"], feedback.warning.default, { bold: true }), + rule(["extmark.agent"], theme.categorical[0][step], { bold: true }), + // V1 migration preserves its selected/inverse foreground in this action state. + rule(["extmark.paste"], theme.text.action.primary.focused, { + background: feedback.warning.default, + bold: true, + }), + rule(["comment", "comment.documentation"], syntax.comment, { italic: true }), + rule(["string", "symbol", "character.special", "character"], syntax.string), + rule(["number", "boolean", "constant", "float"], syntax.number), + rule(["keyword.return", "keyword.conditional", "keyword.repeat", "keyword.coroutine"], syntax.keyword, { + italic: true, + }), + rule(["keyword.type"], syntax.type, { bold: true, italic: true }), + rule(["keyword.function", "function.method"], syntax.function), + rule(["keyword"], syntax.keyword, { italic: true }), + rule(["keyword.import", "string.escape", "string.regexp", "tag.attribute", "keyword.export"], syntax.keyword), + rule(["operator", "keyword.operator", "punctuation.delimiter", "keyword.conditional.ternary"], syntax.operator), + rule( + ["variable", "variable.parameter", "function.method.call", "function.call", "property", "parameter", "field"], + syntax.variable, + ), + rule(["variable.member", "function", "constructor"], syntax.function), + rule(["type", "module", "class", "namespace"], syntax.type), + rule(["type.definition"], syntax.type, { bold: true }), + rule(["punctuation", "punctuation.bracket"], syntax.punctuation), + rule( + ["variable.builtin", "type.builtin", "function.builtin", "module.builtin", "constant.builtin", "variable.super"], + feedback.error.default, + ), + rule(["keyword.directive", "keyword.modifier", "keyword.exception"], syntax.keyword, { italic: true }), + rule(["punctuation.special", "tag.delimiter"], syntax.operator), + rule( + [ + "markup.heading", + "markup.heading.2", + "markup.heading.3", + "markup.heading.4", + "markup.heading.5", + "markup.heading.6", + ], + markdown.heading, + { bold: true }, + ), + rule(["markup.heading.1"], markdown.heading, { bold: true, underline: true }), + rule(["markup.bold", "markup.strong"], markdown.strong, { bold: true }), + rule(["markup.italic"], markdown.emphasis, { italic: true }), + rule(["markup.list"], markdown.listItem), + rule(["markup.quote"], markdown.blockQuote, { italic: true }), + rule(["markup.raw", "markup.raw.block"], markdown.code), + rule(["markup.raw.inline"], markdown.code, { background: theme.background.default }), + rule(["markup.link", "markup.link.url", "string.special", "string.special.url"], markdown.link, { + underline: true, + }), + rule(["markup.link.label"], markdown.linkText, { underline: true }), + rule(["label"], markdown.linkText), + rule(["spell", "nospell"], theme.text.default), + rule(["markup.underline"], theme.text.default, { underline: true }), + rule(["comment.error"], feedback.error.default, { italic: true, bold: true }), + rule(["comment.warning"], feedback.warning.default, { italic: true, bold: true }), + rule(["comment.todo", "comment.note"], feedback.info.default, { italic: true, bold: true }), + rule(["attribute", "annotation"], feedback.warning.default), + rule(["tag"], feedback.error.default), + rule(["markup.strikethrough", "markup.list.unchecked", "debug"], theme.text.subdued), + rule(["markup.list.checked"], feedback.success.default), + rule(["diff.plus"], theme.diff.text.added, { background: theme.diff.background.added }), + rule(["diff.minus"], theme.diff.text.removed, { background: theme.diff.background.removed }), + rule(["diff.delta"], theme.diff.text.context, { background: theme.diff.background.context }), + rule(["error"], feedback.error.default, { bold: true }), + rule(["warning"], feedback.warning.default, { bold: true }), + rule(["info"], feedback.info.default), + ]) +} + +function rule( + scope: string[], + foreground: RGBA, + style: Omit = {}, +): ThemeTokenStyle { + return { scope, style: { foreground, ...style } } +}