From d89cbe036a01af2a58eca867705b0309dbc6943e Mon Sep 17 00:00:00 2001 From: elliot Date: Fri, 25 Jul 2025 11:11:16 -0400 Subject: [PATCH 1/2] Prototype of getting active vscode theme --- apps/vscode-editor/src/theme.ts | 28 ++++++++-------- apps/vscode/src/extension.ts | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/apps/vscode-editor/src/theme.ts b/apps/vscode-editor/src/theme.ts index dcce0d90..64374e69 100644 --- a/apps/vscode-editor/src/theme.ts +++ b/apps/vscode-editor/src/theme.ts @@ -31,15 +31,15 @@ export function editorThemeFromVSCode(fontFamily?: string, fontSizePx?: number) const theme = defaultTheme(); // get vscode theme colors - const colors: Record = {}; + const colors: Record = {}; Object.values(document.getElementsByTagName('html')[0].style) .forEach((rv) => { colors[rv] = document .getElementsByTagName('html')[0] - .style.getPropertyValue(rv) + .style.getPropertyValue(rv); } - ); - + ); + const bodyCls = document.body.classList; const hcLight = bodyCls.contains('vscode-high-contrast-light'); const hcDark = bodyCls.contains('vscode-high-contrast') && !hcLight; @@ -48,10 +48,10 @@ export function editorThemeFromVSCode(fontFamily?: string, fontSizePx?: number) theme.solarizedMode = isSolarizedThemeActive(); theme.cursorColor = colors["--vscode-editorCursor-foreground"]; theme.selectionColor = colors["--vscode-editor-selectionBackground"]; - theme.selectionForegroundColor = colors["--vscode-editor-selectionForeground"] + theme.selectionForegroundColor = colors["--vscode-editor-selectionForeground"]; theme.nodeSelectionColor = colors["--vscode-notebook-focusedCellBorder"]; theme.backgroundColor = colors["--vscode-editor-background"]; - theme.metadataBackgroundColor = theme.backgroundColor; + theme.metadataBackgroundColor = theme.backgroundColor; theme.chunkBackgroundColor = colors["--vscode-notebook-cellEditorBackground"]; theme.spanBackgroundColor = theme.chunkBackgroundColor; theme.divBackgroundColor = theme.chunkBackgroundColor; @@ -62,12 +62,12 @@ export function editorThemeFromVSCode(fontFamily?: string, fontSizePx?: number) theme.linkTextColor = colors["--vscode-textLink-foreground"]; theme.placeholderTextColor = colors["--vscode-editorGhostText-foreground"]; theme.invisibleTextColor = colors["--vscode-editorWhitespace-foreground"]; - theme.markupTextColor = theme.darkMode - ? colors["--vscode-charts-orange"] + theme.markupTextColor = theme.darkMode + ? colors["--vscode-charts-orange"] : colors["--vscode-editorInfo-foreground"]; theme.findTextBackgroundColor = colors["--vscode-editor-foldBackground"]; theme.findTextBorderColor = "transparent"; - theme.borderBackgroundColor = theme.darkMode + theme.borderBackgroundColor = theme.darkMode ? colors["--vscode-titleBar-activeBackground"] : colors["--vscode-titleBar-inactiveBackground"]; theme.gutterBackgroundColor = theme.borderBackgroundColor; @@ -78,9 +78,9 @@ export function editorThemeFromVSCode(fontFamily?: string, fontSizePx?: number) theme.surfaceWidgetTextColor = theme.gutterTextColor; theme.focusOutlineColor = colors["--vscode-focusBorder"]; theme.paneBorderColor = theme.darkMode ? colors["--vscode-commandCenter-border"] : colors["--vscode-panel-border"]; - theme.blockBorderColor = theme.darkMode - ? theme.paneBorderColor - : colors["--vscode-notebook-cellBorderColor"]; + theme.blockBorderColor = theme.darkMode + ? theme.paneBorderColor + : colors["--vscode-notebook-cellBorderColor"]; theme.hrBackgroundColor = theme.highContrast ? colors["--vscode-list-deemphasizedForeground"] : theme.blockBorderColor; theme.fixedWidthFont = colors["--vscode-editor-font-family"]; theme.proportionalFont = fontFamily || defaultPrefs().fontFamily; @@ -91,7 +91,7 @@ export function editorThemeFromVSCode(fontFamily?: string, fontSizePx?: number) const match = editorFontSize.match(/(\d+)px/); fontSizePx = match ? parseInt(match[1]) : 12; } - const fontSizePt = Math.round(fontSizePx / 1.333) ; + const fontSizePt = Math.round(fontSizePx / 1.333); theme.fixedWidthFontSizePt = fontSizePt; theme.proportionalFontSizePt = fontSizePt + 1; theme.suggestWidgetBackgroundColor = colors["--vscode-editorSuggestWidget-background"]; @@ -117,4 +117,4 @@ export function editorThemeFromVSCode(fontFamily?: string, fontSizePx?: number) theme.debugStartForegroundColor = colors["--vscode-debugIcon-startForeground"]; theme.debugStepForgroundColor = colors["--vscode-debugIcon-stepOverForeground"]; return theme; -} \ No newline at end of file +} diff --git a/apps/vscode/src/extension.ts b/apps/vscode/src/extension.ts index b85e7e2b..a94fbdc0 100644 --- a/apps/vscode/src/extension.ts +++ b/apps/vscode/src/extension.ts @@ -25,6 +25,58 @@ import { textFormattingCommands } from "./providers/text-format"; import { activateCodeFormatting } from "./providers/format"; import { activateContextKeySetter } from "./providers/context-keys"; import { ExtensionHost } from "./host"; +import path, { dirname } from "node:path"; +import { readFileSync } from "node:fs"; + +export function getActiveThemeName(): string | undefined { + return vscode.workspace.getConfiguration("workbench").get("colorTheme"); +} +// reference: https://github.com/microsoft/vscode/issues/32813#issuecomment-524174937 +// reference: https://github.com/textX/textX-LS/blob/master/client/src/utils.ts +export function getTokenColorsForTheme(themeName: string | undefined): Map { + const tokenColors = new Map(); + if (!themeName) return tokenColors; + let currentThemePath; + for (const extension of vscode.extensions.all) { + const themes = extension.packageJSON.contributes && extension.packageJSON.contributes.themes; + const currentTheme = themes && themes.find((theme: any) => theme.id === themeName); + if (currentTheme) { + currentThemePath = path.join(extension.extensionPath, currentTheme.path); + break; + } + } + const themePaths = []; + if (currentThemePath) { themePaths.push(currentThemePath); } + while (themePaths.length > 0) { + const themePath = themePaths.pop(); + if (themePath === undefined) return tokenColors; + const theme = loadJSON(themePath); + if (theme) { + if (theme.include) { + themePaths.push(path.join(dirname(themePath), theme.include)); + } + if (theme.tokenColors) { + theme.tokenColors.forEach((rule: any) => { + if (typeof rule.scope === "string" && !tokenColors.has(rule.scope)) { + tokenColors.set(rule.scope, rule.settings); + } else if (rule.scope instanceof Array) { + rule.scope.forEach((scope: any) => { + if (!tokenColors.has(rule.scope)) { + tokenColors.set(scope, rule.settings); + } + }); + } + }); + } + } + } + return tokenColors; +} +export function loadJSON(path: string): any { + try { + return JSON.parse(readFileSync(path).toString()); + } catch { } +} export function activateCommon( context: vscode.ExtensionContext, @@ -32,6 +84,11 @@ export function activateCommon( engine: MarkdownEngine, commands?: Command[] ) { + console.log( + 'HELLO getCurrentTheme', + getActiveThemeName(), + [...getTokenColorsForTheme(getActiveThemeName()).entries()].map(([k, v]) => [k, Object.entries(v)]) + ); // option enter handler activateOptionEnterProvider(context, engine); From afac040ea37787ff20a79de0f9a5e7f9a2bfd668 Mon Sep 17 00:00:00 2001 From: elliot Date: Wed, 26 Nov 2025 14:47:42 -0500 Subject: [PATCH 2/2] WIP prototype with HighlightStyle debugging --- apps/vscode-editor/src/EditorContainer.tsx | 50 +- apps/vscode-editor/src/sync.ts | 84 +- apps/vscode/src/extension.ts | 19 +- .../vscode/src/providers/editor/connection.ts | 2 + apps/vscode/src/providers/editor/editor.ts | 62 +- .../src/panes/editor/context/display.ts | 6 +- .../editor-codemirror/src/behaviors/theme.ts | 1254 ++--------------- .../src/behaviors/themeHighlightStyle.ts | 593 ++++++++ .../src/behaviors/themeSpec.ts | 259 ++++ packages/editor-types/src/display.ts | 3 +- packages/editor-types/src/vscode.ts | 9 +- packages/editor/src/editor/editor-theme.ts | 9 +- packages/editor/src/editor/editor.ts | 94 +- 13 files changed, 1214 insertions(+), 1230 deletions(-) create mode 100644 packages/editor-codemirror/src/behaviors/themeHighlightStyle.ts create mode 100644 packages/editor-codemirror/src/behaviors/themeSpec.ts diff --git a/apps/vscode-editor/src/EditorContainer.tsx b/apps/vscode-editor/src/EditorContainer.tsx index 9bdb6b8a..ed765a61 100644 --- a/apps/vscode-editor/src/EditorContainer.tsx +++ b/apps/vscode-editor/src/EditorContainer.tsx @@ -23,13 +23,13 @@ import { JsonRpcRequestTransport, pathWithForwardSlashes } from 'core'; import { useHotkeys } from "ui-widgets"; -import { - commandHotkeys, - CommandManagerContext, - Commands, - Editor, - EditorUIStore, - setEditorTheme, +import { + commandHotkeys, + CommandManagerContext, + Commands, + Editor, + EditorUIStore, + setEditorTheme, fluentTheme, showContextMenu } from 'editor-ui'; @@ -46,13 +46,13 @@ import styles from './Editor.module.scss'; export interface EditorContainerProps { context: HostContext; host: VisualEditorHostClient; - request: JsonRpcRequestTransport; + request: JsonRpcRequestTransport; store: EditorUIStore; editorId: string; } const EditorContainer: React.FC = (props) => { - + // register keyboard shortcuts and get handlers const [cmState, cmDispatch] = useContext(CommandManagerContext); const hotkeys = useMemo(() => { return commandHotkeys(cmState.commands); }, [cmState.commands]); @@ -62,13 +62,14 @@ const EditorContainer: React.FC = (props) => { useEffect(() => { cmDispatch({ type: "ADD_COMMANDS", payload: editorHostCommands(props.host) }); }, []); - + // one time creation of editorUIContext const [uiContext] = useState(() => new HostEditorUIContext(props.context, props.host)); // setup state for theme const [activeFluentTheme, setActiveFluentTheme] = useState(fluentTheme()); const applyTheme = useCallback((theme: EditorTheme) => { + console.log('applyTheme in EditorContainer', theme) // set editor theme setEditorTheme(theme); @@ -85,7 +86,7 @@ const EditorContainer: React.FC = (props) => { // ensure that keys we handle aren't propagated to vscode const keyboardEventHandler = (handler: React.KeyboardEventHandler) => { return (event: React.KeyboardEvent) => { - + // call handler handler(event); @@ -99,18 +100,18 @@ const EditorContainer: React.FC = (props) => { } }; } - + return ( -
= (props) => { options={{ cannotEditUntitled: true, defaultCellTypePython: true, - initialTheme: editorThemeFromVSCode() + initialTheme: editorThemeFromVSCode() }} />
- + ); } @@ -133,6 +134,9 @@ function editorDisplay(host: VisualEditorHostClient) { openURL(url: string) { host.openURL(url); }, + getThemeData(name:string) { + return host.getThemeData(name) + }, navigateToXRef(file: string, xref: XRef) { host.navigateToXRef(file, xref); }, @@ -152,13 +156,13 @@ function editorDisplay(host: VisualEditorHostClient) { class HostEditorUIContext implements EditorUIContext, ImageChangeSink { - + constructor( - private readonly context: HostContext, - private readonly host: VisualEditorHostClient) + private readonly context: HostContext, + private readonly host: VisualEditorHostClient) { } - + // check if we are the active tab public isActiveTab(): boolean { return true; @@ -238,7 +242,7 @@ class HostEditorUIContext implements EditorUIContext, ImageChangeSink { public async clipboardImage(): Promise { return null; } - + private resolvePath(path: string): string { if (path.startsWith("/") && this.context.projectDir) { return `${this.context.projectDir}/${path.slice(1)}`; diff --git a/apps/vscode-editor/src/sync.ts b/apps/vscode-editor/src/sync.ts index 098dae45..3d08aec6 100644 --- a/apps/vscode-editor/src/sync.ts +++ b/apps/vscode-editor/src/sync.ts @@ -19,24 +19,24 @@ import throttle from "lodash.throttle"; import { WebviewApi } from "vscode-webview"; -import { - jsonRpcPostMessageRequestTransport, - jsonRpcPostMessageServer, - JsonRpcPostMessageTarget, - JsonRpcRequestTransport +import { + jsonRpcPostMessageRequestTransport, + jsonRpcPostMessageServer, + JsonRpcPostMessageTarget, + JsonRpcRequestTransport } from "core"; import { windowJsonRpcPostMessageTarget } from "core-browser"; -import { - VSC_VE_ApplyExternalEdit, +import { + VSC_VE_ApplyExternalEdit, VSC_VE_PrefsChanged, VSC_VE_ImageChanged, VSC_VE_GetMarkdownFromState, VSC_VE_GetSlideIndex, VSC_VE_GetActiveBlockContext, VSC_VE_SetBlockSelection, - VSC_VE_Init, + VSC_VE_Init, VSC_VE_Focus, VSC_VEH_FlushEditorUpdates, VSC_VEH_SaveDocument, @@ -47,14 +47,15 @@ import { VSC_VEH_ReopenSourceMode, VSC_VEH_OnEditorUpdated, VSC_VEH_OnEditorStateChanged, - VSC_VEH_OnEditorReady, + VSC_VEH_OnEditorReady, VSC_VEH_OpenURL, + VSC_VEH_GetThemeData, VSC_VEH_NavigateToXRef, VSC_VEH_NavigateToFile, VSC_VEH_ResolveImageUris, VSC_VEH_ResolveBase64Images, - VSCodeVisualEditor, - VSCodeVisualEditorHost, + VSCodeVisualEditor, + VSCodeVisualEditorHost, EditorServer, EditorServices, XRef, @@ -66,17 +67,17 @@ import { CodeViewSelectionAction } from "editor-types"; -import { - editorJsonRpcServer, - editorJsonRpcServices +import { + editorJsonRpcServer, + editorJsonRpcServices } from "editor-core"; -import { - EditorOperations, - EditorTheme, - PandocWriterOptions, - StateChangeEvent, - UpdateEvent +import { + EditorOperations, + EditorTheme, + PandocWriterOptions, + StateChangeEvent, + UpdateEvent } from "editor"; import { Command, EditorUIStore, readPrefsApi, t, updatePrefsApi } from "editor-ui"; @@ -98,9 +99,9 @@ export function visualEditorJsonRpcRequestTransport(vscode: WebviewApi) // interface to visual editor host (vs code extension) export function visualEditorHostClient( - vscode: WebviewApi, + vscode: WebviewApi, request: JsonRpcRequestTransport -) : VisualEditorHostClient { +): VisualEditorHostClient { return { vscode, server: editorJsonRpcServer(request), @@ -114,12 +115,12 @@ export interface ImageChangeSink { } export async function syncEditorToHost( - editor: EditorOperations, + editor: EditorOperations, imageChange: ImageChangeSink, host: VisualEditorHostClient, store: EditorUIStore, applyTheme: (theme: EditorTheme) => void -) { +) { // get the current prefs const readPrefs = () => readPrefsApi(store); @@ -128,8 +129,8 @@ export async function syncEditorToHost( const writerOptions = () => { const prefs = readPrefs(); const options: PandocWriterOptions = {}; - options.wrap = prefs.markdownWrap === "column" - ? String(prefs.markdownWrapColumn) + options.wrap = prefs.markdownWrap === "column" + ? String(prefs.markdownWrapColumn) : prefs.markdownWrap; options.references = { location: prefs.markdownReferences, @@ -162,7 +163,7 @@ export async function syncEditorToHost( .finally(() => { // done }); - }, kThrottleDelayMs, { leading: false, trailing: true}); + }, kThrottleDelayMs, { leading: false, trailing: true }); // setup communication channel for host visualEditorHostServer(host.vscode, { @@ -178,11 +179,11 @@ export async function syncEditorToHost( // focus editor editor.focus(navigation); - + // visual editor => text editor (just send the state, host will call back for markdown) editor.subscribe(UpdateEvent, () => host.onEditorUpdated(editor.getStateJson())); - editor.subscribe(StateChangeEvent, () => host.onEditorStateChanged(editor.getEditorSourcePos())); - + editor.subscribe(StateChangeEvent, () => host.onEditorStateChanged(editor.getEditorSourcePos())); + // return canonical markdown return result.canonical; @@ -191,18 +192,19 @@ export async function syncEditorToHost( return null; } - } catch(error) { + } catch (error) { editor.onLoadFailed(error); return null; } - + }, async prefsChanged(prefs: Prefs): Promise { + console.log('prefs changed!!!') // save existing writer options (for comparison) const prevOptions = writerOptions(); - + // update prefs await updatePrefsApi(store, prefs); @@ -212,10 +214,10 @@ export async function syncEditorToHost( // if markdown writing options changed then force a refresh const options = writerOptions(); if (prevOptions.wrap !== options.wrap || - prevOptions.references?.location !== options.references?.location || - prevOptions.references?.prefix !== options.references?.prefix) { + prevOptions.references?.location !== options.references?.location || + prevOptions.references?.prefix !== options.references?.prefix) { await host.onEditorUpdated(editor.getStateJson()); - await host.flushEditorUpdates(); + await host.flushEditorUpdates(); } }, @@ -256,7 +258,7 @@ export async function syncEditorToHost( }) // let the host know we are ready - await host.onEditorReady(); + await host.onEditorReady(); } export enum EditorHostCommands { @@ -340,7 +342,7 @@ function visualEditorHostServer(vscode: WebviewApi, editor: VSCodeVisua } -function editorJsonRpcContainer(request: JsonRpcRequestTransport) : VSCodeVisualEditorHost { +function editorJsonRpcContainer(request: JsonRpcRequestTransport): VSCodeVisualEditorHost { return { getHostContext: () => request(VSC_VEH_GetHostContext, []), reopenSourceMode: () => request(VSC_VEH_ReopenSourceMode, []), @@ -352,6 +354,11 @@ function editorJsonRpcContainer(request: JsonRpcRequestTransport) : VSCodeVisual renderDocument: () => request(VSC_VEH_RenderDocument, []), editorResourceUri: (path: string) => request(VSC_VEH_EditorResourceUri, [path]), openURL: (url: string) => request(VSC_VEH_OpenURL, [url]), + getThemeData: async (name: string) => { + const dog = await request(VSC_VEH_GetThemeData, [name]) + // console.log('sync.ts ASHHDAHJASDJASDJ dog', name, dog) + return dog + }, navigateToXRef: (file: string, xref: XRef) => request(VSC_VEH_NavigateToXRef, [file, xref]), navigateToFile: (file: string) => request(VSC_VEH_NavigateToFile, [file]), resolveImageUris: (uris: string[]) => request(VSC_VEH_ResolveImageUris, [uris]), @@ -359,4 +366,3 @@ function editorJsonRpcContainer(request: JsonRpcRequestTransport) : VSCodeVisual selectImage: () => request(VSC_VEH_SelectImage, []) }; } - diff --git a/apps/vscode/src/extension.ts b/apps/vscode/src/extension.ts index a94fbdc0..24bdcb2e 100644 --- a/apps/vscode/src/extension.ts +++ b/apps/vscode/src/extension.ts @@ -87,8 +87,25 @@ export function activateCommon( console.log( 'HELLO getCurrentTheme', getActiveThemeName(), - [...getTokenColorsForTheme(getActiveThemeName()).entries()].map(([k, v]) => [k, Object.entries(v)]) + //[...getTokenColorsForTheme(getActiveThemeName()).entries()].map(([k, v]) => [k, Object.entries(v)]) ); + + context.subscriptions.push( + vscode.window.onDidChangeActiveColorTheme((e) => { + const name = getActiveThemeName() + //const a = getTokenColorsForTheme(getActiveThemeName()).get('variable') + //console.log('YOYOLO theme changed!', e, name, a) + }) + ); + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.colorTheme')) { + // Theme has changed + const currentTheme = vscode.workspace.getConfiguration().get('workbench.colorTheme'); + console.log(`Active theme changed to: ${currentTheme}`); + // You can now use 'currentTheme' for your extension's logic + } + }); + // option enter handler activateOptionEnterProvider(context, engine); diff --git a/apps/vscode/src/providers/editor/connection.ts b/apps/vscode/src/providers/editor/connection.ts index 7fc90685..054e5664 100644 --- a/apps/vscode/src/providers/editor/connection.ts +++ b/apps/vscode/src/providers/editor/connection.ts @@ -38,6 +38,7 @@ import { VSC_VEH_ResolveImageUris, VSC_VEH_ResolveBase64Images, VSC_VEH_SelectImage, + VSC_VEH_GetThemeData, VSCodeVisualEditor, VSCodeVisualEditorHost, VSC_VE_IsFocused, @@ -150,6 +151,7 @@ function editorHostMethods(host: VSCodeVisualEditorHost): Record host.renderDocument(), [VSC_VEH_EditorResourceUri]: args => host.editorResourceUri(args[0]), [VSC_VEH_OpenURL]: args => voidPromise(host.openURL(args[0])), + [VSC_VEH_GetThemeData]: (args) => host.getThemeData(args[0]), [VSC_VEH_NavigateToXRef]: args => voidPromise(host.navigateToXRef(args[0], args[1])), [VSC_VEH_NavigateToFile]: args => voidPromise(host.navigateToFile(args[0])), [VSC_VEH_ResolveImageUris]: args => host.resolveImageUris(args[0]), diff --git a/apps/vscode/src/providers/editor/editor.ts b/apps/vscode/src/providers/editor/editor.ts index 80723103..4f0b1518 100644 --- a/apps/vscode/src/providers/editor/editor.ts +++ b/apps/vscode/src/providers/editor/editor.ts @@ -13,7 +13,7 @@ * */ -import path, { extname, win32 } from "path"; +import path, { dirname, extname, win32 } from "path"; import { determineMode } from "./toggle"; import debounce from "lodash.debounce"; @@ -67,10 +67,65 @@ import { reopenEditorInSourceMode } from "./toggle"; import { ExtensionHost } from "../../host"; -import { TabInputCustom } from "vscode"; +import { TabInputCustom, extensions } from "vscode"; +import { readFileSync } from "fs"; const kVisualModeConfirmed = "visualModeConfirmed"; +function getThemePath(themeName: string | undefined) { + for (const extension of extensions.all) { + const themes = extension.packageJSON.contributes && extension.packageJSON.contributes.themes; + + const currentTheme = themes?.find((theme: any) => theme.id === themeName || theme.label === themeName); + if (currentTheme) { + return path.join(extension.extensionPath, currentTheme.path); + } + } +} + +// reference: https://github.com/microsoft/vscode/issues/32813#issuecomment-524174937 +// reference: https://github.com/textX/textX-LS/blob/master/client/src/utils.ts +// reference: https://macromates.com/blog/2005/introduction-to-scopes/ +export function getTokenColorsForTheme(themeName: string | undefined) { + const tokenColors: { [key: string]: Object } = {}; + let colors: { [key: string]: Object } = {}; + if (!themeName) return tokenColors; + + const currentThemePath = getThemePath(themeName) + const themePaths = []; + if (currentThemePath) { themePaths.push(currentThemePath); } + while (themePaths.length > 0) { + const themePath = themePaths.pop(); + if (themePath === undefined) continue; + const theme = loadJSON(themePath); + if (theme) { + if (theme.include) { + themePaths.push(path.join(dirname(themePath), theme.include)); + } + if (theme.colors) { + colors = { ...colors, ...theme.colors } + } + for (const { scope, settings } of theme.tokenColors ?? []) { + const scopes = typeof scope === 'string' ? + scope.split(',').map(s => s.trim()).filter(s => s.length > 0) : + Array.isArray(scope) ? + scope : + []; + + scopes.forEach((scope) => { + tokenColors[scope] ??= settings + }) + } + } + } + return { colors, tokenColors }; +} +export function loadJSON(path: string): any { + try { + return JSON.parse(readFileSync(path).toString()); + } catch { } +} + export interface QuartoVisualEditor extends QuartoEditor { hasFocus(): Promise; getActiveBlockContext(): Promise; @@ -511,6 +566,9 @@ export class VisualEditorProvider implements CustomTextEditorProvider { navigateToFile: function (file: string): void { navigateToFile(document, file); }, + async getThemeData(themeName: string): Promise { + return getTokenColorsForTheme(themeName) + }, ...documentImageResolver(document, projectDir) }; diff --git a/apps/writer/src/panes/editor/context/display.ts b/apps/writer/src/panes/editor/context/display.ts index 0e1733c7..4de88135 100644 --- a/apps/writer/src/panes/editor/context/display.ts +++ b/apps/writer/src/panes/editor/context/display.ts @@ -19,8 +19,12 @@ import { EditorDisplay, EditorMenuItem, XRef } from "editor"; import { Commands, showContextMenu } from "editor-ui"; -export function editorDisplay(commands: () => Commands) : EditorDisplay { +export function editorDisplay(commands: () => Commands): EditorDisplay { return { + async getThemeData(_name) { + console.log('getThemeData UH OH') + // + }, openURL(_url: string) { // }, diff --git a/packages/editor-codemirror/src/behaviors/theme.ts b/packages/editor-codemirror/src/behaviors/theme.ts index bc673547..3a1b0e1a 100644 --- a/packages/editor-codemirror/src/behaviors/theme.ts +++ b/packages/editor-codemirror/src/behaviors/theme.ts @@ -18,25 +18,51 @@ import { EditorView } from "@codemirror/view"; import { Compartment } from "@codemirror/state"; import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; -import {tags as t} from "@lezer/highlight" - -import { StyleSpec } from 'style-mod'; - -import { CodeViewOptions, EditorTheme, ThemeChangedEvent } from "editor"; +import { EditorTheme, EditorUI, ThemeChangedEvent } from "editor"; import { Behavior, BehaviorContext } from "."; - -export function themeBehavior(context: BehaviorContext) : Behavior { +import * as Style from "./themeHighlightStyle" +import { tags } from "@lezer/highlight" +import { codemirrorThemeSpec } from "./themeSpec"; + +const debugTheme = EditorView.theme({ + ".cm-line span": { + position: "relative", + }, + ".cm-line span:hover::after": { + position: "absolute", + bottom: "100%", + left: 0, + background: "black", + color: "white", + border: "solid 2px", + borderRadius: "5px", + content: "var(--tags)", + width: `max-content`, + padding: "1px 4px", + zIndex: 10, + pointerEvents: "none", + }, +}); +const debugHighlightStyle = HighlightStyle.define( + Object.entries(tags).map(([key, value]) => { + return { tag: value, "--tags": `"tag.${key}"` }; + }) +); +const debug = [debugTheme, syntaxHighlighting(debugHighlightStyle)]; + +export function themeBehavior(context: BehaviorContext): Behavior { const themeConf = new Compartment(); - const setTheme = (cmView: EditorView) => { - + const setTheme = async (cmView: EditorView) => { + const editorTheme = context.pmContext.theme(); const extensions = [ - codemirrorTheme(editorTheme, context.options), - syntaxHighlighting(codemirrorHighlightStyle(editorTheme)) + debug, + //codemirrorThemeSpec(editorTheme, context.options), + //syntaxHighlighting(await codemirrorHighlightStyle(editorTheme, context.pmContext.ui)) ]; cmView.dispatch({ @@ -45,7 +71,6 @@ export function themeBehavior(context: BehaviorContext) : Behavior { }; let unsubscribe: VoidFunction; - return { extensions: [themeConf.of([])], @@ -60,643 +85,54 @@ export function themeBehavior(context: BehaviorContext) : Behavior { } } -function codemirrorTheme(editorTheme: EditorTheme, options: CodeViewOptions) { - - const completion = { Margin: 30, Width: 250 }; - - const styleSpec : { [selector: string]: StyleSpec} = { - "&": { - color: editorTheme.textColor, - backgroundColor: options.classes?.includes('pm-chunk-background-color') - ? editorTheme.chunkBackgroundColor - : editorTheme.backgroundColor, - border: "none", - fontSize: `${editorTheme.fixedWidthFontSizePt}pt` - }, - - "&.cm-editor.cm-focused": { - outline: `1px solid ${editorTheme.focusOutlineColor}` - }, - - ".cm-content": { - fontFamily: `${editorTheme.fixedWidthFont}`, - caretColor: editorTheme.cursorColor - }, - - ".cm-cursor, .cm-dropCursor": {borderLeftColor: editorTheme.cursorColor}, - "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {backgroundColor: editorTheme.selectionColor}, - - ".cm-panels": {backgroundColor: editorTheme.gutterBackgroundColor, color: editorTheme.gutterTextColor}, - ".cm-panels.cm-panels-top": {borderBottom: `2px solid ${editorTheme.paneBorderColor}`}, - ".cm-panels.cm-panels-bottom": {borderTop:`2px solid ${editorTheme.paneBorderColor}`}, - - ".cm-searchMatch": { - backgroundColor: editorTheme.findTextBackgroundColor, - outline: `1px solid${editorTheme.findTextBorderColor}` - }, - ".cm-searchMatch.cm-searchMatch-selected": { - backgroundColor: editorTheme.findTextBackgroundColor - }, - - ".cm-activeLine": {backgroundColor: editorTheme.backgroundColor}, - ".cm-selectionMatch": {backgroundColor: editorTheme.findTextBackgroundColor}, - - "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { - backgroundColor: editorTheme.findTextBackgroundColor - }, - - ".cm-gutters": { - backgroundColor: editorTheme.gutterBackgroundColor, - color: editorTheme.gutterTextColor, - border: "none", - paddingRight: "6px", - fontFamily: editorTheme.fixedWidthFont, - fontSize: `${editorTheme.fixedWidthFontSizePt}pt` - }, - - ".cm-activeLineGutter": { - backgroundColor: editorTheme.backgroundColor - }, - - ".cm-foldPlaceholder": { - backgroundColor: "transparent", - border: "none", - color: editorTheme.lightTextColor - }, - - ".cm-tooltip": { - border: "none", - backgroundColor: editorTheme.backgroundColor - }, - ".cm-tooltip .cm-tooltip-arrow:before": { - borderTopColor: "transparent", - borderBottomColor: "transparent" - }, - ".cm-tooltip .cm-tooltip-arrow:after": { - borderTopColor: editorTheme.paneBorderColor, - borderBottomColor: editorTheme.paneBorderColor - }, - // autocomplete (https://github.com/codemirror/autocomplete/blob/main/src/theme.ts) - - ".cm-tooltip.cm-tooltip-autocomplete": { - "& > ul": { - fontFamily: editorTheme.fixedWidthFont, - fontSize: `${editorTheme.fixedWidthFontSizePt}pt`, - whiteSpace: "nowrap", - overflow: "hidden auto", - maxWidth_fallback: "700px", - maxWidth: "min(700px, 95vw)", - minWidth: "250px", - maxHeight: "212px", - height: "100%", - listStyle: "none", - margin: 0, - padding: 3, - color: editorTheme.suggestWidgetForegroundColor, - backgroundColor: editorTheme.suggestWidgetBackgroundColor, - border: `1px solid ${editorTheme.suggestWidgetBorderColor}`, - - "& > li": { - overflowX: "hidden", - textOverflow: "ellipsis", - cursor: "pointer", - padding: "2px 2px", - lineHeight: 1.15, - display: "flex", - alignItems: "center" - }, - } - }, - - "& .cm-tooltip-autocomplete ul li[aria-selected]": { - background: editorTheme.suggestWidgetSelectedBackgroundColor, - color: editorTheme.suggestWidgetSelectedForegroundColor, - }, - - "& .cm-tooltip-autocomplete-disabled ul li[aria-selected]": { - background: editorTheme.suggestWidgetSelectedBackgroundColor, - }, - - ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": { - content: '"···"', - opacity: 0.5, - display: "block", - textAlign: "center" - }, - - ".cm-tooltip.cm-completionInfo": { - position: "absolute", - padding: "3px 9px", - width: "max-content", - maxWidth: `${completion.Width}px`, - boxSizing: "border-box", - color: editorTheme.suggestWidgetForegroundColor, - backgroundColor: editorTheme.suggestWidgetBackgroundColor, - border: `1px solid ${editorTheme.suggestWidgetBorderColor}` - }, - ".cm-tooltip.cm-completionInfo .cm-completionInfoHeader": { - fontFamily: editorTheme.fixedWidthFont - }, - // links don't work so change hteir appearance - ".cm-tooltip.cm-completionInfo a": { - color: editorTheme.suggestWidgetForegroundColor, - }, - ".cm-tooltip.cm-completionInfo a:hover": { - textDecoration: "none", - }, - ".cm-tooltip.cm-completionInfo p": { - margin: 0, - padding: 0 - }, - ".cm-tooltip.cm-completionInfo p.cm-completionInfoHeader": { - marginBottom: "1em" - }, - - ".cm-completionInfo.cm-completionInfo-left": { right: "100%" }, - ".cm-completionInfo.cm-completionInfo-right": { left: "100%" }, - ".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${completion.Margin}px` }, - ".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${completion.Margin}px` }, - - "& .cm-snippetField": {backgroundColor: editorTheme.invisibleTextColor}, - ".cm-snippetFieldPosition": { - verticalAlign: "text-top", - width: 0, - height: "1.15em", - display: "inline-block", - margin: "0 -0.7px -.7em", - borderLeft: `1.4px dotted ${editorTheme.gutterBackgroundColor}` - }, - - ".cm-completionMatchedText": { - textDecoration: "none", - color: editorTheme.suggestWidgetFocusHighlightForegroundColor - }, - - ".cm-completionDetail": { - marginLeft: "0.5em", - color: editorTheme.lightTextColor, - float: "right", - fontStyle: "normal" - }, - - "& .cm-tooltip-autocomplete ul li[aria-selected] .cm-completionDetail": { - color: editorTheme.suggestWidgetSelectedForegroundColor, - }, - - ".cm-completionIcon": { - fontFamily: "codicon", - fontSize: `${editorTheme.fixedWidthFontSizePt+2}pt`, - display: "inline-block", - width: "1em", - textAlign: "center", - paddingLeft: ".1em", - paddingRight: ".3em", - opacity: "0.8", - boxSizing: "content-box" - }, - - ".cm-completionIcon-function, .cm-completionIcon-method": { - "&:after": { content: "'\\eb5f'" }, - color: editorTheme.symbolIconFunctionForegroundColor - }, - ".cm-completionIcon-class": { - "&:after": { content: "'\\eb5b'" }, - "color": editorTheme.symbolIconClassForegroundColor, - }, - ".cm-completionIcon-interface": { - "&:after": { content: "'\\eb61'" }, - color: editorTheme.symbolIconInterfaceForegroundColor - }, - ".cm-completionIcon-variable": { - "&:after": { content: "'\\ea88'" }, - color: editorTheme.symbolIconVariableForegroundColor - }, - ".cm-completionIcon-constant": { - "&:after": { content: "'\\eb5d'" }, - color: editorTheme.symbolIconConstantForegroundColor - }, - ".cm-completionIcon-type": { - "&:after": { content: "'\\ea92'" }, - color: editorTheme.symbolIconTypeParameterForegroundColor - }, - ".cm-completionIcon-enum": { - "&:after": { content: "'\\ea95'" }, - color: editorTheme.symbolIconEnumForegroundColor - }, - ".cm-completionIcon-property": { - "&:after": { content: "'\\eb65'" }, - color: editorTheme.symbolIconPropertyForegroundColor - }, - ".cm-completionIcon-keyword": { - "&:after": { content: "'\\eb62'" }, - color: editorTheme.symbolIconKeywordForegroundColor - }, - ".cm-completionIcon-namespace": { - "&:after": { content: "'\\ea8b'" }, - color: editorTheme.symbolIconNamespaceForegroundColor - }, - ".cm-completionIcon-text": { - "&:after": { content: "'\\ea93'" }, - color: editorTheme.symbolIconTextForegroundColor - }, - - - "& .cm-tooltip-autocomplete ul li[aria-selected] .cm-completionIcon": { - color: editorTheme.suggestWidgetSelectedIconForegroundColor, - }, - - }; - - if (options.firstLineMeta) { - styleSpec[".cm-content .cm-line:first-of-type, .cm-content .cm-line:first-of-type span"] = { - color: editorTheme.lightTextColor - }; - } - - return EditorView.theme(styleSpec, {dark: editorTheme.darkMode}); - -} // map vscode theme names to highlight themes -const vscodeThemes: Record = { - "Light (Visual Studio)": lightHighlightStyle(), - "Light+ (default light)": lightPlusHighlightStyle(), - "Light+ V2 (Experimental)": lightPlusV2HighlightStyle(), - "Quiet Light": quietLightHighlightStyle(), - "Solarized Light": solarizedLightHighlightStyle(), - "Abyss": darkPlusHighlightStyle(), // abyssHighlightStyle(), - "Dark (Visual Studio)": darkHighlightStyle(), - "Dark+ (default dark)": darkPlusHighlightStyle(), - "Dark+ V2 (Experimental)": darkPlusV2HighlightStyle(), - "Kimbie Dark": darkPlusHighlightStyle(), // kimbieDarkHighlightStyle(), - "Monokai Dimmed": monokaiDimmedHighlightStyle(), - "Monokai": monokaiHighlightStyle(), - "Red": redHighlightStyle(), - "Solarized Dark": solarizedDarkHighlightStyle(), - "Tomorrow Night Blue": tomorrowNightBlueHighlightStyle(), - "Dark High Contrast": highContrastDarkHighlightStyle(), - "Light High Contrast": highContrastLightHighlightStyle(), +const vscodeThemes: Record = { + "Light (Visual Studio)": Style.light(), + "Light+ (default light)": Style.lightPlus(), + "Light+ V2 (Experimental)": Style.lightPlusV2(), + "Quiet Light": Style.quietLight(), + "Solarized Light": Style.solarizedLight(), + "Abyss": Style.darkPlus(), // abyss(), + "Dark (Visual Studio)": Style.dark(), + "Dark+ (default dark)": Style.darkPlus(), + "Dark+ V2 (Experimental)": Style.darkPlusV2(), + "Kimbie Dark": Style.darkPlus(), // kimbieDark(), + "Monokai Dimmed": Style.monokaiDimmed(), + "Monokai": Style.monokai(), + "Red": Style.red(), + "Solarized Dark": Style.solarizedDark(), + "Tomorrow Night Blue": Style.tomorrowNightBlue(), + "Dark High Contrast": Style.highContrastDark(), + "Light High Contrast": Style.highContrastLight(), }; -function codemirrorHighlightStyle(editorTheme: EditorTheme) { - +const highlightStyleHelper = (editorTheme: EditorTheme) => { // first use active vscode theme (if available) const vscodeTheme = document.body.getAttribute('data-vscode-theme-name'); + if (vscodeTheme) { const HighlightStyle = vscodeThemes[vscodeTheme]; if (HighlightStyle) { return HighlightStyle; - } + } } // otherwise use default logic if (editorTheme.solarizedMode) { - return solarizedLightHighlightStyle(); + return Style.solarizedLight(); } else { - const colors = editorTheme.darkMode ? vscodeDarkHighlightColors : vscodeLightHighlightColors; - return HighlightStyle.define([ - {tag: [t.operator, t.operatorKeyword, t.brace], color: colors.operator }, - {tag: [t.heading], color: colors.heading}, - {tag: [t.meta,t.comment], color: colors.comment}, - {tag: [t.keyword, t.moduleKeyword], color: colors.keyword}, - {tag: [t.number], color: colors.number}, - {tag: [t.regexp], color: colors.regexp}, - {tag: [t.definition(t.name)], colors: colors.definition}, - {tag: [t.invalid], color: colors.invalid}, - {tag: [t.string], color: colors.string}, - {tag: [t.bracket, t.angleBracket, t.squareBracket], color: colors.bracket}, - {tag: [t.function(t.variableName)], color: colors.function}, - {tag: [t.className], color: colors.className}, - {tag: [t.controlKeyword], color: colors.controlKeyword}, - {tag: [t.variableName], color: colors.variableName} - ]); + return editorTheme.darkMode ? Style.vscodeDark() : Style.vscodeLight(); } - -} - - -interface CodeMirrorHighlightColors { - operator: string; - heading: string; - comment: string; - keyword: string; - number: string; // also constant - regexp: string; - definition: string; - invalid: string; - string: string; - bracket: string; - function: string; - className: string; - controlKeyword: string; - variableName: string; -} - - -// vscode light -const vscodeLightHighlightColors: CodeMirrorHighlightColors = { - operator: "#000000", - heading: "#000080", - comment: "#008000", - keyword: "#0000ff", - number: "#098658", - regexp: "#811f3f", - definition: "#001080", - invalid: "#cd3131", - string: "#a31515", - bracket: "#000000", - function: "#795e26", - className: "#267f99", - controlKeyword: "#af00db", - variableName: "#0070c1" -} - - - -// vscode dark -const vscodeDarkHighlightColors: CodeMirrorHighlightColors = { - operator: "#d4d4d4", - heading: "#000080", - comment: "#6a9955", - keyword: "#569cd6", - number: "#b5cea8", - regexp: "#646695", - definition: "#9cdcfe", - invalid: "#f44747", - string: "#ce9178", - bracket: "#808080", - function: "#dcdcaa", - className: "#4ec9b0", - controlKeyword: "#c586c0", - variableName: "#4fc1ff" -} - - - - -function darkHighlightStyle() { - const config = { - name: 'dark', - dark: true, - background: '#1E1E1E', - foreground: '#D4D4D4', - selection: '#264F78', - cursor: '#BBBBBB', - dropdownBackground: '#1E1E1E', - dropdownBorder: '#454545', - activeLine: '#264F78', - matchingBracket: '#0064001a', - keyword: '#569cd6', - storage: '#569cd6', - variable: '#569cd6', - parameter: '#BBBBBB', - function: '#dcdcaa', - string: '#ce9178', - constant: '#BBBBBB', - type: '#4ec9b0', - class: '#4ec9b0', - number: '#b5cea8', - comment: '#6A9955', - heading: '#569cd6', - invalid: '#f44747', - regexp: '#d16969', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - - -function darkPlusHighlightStyle() { - const config = { - name: 'darkPlus', - dark: true, - background: '#1E1E1E', - foreground: '#BBBBBB', - selection: '#264F78', - cursor: '#BBBBBB', - dropdownBackground: '#252526', - dropdownBorder: '#454545', - activeLine: '#264F78', - matchingBracket: '#0064001a', - keyword: '#569cd6', - storage: '#569cd6', - variable: '#9CDCFE', - parameter: '#9CDCFE', - function: '#DCDCAA', - string: '#ce9178', - constant: '#569cd6', - type: '#4EC9B0', - class: '#4EC9B0', - number: '#b5cea8', - comment: '#6a9955', - heading: '#000080', - invalid: '#f44747', - regexp: '#646695', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - - -function darkPlusV2HighlightStyle() { - const config = { - name: 'darkPlusV2', - dark: true, - background: '#1f1f1f', - foreground: '#ffffffc5', - selection: '#264F78', - cursor: '#ffffffc5', - dropdownBackground: '#1f1f1f', - dropdownBorder: '#ffffff17', - activeLine: '#264F78', - matchingBracket: '#0064001a', - keyword: '#569cd6', - storage: '#569cd6', - variable: '#ffffffc5', - parameter: '#BBBBBB', - function: '#dcdcaa', - string: '#ce9178', - constant: '#BBBBBB', - type: '#4ec9b0', - class: '#4ec9b0', - number: '#b5cea8', - comment: '#6a9955', - heading: '#000080', - invalid: '#f85149', - regexp: '#646695', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - -function highContrastDarkHighlightStyle() { - const config = { - name: 'highContrastDark', - dark: true, - background: '#000000', - foreground: '#FFFFFF', - selection: '#FFFFFF', - cursor: '#ffffff', - dropdownBackground: '#000000', - dropdownBorder: '#6FC3DF', - activeLine: '#FFFFFF', - matchingBracket: '#FFFFFF', - keyword: '#569cd6', - storage: '#569cd6', - variable: '#9CDCFE', - parameter: '#9CDCFE', - function: '#DCDCAA', - string: '#ce9178', - constant: '#569cd6', - type: '#4EC9B0', - class: '#4EC9B0', - number: '#b5cea8', - comment: '#7ca668', - heading: '#6796e6', - invalid: '#f44747', - regexp: '#d16969', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - -function highContrastLightHighlightStyle() { - const config = { - name: 'highContrastLight', - dark: false, - background: '#ffffff', - foreground: '#292929', - selection: '#0F4A85', - cursor: '#292929', - dropdownBackground: '#ffffff', - dropdownBorder: '#0F4A85', - activeLine: '#0F4A85', - matchingBracket: '#0000', - keyword: '#0F4A85', - storage: '#0F4A85', - variable: '#001080', - parameter: '#001080', - function: '#5e2cbc', - string: '#0F4A85', - constant: '#0F4A85', - type: '#185E73', - class: '#185E73', - number: '#096d48', - comment: '#515151', - heading: '#0F4A85', - invalid: '#B5200D', - regexp: '#811F3F', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); } +async function codemirrorHighlightStyle(editorTheme: EditorTheme, ui: EditorUI) { + const vscodeTheme = document.body.getAttribute('data-vscode-theme-name'); -function lightHighlightStyle() { - const config = { - name: 'light', - dark: false, + const a = { + // name: 'light', + // dark: false, background: '#FFFFFF', foreground: '#000000', selection: '#ADD6FF', @@ -720,491 +156,97 @@ function lightHighlightStyle() { invalid: '#cd3131', regexp: '#811f3f', }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - -function lightPlusHighlightStyle() { - const config = { - name: 'lightPlus', - dark: false, - background: '#ffffff', - foreground: '#333333', - selection: '#ADD6FF', - cursor: '#333333', - dropdownBackground: '#F3F3F3', - dropdownBorder: '#C8C8C8', - activeLine: '#ADD6FF', - matchingBracket: '#0064001a', - keyword: '#0000ff', - storage: '#0000ff', - variable: '#001080', - parameter: '#001080', - function: '#795E26', - string: '#a31515', - constant: '#0000ff', - type: '#267f99', - class: '#267f99', - number: '#098658', - comment: '#008000', - heading: '#000080', - invalid: '#cd3131', - regexp: '#811f3f', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - -function lightPlusV2HighlightStyle() { - const config = { - name: 'lightPlusV2', - dark: false, - background: '#ffffff', - foreground: '#000000e4', - selection: '#ADD6FF', - cursor: '#000000e4', - dropdownBackground: '#ffffff', - dropdownBorder: '#0000001a', - activeLine: '#ADD6FF', - matchingBracket: '#0064001a', - keyword: '#0000ff', - storage: '#0000ff', - variable: '#000000e4', - parameter: '#333333', - function: '#795e26', - string: '#a31515', - constant: '#333333', - type: '#267f99', - class: '#267f99', - number: '#098658', - comment: '#008000', - heading: '#000080', - invalid: '#f85149', - regexp: '#811f3f', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} - + const b: any = {} + for (const [k, v] of Object.entries(a)) { + b[v.toLowerCase()] = k + } -function monokaiHighlightStyle() { - const config = { - name: 'monokai', - dark: true, - background: '#272822', - foreground: '#f8f8f2', - selection: '#878b9180', - cursor: '#f8f8f0', - dropdownBackground: '#272822', - dropdownBorder: '#75715E', - activeLine: '#3e3d32', - matchingBracket: '#3e3d32', - keyword: '#F92672', - storage: '#F92672', - variable: '#F8F8F2', - parameter: '#FD971F', - function: '#A6E22E', - string: '#E6DB74', - constant: '#AE81FF', - type: '#A6E22E', - class: '#A6E22E', - number: '#AE81FF', - comment: '#88846f', - heading: '#A6E22E', - invalid: '#F44747', - regexp: '#E6DB74', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} + const tokenColorsLookup = { + type: ['support.type'], + class: ['support.class', "entity.name.class", "entity.name.type.class"], + comment: ['comment'], + heading: ['markup.heading'], + invalid: ['invalid', "invalid.illegal", "invalid.broken", "invalid.deprecated", "invalid.unimplemented"], + number: ['constant.numeric'], + regexp: ['constant.regexp', "string.regexp"], + string: ['string'], + storage: ['storage'], + keyword: ['keyword'], + variable: ['variable.language', 'variable'], + constant: ['constant.language', 'constant'], + function: ['support.function', 'entity.name.function'], + parameter: ['variable.parameter', 'variable.parameter.function', 'constant.language'], // not great + } + const colorsLookup = { + foreground: ['editor.foreground'], + background: ['editor.background'], + dropdownBackground: ['editor.background'], + selection: ['editor.selectionHighlightBackground', "editor.selectionBackground"], + cursor: ["terminalCursor.foreground"], + type: ["symbolIcon.typeParameterForeground"] + } -function redHighlightStyle() { - const config = { - name: 'red', - dark: true, - background: '#390000', - foreground: '#F8F8F8', - selection: '#750000', - cursor: '#970000', - dropdownBackground: '#390000', - dropdownBorder: '#220000', - activeLine: '#ff000033', - matchingBracket: '#ff000033', - keyword: '#f12727ff', - storage: '#ff6262ff', - variable: '#fb9a4bff', - parameter: '#fb9a4bff', - function: '#ffb454ff', - string: '#cd8d8dff', - constant: '#994646ff', - type: '#9df39fff', - class: '#fec758ff', - number: '#994646ff', - comment: '#e7c0c0ff', - heading: '#fec758ff', - invalid: '#ffffffff', - regexp: '#ffb454ff', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); + const themeData = await ui.display.getThemeData(vscodeTheme!) + // const td = themeData.tokenColors + // console.log('BEEBEEBEE data-vscode-theme-name', vscodeTheme) + // const yahoo = Object.entries(td).map(([scope, { foreground }]: any) => ({ + // scope, + // foreground, + // match: b[foreground?.toLowerCase()] + // })) + // const yahoo2 = groupBy(yahoo, ({ match }) => match); + // console.log('YAHOO2!', yahoo2) + // const baboo = groupBy(Object.entries(themeData.colors).map(([name, color]: any) => ({ + // name, + // color, + // match: b[color?.toLowerCase()] + // })), ({ match }) => match) + // console.log('BABOOaaaa!', themeData.colors) + + const tt = highlightStyleHelper(editorTheme) + + const myColors = { + ...a, + ...deleteUndefineds(objValueMap(colorsLookup, (v: any) => first(v, (vv: any) => themeData.colors[vv]))), + ...deleteUndefineds(objValueMap(tokenColorsLookup, (v: any) => first(v, (vv: any) => themeData.tokenColors[vv]?.foreground))), + } as Style.CodeHighlightConfig + Style.highlightStyleFromConfig(myColors) + + console.log('THEME DATA!!!', themeData) + // TODO: separate scopes by period and use precedence + // our assumption about space separation is also incorrect + + return Style.highlightStyleFromConfig(myColors) + +} + +const deleteUndefineds = (ob: { [k: string]: any }) => { + const res: { [k: string]: any } = {} + + for (const [k, v] of Object.entries(ob)) { + if (v) res[k] = v + } + return res } - -function monokaiDimmedHighlightStyle() { - const config = { - name: 'monokaiDimmed', - dark: true, - background: '#1e1e1e', - foreground: '#c5c8c6', - selection: '#676b7180', - cursor: '#c07020', - dropdownBackground: '#1e1e1e', - dropdownBorder: '#454545', - activeLine: '#303030', - matchingBracket: '#303030', - keyword: '#6089B4', - storage: '#9872A2', - variable: '#6089B4', - parameter: '#6089B4', - function: '#CE6700', - string: '#9AA83A', - constant: '#8080FF', - type: '#9B0000', - class: '#9B0000', - number: '#6089B4', - comment: '#9A9B99', - heading: '#D0B344', - invalid: '#FF0B00', - regexp: '#9AA83A', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); +const first = (ar: any[], f: Function) => { + for (const a of ar) { + const fa = f(a) + if (fa) return fa + } } -function quietLightHighlightStyle() { - const config = { - name: 'quietLight', - dark: false, - background: '#F5F5F5', - foreground: '#333333', - selection: '#C9D0D9', - cursor: '#54494B', - dropdownBackground: '#F5F5F5', - dropdownBorder: '#C8C8C8', - activeLine: '#E4F6D4', - matchingBracket: '#E4F6D4', - keyword: '#4B69C6', - storage: '#4B69C6', - variable: '#7A3E9D', - parameter: '#7A3E9D', - function: '#AA3731', - string: '#448C27', - constant: '#9C5D27', - type: '#7A3E9D', - class: '#267f99', - number: '#9C5D27', - comment: '#AAAAAA', - heading: '#AA3731', - invalid: '#cd3131', - regexp: '#4B69C6', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} +const objValueMap = (obj: Object, f: Function) => + Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, f(v)])) -function solarizedDarkHighlightStyle() { - const config = { - name: 'solarizedDark', - dark: true, - background: '#002B36', - foreground: '#93A1A1', - selection: '#274642', - cursor: '#D30102', - dropdownBackground: '#002B36', - dropdownBorder: '#2AA19899', - activeLine: '#073642', - matchingBracket: '#073642', - keyword: '#859900', - storage: '#93A1A1', - variable: '#268BD2', - parameter: '#268BD2', - function: '#268BD2', - string: '#2AA198', - constant: '#CB4B16', - type: '#CB4B16', - class: '#CB4B16', - number: '#D33682', - comment: '#586E75', - heading: '#268BD2', - invalid: '#DC322F', - regexp: '#DC322F', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} -function solarizedLightHighlightStyle() { - const config = { - name: 'solarizedLight', - dark: false, - background: '#FDF6E3', - foreground: '#586E75', - selection: '#EEE8D5', - cursor: '#657B83', - dropdownBackground: '#FDF6E3', - dropdownBorder: '#D3AF86', - activeLine: '#EEE8D5', - matchingBracket: '#EEE8D5', - keyword: '#859900', - storage: '#586E75', - variable: '#268BD2', - parameter: '#268BD2', - function: '#268BD2', - string: '#2AA198', - constant: '#CB4B16', - type: '#CB4B16', - class: '#CB4B16', - number: '#D33682', - comment: '#93A1A1', - heading: '#268BD2', - invalid: '#DC322F', - regexp: '#DC322F', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); -} +function groupBy(list: any[], f: (a: any) => any) { + const map: { [k: string]: any } = {}; + list.forEach((item: any) => { + const fitem = f(item) + map[fitem] = [...(map[fitem] ?? []), item] + }); -function tomorrowNightBlueHighlightStyle() { - const config = { - name: 'tomorrowNightBlue', - dark: true, - background: '#002451', - foreground: '#ffffff', - selection: '#003f8e', - cursor: '#ffffff', - dropdownBackground: '#002451', - dropdownBorder: '#454545', - activeLine: '#00346e', - matchingBracket: '#00346e', - keyword: '#EBBBFF', - storage: '#EBBBFF', - variable: '#FF9DA4', - parameter: '#FF9DA4', - function: '#BBDAFF', - string: '#D1F1A9', - constant: '#FFC58F', - type: '#FFEEAD', - class: '#FFEEAD', - number: '#FFC58F', - comment: '#7285B7', - heading: '#D1F1A9', - invalid: '#a92049', - regexp: '#D1F1A9', - }; - return HighlightStyle.define([ - {tag: t.keyword, color: config.keyword}, - {tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable}, - {tag: [t.propertyName], color: config.function}, - {tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string}, - {tag: [t.function(t.variableName), t.labelName], color: config.function}, - {tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant}, - {tag: [t.definition(t.name), t.separator], color: config.variable}, - {tag: [t.className], color: config.class}, - {tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number}, - {tag: [t.typeName], color: config.type, fontStyle: config.type}, - {tag: [t.operator, t.operatorKeyword], color: config.keyword}, - {tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp}, - {tag: [t.meta, t.comment], color: config.comment}, - {tag: t.strong, fontWeight: 'bold'}, - {tag: t.emphasis, fontStyle: 'italic'}, - {tag: t.link, textDecoration: 'underline'}, - {tag: t.heading, fontWeight: 'bold', color: config.heading}, - {tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable}, - {tag: t.invalid, color: config.invalid}, - {tag: t.strikethrough, textDecoration: 'line-through'}, - ]); + return map; } - - - diff --git a/packages/editor-codemirror/src/behaviors/themeHighlightStyle.ts b/packages/editor-codemirror/src/behaviors/themeHighlightStyle.ts new file mode 100644 index 00000000..3ed9f8cf --- /dev/null +++ b/packages/editor-codemirror/src/behaviors/themeHighlightStyle.ts @@ -0,0 +1,593 @@ +import { HighlightStyle } from "@codemirror/language"; +import { tags as t } from "@lezer/highlight" + + +//============================ +// Highlight Style Definitions +//============================ + + +// vscode light +export const vscodeLight = () => highlightStyleFromColors({ + operator: "#000000", + heading: "#000080", + comment: "#008000", + keyword: "#0000ff", + number: "#098658", + regexp: "#811f3f", + definition: "#001080", + invalid: "#cd3131", + string: "#a31515", + bracket: "#000000", + function: "#795e26", + className: "#267f99", + controlKeyword: "#af00db", + variableName: "#0070c1" +}) + +// vscode dark +export const vscodeDark = () => highlightStyleFromColors({ + operator: "#d4d4d4", + heading: "#000080", + comment: "#6a9955", + keyword: "#569cd6", + number: "#b5cea8", + regexp: "#646695", + definition: "#9cdcfe", + invalid: "#f44747", + string: "#ce9178", + bracket: "#808080", + function: "#dcdcaa", + className: "#4ec9b0", + controlKeyword: "#c586c0", + variableName: "#4fc1ff" +}) + +export function dark() { + const config = { + name: 'dark', + dark: true, + background: '#1E1E1E', + foreground: '#D4D4D4', + selection: '#264F78', + cursor: '#BBBBBB', + dropdownBackground: '#1E1E1E', + dropdownBorder: '#454545', + activeLine: '#264F78', + matchingBracket: '#0064001a', + keyword: '#569cd6', + storage: '#569cd6', + variable: '#569cd6', + parameter: '#BBBBBB', + function: '#dcdcaa', + string: '#ce9178', + constant: '#BBBBBB', + type: '#4ec9b0', + class: '#4ec9b0', + number: '#b5cea8', + comment: '#6A9955', + heading: '#569cd6', + invalid: '#f44747', + regexp: '#d16969', + }; + return highlightStyleFromConfig(config); +} + + +export function darkPlus() { + const config = { + name: 'darkPlus', + dark: true, + background: '#1E1E1E', + foreground: '#BBBBBB', + selection: '#264F78', + cursor: '#BBBBBB', + dropdownBackground: '#252526', + dropdownBorder: '#454545', + activeLine: '#264F78', + matchingBracket: '#0064001a', + keyword: '#569cd6', + storage: '#569cd6', + variable: '#9CDCFE', + parameter: '#9CDCFE', + function: '#DCDCAA', + string: '#ce9178', + constant: '#569cd6', + type: '#4EC9B0', + class: '#4EC9B0', + number: '#b5cea8', + comment: '#6a9955', + heading: '#000080', + invalid: '#f44747', + regexp: '#646695', + }; + return highlightStyleFromConfig(config); +} + + +export function darkPlusV2() { + const config = { + name: 'darkPlusV2', + dark: true, + background: '#1f1f1f', + foreground: '#ffffffc5', + selection: '#264F78', + cursor: '#ffffffc5', + dropdownBackground: '#1f1f1f', + dropdownBorder: '#ffffff17', + activeLine: '#264F78', + matchingBracket: '#0064001a', + keyword: '#569cd6', + storage: '#569cd6', + variable: '#ffffffc5', + parameter: '#BBBBBB', + function: '#dcdcaa', + string: '#ce9178', + constant: '#BBBBBB', + type: '#4ec9b0', + class: '#4ec9b0', + number: '#b5cea8', + comment: '#6a9955', + heading: '#000080', + invalid: '#f85149', + regexp: '#646695', + }; + return highlightStyleFromConfig(config); +} + +export function highContrastDark() { + const config = { + name: 'highContrastDark', + dark: true, + background: '#000000', + foreground: '#FFFFFF', + selection: '#FFFFFF', + cursor: '#ffffff', + dropdownBackground: '#000000', + dropdownBorder: '#6FC3DF', + activeLine: '#FFFFFF', + matchingBracket: '#FFFFFF', + keyword: '#569cd6', + storage: '#569cd6', + variable: '#9CDCFE', + parameter: '#9CDCFE', + function: '#DCDCAA', + string: '#ce9178', + constant: '#569cd6', + type: '#4EC9B0', + class: '#4EC9B0', + number: '#b5cea8', + comment: '#7ca668', + heading: '#6796e6', + invalid: '#f44747', + regexp: '#d16969', + }; + return highlightStyleFromConfig(config); +} + +export function highContrastLight() { + const config = { + name: 'highContrastLight', + dark: false, + background: '#ffffff', + foreground: '#292929', + selection: '#0F4A85', + cursor: '#292929', + dropdownBackground: '#ffffff', + dropdownBorder: '#0F4A85', + activeLine: '#0F4A85', + matchingBracket: '#0000', + keyword: '#0F4A85', + storage: '#0F4A85', + variable: '#001080', + parameter: '#001080', + function: '#5e2cbc', + string: '#0F4A85', + constant: '#0F4A85', + type: '#185E73', + class: '#185E73', + number: '#096d48', + comment: '#515151', + heading: '#0F4A85', + invalid: '#B5200D', + regexp: '#811F3F', + }; + return highlightStyleFromConfig(config); +} + + +export function light() { + const config = { + name: 'light', + dark: false, + background: '#FFFFFF', + foreground: '#000000', + selection: '#ADD6FF', + cursor: '#333333', + dropdownBackground: '#FFFFFF', + dropdownBorder: '#C8C8C8', + activeLine: '#ADD6FF', + matchingBracket: '#0064001a', + keyword: '#0000ff', + storage: '#0000ff', + variable: '#0000ff', + parameter: '#333333', + function: '#795e26', + string: '#a31515', + constant: '#333333', + type: '#267f99', + class: '#267f99', + number: '#098658', + comment: '#008000', + heading: '#800000', + invalid: '#cd3131', + regexp: '#811f3f', + }; + return highlightStyleFromConfig(config); +} + +export function lightPlus() { + const config = { + name: 'lightPlus', + dark: false, + background: '#ffffff', + foreground: '#333333', + selection: '#ADD6FF', + cursor: '#333333', + dropdownBackground: '#F3F3F3', + dropdownBorder: '#C8C8C8', + activeLine: '#ADD6FF', + matchingBracket: '#0064001a', + keyword: '#0000ff', + storage: '#0000ff', + variable: '#001080', + parameter: '#001080', + function: '#795E26', + string: '#a31515', + constant: '#0000ff', + type: '#267f99', + class: '#267f99', + number: '#098658', + comment: '#008000', + heading: '#000080', + invalid: '#cd3131', + regexp: '#811f3f', + }; + return highlightStyleFromConfig(config); +} + +export function lightPlusV2() { + const config = { + name: 'lightPlusV2', + dark: false, + background: '#ffffff', + foreground: '#000000e4', + selection: '#ADD6FF', + cursor: '#000000e4', + dropdownBackground: '#ffffff', + dropdownBorder: '#0000001a', + activeLine: '#ADD6FF', + matchingBracket: '#0064001a', + keyword: '#0000ff', + storage: '#0000ff', + variable: '#000000e4', + parameter: '#333333', + function: '#795e26', + string: '#a31515', + constant: '#333333', + type: '#267f99', + class: '#267f99', + number: '#098658', + comment: '#008000', + heading: '#000080', + invalid: '#f85149', + regexp: '#811f3f', + }; + return highlightStyleFromConfig(config); +} + + +export function monokai() { + const config = { + name: 'monokai', + dark: true, + background: '#272822', + foreground: '#f8f8f2', + selection: '#878b9180', + cursor: '#f8f8f0', + dropdownBackground: '#272822', + dropdownBorder: '#75715E', + activeLine: '#3e3d32', + matchingBracket: '#3e3d32', + keyword: '#F92672', + storage: '#F92672', + variable: '#F8F8F2', + parameter: '#FD971F', + function: '#A6E22E', + string: '#E6DB74', + constant: '#AE81FF', + type: '#A6E22E', + class: '#A6E22E', + number: '#AE81FF', + comment: '#88846f', + heading: '#A6E22E', + invalid: '#F44747', + regexp: '#E6DB74', + }; + return highlightStyleFromConfig(config); +} + +export function red() { + const config = { + name: 'red', + dark: true, + background: '#390000', + foreground: '#F8F8F8', + selection: '#750000', + cursor: '#970000', + dropdownBackground: '#390000', + dropdownBorder: '#220000', + activeLine: '#ff000033', + matchingBracket: '#ff000033', + keyword: '#f12727ff', + storage: '#ff6262ff', + variable: '#fb9a4bff', + parameter: '#fb9a4bff', + function: '#ffb454ff', + string: '#cd8d8dff', + constant: '#994646ff', + type: '#9df39fff', + class: '#fec758ff', + number: '#994646ff', + comment: '#e7c0c0ff', + heading: '#fec758ff', + invalid: '#ffffffff', + regexp: '#ffb454ff', + }; + return highlightStyleFromConfig(config); +} + + +export function monokaiDimmed() { + const config = { + name: 'monokaiDimmed', + dark: true, + background: '#1e1e1e', + foreground: '#c5c8c6', + selection: '#676b7180', + cursor: '#c07020', + dropdownBackground: '#1e1e1e', + dropdownBorder: '#454545', + activeLine: '#303030', + matchingBracket: '#303030', + keyword: '#6089B4', + storage: '#9872A2', + variable: '#6089B4', + parameter: '#6089B4', + function: '#CE6700', + string: '#9AA83A', + constant: '#8080FF', + type: '#9B0000', + class: '#9B0000', + number: '#6089B4', + comment: '#9A9B99', + heading: '#D0B344', + invalid: '#FF0B00', + regexp: '#9AA83A', + }; + return highlightStyleFromConfig(config); +} + +export function quietLight() { + const config = { + name: 'quietLight', + dark: false, + background: '#F5F5F5', + foreground: '#333333', + selection: '#C9D0D9', + cursor: '#54494B', + dropdownBackground: '#F5F5F5', + dropdownBorder: '#C8C8C8', + activeLine: '#E4F6D4', + matchingBracket: '#E4F6D4', + keyword: '#4B69C6', + storage: '#4B69C6', + variable: '#7A3E9D', + parameter: '#7A3E9D', + function: '#AA3731', + string: '#448C27', + constant: '#9C5D27', + type: '#7A3E9D', + class: '#267f99', + number: '#9C5D27', + comment: '#AAAAAA', + heading: '#AA3731', + invalid: '#cd3131', + regexp: '#4B69C6', + }; + return highlightStyleFromConfig(config); +} + +export function solarizedDark() { + const config = { + name: 'solarizedDark', + dark: true, + background: '#002B36', + foreground: '#93A1A1', + selection: '#274642', + cursor: '#D30102', + dropdownBackground: '#002B36', + dropdownBorder: '#2AA19899', + activeLine: '#073642', + matchingBracket: '#073642', + keyword: '#859900', + storage: '#93A1A1', + variable: '#268BD2', + parameter: '#268BD2', + function: '#268BD2', + string: '#2AA198', + constant: '#CB4B16', + type: '#CB4B16', + class: '#CB4B16', + number: '#D33682', + comment: '#586E75', + heading: '#268BD2', + invalid: '#DC322F', + regexp: '#DC322F', + }; + return highlightStyleFromConfig(config); +} + +export function solarizedLight() { + const config = { + name: 'solarizedLight', + dark: false, + background: '#FDF6E3', + foreground: '#586E75', + selection: '#EEE8D5', + cursor: '#657B83', + dropdownBackground: '#FDF6E3', + dropdownBorder: '#D3AF86', + activeLine: '#EEE8D5', + matchingBracket: '#EEE8D5', + keyword: '#859900', + storage: '#586E75', + variable: '#268BD2', + parameter: '#268BD2', + function: '#268BD2', + string: '#2AA198', + constant: '#CB4B16', + type: '#CB4B16', + class: '#CB4B16', + number: '#D33682', + comment: '#93A1A1', + heading: '#268BD2', + invalid: '#DC322F', + regexp: '#DC322F', + }; + return highlightStyleFromConfig(config); +} + + +export function tomorrowNightBlue() { + const config = { + name: 'tomorrowNightBlue', + dark: true, + background: '#002451', + foreground: '#ffffff', + selection: '#003f8e', + cursor: '#ffffff', + dropdownBackground: '#002451', + dropdownBorder: '#454545', + activeLine: '#00346e', + matchingBracket: '#00346e', + keyword: '#EBBBFF', + storage: '#EBBBFF', + variable: '#FF9DA4', + parameter: '#FF9DA4', + function: '#BBDAFF', + string: '#D1F1A9', + constant: '#FFC58F', + type: '#FFEEAD', + class: '#FFEEAD', + number: '#FFC58F', + comment: '#7285B7', + heading: '#D1F1A9', + invalid: '#a92049', + regexp: '#D1F1A9', + }; + return highlightStyleFromConfig(config); +} + + +//============================ +// Types and helper functions +//============================ + + +export interface CodeMirrorHighlightColors { + operator: string; + heading: string; + comment: string; + keyword: string; + number: string; // also constant + regexp: string; + definition: string; + invalid: string; + string: string; + bracket: string; + function: string; + className: string; + controlKeyword: string; + variableName: string; +} +function highlightStyleFromColors(colors: CodeMirrorHighlightColors) { + return HighlightStyle.define([ + { tag: [t.operator, t.operatorKeyword, t.brace], color: colors.operator }, + { tag: [t.heading], color: colors.heading }, + { tag: [t.meta, t.comment], color: colors.comment }, + { tag: [t.keyword, t.moduleKeyword], color: colors.keyword }, + { tag: [t.number], color: colors.number }, + { tag: [t.regexp], color: colors.regexp }, + { tag: [t.definition(t.name)], colors: colors.definition }, + { tag: [t.invalid], color: colors.invalid }, + { tag: [t.string], color: colors.string }, + { tag: [t.bracket, t.angleBracket, t.squareBracket], color: colors.bracket }, + { tag: [t.function(t.variableName)], color: colors.function }, + { tag: [t.className], color: colors.className }, + { tag: [t.controlKeyword], color: colors.controlKeyword }, + { tag: [t.variableName], color: colors.variableName } + ]) +} + +export type CodeHighlightConfig = { + name: string, + dark: boolean, + background: string + foreground: string + selection: string + cursor: string + dropdownBackground: string + dropdownBorder: string + activeLine: string + matchingBracket: string, + keyword: string + storage: string + variable: string + parameter: string + function: string + string: string + constant: string + type: string + class: string + number: string + comment: string + heading: string + invalid: string + regexp: string +} +// ideally we would have: +export function highlightStyleFromConfig(config: CodeHighlightConfig) { + return HighlightStyle.define([ + { tag: t.keyword, color: config.keyword }, + { tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable }, + { tag: [t.propertyName], color: config.function }, + { tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string }, + { tag: [t.function(t.variableName), t.labelName], color: config.function }, + { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant }, + { tag: [t.definition(t.name), t.separator], color: config.variable }, + { tag: [t.className], color: config.class }, + { tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number }, + { tag: [t.typeName], color: config.type, fontStyle: config.type }, + { tag: [t.operator, t.operatorKeyword], color: config.keyword }, + { tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp }, + { tag: [t.meta, t.comment], color: config.comment }, + { tag: t.strong, fontWeight: 'bold' }, + { tag: t.emphasis, fontStyle: 'italic' }, + { tag: t.link, textDecoration: 'underline' }, + { tag: t.heading, fontWeight: 'bold', color: config.heading }, + { tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable }, + { tag: t.invalid, color: config.invalid }, + { tag: t.strikethrough, textDecoration: 'line-through' }, + ]) +} diff --git a/packages/editor-codemirror/src/behaviors/themeSpec.ts b/packages/editor-codemirror/src/behaviors/themeSpec.ts new file mode 100644 index 00000000..db205fb5 --- /dev/null +++ b/packages/editor-codemirror/src/behaviors/themeSpec.ts @@ -0,0 +1,259 @@ +import { EditorView } from "@codemirror/view"; +import { StyleSpec } from 'style-mod'; +import { CodeViewOptions, EditorTheme } from "editor"; + +export function codemirrorThemeSpec(editorTheme: EditorTheme, options: CodeViewOptions) { + + const completion = { Margin: 30, Width: 250 }; + + const styleSpec: { [selector: string]: StyleSpec } = { + "&": { + color: editorTheme.textColor, + backgroundColor: options.classes?.includes('pm-chunk-background-color') + ? editorTheme.chunkBackgroundColor + : editorTheme.backgroundColor, + border: "none", + fontSize: `${editorTheme.fixedWidthFontSizePt}pt` + }, + + "&.cm-editor.cm-focused": { + outline: `1px solid ${editorTheme.focusOutlineColor}` + }, + + ".cm-content": { + fontFamily: `${editorTheme.fixedWidthFont}`, + caretColor: editorTheme.cursorColor + }, + + ".cm-cursor, .cm-dropCursor": { borderLeftColor: editorTheme.cursorColor }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: editorTheme.selectionColor }, + + ".cm-panels": { backgroundColor: editorTheme.gutterBackgroundColor, color: editorTheme.gutterTextColor }, + ".cm-panels.cm-panels-top": { borderBottom: `2px solid ${editorTheme.paneBorderColor}` }, + ".cm-panels.cm-panels-bottom": { borderTop: `2px solid ${editorTheme.paneBorderColor}` }, + + ".cm-searchMatch": { + backgroundColor: editorTheme.findTextBackgroundColor, + outline: `1px solid${editorTheme.findTextBorderColor}` + }, + ".cm-searchMatch.cm-searchMatch-selected": { + backgroundColor: editorTheme.findTextBackgroundColor + }, + + ".cm-activeLine": { backgroundColor: editorTheme.backgroundColor }, + ".cm-selectionMatch": { backgroundColor: editorTheme.findTextBackgroundColor }, + + "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { + backgroundColor: editorTheme.findTextBackgroundColor + }, + + ".cm-gutters": { + backgroundColor: editorTheme.gutterBackgroundColor, + color: editorTheme.gutterTextColor, + border: "none", + paddingRight: "6px", + fontFamily: editorTheme.fixedWidthFont, + fontSize: `${editorTheme.fixedWidthFontSizePt}pt` + }, + + ".cm-activeLineGutter": { + backgroundColor: editorTheme.backgroundColor + }, + + ".cm-foldPlaceholder": { + backgroundColor: "transparent", + border: "none", + color: editorTheme.lightTextColor + }, + + ".cm-tooltip": { + border: "none", + backgroundColor: editorTheme.backgroundColor + }, + ".cm-tooltip .cm-tooltip-arrow:before": { + borderTopColor: "transparent", + borderBottomColor: "transparent" + }, + ".cm-tooltip .cm-tooltip-arrow:after": { + borderTopColor: editorTheme.paneBorderColor, + borderBottomColor: editorTheme.paneBorderColor + }, + // autocomplete (https://github.com/codemirror/autocomplete/blob/main/src/theme.ts) + + ".cm-tooltip.cm-tooltip-autocomplete": { + "& > ul": { + fontFamily: editorTheme.fixedWidthFont, + fontSize: `${editorTheme.fixedWidthFontSizePt}pt`, + whiteSpace: "nowrap", + overflow: "hidden auto", + maxWidth_fallback: "700px", + maxWidth: "min(700px, 95vw)", + minWidth: "250px", + maxHeight: "212px", + height: "100%", + listStyle: "none", + margin: 0, + padding: 3, + color: editorTheme.suggestWidgetForegroundColor, + backgroundColor: editorTheme.suggestWidgetBackgroundColor, + border: `1px solid ${editorTheme.suggestWidgetBorderColor}`, + + "& > li": { + overflowX: "hidden", + textOverflow: "ellipsis", + cursor: "pointer", + padding: "2px 2px", + lineHeight: 1.15, + display: "flex", + alignItems: "center" + }, + } + }, + + "& .cm-tooltip-autocomplete ul li[aria-selected]": { + background: editorTheme.suggestWidgetSelectedBackgroundColor, + color: editorTheme.suggestWidgetSelectedForegroundColor, + }, + + "& .cm-tooltip-autocomplete-disabled ul li[aria-selected]": { + background: editorTheme.suggestWidgetSelectedBackgroundColor, + }, + + ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": { + content: '"···"', + opacity: 0.5, + display: "block", + textAlign: "center" + }, + + ".cm-tooltip.cm-completionInfo": { + position: "absolute", + padding: "3px 9px", + width: "max-content", + maxWidth: `${completion.Width}px`, + boxSizing: "border-box", + color: editorTheme.suggestWidgetForegroundColor, + backgroundColor: editorTheme.suggestWidgetBackgroundColor, + border: `1px solid ${editorTheme.suggestWidgetBorderColor}` + }, + ".cm-tooltip.cm-completionInfo .cm-completionInfoHeader": { + fontFamily: editorTheme.fixedWidthFont + }, + // links don't work so change hteir appearance + ".cm-tooltip.cm-completionInfo a": { + color: editorTheme.suggestWidgetForegroundColor, + }, + ".cm-tooltip.cm-completionInfo a:hover": { + textDecoration: "none", + }, + ".cm-tooltip.cm-completionInfo p": { + margin: 0, + padding: 0 + }, + ".cm-tooltip.cm-completionInfo p.cm-completionInfoHeader": { + marginBottom: "1em" + }, + + ".cm-completionInfo.cm-completionInfo-left": { right: "100%" }, + ".cm-completionInfo.cm-completionInfo-right": { left: "100%" }, + ".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${completion.Margin}px` }, + ".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${completion.Margin}px` }, + + "& .cm-snippetField": { backgroundColor: editorTheme.invisibleTextColor }, + ".cm-snippetFieldPosition": { + verticalAlign: "text-top", + width: 0, + height: "1.15em", + display: "inline-block", + margin: "0 -0.7px -.7em", + borderLeft: `1.4px dotted ${editorTheme.gutterBackgroundColor}` + }, + + ".cm-completionMatchedText": { + textDecoration: "none", + color: editorTheme.suggestWidgetFocusHighlightForegroundColor + }, + + ".cm-completionDetail": { + marginLeft: "0.5em", + color: editorTheme.lightTextColor, + float: "right", + fontStyle: "normal" + }, + + "& .cm-tooltip-autocomplete ul li[aria-selected] .cm-completionDetail": { + color: editorTheme.suggestWidgetSelectedForegroundColor, + }, + + ".cm-completionIcon": { + fontFamily: "codicon", + fontSize: `${editorTheme.fixedWidthFontSizePt + 2}pt`, + display: "inline-block", + width: "1em", + textAlign: "center", + paddingLeft: ".1em", + paddingRight: ".3em", + opacity: "0.8", + boxSizing: "content-box" + }, + + ".cm-completionIcon-function, .cm-completionIcon-method": { + "&:after": { content: "'\\eb5f'" }, + color: editorTheme.symbolIconFunctionForegroundColor + }, + ".cm-completionIcon-class": { + "&:after": { content: "'\\eb5b'" }, + "color": editorTheme.symbolIconClassForegroundColor, + }, + ".cm-completionIcon-interface": { + "&:after": { content: "'\\eb61'" }, + color: editorTheme.symbolIconInterfaceForegroundColor + }, + ".cm-completionIcon-variable": { + "&:after": { content: "'\\ea88'" }, + color: editorTheme.symbolIconVariableForegroundColor + }, + ".cm-completionIcon-constant": { + "&:after": { content: "'\\eb5d'" }, + color: editorTheme.symbolIconConstantForegroundColor + }, + ".cm-completionIcon-type": { + "&:after": { content: "'\\ea92'" }, + color: editorTheme.symbolIconTypeParameterForegroundColor + }, + ".cm-completionIcon-enum": { + "&:after": { content: "'\\ea95'" }, + color: editorTheme.symbolIconEnumForegroundColor + }, + ".cm-completionIcon-property": { + "&:after": { content: "'\\eb65'" }, + color: editorTheme.symbolIconPropertyForegroundColor + }, + ".cm-completionIcon-keyword": { + "&:after": { content: "'\\eb62'" }, + color: editorTheme.symbolIconKeywordForegroundColor + }, + ".cm-completionIcon-namespace": { + "&:after": { content: "'\\ea8b'" }, + color: editorTheme.symbolIconNamespaceForegroundColor + }, + ".cm-completionIcon-text": { + "&:after": { content: "'\\ea93'" }, + color: editorTheme.symbolIconTextForegroundColor + }, + + + "& .cm-tooltip-autocomplete ul li[aria-selected] .cm-completionIcon": { + color: editorTheme.suggestWidgetSelectedIconForegroundColor, + }, + + }; + + if (options.firstLineMeta) { + styleSpec[".cm-content .cm-line:first-of-type, .cm-content .cm-line:first-of-type span"] = { + color: editorTheme.lightTextColor + }; + } + + return EditorView.theme(styleSpec, { dark: editorTheme.darkMode }); +} diff --git a/packages/editor-types/src/display.ts b/packages/editor-types/src/display.ts index ca9ef5ca..c4b13db9 100644 --- a/packages/editor-types/src/display.ts +++ b/packages/editor-types/src/display.ts @@ -18,9 +18,8 @@ import { XRef } from "./xref"; export interface EditorDisplay { openURL: (url: string) => void; + getThemeData: (name: string) => Promise; navigateToXRef: (file: string, xref: XRef) => void; navigateToFile: (file: string) => void; showContextMenu?: (items: EditorMenuItem[], clientX: number, clientY: number) => Promise; } - - diff --git a/packages/editor-types/src/vscode.ts b/packages/editor-types/src/vscode.ts index 37099678..7e0d720c 100644 --- a/packages/editor-types/src/vscode.ts +++ b/packages/editor-types/src/vscode.ts @@ -43,6 +43,7 @@ export const VSC_VEH_RenderDocument = 'vsc_veh_render_document'; export const VSC_VEH_EditorResourceUri = 'vsc_veh_editor_resource_url'; export const VSC_VEH_OpenURL = 'vsc_veh_open_url'; +export const VSC_VEH_GetThemeData = 'vsc_veh_get_theme_data' export const VSC_VEH_NavigateToXRef = 'vsc_veh_navigate_to_xref'; export const VSC_VEH_NavigateToFile = 'vsc_veh_navigate_to_file'; @@ -53,7 +54,7 @@ export const VSC_VEH_SelectImage = 'vsc_veh_select_image'; export type NavLocation = XRef | SourcePos; export interface VSCodeVisualEditor { - init: (markdown: string, navigation?: NavLocation) => Promise; + init: (markdown: string, navigation?: NavLocation) => Promise; focus: (navigation?: NavLocation) => Promise; isFocused: () => Promise; getMarkdownFromState: (state: unknown) => Promise; @@ -76,14 +77,12 @@ export interface HostContext { export interface VSCodeVisualEditorHost extends EditorDisplay, EditorUIImageResolver { getHostContext: () => Promise; reopenSourceMode: () => Promise; - onEditorReady: () => Promise; + onEditorReady: () => Promise; onEditorUpdated: (state: unknown) => Promise; onEditorStateChanged: (sourcePos: SourcePos) => Promise; flushEditorUpdates: () => Promise; saveDocument: () => Promise; renderDocument: () => Promise; editorResourceUri: (path: string) => Promise; + getThemeData: (themeName: string) => Promise } - - - diff --git a/packages/editor/src/editor/editor-theme.ts b/packages/editor/src/editor/editor-theme.ts index 19b46382..86a8918b 100644 --- a/packages/editor/src/editor/editor-theme.ts +++ b/packages/editor/src/editor/editor-theme.ts @@ -58,7 +58,7 @@ export interface EditorTheme { suggestWidgetHighlightForegroundColor: string; suggestWidgetSelectedBackgroundColor: string; suggestWidgetSelectedForegroundColor: string; - suggestWidgetSelectedIconForegroundColor: string; + suggestWidgetSelectedIconForegroundColor: string; symbolIconClassForegroundColor: string; symbolIconConstantForegroundColor: string; symbolIconEnumForegroundColor: string; @@ -141,7 +141,7 @@ export function defaultTheme(): EditorTheme { suggestWidgetHighlightForegroundColor: "#0066bf", suggestWidgetSelectedBackgroundColor: "#0060c0", suggestWidgetSelectedForegroundColor: "#ffffff", - suggestWidgetSelectedIconForegroundColor: "#ffffff", + suggestWidgetSelectedIconForegroundColor: "#ffffff", symbolIconClassForegroundColor: '#D67E00', symbolIconConstantForegroundColor: "#616161", symbolIconEnumForegroundColor: '#D67E00', @@ -178,6 +178,7 @@ export function defaultTheme(): EditorTheme { } export function applyTheme(theme: EditorTheme) { + console.log('APPLY THEME INTERNAL') // merge w/ defaults const defaults = defaultTheme(); theme = { @@ -244,7 +245,7 @@ export function applyTheme(theme: EditorTheme) { .pm-default-theme .pm-list-item-selected, .pm-default-theme .pm-grid-item-selected { background-color: ${defaults.findTextBackgroundColor} !important; - box-shadow: 0 0 0 1px ${defaults.findTextBorderColor}; + box-shadow: 0 0 0 1px ${defaults.findTextBorderColor}; border-radius: 3px; } .pm-default-theme .pm-rstudio-button { @@ -311,7 +312,7 @@ export function applyTheme(theme: EditorTheme) { .pm-list-item-selected, .pm-grid-item-selected { background-color: ${theme.findTextBackgroundColor} !important; - box-shadow: 0 0 0 1px ${theme.findTextBorderColor}; + box-shadow: 0 0 0 1px ${theme.findTextBorderColor}; border-radius: 3px; } .pm-selected-text { diff --git a/packages/editor/src/editor/editor.ts b/packages/editor/src/editor/editor.ts index 5773b0fe..cfe13c42 100644 --- a/packages/editor/src/editor/editor.ts +++ b/packages/editor/src/editor/editor.ts @@ -36,9 +36,9 @@ import { UIToolsAttr, UIToolsImage, EditorMenus, - EditorServer, - EditingOutlineLocation, - EditorOutline, + EditorServer, + EditingOutlineLocation, + EditorOutline, SourcePos, NavLocation, CodeViewActiveBlockContext, @@ -177,7 +177,7 @@ export interface EditorSetMarkdownResult { // unparsed meta unparsed_meta: { [key: string]: unknown }; - // updated outline + // updated outline location: EditingOutlineLocation; } @@ -292,30 +292,30 @@ export interface EditorOperations { ): Promise; // get content - getStateJson() : unknown; - getMarkdownFromStateJson(stateJson: unknown, options: PandocWriterOptions) : Promise; - getMarkdown(options: PandocWriterOptions) : Promise; + getStateJson(): unknown; + getMarkdownFromStateJson(stateJson: unknown, options: PandocWriterOptions): Promise; + getMarkdown(options: PandocWriterOptions): Promise; getEditorSourcePos(): SourcePos; getSlideIndex(): number; // codeviews - getCodeViewActiveBlockContext() : CodeViewActiveBlockContext | undefined; - setBlockSelection(context: CodeViewActiveBlockContext, action: CodeViewSelectionAction) : void; + getCodeViewActiveBlockContext(): CodeViewActiveBlockContext | undefined; + setBlockSelection(context: CodeViewActiveBlockContext, action: CodeViewSelectionAction): void; // subsystems - getFindReplace() : EditorFindReplace | undefined + getFindReplace(): EditorFindReplace | undefined // activation/navigation blur(): void; focus(navigation?: NavLocation): void; hasFocus(): boolean; navigate(type: NavigationType, id: string, recordCurrent: boolean, animate?: boolean): void; - navigateToSourcePos(pos: SourcePos) : void; + navigateToSourcePos(pos: SourcePos): void; // theme/content applyTheme(theme: EditorTheme): void; setMaxContentWidth(maxWidth: number, minPadding?: number): void; - + // events subscribe(event: EventType | string, handler: EventHandler): VoidFunction; @@ -326,7 +326,7 @@ export interface EditorOperations { onLoadFailed(error: unknown): void; } -export class Editor { +export class Editor { // core context passed from client private readonly parent: HTMLElement; private readonly context: EditorContext; @@ -416,7 +416,7 @@ export class Editor { docTypes: format.docTypes || [], }; - + // provide context defaults const defaultImages = defaultEditorUIImages(); @@ -470,7 +470,7 @@ export class Editor { this.format = format; this.keybindings = {}; this.pandocFormat = pandocFormat; - this.pandocCapabilities = pandocCapabilities; + this.pandocCapabilities = pandocCapabilities; // create core extensions this.extensions = this.initExtensions(); @@ -601,7 +601,7 @@ export class Editor { // provide option defaults options = pandocWriterOptions(options); - + // get the result const result = await this.pandocConverter.toProsemirror(markdown, this.pandocFormat); const { doc, line_wrapping, unrecognized, example_lists, unparsed_meta } = result; @@ -638,7 +638,7 @@ export class Editor { try { setTextSelection(loc.pos)(tr); } catch (e) { - // do-nothing, this error can happen and shouldn't result in + // do-nothing, this error can happen and shouldn't result in // a failure to setMarkdown } } @@ -680,7 +680,7 @@ export class Editor { return this.state.doc.attrs.initial; } - public getStateJson() : unknown { + public getStateJson(): unknown { return this.state.toJSON(); } @@ -843,7 +843,7 @@ export class Editor { } public hasFocus() { - return this.view.hasFocus() || + return this.view.hasFocus() || (window.document.hasFocus() && this.parent.contains(window.document.activeElement)) } @@ -851,12 +851,12 @@ export class Editor { (this.view.dom as HTMLElement).blur(); } - public getCodeViewActiveBlockContext() : CodeViewActiveBlockContext | undefined { + public getCodeViewActiveBlockContext(): CodeViewActiveBlockContext | undefined { return codeViewActiveBlockContext(this.state); } public setBlockSelection( - context: CodeViewActiveBlockContext, + context: CodeViewActiveBlockContext, action: CodeViewSelectionAction) { codeViewSetBlockSelection(this.view, context, action); } @@ -880,11 +880,11 @@ export class Editor { } } - public navigateToSourcePos(pos: SourcePos) { - + public navigateToSourcePos(pos: SourcePos) { + // find the index let cursorIndex = -1; - for (let i=(pos.locations.length-1); i>=0; i--) { + for (let i = (pos.locations.length - 1); i >= 0; i--) { if (pos.pos >= pos.locations[i].pos) { cursorIndex = i; break; @@ -895,11 +895,11 @@ export class Editor { let targetPos = editingRootNode(this.view.state.selection)!.pos; if (cursorIndex !== -1) { const locations = this.getEditorSourcePos().locations; - targetPos = (locations[cursorIndex] || locations[locations.length-1]).pos; + targetPos = (locations[cursorIndex] || locations[locations.length - 1]).pos; } // navigate - this.navigate(NavigationType.Pos, String(targetPos), false, false); + this.navigate(NavigationType.Pos, String(targetPos), false, false); } public resize() { @@ -944,7 +944,7 @@ export class Editor { // set global mode classes this.parent.classList.toggle('pm-dark-mode', !!theme.darkMode); this.parent.classList.toggle('pm-solarized-mode', !!theme.solarizedMode); - + // apply the rest of the theme applyTheme(theme); @@ -989,7 +989,7 @@ export class Editor { // note that calling this may put the editor in an 'ignore selection changes' // state so anyone calling this must also call contextMenuDismissed when // the menu is no longer in play (either ignored or dismissed) - public contextMenu(event: Event) : ContextMenuSource | undefined { + public contextMenu(event: Event): ContextMenuSource | undefined { if (event.target && event.target instanceof Node) { const pos = this.view.posAtDOM(event.target, 0); if (pos !== -1) { @@ -1088,13 +1088,13 @@ export class Editor { ); } - private createEditorMarkdown() : EditorMarkdown { + private createEditorMarkdown(): EditorMarkdown { const markdownFilter = markInputRuleFilter(this.schema, this.extensions.pandocMarks()); return { allowMarkdownPaste(state) { return markdownFilter(state); }, - markdownToSlice: async (markdown) : Promise => { + markdownToSlice: async (markdown): Promise => { // convert markdown to prosemirror const result = await this.pandocConverter.toProsemirror(markdown, this.pandocFormat); @@ -1114,7 +1114,7 @@ export class Editor { // this.editorMarkdown is not yet created when we call this so we setup a // proxy (chicken/egg: EditorMarkdown needs the initialized extensions) - private editorMarkdownProxy() : EditorMarkdown { + private editorMarkdownProxy(): EditorMarkdown { return { allowMarkdownPaste: (state) => { return this.editorMarkdown.allowMarkdownPaste(state); @@ -1144,10 +1144,10 @@ export class Editor { if (this.context.ui.spelling !== undefined) { this.extensions.registerPlugins( [realtimeSpellingPlugin( - this.schema, - this.extensions.pandocMarks(), - this.context.ui.spelling, - this.context.ui.prefs, + this.schema, + this.extensions.pandocMarks(), + this.context.ui.spelling, + this.context.ui.prefs, this.events )], true, @@ -1155,7 +1155,7 @@ export class Editor { this.extensions.register([{ contextMenuHandlers: () => [ spellingContextMenuHandler( - this.context.ui.spelling!, + this.context.ui.spelling!, this.context.ui.context.translateText ) ] @@ -1206,7 +1206,7 @@ export class Editor { return handleTextInput(view, from, to, text); }; - + // eslint-disable-next-line @typescript-eslint/no-explicit-any plugin.props.handleTextInput = customHandleTextInput; return plugin; @@ -1234,7 +1234,7 @@ export class Editor { } }, contextmenu: (_view: EditorView, event: Event) => { - + // don't handle if there is no imperative context menu handler if (!this.context.ui.display.showContextMenu) { return false; @@ -1266,7 +1266,7 @@ export class Editor { }); } - private preventSelectionChangePlugin() : Plugin { + private preventSelectionChangePlugin(): Plugin { return new Plugin({ filterTransaction: (tr: Transaction, state: EditorState) => { if (this.preventSelectionChange?.(state)) { @@ -1278,12 +1278,12 @@ export class Editor { }) } - private clipboardToDOMPlugin() : Plugin { + private clipboardToDOMPlugin(): Plugin { const schema = this.schema; return new Plugin({ key: new PluginKey('clipboard-to-dom'), - props: { - transformCopied(slice: Slice) : Slice { + props: { + transformCopied(slice: Slice): Slice { const newSlice = mapSlice(slice, node => { if (node.isText) { const clipboardMark = node.marks.find(mark => !!mark.type.spec.attrs?.clipboard); @@ -1292,17 +1292,17 @@ export class Editor { const attrs = { ...clipboardMark.attrs, clipboard: true }; const newMark = clipboardMark.type.create(attrs); marks = newMark.addToSet(marks); - return schema.text(node.textContent, marks); + return schema.text(node.textContent, marks); } else { return null; } } else { return null; } - }); + }); return newSlice; } - } + } }); } @@ -1345,7 +1345,7 @@ export class Editor { handleKeyDown: (view: EditorView, event: KeyboardEvent) => { // workaround for Ctrl+ keys on windows desktop if (this.context.ui.context.isWindowsDesktop() && - qtWebEngineVersion() !== undefined) { + qtWebEngineVersion() !== undefined) { const keyEvent = event as KeyboardEvent; if (keyEvent.ctrlKey) { const keyCommand = ctrlKeyCodes[keyEvent.code]; @@ -1455,7 +1455,7 @@ function navigationIdForSelection(state: EditorState): string | null { } } -function pandocWriterOptions(options: PandocWriterOptions) : PandocWriterOptions { +function pandocWriterOptions(options: PandocWriterOptions): PandocWriterOptions { return { atxHeaders: true, ...options