Line data Source code
1 : import { EditorView } from '@codemirror/view';
2 : import { Extension } from '@codemirror/state';
3 : import { HighlightStyle, TagStyle, syntaxHighlighting } from '@codemirror/language';
4 : import { StyleSpec } from 'style-mod';
5 :
6 : export interface CreateThemeOptions {
7 : /**
8 : * Settings to customize the look of the editor, like background, gutter, selection and others.
9 : */
10 : settings: Settings;
11 : /** Syntax highlighting styles. */
12 : styles: TagStyle[];
13 : }
14 :
15 : export interface Settings {
16 : /** Editor background color. */
17 : background?: string;
18 : /** Editor background image. */
19 : backgroundImage?: string;
20 : /** Default text color. */
21 : foreground?: string;
22 : /** Caret color. */
23 : caret?: string;
24 : /** Selection background. */
25 : selection?: string;
26 : /** Selection match background. */
27 : selectionMatch?: string;
28 : /** Background of highlighted lines. */
29 : lineHighlight?: string;
30 : /** Gutter background. */
31 : gutterBackground?: string;
32 : /** Text color inside gutter. */
33 : gutterForeground?: string;
34 : /** Text active color inside gutter. */
35 : gutterActiveForeground?: string;
36 : /** Gutter right border color. */
37 : gutterBorder?: string;
38 : /** set editor font */
39 : fontFamily?: string;
40 : }
41 :
42 : /// Create a theme of the editors.
43 : /// The colors are CSS colors that may refer to the tokens of the theme of the app. CodeMirror's own base theme
44 : /// picks its light variant, the theme covers the parts it colors: panels, tooltips, matches and brackets.
45 1 : export const createTheme = ({ settings = {}, styles = [] }: CreateThemeOptions): Extension => {
46 1 : const themeOptions: Record<string, StyleSpec> = {
47 1 : '.cm-gutters': {},
48 1 : };
49 1 : const baseStyle: StyleSpec = {};
50 1 : if (settings.background) {
51 1 : baseStyle.backgroundColor = settings.background;
52 : }
53 1 : if (settings.backgroundImage) {
54 1 : baseStyle.backgroundImage = settings.backgroundImage;
55 : }
56 1 : if (settings.foreground) {
57 1 : baseStyle.color = settings.foreground;
58 : }
59 1 : if (settings.background || settings.foreground) {
60 1 : themeOptions['&'] = baseStyle;
61 : }
62 :
63 1 : if (settings.fontFamily) {
64 1 : themeOptions['&.cm-editor .cm-scroller'] = {
65 1 : fontFamily: settings.fontFamily,
66 1 : };
67 : }
68 1 : if (settings.gutterBackground) {
69 1 : themeOptions['.cm-gutters'].backgroundColor = settings.gutterBackground;
70 : }
71 1 : if (settings.gutterForeground) {
72 1 : themeOptions['.cm-gutters'].color = settings.gutterForeground;
73 : }
74 1 : if (settings.gutterBorder) {
75 1 : themeOptions['.cm-gutters'].borderRightColor = settings.gutterBorder;
76 : }
77 :
78 1 : if (settings.caret) {
79 1 : themeOptions['.cm-content'] = {
80 1 : caretColor: settings.caret,
81 1 : };
82 1 : themeOptions['.cm-cursor, .cm-dropCursor'] = {
83 1 : borderLeftColor: settings.caret,
84 1 : };
85 1 : }
86 1 : const activeLineGutterStyle: StyleSpec = {};
87 1 : if (settings.gutterActiveForeground) {
88 1 : activeLineGutterStyle.color = settings.gutterActiveForeground;
89 : }
90 1 : if (settings.lineHighlight) {
91 1 : themeOptions['.cm-activeLine'] = {
92 1 : backgroundColor: settings.lineHighlight,
93 1 : };
94 1 : activeLineGutterStyle.backgroundColor = settings.lineHighlight;
95 1 : }
96 1 : themeOptions['.cm-activeLineGutter'] = activeLineGutterStyle;
97 :
98 1 : if (settings.selection) {
99 1 : themeOptions[
100 1 : '&.cm-focused .cm-selectionBackground, & .cm-selectionLayer .cm-selectionBackground'
101 1 : ] = {
102 1 : background: settings.selection + ' !important',
103 1 : };
104 : }
105 1 : if (settings.selectionMatch) {
106 1 : themeOptions['& .cm-selectionMatch'] = {
107 1 : backgroundColor: settings.selectionMatch,
108 1 : };
109 : }
110 1 : themeOptions['.cm-panels'] = {
111 1 : backgroundColor: 'var(--color_bg_subtle)',
112 1 : color: 'var(--color_fg)',
113 1 : };
114 1 : themeOptions['.cm-panels.cm-panels-top'] = { borderBottom: '1px solid var(--color_border)' };
115 1 : themeOptions['.cm-panels.cm-panels-bottom'] = { borderTop: '1px solid var(--color_border)' };
116 1 : themeOptions['.cm-tooltip'] = {
117 1 : backgroundColor: 'var(--color_bg_card)',
118 1 : color: 'var(--color_fg)',
119 1 : border: '1px solid var(--color_border)',
120 1 : };
121 1 : themeOptions['.cm-tooltip-autocomplete ul li[aria-selected]'] = {
122 1 : backgroundColor: 'var(--color_accent_muted)',
123 1 : color: 'var(--color_fg)',
124 1 : };
125 1 : themeOptions['.cm-searchMatch'] = {
126 1 : backgroundColor: 'var(--color_attention_muted)',
127 1 : outline: '1px solid var(--color_attention_fg)',
128 1 : };
129 1 : themeOptions['.cm-searchMatch.cm-searchMatch-selected'] = {
130 1 : backgroundColor: 'var(--color_accent_muted)',
131 1 : };
132 1 : themeOptions['.cm-matchingBracket, &.cm-focused .cm-matchingBracket'] = {
133 1 : backgroundColor: 'var(--color_bg_selected)',
134 1 : };
135 1 : themeOptions['.cm-nonmatchingBracket, &.cm-focused .cm-nonmatchingBracket'] = {
136 1 : backgroundColor: 'var(--color_danger_muted)',
137 1 : };
138 1 : const themeExtension = EditorView.theme(themeOptions);
139 :
140 1 : const highlightStyle = HighlightStyle.define(styles);
141 1 : const extension = [themeExtension, syntaxHighlighting(highlightStyle)];
142 :
143 1 : return extension;
144 1 : };
145 :
146 : export default createTheme;
|