From 1d5d95eab234190837c258efa9fef207621f8570 Mon Sep 17 00:00:00 2001 From: Junhao Liao Date: Mon, 16 Sep 2024 16:58:54 -0400 Subject: [PATCH] new-log-viewer: Add custom Monaco editor themes and a Monaco language for logs. (#69) --- .../Editor/MonacoInstance/index.tsx | 15 ++++- .../Editor/MonacoInstance/language.ts | 55 +++++++++++++++++++ .../components/Editor/MonacoInstance/theme.ts | 45 +++++++++++++++ .../Editor/MonacoInstance/typings.ts | 12 ++++ .../components/Editor/MonacoInstance/utils.ts | 9 +++ .../src/components/Editor/index.tsx | 40 +++++++++----- 6 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 new-log-viewer/src/components/Editor/MonacoInstance/language.ts create mode 100644 new-log-viewer/src/components/Editor/MonacoInstance/theme.ts diff --git a/new-log-viewer/src/components/Editor/MonacoInstance/index.tsx b/new-log-viewer/src/components/Editor/MonacoInstance/index.tsx index 70441b44..0d0af05d 100644 --- a/new-log-viewer/src/components/Editor/MonacoInstance/index.tsx +++ b/new-log-viewer/src/components/Editor/MonacoInstance/index.tsx @@ -23,9 +23,11 @@ import "./index.css"; interface MonacoEditorProps { + actions: ActionType[], lineNum: number, text: string, - actions: ActionType[], + themeName: "dark" | "light", + beforeMount?: BeforeMountCallback, beforeTextUpdate?: BeforeTextUpdateCallback, onCursorExplicitPosChange: CursorExplicitPosChangeCallback, @@ -40,9 +42,10 @@ interface MonacoEditorProps { * the editor. * * @param props + * @param props.actions * @param props.lineNum * @param props.text - * @param props.actions + * @param props.themeName * @param props.beforeMount * @param props.beforeTextUpdate * @param props.onCursorExplicitPosChange @@ -52,9 +55,10 @@ interface MonacoEditorProps { * @return */ const MonacoInstance = ({ + actions, lineNum, text, - actions, + themeName, beforeMount, beforeTextUpdate, onMount, @@ -98,6 +102,11 @@ const MonacoInstance = ({ onMount, ]); + // On `themeName` update, set the theme in the editor. + useEffect(() => { + monaco.editor.setTheme(themeName); + }, [themeName]); + // On `text` update, set the text and position cursor in the editor. useEffect(() => { if (null === editorRef.current) { diff --git a/new-log-viewer/src/components/Editor/MonacoInstance/language.ts b/new-log-viewer/src/components/Editor/MonacoInstance/language.ts new file mode 100644 index 00000000..e518fd43 --- /dev/null +++ b/new-log-viewer/src/components/Editor/MonacoInstance/language.ts @@ -0,0 +1,55 @@ +import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; + +import {TOKEN_NAME} from "./typings"; + + +const LOG_LANGUAGE_NAME = "logLanguage"; + + +/** + * Registers a custom log language in the Monaco editor. + */ +const setupCustomLogLanguage = () => { + monaco.languages.register({ + id: LOG_LANGUAGE_NAME, + }); + monaco.languages.setMonarchTokensProvider(LOG_LANGUAGE_NAME, { + tokenizer: { + root: [ + [ + "INFO", + TOKEN_NAME.CUSTOM_INFO, + ], + [ + "WARN", + TOKEN_NAME.CUSTOM_WARN, + ], + [ + "ERROR", + TOKEN_NAME.CUSTOM_ERROR, + ], + [ + "FATAL", + TOKEN_NAME.CUSTOM_FATAL, + ], + [ + /(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3})Z/, + TOKEN_NAME.CUSTOM_DATE, + ], + [ + /^[\t ]*at.*$/, + TOKEN_NAME.CUSTOM_EXCEPTION, + ], + [ + /(\d+(?:\.\d+)?([eE])([+-])[0-9](\.[0-9])?|\d+(?:\.\d+)?)/, + TOKEN_NAME.CUSTOM_NUMBER, + ], + ], + }, + }); +}; + +export { + LOG_LANGUAGE_NAME, + setupCustomLogLanguage, +}; diff --git a/new-log-viewer/src/components/Editor/MonacoInstance/theme.ts b/new-log-viewer/src/components/Editor/MonacoInstance/theme.ts new file mode 100644 index 00000000..b622ec0b --- /dev/null +++ b/new-log-viewer/src/components/Editor/MonacoInstance/theme.ts @@ -0,0 +1,45 @@ +import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; + +import {THEME_NAME} from "../../../typings/config"; +import {TOKEN_NAME} from "./typings"; + + +/** + * Sets up custom themes for the Monaco editor. + */ +const setupThemes = () => { + monaco.editor.defineTheme(THEME_NAME.DARK, { + base: "vs-dark", + inherit: true, + rules: [ + {token: TOKEN_NAME.CUSTOM_INFO, foreground: "#098658"}, + {token: TOKEN_NAME.CUSTOM_WARN, foreground: "#ce9178"}, + {token: TOKEN_NAME.CUSTOM_ERROR, foreground: "#ce9178", fontStyle: "bold"}, + {token: TOKEN_NAME.CUSTOM_FATAL, foreground: "#ce9178", fontStyle: "bold"}, + {token: TOKEN_NAME.CUSTOM_DATE, foreground: "#529955"}, + {token: TOKEN_NAME.CUSTOM_EXCEPTION, foreground: "#ce723b", fontStyle: "italic"}, + {token: TOKEN_NAME.CUSTOM_NUMBER, foreground: "#3f9ccb"}, + {token: TOKEN_NAME.COMMENT, foreground: "#008000"}, + ], + colors: { + "editor.lineHighlightBackground": "#3c3c3c", + }, + }); + monaco.editor.defineTheme(THEME_NAME.LIGHT, { + base: "vs", + inherit: true, + rules: [ + {token: TOKEN_NAME.CUSTOM_INFO, foreground: "#098658"}, + {token: TOKEN_NAME.CUSTOM_WARN, foreground: "#b81560"}, + {token: TOKEN_NAME.CUSTOM_ERROR, foreground: "#ac1515", fontStyle: "bold"}, + {token: TOKEN_NAME.CUSTOM_FATAL, foreground: "#ac1515", fontStyle: "bold"}, + {token: TOKEN_NAME.CUSTOM_DATE, foreground: "#008000"}, + {token: TOKEN_NAME.CUSTOM_EXCEPTION, foreground: "#ce723b", fontStyle: "italic"}, + {token: TOKEN_NAME.CUSTOM_NUMBER, foreground: "#3f9ccb"}, + ], + colors: {}, + }); +}; + + +export {setupThemes}; diff --git a/new-log-viewer/src/components/Editor/MonacoInstance/typings.ts b/new-log-viewer/src/components/Editor/MonacoInstance/typings.ts index e305bd2d..3a2acf7d 100644 --- a/new-log-viewer/src/components/Editor/MonacoInstance/typings.ts +++ b/new-log-viewer/src/components/Editor/MonacoInstance/typings.ts @@ -7,6 +7,17 @@ import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; import {ACTION_NAME} from "../../../utils/actions"; +enum TOKEN_NAME { + CUSTOM_INFO = "customInfo", + CUSTOM_WARN = "customWarn", + CUSTOM_ERROR = "customError", + CUSTOM_FATAL = "customFatal", + CUSTOM_DATE = "customDate", + CUSTOM_EXCEPTION = "customException", + CUSTOM_NUMBER = "customNumber", + COMMENT = "comment", +} + /** * Gets called when the cursor position is explicitly changed in the editor. * @@ -55,6 +66,7 @@ interface CustomMonacoEditorHandlers { onCustomAction?: CustomActionCallback, } +export {TOKEN_NAME}; export type { BeforeMountCallback, BeforeTextUpdateCallback, diff --git a/new-log-viewer/src/components/Editor/MonacoInstance/utils.ts b/new-log-viewer/src/components/Editor/MonacoInstance/utils.ts index 16b8e42c..9ab6af59 100644 --- a/new-log-viewer/src/components/Editor/MonacoInstance/utils.ts +++ b/new-log-viewer/src/components/Editor/MonacoInstance/utils.ts @@ -7,6 +7,11 @@ import { setupFocusOnBacktickDown, setupMobileZoom, } from "./actions"; +import { + LOG_LANGUAGE_NAME, + setupCustomLogLanguage, +} from "./language"; +import {setupThemes} from "./theme"; import {CustomMonacoEditorHandlers} from "./typings"; @@ -38,12 +43,16 @@ const createMonacoEditor = ( actions: ActionType[], handlers: CustomMonacoEditorHandlers ): monaco.editor.IStandaloneCodeEditor => { + setupCustomLogLanguage(); + setupThemes(); + const editor = monaco.editor.create( editorContainer, { // eslint-disable-next-line no-warning-comments // TODO: Add custom observer to debounce automatic layout automaticLayout: true, + language: LOG_LANGUAGE_NAME, maxTokenizationLineLength: 30_000, mouseWheelZoom: true, readOnly: true, diff --git a/new-log-viewer/src/components/Editor/index.tsx b/new-log-viewer/src/components/Editor/index.tsx index f09810c0..88d0c46d 100644 --- a/new-log-viewer/src/components/Editor/index.tsx +++ b/new-log-viewer/src/components/Editor/index.tsx @@ -8,13 +8,18 @@ import { import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js"; +import {useColorScheme} from "@mui/joy"; + import {StateContext} from "../../contexts/StateContextProvider"; import { updateWindowUrlHashParams, UrlContext, } from "../../contexts/UrlContextProvider"; import {Nullable} from "../../typings/common"; -import {CONFIG_KEY} from "../../typings/config"; +import { + CONFIG_KEY, + THEME_NAME, +} from "../../typings/config"; import {BeginLineNumToLogEventNumMap} from "../../typings/worker"; import { ACTION_NAME, @@ -36,12 +41,28 @@ import {goToPositionAndCenter} from "./MonacoInstance/utils"; import "./index.css"; +/** + * Resets the cached page size in case it causes a client OOM. If it doesn't, the saved value + * will be restored when {@link restoreCachedPageSize} is called. + */ +const resetCachedPageSize = () => { + const error = setConfig( + {key: CONFIG_KEY.PAGE_SIZE, value: CONFIG_DEFAULT[CONFIG_KEY.PAGE_SIZE]} + ); + + if (null !== error) { + console.error(`Unexpected error returned by setConfig(): ${error}`); + } +}; + /** * Renders a read-only editor for viewing logs. * * @return */ const Editor = () => { + const {mode, systemMode} = useColorScheme(); + const {beginLineNumToLogEventNum, logData, numEvents} = useContext(StateContext); const {logEventNum} = useContext(UrlContext); @@ -100,20 +121,6 @@ const Editor = () => { }); }, []); - /** - * Resets the cached page size in case it causes a client OOM. If it doesn't, the saved value - * will be restored when {@link restoreCachedPageSize} is called. - */ - const resetCachedPageSize = useCallback(() => { - const error = setConfig( - {key: CONFIG_KEY.PAGE_SIZE, value: CONFIG_DEFAULT[CONFIG_KEY.PAGE_SIZE]} - ); - - if (null !== error) { - console.error(`Unexpected error returned by setConfig(): ${error}`); - } - }, []); - /** * Restores the cached page size that was unset in {@link resetCachedPageSize}; */ @@ -192,6 +199,9 @@ const Editor = () => { beforeTextUpdate={resetCachedPageSize} lineNum={lineNum} text={logData} + themeName={(("system" === mode) ? + systemMode : + mode) ?? THEME_NAME.DARK} onCursorExplicitPosChange={handleCursorExplicitPosChange} onCustomAction={handleEditorCustomAction} onMount={handleMount}