diff --git a/packages/extensions/themes/README.md b/packages/extensions/themes/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/extensions/themes/package.json b/packages/extensions/themes/package.json index a2ce849..a8ea954 100644 --- a/packages/extensions/themes/package.json +++ b/packages/extensions/themes/package.json @@ -18,7 +18,13 @@ "dev": "concurrently --kill-others \"pnpm tsc:watch\" \"pnpm rollup:watch\"", "build": "tsc && rollup -c" }, - "keywords": [], + "keywords": [ + "tidbcloud", + "sql", + "editor", + "extensions", + "themes" + ], "author": "", "license": "MIT", "devDependencies": { diff --git a/packages/extensions/themes/src/bbedit.ts b/packages/extensions/themes/src/bbedit.ts deleted file mode 100644 index 1095a58..0000000 --- a/packages/extensions/themes/src/bbedit.ts +++ /dev/null @@ -1,110 +0,0 @@ -// Ref: -// - https://codemirror.net/examples/styling -// - https://github.com/uiwjs/react-codemirror/blob/master/themes/theme/src/index.tsx - -import { HighlightStyle, syntaxHighlighting } from '@codemirror/language' -import { Extension } from '@codemirror/state' -import { EditorView } from '@codemirror/view' -import { tags as t } from '@lezer/highlight' - -export const bbeditTheme = EditorView.theme( - { - // base - '&': { - color: '#000000', - backgroundColor: '#FFFFFF' - }, - - // caret: insert cursor color - '.cm-content': { - caretColor: '#009AE5' - }, - '&.cm-focused .cm-cursor': { - borderLeftColor: '#009AE5' - }, - - // selection - '&.cm-focused .cm-selectionBackground, ::selection': { - backgroundColor: '#0CA6F21A' - }, - '& .cm-selectionMatch': { - backgroundColor: 'transparent' - }, - - // line highlight - '.cm-activeLineGutter': { - backgroundColor: '#0CA6F20D' - }, - '.cm-activeLine': { - backgroundColor: '#0CA6F20D' - }, - - // gutter - '.cm-gutters': { - backgroundColor: '#FFFFFF', - color: '#999', - border: 'none' - } - }, - { dark: false } -) - -export const bbeditHighlightStyle = HighlightStyle.define([ - { - tag: [t.meta, t.comment], - color: '#3BAF6D' - }, - { - tag: [t.keyword, t.strong], - color: '#009AE6' - }, - { - tag: [t.number], - color: '#EB4799' - }, - { - tag: [t.string], - color: '#EB4799' - }, - { - tag: [t.variableName], - color: '#056142' - }, - { - tag: [t.escape], - color: '#40BF6A' - }, - { - tag: [t.tagName], - color: '#2152C4' - }, - { - tag: [t.heading], - color: '#2152C4' - }, - { - tag: [t.quote], - color: '#333333' - }, - { - tag: [t.list], - color: '#C20A94' - }, - { - tag: [t.documentMeta], - color: '#999999' - }, - { - tag: [t.function(t.variableName)], - color: '#1A0099' - }, - { - tag: [t.definition(t.typeName), t.typeName], - color: '#6D79DE' - } -]) - -export const bbedit: Extension = [ - bbeditTheme, - syntaxHighlighting(bbeditHighlightStyle) -] diff --git a/packages/extensions/themes/src/dark.ts b/packages/extensions/themes/src/dark.ts new file mode 100644 index 0000000..6c5e404 --- /dev/null +++ b/packages/extensions/themes/src/dark.ts @@ -0,0 +1,80 @@ +import { tags as t } from '@lezer/highlight' + +import { CreateThemeOptions, createTheme } from './theme-utils' + +export const defaultSettingsDark: CreateThemeOptions['settings'] = { + background: '#282c34', + color: '#abb2bf', + caret: '#528bff', + selection: '#151C26', + selectionMatch: '#064470', + searchMatch: '#6199ff2f', + gutterBackground: '#282c34', + gutterColor: '#7d8799', + gutterBorder: 'none', + lineHighlight: '#6699ff0b', + tooltipBackground: '#353a42', + tooltipBorder: 'none', + autocompleteActiveBackground: '#6699ff0b', + autocompleteActiveColor: '#abb2bf' +} + +export const dark = ((options?: any) => { + const { theme = 'dark', settings = {}, styles = [] } = options || {} + + return createTheme({ + theme: theme, + settings: { + ...defaultSettingsDark, + ...settings + }, + styles: [ + { tag: t.keyword, color: '#25AFF4' }, + { + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + color: '#e06c75' + }, + { tag: [t.function(t.variableName), t.labelName], color: '#1A0099' }, + { + tag: [t.color, t.constant(t.name), t.standard(t.name)], + color: '#d19a66' + }, + { tag: [t.definition(t.name), t.separator], color: '#abb2bf' }, + { + tag: [ + t.typeName, + t.className, + t.changed, + t.annotation, + t.modifier, + t.self, + t.namespace + ], + color: '#8992F5' + }, + { tag: [t.number], color: '#D65C99' }, + { + tag: [ + t.operator, + t.operatorKeyword, + t.url, + t.escape, + t.regexp, + t.link, + t.special(t.string) + ], + color: '#56b6c2' + }, + { tag: [t.meta, t.comment], color: '#4FB07F' }, + { tag: t.strong, fontWeight: 'bold' }, + { tag: t.heading, color: '#e06c75' }, + { tag: [t.atom, t.bool, t.special(t.variableName)], color: '#d19a66' }, + { + tag: [t.processingInstruction, t.string, t.inserted], + color: '#B96CE0' + }, + { tag: t.invalid, color: '#ffffff' }, + ...styles + ] + }) +})() diff --git a/packages/extensions/themes/src/index.ts b/packages/extensions/themes/src/index.ts index 42d753c..86c67a5 100644 --- a/packages/extensions/themes/src/index.ts +++ b/packages/extensions/themes/src/index.ts @@ -1,2 +1,3 @@ -export * from './bbedit' -export * from './one-dark' +export * from './light' +export * from './dark' +export * from './theme-utils' diff --git a/packages/extensions/themes/src/light.ts b/packages/extensions/themes/src/light.ts new file mode 100644 index 0000000..926093c --- /dev/null +++ b/packages/extensions/themes/src/light.ts @@ -0,0 +1,44 @@ +import { tags as t } from '@lezer/highlight' + +import { CreateThemeOptions, createTheme } from './theme-utils' + +export const defaultLightSettings: CreateThemeOptions['settings'] = { + background: '#FFFFFF', + color: '#000000', + caret: '#009AE5', + selection: '#E7F7FE', + selectionMatch: '#CEEDFC', + gutterBackground: '#FFFFFF', + gutterColor: '#999', + gutterBorder: 'none', + lineHighlight: '#F5FBFE', + gutterActiveForeground: '#0CA6F20D' +} + +export const light = ((options?: any) => { + const { theme = 'light', settings = {}, styles = [] } = options || {} + + return createTheme({ + theme: theme, + settings: { + ...defaultLightSettings, + ...settings + }, + styles: [ + { tag: [t.meta, t.comment], color: '#3BAF6D' }, + { tag: [t.keyword, t.strong], color: '#027DBB' }, + { tag: [t.number], color: '#DF2271' }, + { tag: [t.string], color: '#843FA6' }, + { tag: [t.variableName], color: '#056142' }, + { tag: [t.escape], color: '#40BF6A' }, + { tag: [t.tagName], color: '#2152C4' }, + { tag: [t.heading], color: '#2152C4' }, + { tag: [t.quote], color: '#333333' }, + { tag: [t.list], color: '#C20A94' }, + { tag: [t.documentMeta], color: '#999999' }, + { tag: [t.function(t.variableName)], color: '#1A0099' }, + { tag: [t.definition(t.typeName), t.typeName], color: '#6D79DE' }, + ...styles + ] + }) +})() diff --git a/packages/extensions/themes/src/one-dark.ts b/packages/extensions/themes/src/one-dark.ts deleted file mode 100644 index c421fb2..0000000 --- a/packages/extensions/themes/src/one-dark.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Cope from https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts as ref - -import { HighlightStyle, syntaxHighlighting } from '@codemirror/language' -import { Extension } from '@codemirror/state' -import { EditorView } from '@codemirror/view' -import { tags as t } from '@lezer/highlight' - -// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors - -const chalky = '#e5c07b', - coral = '#e06c75', - cyan = '#56b6c2', - invalid = '#ffffff', - ivory = '#abb2bf', - stone = '#7d8799', // Brightened compared to original to increase contrast - malibu = '#61afef', - sage = '#98c379', - whiskey = '#d19a66', - violet = '#c678dd', - darkBackground = '#21252b', - highlightBackground = '#2c313a', - background = '#282c34', - tooltipBackground = '#353a42', - selection = '#3E4451', - cursor = '#528bff' - -/// The colors used in the theme, as CSS color strings. -export const color = { - chalky, - coral, - cyan, - invalid, - ivory, - stone, - malibu, - sage, - whiskey, - violet, - darkBackground, - highlightBackground, - background, - tooltipBackground, - selection, - cursor -} - -/// The editor theme styles for One Dark. -export const oneDarkTheme = EditorView.theme( - { - '&': { - color: ivory, - backgroundColor: background - }, - - '.cm-content': { - caretColor: cursor - }, - - '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor }, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': - { backgroundColor: selection }, - - '.cm-panels': { backgroundColor: darkBackground, color: ivory }, - '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, - '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, - - '.cm-searchMatch': { - backgroundColor: '#72a1ff59', - outline: '1px solid #457dff' - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: '#6199ff2f' - }, - - '.cm-activeLine': { backgroundColor: '#6699ff0b' }, - '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: '#bad0f847' - }, - - '.cm-gutters': { - backgroundColor: background, - color: stone, - border: 'none' - }, - - '.cm-activeLineGutter': { - backgroundColor: highlightBackground - }, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: '#ddd' - }, - - '.cm-tooltip': { - border: 'none', - backgroundColor: tooltipBackground - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: tooltipBackground, - borderBottomColor: tooltipBackground - }, - '.cm-tooltip-autocomplete': { - '& > ul > li[aria-selected]': { - backgroundColor: highlightBackground, - color: ivory - } - } - }, - { dark: true } -) - -/// The highlighting style for code in the One Dark theme. -export const oneDarkHighlightStyle = HighlightStyle.define([ - { tag: t.keyword, color: violet }, - { - tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], - color: coral - }, - { tag: [t.function(t.variableName), t.labelName], color: malibu }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey }, - { tag: [t.definition(t.name), t.separator], color: ivory }, - { - tag: [ - t.typeName, - t.className, - t.number, - t.changed, - t.annotation, - t.modifier, - t.self, - t.namespace - ], - color: chalky - }, - { - tag: [ - t.operator, - t.operatorKeyword, - t.url, - t.escape, - t.regexp, - t.link, - t.special(t.string) - ], - color: cyan - }, - { tag: [t.meta, t.comment], color: stone }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.link, color: stone, textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: coral }, - { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey }, - { tag: [t.processingInstruction, t.string, t.inserted], color: sage }, - { tag: t.invalid, color: invalid } -]) - -/// Extension to enable the One Dark theme (both the editor theme and -/// the highlight style). -export const oneDark: Extension = [ - oneDarkTheme, - syntaxHighlighting(oneDarkHighlightStyle) -] diff --git a/packages/extensions/themes/src/theme-utils.ts b/packages/extensions/themes/src/theme-utils.ts new file mode 100644 index 0000000..10949ec --- /dev/null +++ b/packages/extensions/themes/src/theme-utils.ts @@ -0,0 +1,187 @@ +import { EditorView } from '@codemirror/view' +import { Extension } from '@codemirror/state' +import { + HighlightStyle, + TagStyle, + syntaxHighlighting +} from '@codemirror/language' +import { StyleSpec } from 'style-mod' + +export interface CreateThemeOptions { + /** + * Theme inheritance. Determines which styles CodeMirror will apply by default. + */ + theme: Theme + /** + * Settings to customize the look of the editor, like background, gutter, selection and others. + */ + settings: Settings + /** Syntax highlighting styles. */ + styles: TagStyle[] +} + +type Theme = 'light' | 'dark' + +export interface Settings { + /** Editor background color. */ + background?: string + /** Editor background image. */ + backgroundImage?: string + /** Default text color. */ + color?: string + /** Caret color. */ + caret?: string + /** Selection background. */ + selection?: string + /** Selection match background. */ + selectionMatch?: string + /** Search match background. */ + searchMatch?: string + /** Background of highlighted lines. */ + lineHighlight?: string + /** Gutter background. */ + gutterBackground?: string + /** Text color inside gutter. */ + gutterColor?: string + /** Text active color inside gutter. */ + gutterActiveForeground?: string + /** Gutter right border color. */ + gutterBorder?: string + /** editor font */ + fontFamily?: string + /** editor font size */ + fontSize?: StyleSpec['fontSize'] + /** tooltip card background */ + tooltipBackground?: string + /** tooltip card border style */ + tooltipBorder?: string + /** autocomplte dropdown active item background */ + autocompleteActiveBackground?: string + /** autocomplte dropdown active item color */ + autocompleteActiveColor?: string +} + +export const createTheme = ({ + theme, + settings = {}, + styles = [] +}: CreateThemeOptions): Extension => { + const themeOptions: Record = { + '.cm-gutters': {} + } + const baseStyle: StyleSpec = {} + if (settings.background) { + baseStyle.backgroundColor = settings.background + } + if (settings.backgroundImage) { + baseStyle.backgroundImage = settings.backgroundImage + } + if (settings.color) { + baseStyle.color = settings.color + } + if (settings.fontSize) { + baseStyle.fontSize = settings.fontSize + } + if (settings.background || settings.color) { + themeOptions['&'] = baseStyle + } + + if (settings.fontFamily) { + themeOptions['&.cm-editor .cm-scroller'] = { + fontFamily: settings.fontFamily + } + } + + if (settings.caret) { + themeOptions['.cm-content'] = { + caretColor: settings.caret + } + themeOptions['.cm-cursor, .cm-dropCursor'] = { + borderLeftColor: settings.caret + } + } + + if (settings.selection) { + themeOptions[ + '&.cm-focused .cm-selectionBackground, & .cm-line::selection, & .cm-selectionLayer .cm-selectionBackground, .cm-content ::selection' + ] = { + background: settings.selection + ' !important' + } + } + + if (settings.selectionMatch) { + themeOptions['& .cm-selectionMatch'] = { + backgroundColor: settings.selectionMatch + } + } + + if (settings.searchMatch) { + themeOptions['.cm-searchMatch.cm-searchMatch-selected'] = { + backgroundColor: settings.searchMatch + } + } + + if (settings.gutterBackground) { + themeOptions['.cm-gutters'].backgroundColor = settings.gutterBackground + } + if (settings.gutterColor) { + themeOptions['.cm-gutters'].color = settings.gutterColor + } + if (settings.gutterBorder) { + themeOptions['.cm-gutters'].borderRightColor = settings.gutterBorder + } + + let activeLineGutterStyle: StyleSpec = {} + if (settings.gutterActiveForeground) { + activeLineGutterStyle.color = settings.gutterActiveForeground + } + + if (settings.lineHighlight) { + themeOptions['.cm-activeLine'] = { + backgroundColor: settings.lineHighlight + } + activeLineGutterStyle.backgroundColor = settings.lineHighlight + } + themeOptions['.cm-activeLineGutter'] = activeLineGutterStyle + + if (settings.tooltipBackground || settings.tooltipBorder) { + const tooltipStyle: StyleSpec = {} + if (settings.tooltipBackground) { + tooltipStyle.backgroundColor = settings.tooltipBackground + themeOptions['.cm-tooltip .cm-tooltip-arrow:after'] = { + borderTopColor: settings.tooltipBackground, + borderBottomColor: settings.tooltipBackground + } + } + if (settings.tooltipBorder) { + tooltipStyle.border = settings.tooltipBorder + } + themeOptions['.cm-tooltip'] = tooltipStyle + } + + themeOptions['.cm-tooltip .cm-tooltip-arrow:before'] = { + borderTopColor: 'transparent', + borderBottomColor: 'transparent' + } + + const autocompleteStyle: StyleSpec = {} + if (settings.autocompleteActiveBackground) { + autocompleteStyle.backgroundColor = settings.autocompleteActiveBackground + } + if (settings.autocompleteActiveColor) { + autocompleteStyle.color = settings.autocompleteActiveColor + } + themeOptions['.cm-tooltip-autocomplete'] = { + '& > ul > li[aria-selected]': autocompleteStyle + } + + const themeExtension = EditorView.theme(themeOptions, { + dark: theme === 'dark' + }) + const highlightStyle = HighlightStyle.define(styles) + const extension = [themeExtension, syntaxHighlighting(highlightStyle)] + + return extension +} + +export default createTheme diff --git a/packages/playground/index.html b/packages/playground/index.html index 11aad19..1c94bfe 100644 --- a/packages/playground/index.html +++ b/packages/playground/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + Ti SQL Editor
diff --git a/packages/playground/src/components/biz/editor-panel/editor.tsx b/packages/playground/src/components/biz/editor-panel/editor.tsx index 9baf684..890fb8f 100644 --- a/packages/playground/src/components/biz/editor-panel/editor.tsx +++ b/packages/playground/src/components/biz/editor-panel/editor.tsx @@ -5,7 +5,7 @@ import { SQLConfig } from '@codemirror/lang-sql' import { SQLEditor } from '@tidbcloud/tisqleditor-react' import { saveHelper } from '@tidbcloud/tisqleditor-extension-save-helper' -import { bbedit, oneDark } from '@tidbcloud/tisqleditor-extension-themes' +import { light, dark } from '@tidbcloud/tisqleditor-extension-themes' import { curSqlGutter } from '@tidbcloud/tisqleditor-extension-cur-sql-gutter' import { useDbLinter, @@ -105,7 +105,7 @@ export function Editor() { className="h-full" editorId="loading" doc="" - theme={isDark ? oneDark : bbedit} + theme={isDark ? dark : light} extraExts={[ placeholder('loading...'), // both needed to prevent user from typing @@ -122,7 +122,7 @@ export function Editor() { editorId={activeFile.id} doc={activeFile.content} sqlConfig={sqlConfig} - theme={isDark ? oneDark : bbedit} + theme={isDark ? dark : light} extraExts={extraExts} /> )