2026-01-18 17:29:22 +02:00
|
|
|
import type { Extension } from '@codemirror/state';
|
|
|
|
|
|
|
|
|
|
import { EditorView } from '@codemirror/view';
|
2026-02-10 12:57:43 +02:00
|
|
|
import { HighlightStyle, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
|
|
|
import { classHighlighter, tags as t } from '@lezer/highlight';
|
2026-01-18 17:29:22 +02:00
|
|
|
|
|
|
|
|
import type { Theme } from '@/types/theme';
|
|
|
|
|
|
|
|
|
|
export function createFlexokiCodeMirrorTheme(theme: Theme): Extension {
|
|
|
|
|
const isDark = theme.metadata.variant === 'dark';
|
|
|
|
|
|
|
|
|
|
const monoFont = theme.config?.fonts?.mono || 'monospace';
|
|
|
|
|
const highlights = theme.colors.syntax.highlights || {};
|
|
|
|
|
const tokens = theme.colors.syntax.tokens || {};
|
|
|
|
|
|
|
|
|
|
const ui = EditorView.theme({
|
|
|
|
|
'&': {
|
|
|
|
|
backgroundColor: 'var(--background)',
|
|
|
|
|
color: theme.colors.syntax.base.foreground,
|
|
|
|
|
fontSize: 'var(--text-code)',
|
|
|
|
|
lineHeight: '1.5rem',
|
|
|
|
|
},
|
|
|
|
|
'.cm-scroller': {
|
|
|
|
|
fontFamily: monoFont,
|
|
|
|
|
backgroundColor: 'var(--background)',
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/* StreamLanguage/legacy-modes tokens (class-based) */
|
|
|
|
|
'.cm-comment': {
|
|
|
|
|
color: theme.colors.syntax.base.comment,
|
|
|
|
|
},
|
|
|
|
|
'.cm-keyword': {
|
|
|
|
|
color: theme.colors.syntax.base.keyword,
|
|
|
|
|
},
|
|
|
|
|
'.cm-string': {
|
|
|
|
|
color: theme.colors.syntax.base.string,
|
|
|
|
|
},
|
|
|
|
|
'.cm-string-2': {
|
|
|
|
|
color: tokens.stringEscape || theme.colors.syntax.base.string,
|
|
|
|
|
},
|
|
|
|
|
'.cm-number': {
|
|
|
|
|
color: theme.colors.syntax.base.number,
|
|
|
|
|
},
|
2026-02-10 12:57:43 +02:00
|
|
|
'.cm-operator': {
|
|
|
|
|
color: theme.colors.syntax.base.operator,
|
|
|
|
|
},
|
|
|
|
|
'.cm-punctuation': {
|
|
|
|
|
color: tokens.punctuation || theme.colors.syntax.base.comment,
|
|
|
|
|
},
|
2026-01-18 17:29:22 +02:00
|
|
|
'.cm-atom': {
|
|
|
|
|
color: tokens.boolean || theme.colors.syntax.base.number,
|
|
|
|
|
},
|
|
|
|
|
'.cm-builtin': {
|
|
|
|
|
color: tokens.functionCall || theme.colors.syntax.base.function,
|
|
|
|
|
},
|
|
|
|
|
'.cm-def': {
|
|
|
|
|
color: tokens.variableGlobal || theme.colors.syntax.base.variable,
|
|
|
|
|
},
|
|
|
|
|
// Legacy shell flags (--foo, -bar)
|
|
|
|
|
'.cm-attribute': {
|
|
|
|
|
color: tokens.variableOther || tokens.variableProperty || theme.colors.syntax.base.operator,
|
|
|
|
|
},
|
|
|
|
|
'.cm-meta': {
|
|
|
|
|
color: theme.colors.syntax.base.comment,
|
|
|
|
|
},
|
|
|
|
|
'.cm-property': {
|
|
|
|
|
color: tokens.variableProperty || theme.colors.syntax.base.keyword,
|
|
|
|
|
},
|
|
|
|
|
'.cm-variable': {
|
|
|
|
|
color: theme.colors.syntax.base.variable,
|
|
|
|
|
},
|
|
|
|
|
'.cm-variable-2': {
|
|
|
|
|
color: tokens.variableOther || theme.colors.syntax.base.function,
|
|
|
|
|
},
|
|
|
|
|
'.cm-variable-3': {
|
|
|
|
|
color: tokens.variableGlobal || theme.colors.syntax.base.type,
|
|
|
|
|
},
|
|
|
|
|
'.cm-tag': {
|
|
|
|
|
color: tokens.tag || theme.colors.syntax.base.keyword,
|
|
|
|
|
},
|
|
|
|
|
'.cm-link': {
|
|
|
|
|
color: tokens.url || theme.colors.syntax.base.keyword,
|
|
|
|
|
textDecoration: 'underline',
|
|
|
|
|
},
|
2026-02-10 12:57:43 +02:00
|
|
|
|
2026-01-18 17:29:22 +02:00
|
|
|
'.cm-content': {
|
|
|
|
|
caretColor: theme.colors.interactive.cursor,
|
|
|
|
|
},
|
|
|
|
|
'.cm-cursor, .cm-dropCursor': {
|
|
|
|
|
borderLeftColor: theme.colors.interactive.cursor,
|
|
|
|
|
},
|
|
|
|
|
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection': {
|
|
|
|
|
backgroundColor: theme.colors.interactive.selection,
|
|
|
|
|
},
|
|
|
|
|
'.cm-gutters': {
|
|
|
|
|
backgroundColor: 'var(--background)',
|
|
|
|
|
color: highlights.lineNumber || theme.colors.syntax.base.comment,
|
|
|
|
|
borderRight: `1px solid ${theme.colors.interactive.border}`,
|
|
|
|
|
position: 'sticky',
|
|
|
|
|
paddingRight: '8px',
|
|
|
|
|
left: 0,
|
|
|
|
|
zIndex: 2,
|
|
|
|
|
boxShadow: `0 0 0 var(--background)`,
|
|
|
|
|
},
|
|
|
|
|
'.cm-gutter': {
|
|
|
|
|
backgroundColor: 'var(--background)',
|
|
|
|
|
},
|
|
|
|
|
'.cm-gutterElement': {
|
|
|
|
|
backgroundColor: 'var(--background)',
|
|
|
|
|
},
|
|
|
|
|
'.cm-lineNumbers': {
|
|
|
|
|
backgroundColor: 'var(--background)',
|
|
|
|
|
},
|
|
|
|
|
'.cm-lineNumbers .cm-gutterElement': {
|
|
|
|
|
paddingLeft: '8px',
|
|
|
|
|
paddingRight: '8px',
|
|
|
|
|
minWidth: '42px',
|
|
|
|
|
},
|
|
|
|
|
'.cm-activeLineGutter': {
|
|
|
|
|
color: highlights.lineNumberActive || theme.colors.syntax.base.foreground,
|
|
|
|
|
},
|
|
|
|
|
'.cm-activeLine': {
|
|
|
|
|
backgroundColor: theme.colors.surface.overlay,
|
|
|
|
|
},
|
2026-02-12 11:37:34 -03:00
|
|
|
'.cm-panels': {
|
|
|
|
|
backgroundColor: theme.colors.surface.elevated,
|
|
|
|
|
color: theme.colors.surface.foreground,
|
|
|
|
|
},
|
|
|
|
|
'.cm-panels-top': {
|
|
|
|
|
borderBottom: `1px solid ${theme.colors.interactive.border}`,
|
|
|
|
|
boxShadow: `0 1px 0 ${theme.colors.surface.subtle}`,
|
|
|
|
|
},
|
|
|
|
|
'.cm-panel.cm-search': {
|
|
|
|
|
padding: '0.625rem 0.75rem',
|
|
|
|
|
backgroundColor: theme.colors.surface.elevated,
|
|
|
|
|
},
|
|
|
|
|
'.cm-search': {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexWrap: 'wrap',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: '0.5rem',
|
|
|
|
|
color: theme.colors.surface.foreground,
|
|
|
|
|
fontSize: '0.8125rem',
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-textfield': {
|
|
|
|
|
minHeight: '2rem',
|
|
|
|
|
padding: '0.375rem 0.625rem',
|
|
|
|
|
borderRadius: '0.5rem',
|
|
|
|
|
border: `1px solid ${theme.colors.interactive.border}`,
|
|
|
|
|
backgroundColor: theme.colors.surface.background,
|
|
|
|
|
color: theme.colors.surface.foreground,
|
|
|
|
|
outline: 'none',
|
|
|
|
|
transition: 'border-color 120ms ease, box-shadow 120ms ease',
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-textfield:hover': {
|
|
|
|
|
borderColor: theme.colors.interactive.borderHover,
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-textfield:focus, .cm-search .cm-textfield:focus-visible': {
|
|
|
|
|
borderColor: theme.colors.interactive.borderFocus,
|
|
|
|
|
boxShadow: `0 0 0 2px ${theme.colors.interactive.focusRing}`,
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-button': {
|
|
|
|
|
minHeight: '2rem',
|
|
|
|
|
padding: '0.375rem 0.625rem',
|
|
|
|
|
borderRadius: '0.5rem',
|
|
|
|
|
border: `1px solid ${theme.colors.interactive.border}`,
|
|
|
|
|
backgroundColor: theme.colors.surface.background,
|
|
|
|
|
color: theme.colors.surface.foreground,
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
transition: 'background-color 120ms ease, border-color 120ms ease, color 120ms ease',
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-button:hover': {
|
|
|
|
|
backgroundColor: theme.colors.interactive.hover,
|
|
|
|
|
borderColor: theme.colors.interactive.borderHover,
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-button:active': {
|
|
|
|
|
backgroundColor: theme.colors.interactive.active,
|
|
|
|
|
borderColor: theme.colors.interactive.borderFocus,
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-button:focus, .cm-search .cm-button:focus-visible': {
|
|
|
|
|
outline: 'none',
|
|
|
|
|
boxShadow: `0 0 0 2px ${theme.colors.interactive.focusRing}`,
|
|
|
|
|
},
|
|
|
|
|
'.cm-search .cm-button:disabled': {
|
|
|
|
|
opacity: 0.55,
|
|
|
|
|
cursor: 'not-allowed',
|
|
|
|
|
backgroundColor: theme.colors.surface.muted,
|
|
|
|
|
color: theme.colors.surface.mutedForeground,
|
|
|
|
|
},
|
|
|
|
|
'.cm-searchMatch': {
|
|
|
|
|
backgroundColor: theme.colors.status.infoBackground,
|
|
|
|
|
boxShadow: `inset 0 0 0 1px ${theme.colors.status.infoBorder}`,
|
|
|
|
|
borderRadius: '2px',
|
|
|
|
|
},
|
|
|
|
|
'.cm-searchMatch.cm-searchMatch-selected': {
|
|
|
|
|
backgroundColor: theme.colors.interactive.selection,
|
|
|
|
|
color: theme.colors.interactive.selectionForeground,
|
|
|
|
|
boxShadow: `inset 0 0 0 1px ${theme.colors.interactive.borderFocus}`,
|
|
|
|
|
},
|
2026-01-18 17:29:22 +02:00
|
|
|
'&.cm-focused': {
|
|
|
|
|
outline: 'none',
|
|
|
|
|
},
|
|
|
|
|
}, { dark: isDark });
|
|
|
|
|
|
|
|
|
|
const syntax = HighlightStyle.define([
|
2026-02-10 12:57:43 +02:00
|
|
|
{ tag: [t.comment, t.docComment, t.meta, t.documentMeta], class: 'cm-comment' },
|
|
|
|
|
{ tag: [t.keyword, t.controlKeyword, t.moduleKeyword, t.definitionKeyword, t.modifier], class: 'cm-keyword' },
|
|
|
|
|
{ tag: [t.operatorKeyword, t.operator, t.derefOperator, t.updateOperator, t.definitionOperator, t.typeOperator, t.controlOperator, t.logicOperator, t.bitwiseOperator, t.arithmeticOperator, t.compareOperator], class: 'cm-operator' },
|
|
|
|
|
{ tag: [t.punctuation, t.separator, t.bracket, t.paren, t.brace, t.squareBracket, t.angleBracket], class: 'cm-punctuation' },
|
|
|
|
|
|
|
|
|
|
{ tag: [t.string, t.regexp, t.attributeValue, t.special(t.string), t.monospace], class: 'cm-string' },
|
|
|
|
|
{ tag: t.escape, class: 'cm-string-2' },
|
|
|
|
|
{ tag: [t.number, t.bool, t.atom, t.null, t.self], class: 'cm-number' },
|
|
|
|
|
|
|
|
|
|
{ tag: [t.function(t.variableName), t.function(t.definition(t.variableName)), t.function(t.propertyName), t.standard(t.variableName), t.special(t.variableName)], class: 'cm-builtin' },
|
|
|
|
|
{ tag: t.definition(t.variableName), class: 'cm-def' },
|
|
|
|
|
{ tag: [t.variableName, t.local(t.variableName), t.constant(t.variableName), t.literal], class: 'cm-variable' },
|
|
|
|
|
{ tag: t.propertyName, class: 'cm-property' },
|
|
|
|
|
{ tag: t.attributeName, class: 'cm-attribute' },
|
|
|
|
|
|
|
|
|
|
{ tag: [t.className, t.typeName, t.namespace], class: 'cm-variable-3' },
|
|
|
|
|
{ tag: [t.tagName, t.labelName, t.annotation, t.macroName], class: 'cm-tag' },
|
|
|
|
|
{ tag: t.link, class: 'cm-link' },
|
|
|
|
|
|
|
|
|
|
{ tag: [t.heading, t.heading1, t.heading2, t.heading3, t.heading4, t.heading5, t.heading6], class: 'cm-keyword' },
|
2026-01-18 17:29:22 +02:00
|
|
|
]);
|
|
|
|
|
|
2026-02-10 12:57:43 +02:00
|
|
|
return [
|
|
|
|
|
ui,
|
|
|
|
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
|
|
|
syntaxHighlighting(classHighlighter),
|
|
|
|
|
syntaxHighlighting(syntax),
|
|
|
|
|
];
|
2026-01-18 17:29:22 +02:00
|
|
|
}
|