diff --git a/dinky-web/src/components/CustomEditor/CodeEdit/index.tsx b/dinky-web/src/components/CustomEditor/CodeEdit/index.tsx index 08467ed849..ab09cb6b63 100644 --- a/dinky-web/src/components/CustomEditor/CodeEdit/index.tsx +++ b/dinky-web/src/components/CustomEditor/CodeEdit/index.tsx @@ -21,15 +21,14 @@ import * as monaco from 'monaco-editor'; import { editor, languages, Position } from 'monaco-editor'; import { buildAllSuggestionsToEditor } from '@/components/CustomEditor/CodeEdit/function'; -import EditorFloatBtn from '@/components/CustomEditor/EditorFloatBtn'; import { LoadCustomEditorLanguageWithCompletion } from '@/components/CustomEditor/languages'; import { StateType } from '@/pages/DataStudio/model'; import { MonacoEditorOptions } from '@/types/Public/data'; import { convertCodeEditTheme } from '@/utils/function'; -import { Editor, Monaco, OnChange, useMonaco } from '@monaco-editor/react'; +import { Editor, Monaco, OnChange } from '@monaco-editor/react'; import { connect } from '@umijs/max'; import useMemoCallback from 'rc-menu/es/hooks/useMemoCallback'; -import { memo, useCallback, useEffect, useRef } from 'react'; +import { memo, useCallback, useRef } from 'react'; import ITextModel = editor.ITextModel; import CompletionItem = languages.CompletionItem; import CompletionContext = languages.CompletionContext; @@ -48,7 +47,6 @@ export type CodeEditFormProps = { readOnly?: boolean; lineNumbers?: string; autoWrap?: string; - showFloatButton?: boolean; editorDidMount?: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void; enableSuggestions?: boolean; monacoRef?: any; @@ -81,26 +79,14 @@ const CodeEdit = (props: CodeEditFormProps & connect) => { enableSuggestions = false, // enable suggestions suggestionsData, // suggestions data autoWrap = 'on', // auto wrap - showFloatButton = false, editorDidMount, editorRef, monacoRef, tabs: { activeKey } } = props; - const editorInstance = useRef(editorRef); - const monacoInstance = useRef(monacoRef); - - const { ScrollType } = editor; - - // 使用编辑器钩子, 拿到编辑器实例 - const monacoHook = useMonaco(); - - useEffect(() => { - convertCodeEditTheme(editorInstance.current); - // 需要调用 手动注册下自定义语言 - LoadCustomEditorLanguageWithCompletion(monacoInstance.current); - }, [monacoHook]); + const editorInstance = useRef(editorRef); + const monacoInstance = useRef(monacoRef); /** * build all suggestions @@ -141,18 +127,13 @@ const CodeEdit = (props: CodeEditFormProps & connect) => { }); } - // editorInstance?.current?.onDidChangeModelContent?.(() => { - // console.log(editorInstance?.current, 'editorInstance') - // editorInstance?.current?.trigger('action', 'editor.action.triggerSuggest'); - // }); - /** * editorDidMount * @param {editor.IStandaloneCodeEditor} editor * @param monacoIns */ const editorDidMountChange = (editor: editor.IStandaloneCodeEditor, monacoIns: Monaco) => { - if (editorRef?.current && monacoRef?.current && editorDidMount) { + if (editorDidMount) { editorDidMount(editor, monacoIns); } editorInstance.current = editor; @@ -160,57 +141,12 @@ const CodeEdit = (props: CodeEditFormProps & connect) => { if (enableSuggestions) { reloadCompilation(monacoInstance.current); } - // register TypeScript language service - monacoIns.languages.register({ - id: language || 'typescript' - }); editor.layout(); editor.focus(); }; - /** - * handle scroll to top - */ - const handleBackTop = () => { - editorInstance?.current?.revealLine(1); - }; - - /** - * handle scroll to bottom - */ - const handleBackBottom = () => { - editorInstance?.current?.revealLine(editorInstance?.current?.getModel()?.getLineCount()); - }; - - /** - * handle scroll to down - */ - const handleDownScroll = () => { - editorInstance?.current?.setScrollPosition( - { scrollTop: editorInstance?.current?.getScrollTop() + 500 }, - ScrollType.Smooth - ); - }; - - /** - * handle scroll to up - */ - const handleUpScroll = () => { - editorInstance?.current?.setScrollPosition( - { scrollTop: editorInstance?.current?.getScrollTop() - 500 }, - ScrollType.Smooth - ); - }; - // todo: 标记错误信息 - const restEditBtnProps = { - handleBackTop, - handleBackBottom, - handleUpScroll, - handleDownScroll - }; - const finalEditorOptions = { ...MonacoEditorOptions, // set default options tabCompletion: 'on', // tab 补全 @@ -297,10 +233,10 @@ const CodeEdit = (props: CodeEditFormProps & connect) => { <>
{ - if (!monacoInstance?.current) { - monacoInstance.current = monaco; - } + beforeMount={(monaco) => { + // 挂载前加载语言 | before mount load language + monacoInstance.current = monaco; + LoadCustomEditorLanguageWithCompletion(monaco); }} width={width} height={height} @@ -310,9 +246,8 @@ const CodeEdit = (props: CodeEditFormProps & connect) => { className={'editor-develop'} onMount={editorDidMountChange} onChange={onChange} - theme={convertCodeEditTheme()} + theme={convertCodeEditTheme(editorInstance?.current)} /> - {showFloatButton && }
); diff --git a/dinky-web/src/components/CustomEditor/CodeShow/index.tsx b/dinky-web/src/components/CustomEditor/CodeShow/index.tsx index fe207967a4..8a7a916972 100644 --- a/dinky-web/src/components/CustomEditor/CodeShow/index.tsx +++ b/dinky-web/src/components/CustomEditor/CodeShow/index.tsx @@ -22,21 +22,15 @@ import { LoadCustomEditorLanguage } from '@/components/CustomEditor/languages'; import { Loading } from '@/pages/Other/Loading'; import { MonacoEditorOptions } from '@/types/Public/data'; import { convertCodeEditTheme } from '@/utils/function'; -import { Editor, useMonaco } from '@monaco-editor/react'; + import { Col, Row } from 'antd'; import { editor } from 'monaco-editor'; import { EditorLanguage } from 'monaco-editor/esm/metadata'; -import { CSSProperties, useEffect, useRef, useState } from 'react'; -import FullscreenBtn from '../FullscreenBtn'; -// loader.config({monaco}); -/** - * props - * todo: - * 1. Realize full screen/exit full screen in the upper right corner of the editor (Visible after opening) - * - The full screen button is done, but the full screen is not implemented - * 2. Callback for right-clicking to clear logs (optional, not required) - */ +import FullscreenBtn from '@/components/CustomEditor/FullscreenBtn'; +import { Editor, Monaco } from '@monaco-editor/react'; +import { CSSProperties, memo, useRef, useState } from 'react'; + export type CodeShowFormProps = { height?: string | number; width?: string; @@ -44,6 +38,7 @@ export type CodeShowFormProps = { options?: any; code: string; lineNumbers?: string; + enableMiniMap?: boolean; autoWrap?: string; showFloatButton?: boolean; refreshLogCallback?: () => void; @@ -75,7 +70,8 @@ const CodeShow = (props: CodeShowFormProps) => { autoWrap = 'on', // auto wrap showFloatButton = false, refreshLogCallback, - fullScreenBtn = false + fullScreenBtn = false, + enableMiniMap = false } = props; const { ScrollType } = editor; @@ -86,28 +82,19 @@ const CodeShow = (props: CodeShowFormProps) => { const [stopping, setStopping] = useState(false); const [autoRefresh, setAutoRefresh] = useState(false); const [fullScreen, setFullScreen] = useState(false); - const editorInstance = useRef(); + const editorInstance = useRef(); + const monacoInstance = useRef(); const [timer, setTimer] = useState(); - // 使用编辑器钩子, 拿到编辑器实例 - const monaco = useMonaco(); - - useEffect(() => { - convertCodeEditTheme(monaco?.editor); - // 需要调用 手动注册下自定义语言 - LoadCustomEditorLanguage(monaco); - }, [monaco]); - /** * handle sync log - * @returns {Promise} */ const handleSyncLog = async () => { setLoading(true); setInterval(() => { refreshLogCallback?.(); - setLoading(false); }, 1000); + setLoading(false); }; /** @@ -144,6 +131,7 @@ const CodeShow = (props: CodeShowFormProps) => { * handle scroll to bottom */ const handleBackBottom = () => { + // @ts-ignore editorInstance?.current?.revealLine(editorInstance?.current?.getModel().getLineCount()); }; @@ -170,9 +158,11 @@ const CodeShow = (props: CodeShowFormProps) => { /** * editorDidMount * @param {editor.IStandaloneCodeEditor} editor + * @param monaco {Monaco} */ - const editorDidMount = (editor: editor.IStandaloneCodeEditor) => { + const editorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => { editorInstance.current = editor; + monacoInstance.current = monaco; editor.layout(); editor.focus(); if (scrollBeyondLastLine) { @@ -218,6 +208,11 @@ const CodeShow = (props: CodeShowFormProps) => { {/* editor */} { + // 挂载前加载语言 | before mount load language + LoadCustomEditorLanguage(monacoInstance.current); + monacoInstance.current = monaco; + }} width={width} height={height} loading={} @@ -226,13 +221,14 @@ const CodeShow = (props: CodeShowFormProps) => { options={{ scrollBeyondLastLine: false, readOnly: true, + glyphMargin: false, wordWrap: autoWrap, autoDetectHighContrast: true, selectOnLineNumbers: true, fixedOverflowWidgets: true, autoClosingDelete: 'always', lineNumbers, - minimap: { enabled: false }, + minimap: { enabled: enableMiniMap }, scrollbar: { // Subtle shadows to the left & top. Defaults to true. useShadows: false, @@ -257,7 +253,7 @@ const CodeShow = (props: CodeShowFormProps) => { ...options }} onMount={editorDidMount} - theme={convertCodeEditTheme()} + theme={convertCodeEditTheme(editorInstance?.current)} /> {showFloatButton && ( @@ -270,4 +266,4 @@ const CodeShow = (props: CodeShowFormProps) => { ); }; -export default CodeShow; +export default memo(CodeShow); diff --git a/dinky-web/src/components/CustomEditor/languages/flinksql/constants.ts b/dinky-web/src/components/CustomEditor/languages/constants.ts similarity index 95% rename from dinky-web/src/components/CustomEditor/languages/flinksql/constants.ts rename to dinky-web/src/components/CustomEditor/languages/constants.ts index e82d56c138..0aa16cd47a 100644 --- a/dinky-web/src/components/CustomEditor/languages/flinksql/constants.ts +++ b/dinky-web/src/components/CustomEditor/languages/constants.ts @@ -46,3 +46,8 @@ export const TokenClassConsts = { VARIABLE: 'variable', WHITE: 'white' }; + +export enum CustomEditorLanguage { + JavaLog = 'javalog', + FlinkSQL = 'flinksql' +} diff --git a/dinky-web/src/components/CustomEditor/languages/flinksql/function.ts b/dinky-web/src/components/CustomEditor/languages/flinksql/function.ts new file mode 100644 index 0000000000..6771e26563 --- /dev/null +++ b/dinky-web/src/components/CustomEditor/languages/flinksql/function.ts @@ -0,0 +1,259 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + CustomEditorLanguage, + TokenClassConsts +} from '@/components/CustomEditor/languages/constants'; +import { + EXTEND_SQL_KEYWORD, + FLINK_SQL_BUILTIN_FUNCTIONS, + FLINK_SQL_KEYWORD, + FLINK_SQL_OPERATORS, + FLINK_SQL_SCOPE_KEYWORDS, + FLINK_SQL_TYPE_KEYWORDS +} from '@/components/CustomEditor/languages/flinksql/keyword'; +import { Monaco } from '@monaco-editor/react'; +import { editor, Position } from 'monaco-editor'; + +export function buildFlinkSQLConfiguration(monaco?: Monaco | undefined) { + monaco?.languages?.setLanguageConfiguration(CustomEditorLanguage.FlinkSQL, { + comments: { + lineComment: '-- ', + blockComment: ['/*', '*/'] + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ['"', '"'], + ["'", "'"], + ['`', '`'], + ['<', '>'] + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' }, + { open: '<', close: '>' } + ], + indentationRules: { + increaseIndentPattern: /^\s*(\w+\s+)+\w/, + decreaseIndentPattern: /^\s*(\w+\s+)+\w/ + }, + onEnterRules: [ + { + beforeText: /^\s*(\/\*)/, + afterText: /^\s*\*\/$/, + action: { indentAction: monaco?.languages?.IndentAction.IndentOutdent, appendText: ' * ' } + } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' }, + { open: '<', close: '>' } + ], + colorizedBracketPairs: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ['<', '>'], + ['"', '"'], + ["'", "'"], + ['`', '`'] + ], + + folding: { + offSide: true, + markers: { + start: + /((create|alter|drop|rename\s+)?(TABLE|DATABASE|STREAM|FUNCTION|PROCEDURE|PACKAGE|TYPE|TRIGGER|INDEX|SCHEMA|VIEW)\b)|((EXECUTE\s+)?(JAR|CDCSOURCE)?\b)/i, + end: /\)\\;\b/i + } + } + }); +} + +export function buildMonarchTokensProvider(monaco?: Monaco | undefined) { + monaco?.languages.setMonarchTokensProvider(CustomEditorLanguage.FlinkSQL, { + defaultToken: '', + tokenPostfix: '.sql', + keywords: [...FLINK_SQL_KEYWORD, ...EXTEND_SQL_KEYWORD], + operators: FLINK_SQL_OPERATORS, + ignoreCase: true, // 忽略大小写 + builtinFunctions: FLINK_SQL_BUILTIN_FUNCTIONS, + builtinVariables: [], + typeKeywords: FLINK_SQL_TYPE_KEYWORDS, + scopeKeywords: FLINK_SQL_SCOPE_KEYWORDS, + pseudoColumns: [], + comments: { + lineComment: '--', + blockComment: ['/*', '*/'] + }, + brackets: [ + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.bracket' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + { open: '<', close: '>', token: 'delimiter.angle' } + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' }, + { open: '<', close: '>' } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' }, + { open: '<', close: '>' } + ], + folding: { + markers: { + start: + /((CREATE|ALERT|DROP|USE\s+)?(TABLE|DATABASE|STREAM|FUNCTION|CATALOG|SCHEMA|VIEW)\b)|((EXECUTE\s+)?(JAR|CDCSOURCE)?\b)/i, + end: /\)\\;\b/i + } + }, + tokenizer: { + root: [ + { include: '@comments' }, + { include: '@whitespace' }, + { include: '@pseudoColumns' }, + { include: '@numbers' }, + { include: '@strings' }, + { include: '@complexIdentifiers' }, + { include: '@scopes' }, + { include: '@complexDataTypes' }, + [ + /[\w@#$]+/, + { + cases: { + '@scopeKeywords': TokenClassConsts.KEYWORD_SCOPE, + '@operators': TokenClassConsts.OPERATOR_KEYWORD, + '@typeKeywords': TokenClassConsts.TYPE, + '@builtinVariables': TokenClassConsts.VARIABLE, + '@builtinFunctions': TokenClassConsts.PREDEFINED, + '@keywords': TokenClassConsts.KEYWORD, + '@default': TokenClassConsts.IDENTIFIER, + fontWeight: 'normal' + } + } + ] + ], + whitespace: [[/[\s\t\r\n]+/, TokenClassConsts.WHITE]], + comments: [ + [/--+.*/, TokenClassConsts.COMMENT], + [/\/\*/, { token: TokenClassConsts.COMMENT_QUOTE, next: '@comment' }] + ], + comment: [ + [/[^*/]+/, TokenClassConsts.COMMENT], + [/\/\*/, { token: TokenClassConsts.COMMENT_QUOTE, next: '@push' }], // nested comment not allowed :-( + [/\*\//, { token: TokenClassConsts.COMMENT_QUOTE, next: '@pop' }], + [/./, TokenClassConsts.COMMENT] + ], + pseudoColumns: [ + [ + /[$][A-Za-z_][\w@#$]*/, + { + cases: { + '@pseudoColumns': TokenClassConsts.PREDEFINED, + '@default': TokenClassConsts.IDENTIFIER + } + } + ] + ], + numbers: [ + [/0[xX][0-9a-fA-F]*/, TokenClassConsts.NUMBER_HEX], + [/[$][+-]*\d*(\.\d*)?/, TokenClassConsts.NUMBER], + [/((\d+(\.\d*)?)|(\.\d+))([eE][\\\-+]?\d+)?/, TokenClassConsts.NUMBER] + ], + strings: [[/'/, { token: 'custom-error', next: '@string' }]], + string: [ + [/[^']+/, TokenClassConsts.STRING], + [/''/, TokenClassConsts.STRING], + [/'/, { token: TokenClassConsts.STRING, next: '@pop' }] + ], + complexIdentifiers: [ + [/`/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@quotedIdentifier' }] + ], + quotedIdentifier: [ + [/[^`]+/, TokenClassConsts.IDENTIFIER_QUOTE], + [/``/, TokenClassConsts.IDENTIFIER_QUOTE], + [/`/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@pop' }] + ], + scopes: [ + [/(EXECUTE\s+)?JAR\s+/i, TokenClassConsts.KEYWORD_SCOPE], + [/(EXECUTE\s+)?CDCSOURCE\s/i, TokenClassConsts.KEYWORD_SCOPE], + [/(PRINT\s+)?\s/i, TokenClassConsts.KEYWORD_SCOPE] + ], + complexDataTypes: [ + [/DOUBLE\s+PRECISION\b/i, { token: TokenClassConsts.TYPE }], + [/DECIMAL\s+PRECISION\b/i, { token: TokenClassConsts.TYPE }], + [/TIMESTAMP\s+PRECISION\b/i, { token: TokenClassConsts.TYPE }], + [/WITHOUT\s+TIME\s+ZONE\b/i, { token: TokenClassConsts.TYPE }], + [/WITH\s+LOCAL\s+TIME\s+ZONE\b/i, { token: TokenClassConsts.TYPE }], + [/WITH\s+TIME\s+ZONE\b/i, { token: TokenClassConsts.TYPE }], + [/WITH\s+TIME\s+ZONE\s+OFFSET\b/i, { token: TokenClassConsts.TYPE }], + [/WITH\s+LOCAL\s+TIME\s+ZONE\s+OFFSET\b/i, { token: TokenClassConsts.TYPE }] + ] + }, + unicode: true + }); +} + +function buildFlinkSQLCompletionItem(monaco: Monaco, model: editor.IModel, position: Position) { + const word = model.getWordUntilPosition(position); + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; + return { + suggestions: FLINK_SQL_KEYWORD.map((item) => { + return { + label: item, + range: range, + kind: monaco.languages.CompletionItemKind.Keyword, + insertText: item + }; + }) + }; +} + +export function registerFlinkSQLCompilation(monaco?: Monaco | undefined) { + monaco?.languages?.registerCompletionItemProvider(CustomEditorLanguage.FlinkSQL, { + provideCompletionItems: (model, position) => + buildFlinkSQLCompletionItem(monaco, model, position) + }); +} diff --git a/dinky-web/src/components/CustomEditor/languages/flinksql/index.tsx b/dinky-web/src/components/CustomEditor/languages/flinksql/index.tsx index 9fb4def318..4fe58de166 100644 --- a/dinky-web/src/components/CustomEditor/languages/flinksql/index.tsx +++ b/dinky-web/src/components/CustomEditor/languages/flinksql/index.tsx @@ -17,221 +17,26 @@ * */ -import { TokenClassConsts } from '@/components/CustomEditor/languages/flinksql/constants'; +import { CustomEditorLanguage } from '@/components/CustomEditor/languages/constants'; import { - EXTEND_SQL_KEYWORD, - FLINK_SQL_BUILTIN_FUNCTIONS, - FLINK_SQL_KEYWORD, - FLINK_SQL_OPERATORS, - FLINK_SQL_SCOPE_KEYWORDS, - FLINK_SQL_TYPE_KEYWORDS -} from '@/components/CustomEditor/languages/flinksql/keyword'; + buildFlinkSQLConfiguration, + buildMonarchTokensProvider, + registerFlinkSQLCompilation +} from '@/components/CustomEditor/languages/flinksql/function'; import { Monaco } from '@monaco-editor/react'; -export function FlinkSQLLanguage(monaco: Monaco | null, registerCompletion: boolean) { +export function FlinkSQLLanguage(monaco?: Monaco | undefined, registerCompletion = false) { // Register a new language monaco?.languages.register({ - id: 'flinksql', + id: CustomEditorLanguage.FlinkSQL, extensions: ['.sql'], - aliases: ['flinksql', 'fsql', 'flinksql', 'flinkSQL', 'FlinkSQL'] + aliases: ['flinksql', 'fsql', 'flinkSQL', 'FlinkSQL'] }); - monaco?.languages.setMonarchTokensProvider('flinksql', { - defaultToken: '', - tokenPostfix: '.sql', - keywords: [...FLINK_SQL_KEYWORD, ...EXTEND_SQL_KEYWORD], - operators: FLINK_SQL_OPERATORS, - ignoreCase: true, // 忽略大小写 - builtinFunctions: FLINK_SQL_BUILTIN_FUNCTIONS, - builtinVariables: [], - typeKeywords: FLINK_SQL_TYPE_KEYWORDS, - scopeKeywords: FLINK_SQL_SCOPE_KEYWORDS, - pseudoColumns: [], - comments: { - lineComment: '--', - blockComment: ['/*', '*/'] - }, - brackets: [ - { open: '{', close: '}', token: 'delimiter.curly' }, - { open: '[', close: ']', token: 'delimiter.bracket' }, - { open: '(', close: ')', token: 'delimiter.parenthesis' } - ], - autoClosingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"' }, - { open: "'", close: "'" }, - { open: '`', close: '`' } - ], - surroundingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"' }, - { open: "'", close: "'" }, - { open: '`', close: '`' } - ], - folding: { - markers: { - start: - /((create|alter|drop|rename\s+)?(TABLE|DATABASE|STREAM|FUNCTION|PROCEDURE|PACKAGE|TYPE|TRIGGER|INDEX|SCHEMA|VIEW)\b)|((EXECUTE\s+)?(JAR|CDCSOURCE)?\b)/i, - end: /\)\\;\b/i - } - }, - tokenizer: { - root: [ - { include: '@comments' }, - { include: '@whitespace' }, - { include: '@pseudoColumns' }, - { include: '@numbers' }, - { include: '@strings' }, - { include: '@complexIdentifiers' }, - { include: '@scopes' }, - { include: '@complexDataTypes' }, - [ - /[\w@#$]+/, - { - cases: { - '@scopeKeywords': TokenClassConsts.KEYWORD_SCOPE, - '@operators': TokenClassConsts.OPERATOR_KEYWORD, - '@typeKeywords': TokenClassConsts.TYPE, - '@builtinVariables': TokenClassConsts.VARIABLE, - '@builtinFunctions': TokenClassConsts.PREDEFINED, - '@keywords': TokenClassConsts.KEYWORD, - '@default': TokenClassConsts.IDENTIFIER, - fontWeight: 'normal' - } - } - ] - ], - whitespace: [[/[\s\t\r\n]+/, TokenClassConsts.WHITE]], - comments: [ - [/--+.*/, TokenClassConsts.COMMENT], - [/\/\*/, { token: TokenClassConsts.COMMENT_QUOTE, next: '@comment' }] - ], - comment: [ - [/[^*/]+/, TokenClassConsts.COMMENT], - [/\/\*/, { token: TokenClassConsts.COMMENT_QUOTE, next: '@push' }], // nested comment not allowed :-( - [/\*\//, { token: TokenClassConsts.COMMENT_QUOTE, next: '@pop' }], - [/./, TokenClassConsts.COMMENT] - ], - pseudoColumns: [ - [ - /[$][A-Za-z_][\w@#$]*/, - { - cases: { - '@pseudoColumns': TokenClassConsts.PREDEFINED, - '@default': TokenClassConsts.IDENTIFIER - } - } - ] - ], - numbers: [ - [/0[xX][0-9a-fA-F]*/, TokenClassConsts.NUMBER_HEX], - [/[$][+-]*\d*(\.\d*)?/, TokenClassConsts.NUMBER], - [/((\d+(\.\d*)?)|(\.\d+))([eE][\\\-+]?\d+)?/, TokenClassConsts.NUMBER] - ], - strings: [[/'/, { token: 'custom-error', next: '@string' }]], - string: [ - [/[^']+/, TokenClassConsts.STRING], - [/''/, TokenClassConsts.STRING], - [/'/, { token: TokenClassConsts.STRING, next: '@pop' }] - ], - complexIdentifiers: [ - [/`/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@quotedIdentifier' }] - ], - quotedIdentifier: [ - [/[^`]+/, TokenClassConsts.IDENTIFIER_QUOTE], - [/``/, TokenClassConsts.IDENTIFIER_QUOTE], - [/`/, { token: TokenClassConsts.IDENTIFIER_QUOTE, next: '@pop' }] - ], - scopes: [ - [/(EXECUTE\s+)?JAR\s+/i, TokenClassConsts.KEYWORD_SCOPE], - [/(EXECUTE\s+)?CDCSOURCE\s/i, TokenClassConsts.KEYWORD_SCOPE], - [/(PRINT\s+)?\s/i, TokenClassConsts.KEYWORD_SCOPE] - ], - complexDataTypes: [ - [/DOUBLE\s+PRECISION\b/i, { token: TokenClassConsts.TYPE }], - [/WITHOUT\s+TIME\s+ZONE\b/i, { token: TokenClassConsts.TYPE }], - [/WITH\s+LOCAL\s+TIME\s+ZONE\b/i, { token: TokenClassConsts.TYPE }] - ] - }, - unicode: true - }); - - registerCompletion && - monaco?.languages?.registerCompletionItemProvider('flinksql', { - provideCompletionItems: function (model, position) { - const word = model.getWordUntilPosition(position); - const range = { - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - startColumn: word.startColumn, - endColumn: word.endColumn - }; - return { - suggestions: FLINK_SQL_KEYWORD.map((item) => { - return { - label: item, - range: range, - kind: monaco.languages.CompletionItemKind.Keyword, - insertText: item - }; - }) - }; - } - }); + buildMonarchTokensProvider(monaco); - monaco?.languages?.setLanguageConfiguration('flinksql', { - comments: { - lineComment: '--', - blockComment: ['/*', '*/'] - }, - brackets: [ - ['{', '}'], - ['[', ']'], - ['(', ')'] - ], - autoClosingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"' }, - { open: "'", close: "'" }, - { open: '`', close: '`' } - ], - indentationRules: { - increaseIndentPattern: /^\s*(\w+\s+)+\w/, - decreaseIndentPattern: /^\s*(\w+\s+)+\w/ - }, - onEnterRules: [ - { - beforeText: /^\s*(\/\*)/, - afterText: /^\s*\*\/$/, - action: { indentAction: monaco.languages.IndentAction.IndentOutdent, appendText: ' * ' } - } - ], - surroundingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"' }, - { open: "'", close: "'" }, - { open: '`', close: '`' } - ], - colorizedBracketPairs: [ - ['{', '}'], - ['[', ']'], - ['(', ')'] - ], - - folding: { - offSide: true, - markers: { - start: - /((create|alter|drop|rename\s+)?(TABLE|DATABASE|STREAM|FUNCTION|PROCEDURE|PACKAGE|TYPE|TRIGGER|INDEX|SCHEMA|VIEW)\b)|((EXECUTE\s+)?(JAR|CDCSOURCE)?\b)/i, - end: /\)\\;\b/i - } - } - }); + // Register a completion item provider for the new language + if (registerCompletion) { + registerFlinkSQLCompilation(monaco); + } + buildFlinkSQLConfiguration(monaco); } diff --git a/dinky-web/src/components/CustomEditor/languages/index.tsx b/dinky-web/src/components/CustomEditor/languages/index.tsx index 92dc3e0d12..86fc4fea24 100644 --- a/dinky-web/src/components/CustomEditor/languages/index.tsx +++ b/dinky-web/src/components/CustomEditor/languages/index.tsx @@ -17,52 +17,44 @@ * */ +import { CustomEditorLanguage } from '@/components/CustomEditor/languages/constants'; import { FlinkSQLLanguage } from '@/components/CustomEditor/languages/flinksql'; import { LogLanguage } from '@/components/CustomEditor/languages/javalog'; import { Monaco } from '@monaco-editor/react'; /** - * 加载自定义语言 关键字 + * 避免重复加载语言, 通过获取到 language 的 id 来判断是否已经加载过 * @param monaco - * @constructor + * @param language */ -// function LoadLanguagesKeyWord(monaco: Monaco | null) { -// if (!monaco) { -// return; -// } -// monaco?.languages?.getLanguages().forEach((language) => { -// return monaco?.languages?.registerCompletionItemProvider(language.id , { -// provideCompletionItems: function (model, position) { -// const word = model.getWordUntilPosition(position); -// const range = { -// startLineNumber: position.lineNumber, -// endLineNumber: position.lineNumber, -// startColumn: word.startColumn, -// endColumn: word.endColumn -// }; -// return { -// suggestions: FLINK_SQL_KEYWORD.map((item) => { -// return { -// label: item, -// range: range, -// kind: monaco.languages.CompletionItemKind.Keyword, -// insertText: item -// }; -// }) -// }; -// } -// }); -// }) -// } +function canLoadLanguage(monaco: Monaco | undefined, language: string) { + return !monaco?.languages?.getEncodedLanguageId(language); +} -export function LoadCustomEditorLanguage(monaco: Monaco | null) { - // LoadLanguagesKeyWord(monaco); - LogLanguage(monaco); - FlinkSQLLanguage(monaco, false); +/** + * 加载自定义语言 (不带自动补全) + * @param monaco + * @param registerCompletion 是否注册自动补全 (默认不注册) + * @constructor + */ +export function LoadCustomEditorLanguage( + monaco?: Monaco | undefined, + registerCompletion: boolean = false +) { + if (canLoadLanguage(monaco, CustomEditorLanguage.FlinkSQL)) { + FlinkSQLLanguage(monaco, registerCompletion); + } + console.log(canLoadLanguage(monaco, CustomEditorLanguage.JavaLog)); + if (canLoadLanguage(monaco, CustomEditorLanguage.JavaLog)) { + LogLanguage(monaco); + } } -export function LoadCustomEditorLanguageWithCompletion(monaco: Monaco | null) { - // LoadLanguagesKeyWord(monaco); - LogLanguage(monaco); - FlinkSQLLanguage(monaco, true); +/** + * 加载自定义语言 (带自动补全) + * @param monaco + * @constructor + */ +export function LoadCustomEditorLanguageWithCompletion(monaco?: Monaco | undefined) { + LoadCustomEditorLanguage(monaco, true); } diff --git a/dinky-web/src/components/CustomEditor/languages/javalog/function.ts b/dinky-web/src/components/CustomEditor/languages/javalog/function.ts new file mode 100644 index 0000000000..fa4333461f --- /dev/null +++ b/dinky-web/src/components/CustomEditor/languages/javalog/function.ts @@ -0,0 +1,89 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { CustomEditorLanguage } from '@/components/CustomEditor/languages/constants'; +import { JAVA_LOG_KEYWORD } from '@/components/CustomEditor/languages/javalog/keyword'; +import { Monaco } from '@monaco-editor/react'; + +export function buildMonarchTokensProvider(monaco?: Monaco | undefined) { + monaco?.languages.setMonarchTokensProvider(CustomEditorLanguage.JavaLog, { + defaultToken: '', + tokenPostfix: '.log', + keywords: JAVA_LOG_KEYWORD, + operators: [], + ignoreCase: true, // 忽略大小写 + builtinFunctions: [], + builtinVariables: [], + typeKeywords: [], + scopeKeywords: [], + pseudoColumns: [], + brackets: [ + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.bracket' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + { open: '<', close: '>', token: 'delimiter.angle' } + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' }, + { open: '<', close: '>' } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + { open: '`', close: '`' }, + { open: '<', close: '>' } + ], + folding: { + markers: { + start: + /((CREATE|ALERT|DROP|USE\s+)?(TABLE|DATABASE|STREAM|FUNCTION|CATALOG|SCHEMA|VIEW)\b)|((EXECUTE\s+)?(JAR|CDCSOURCE)?\b)/i, + end: /\)\\;\b/i + } + }, + tokenizer: { + root: [ + // 默认不区分大小写 // + [/\[(\w*-\d*)+\]/, 'custom-thread'], + [/(\w+(\.))+(\w+)(\(\d+\))?(:){1}/, 'custom-class'], + [/(\w+(\.))+(\w+)(\(\d+\))?\s+/, 'custom-class'], + [/error/, 'custom-error'], + [/warring/, 'custom-warning'], + [/warn/, 'custom-warning'], + [/info/, 'custom-info'], + [ + /^[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\s+(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d\.\d{3}/, + 'custom-date' + ], + [ + /[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\s+(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d\s(CST)/, + 'custom-date' + ] + ] + }, + unicode: true + }); +} diff --git a/dinky-web/src/components/CustomEditor/languages/javalog/index.tsx b/dinky-web/src/components/CustomEditor/languages/javalog/index.tsx index 60a2261eb8..791c505fde 100644 --- a/dinky-web/src/components/CustomEditor/languages/javalog/index.tsx +++ b/dinky-web/src/components/CustomEditor/languages/javalog/index.tsx @@ -17,35 +17,17 @@ * */ -export function LogLanguage(monaco: any) { +import { CustomEditorLanguage } from '@/components/CustomEditor/languages/constants'; +import { buildMonarchTokensProvider } from '@/components/CustomEditor/languages/javalog/function'; +import { Monaco } from '@monaco-editor/react'; + +export function LogLanguage(monaco: Monaco | undefined) { // Register a new language monaco?.languages.register({ - id: 'javalog', + id: CustomEditorLanguage.JavaLog, extensions: ['.log'], - aliases: ['javalog', 'Javalog', 'Javalog', 'jl', 'log'] - }); - monaco?.languages.setMonarchTokensProvider('javalog', { - tokenizer: { - root: [ - // 默认不区分大小写 // - [/\[(\w*-\d*)+\]/, 'custom-thread'], - [/(\w+(\.))+(\w+)(\(\d+\))?(:){1}/, 'custom-class'], - [/(\w+(\.))+(\w+)(\(\d+\))?\s+/, 'custom-class'], - [/error/, 'custom-error'], - [/warring/, 'custom-warning'], - [/warn/, 'custom-warning'], - [/info/, 'custom-info'], - [ - /^[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\s+(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d\.\d{3}/, - 'custom-date' - ], - [ - /[1-9]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\s+(20|21|22|23|[0-1]\d):[0-5]\d:[0-5]\d\s(CST)/, - 'custom-date' - ] - ] - }, - ignoreCase: true, - unicode: true + aliases: ['javalog', 'Javalog', 'jl', 'log'] }); + + buildMonarchTokensProvider(monaco); } diff --git a/dinky-web/src/components/CustomEditor/languages/javalog/keyword.ts b/dinky-web/src/components/CustomEditor/languages/javalog/keyword.ts new file mode 100644 index 0000000000..eeceb05303 --- /dev/null +++ b/dinky-web/src/components/CustomEditor/languages/javalog/keyword.ts @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export const JAVA_LOG_KEYWORD: string[] = [ + 'Start Process', + 'Start Process Step', + 'SUBMIT_PRECHECK', + 'SUBMIT_EXECUTE', + 'SUBMIT_BUILD_CONFIG', + 'info', + 'debug', + 'error', + 'warn', + 'trace', + 'fatal', + 'log', + 'logger', + 'root', + 'level', + 'additivity', + 'appender', + 'logger' +]; diff --git a/dinky-web/src/locales/en-US/global.ts b/dinky-web/src/locales/en-US/global.ts index 9249b1187d..c9e913ae35 100644 --- a/dinky-web/src/locales/en-US/global.ts +++ b/dinky-web/src/locales/en-US/global.ts @@ -81,6 +81,7 @@ export default { 'button.upScroll': 'Up Scroll', 'button.stopRefresh': 'Stop Auto Refresh', 'button.startRefresh': 'Start Auto Refresh(5s/e)', + 'button.autoWrap': 'Auto Wrap/Unwrap', 'menu.menu': 'Menu', 'right.menu.open': 'Open', diff --git a/dinky-web/src/locales/en-US/shortcutKey.ts b/dinky-web/src/locales/en-US/shortcutKey.ts index 86d8e22ee5..9e235bafe4 100644 --- a/dinky-web/src/locales/en-US/shortcutKey.ts +++ b/dinky-web/src/locales/en-US/shortcutKey.ts @@ -21,30 +21,9 @@ export default { 'shortcut.title': 'Shortcut key', 'shortcut.key.save': 'Save', 'shortcut.key.check': 'Check', - 'shortcut.key.beautify': 'Beautify', - 'shortcut.key.fullscreen': 'Full Screen', - 'shortcut.key.fullscreenClose': 'Close the popup and full screen', - 'shortcut.key.more': 'More shortcut keys', - 'shortcut.key.search': 'Search', - 'shortcut.key.replace': 'Replace', - 'shortcut.key.revoke': 'Revoke', - 'shortcut.key.redo': 'Redo', - 'shortcut.key.notes': 'Notes', - 'shortcut.key.indent': 'Indent', - 'shortcut.key.removeIndent': 'Remove indent', - 'shortcut.key.selectToEnd': 'Select subsequent', 'shortcut.key.format': 'Format', - 'shortcut.key.editMultiline': 'Edit multiple lines', - 'shortcut.key.copyRow': 'Copy a row', - 'shortcut.key.deleteRow': 'Delete a row', - 'shortcut.key.matchNext': 'Match Next', - 'shortcut.key.matchPrevious': 'Match Previous', - 'shortcut.key.goNextHighlight': 'Go to Next Highlight', - 'shortcut.key.goPreviousHighlight': 'Go to Previous Highlight', - 'shortcut.key.appendLineBefore': 'Append a Newline Before', - 'shortcut.key.appendLine': 'Append Newline', - 'shortcut.key.transpositionUpAndDown': 'Transposition up and down', - 'shortcut.key.collapseOrExpand': 'Collapse/Expand', - - 'shortcut.key.checked': 'Checked' + 'shortcut.key.formatSelection': 'Format Selection Content', + 'shortcut.key.notes': 'Notes/Cancel notes this line(area)', + 'shortcut.key.upperCase': 'Upper Case', + 'shortcut.key.lowerCase': 'Lower Case' }; diff --git a/dinky-web/src/locales/zh-CN/global.ts b/dinky-web/src/locales/zh-CN/global.ts index e963e8502f..8aded36ae3 100644 --- a/dinky-web/src/locales/zh-CN/global.ts +++ b/dinky-web/src/locales/zh-CN/global.ts @@ -81,6 +81,7 @@ export default { 'button.upScroll': '向上滚动', 'button.stopRefresh': '停止自动刷新', 'button.startRefresh': '开始自动刷新(5s/次)', + 'button.autoWrap': '自动换行/不换行', 'menu.menu': '菜单', 'right.menu.open': '打开', diff --git a/dinky-web/src/locales/zh-CN/shortcutKey.ts b/dinky-web/src/locales/zh-CN/shortcutKey.ts index be1d877909..2ed422e837 100644 --- a/dinky-web/src/locales/zh-CN/shortcutKey.ts +++ b/dinky-web/src/locales/zh-CN/shortcutKey.ts @@ -21,30 +21,9 @@ export default { 'shortcut.title': '快捷键', 'shortcut.key.save': '保存', 'shortcut.key.check': '校验', - 'shortcut.key.beautify': '美化', - 'shortcut.key.fullscreen': '全屏', - 'shortcut.key.fullscreenClose': '关闭弹框及全屏', - 'shortcut.key.more': '更多快捷键', - 'shortcut.key.search': '搜索', - 'shortcut.key.replace': '替换', - 'shortcut.key.revoke': '撤销', - 'shortcut.key.redo': '重做', - 'shortcut.key.notes': '注释', - 'shortcut.key.indent': '缩进', - 'shortcut.key.removeIndent': '取消缩进', - 'shortcut.key.selectToEnd': '选中后续', 'shortcut.key.format': '格式化', - 'shortcut.key.editMultiline': '编辑多行', - 'shortcut.key.copyRow': '复制一行', - 'shortcut.key.deleteRow': '删除一行', - 'shortcut.key.matchNext': '匹配下一个', - 'shortcut.key.matchPrevious': '匹配上一个', - 'shortcut.key.goNextHighlight': '前往下一个高亮', - 'shortcut.key.goPreviousHighlight': '前往上一个高亮', - 'shortcut.key.appendLineBefore': '前面追加换行', - 'shortcut.key.appendLine': '追加换行', - 'shortcut.key.transpositionUpAndDown': '上下换位', - 'shortcut.key.collapseOrExpand': '折叠/展开', - - 'shortcut.key.checked': '选中' + 'shortcut.key.formatSelection': '格式化选中内容', + 'shortcut.key.notes': '注释/取消注释该行(区域)', + 'shortcut.key.upperCase': '转为大写', + 'shortcut.key.lowerCase': '转为小写' }; diff --git a/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx b/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx index 61060057f8..2f8b17daa4 100644 --- a/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx +++ b/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx @@ -180,6 +180,8 @@ const ConsoleContent = (props: ConsoleProps) => { code={selectNode?.log ? selectNode.log : ''} height={props.height - 53} language={'javalog'} + lineNumbers={'off'} + enableMiniMap showFloatButton /> diff --git a/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/DiffModal.tsx b/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/DiffModal.tsx index 83e00dd6ea..9bd4716338 100644 --- a/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/DiffModal.tsx +++ b/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/DiffModal.tsx @@ -17,6 +17,7 @@ * */ +import { LoadCustomEditorLanguage } from '@/components/CustomEditor/languages'; import { DIFF_EDITOR_PARAMS, PARAM_DIFF_TABLE_COL @@ -39,7 +40,7 @@ export type DiffModalProps = { }; const DiffModal: React.FC = (props) => { - const { diffs, open, fileName, language = 'sql', onUse } = props; + const { diffs, open, fileName, language = 'flinksql', onUse } = props; // Find the diff object with key 'statement' const statementDiff = diffs.find((diff) => diff.key === 'statement'); // Filter out the diff objects with key other than 'statement' @@ -62,6 +63,8 @@ const DiffModal: React.FC = (props) => { LoadCustomEditorLanguage(monaco)} original={statementDiff?.server} modified={statementDiff?.cache} theme={convertCodeEditTheme()} diff --git a/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/constants.tsx b/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/constants.tsx index 35d7323bdb..799d8383e7 100644 --- a/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/constants.tsx +++ b/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/constants.tsx @@ -35,8 +35,7 @@ export const DIFF_EDITOR_PARAMS = { mouseWheelZoom: true, automaticLayout: true }, - language: 'sql', - theme: 'vs-dark' + language: 'flinksql' }; export const TASK_VAR_FILTER = [ diff --git a/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/index.tsx b/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/index.tsx index 29ffce954d..083f4913e2 100644 --- a/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/index.tsx +++ b/dinky-web/src/pages/DataStudio/MiddleContainer/Editor/index.tsx @@ -31,23 +31,22 @@ import { } from '@/pages/DataStudio/model'; import { JOB_LIFE_CYCLE } from '@/pages/DevOps/constants'; import { API_CONSTANTS } from '@/services/endpoints'; +import { registerEditorKeyBindingAndAction } from '@/utils/function'; import { l } from '@/utils/intl'; import { connect, useRequest } from '@@/exports'; import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; import { Monaco } from '@monaco-editor/react'; import { Button, Spin } from 'antd'; -import { editor, KeyCode, KeyMod } from 'monaco-editor'; +import { editor } from 'monaco-editor'; import React, { useEffect, useState } from 'react'; -import { format } from 'sql-formatter'; export type EditorProps = { tabsItem: DataStudioTabsItemType; - monacoInstance: Monaco; height?: number; }; -const CodeEditor: React.FC = (props) => { - const { tabsItem, dispatch, height, monacoInstance, editorInstance } = props; +const CodeEditor: React.FC = (props) => { + const { tabsItem, dispatch, height } = props; useEffect(() => { dispatch({ @@ -61,8 +60,6 @@ const CodeEditor: React.FC = (props) => { const [isModalOpen, setIsModalOpen] = useState(false); const [diff, setDiff] = useState([]); const { fullscreen, setFullscreen } = useEditor(); - // const editorInstance = useRef(monacoInstance?.current?.editor); - // const monacoInstance = useRef(); const currentData = tabsItem.params.taskData; @@ -105,8 +102,8 @@ const CodeEditor: React.FC = (props) => { const editorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => { editor.layout(); editor.focus(); - monacoInstance.current = monaco; - editorInstance.current = editor; + tabsItem.editorInstance = editor; + tabsItem.monacoInstance = monaco; editor.onDidChangeCursorPosition((e) => { props.footContainer.codePosition = [e.position.lineNumber, e.position.column]; @@ -115,11 +112,7 @@ const CodeEditor: React.FC = (props) => { payload: { ...props.footContainer } }); }); - - editor.addCommand(KeyMod.Alt | KeyCode.Digit3, () => { - editor?.trigger('anyString', 'editor.action.formatDocument', ''); - editor.setValue(format(editor.getValue())); - }); + registerEditorKeyBindingAndAction(editor); }; const handleEditChange = (v: string | undefined) => { @@ -142,15 +135,15 @@ const CodeEditor: React.FC = (props) => { = (props) => { title={l('global.fullScreen.exit')} icon={} onClick={() => { - editorInstance?.current?.layout(); + tabsItem?.editorInstance?.current?.layout(); setFullscreen(false); }} /> @@ -191,7 +184,7 @@ const CodeEditor: React.FC = (props) => { title={l('global.fullScreen')} icon={} onClick={() => { - editorInstance?.current?.layout(); + tabsItem?.editorInstance?.current?.layout(); setFullscreen(true); }} /> diff --git a/dinky-web/src/pages/DataStudio/MiddleContainer/KeyBoard/constant.tsx b/dinky-web/src/pages/DataStudio/MiddleContainer/KeyBoard/constant.tsx index b0b4693f41..e114946b64 100644 --- a/dinky-web/src/pages/DataStudio/MiddleContainer/KeyBoard/constant.tsx +++ b/dinky-web/src/pages/DataStudio/MiddleContainer/KeyBoard/constant.tsx @@ -33,114 +33,26 @@ export const KEY_BOARD_MIDDLE = [ { key: 'alt3', label: 'Alt / Option + 3', - description: l('shortcut.key.beautify') - } - // { - // key: 'f2', - // label: 'F2', - // description: l('shortcut.key.fullscreen') - // }, - // { - // key: 'esc', - // label: 'Esc', - // description: l('shortcut.key.fullscreenClose') - // } -]; - -export const KEY_BOARD_RIGHT_SLIDER = [ - { - key: 'f1', - label: 'F1', - description: l('shortcut.key.more') - }, - { - key: 'ctrlf', - label: 'Ctrl + F', - description: l('shortcut.key.search') - }, - { - key: 'ctrlh', - label: 'Ctrl + H', - description: l('shortcut.key.replace') - }, - { - key: 'ctrlz', - label: 'Ctrl + Z', - description: l('shortcut.key.revoke') - }, - { - key: 'ctrlY', - label: 'Ctrl + Y', - description: l('shortcut.key.redo') - }, - { - key: 'ctrl/', - label: 'Ctrl + /', - description: l('shortcut.key.notes') - }, - { - key: 'ctrl+alt+right', - label: 'Shift + Alt + Right', - description: l('shortcut.key.selectToEnd') - }, - { - key: 'ctrl+alt+f', - label: 'Shift + Alt + F', description: l('shortcut.key.format') }, { - key: 'ctrl+shift+up', - label: 'Ctrl + Shift + Up/Down', - description: l('shortcut.key.editMultiline') + key: 'alt4', + label: 'Alt / Option + 4', + description: l('shortcut.key.formatSelection') }, { - key: 'ctrl+alt+up', - label: 'Ctrl + Alt + Up/Down', - description: l('shortcut.key.copyRow') - }, - { - key: 'ctrl+shift+k', - label: 'Ctrl + Shift + K', - description: l('shortcut.key.deleteRow') - }, - { - key: 'ctrl+f3', - label: 'Ctrl + F3', - description: l('shortcut.key.matchNext') - }, - { - key: 'ctrl+shift+f3', - label: 'Ctrl + Shift + F3', - description: l('shortcut.key.matchPrevious') - }, - { - key: 'f7', - label: 'F7', - description: l('shortcut.key.goNextHighlight') - }, - { - key: 'shift+f7', - label: 'Shift +F7', - description: l('shortcut.key.goPreviousHighlight') - }, - { - key: 'ctrl+shift+end', - label: 'Ctrl + Shift + End', - description: l('shortcut.key.appendLineBefore') - }, - { - key: 'ctrl+end', - label: 'Ctrl + End', - description: l('shortcut.key.appendLine') + key: 'alt/', + label: 'Alt / Option + /', + description: l('shortcut.key.notes') }, { - key: 'alt+up/down', - label: 'Alt + Up/Down', - description: l('shortcut.key.transpositionUpAndDown') + key: 'altu', + label: 'Alt / Option + U', + description: l('shortcut.key.upperCase') }, { - key: 'ctrl+shift+[/]', - label: 'Ctrl + Shift + [/]', - description: l('shortcut.key.collapseOrExpand') + key: 'altl', + label: 'Alt / Option + L', + description: l('shortcut.key.lowerCase') } ]; diff --git a/dinky-web/src/pages/DataStudio/model.ts b/dinky-web/src/pages/DataStudio/model.ts index 2afda62b41..634067c858 100644 --- a/dinky-web/src/pages/DataStudio/model.ts +++ b/dinky-web/src/pages/DataStudio/model.ts @@ -162,8 +162,8 @@ export interface TabsItemType { icon: any; closable: boolean; path: string[]; - monacoInstance: Monaco; - editorInstance: editor.IStandaloneCodeEditor; + monacoInstance: React.RefObject; + editorInstance: React.RefObject; console: ConsoleType; isModified: boolean; } diff --git a/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/LogList/LogsShow/index.tsx b/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/LogList/LogsShow/index.tsx index b0b8cb2f65..73409ec9e4 100644 --- a/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/LogList/LogsShow/index.tsx +++ b/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/LogList/LogsShow/index.tsx @@ -25,7 +25,7 @@ import React from 'react'; const CodeEditProps: any = { width: '100%', lineNumbers: 'on', - language: 'java' + language: 'javalog' }; type LogsShowProps = { diff --git a/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/RootLogs/index.tsx b/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/RootLogs/index.tsx index a5874ad16f..0ee129b68d 100644 --- a/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/RootLogs/index.tsx +++ b/dinky-web/src/pages/SettingCenter/SystemLogs/TagInfo/RootLogs/index.tsx @@ -37,7 +37,7 @@ const RootLogs = () => { const queryLogs = async () => { const result = await queryDataByParams(API_CONSTANTS.SYSTEM_ROOT_LOG); - setCode(result); + setCode(result as string); }; useEffect(() => { diff --git a/dinky-web/src/utils/function.tsx b/dinky-web/src/utils/function.tsx index d170ce7e93..9eb97c0480 100644 --- a/dinky-web/src/utils/function.tsx +++ b/dinky-web/src/utils/function.tsx @@ -45,9 +45,9 @@ import { l } from '@/utils/intl'; import dayjs from 'dayjs'; import cookies from 'js-cookie'; import { trim } from 'lodash'; -import { editor } from 'monaco-editor'; +import { editor, KeyCode, KeyMod } from 'monaco-editor'; import path from 'path'; -import { useEffect, useState } from 'react'; +import { format } from 'sql-formatter'; /** * get language by localStorage's umi_locale , if not exist , return zh-CN @@ -124,6 +124,120 @@ export function getLocalTheme(): string { return localStorage.getItem(THEME.NAV_THEME) ?? THEME.dark; } +/** + * register editor key binding | 注册编辑器快捷键 + * + * @param editorInstance + */ +function registerEditorKeyBinding(editorInstance?: editor.IStandaloneCodeEditor) { + // 添加 ctrl + z 撤销 + editorInstance?.addCommand(KeyMod.CtrlCmd | KeyCode.KeyZ, () => { + editorInstance?.trigger('anyString', 'undo', ''); + }); + // 添加 ctrl + y 恢复 + editorInstance?.addCommand(KeyMod.CtrlCmd | KeyCode.KeyY, () => { + editorInstance?.trigger('anyString', 'redo', ''); + }); + // 格式化所有代码 + editorInstance?.addCommand(KeyMod.Alt | KeyCode.Digit3, () => { + editorInstance?.trigger('anyString', 'editor.action.formatDocument', ''); + editorInstance?.setValue(format(editorInstance?.getValue())); + }); + // 格式化选定内容 + editorInstance?.addCommand(KeyMod.Alt | KeyCode.Digit4, () => { + editorInstance?.trigger('anyString', 'editor.action.formatSelection', ''); + editorInstance?.setValue(format(editorInstance?.getValue())); + }); +} + +/** + *

如果使用了 sql-formatter , 可以使用以下参数


+ * language: the SQL dialect to use (when using format()). | 要使用的SQL方言(当使用format()时)
+ * dialect: the SQL dialect to use (when using formatDialect() since version 12). | 要使用的SQL方言(自版本12起)
+ * tabWidth: amount of indentation to use. | 要使用的缩进量
+ * useTabs: to use tabs for indentation. | 要使用制表符进行缩进
+ * keywordCase: uppercases or lowercases keywords. | 关键字大小写
+ * identifierCase: uppercases or lowercases identifiers. (experimental!) | 标识符大小写
+ * indentStyle: defines overall indentation style.| 总体缩进样式
+ * logicalOperatorNewline: newline before or after boolean operator (AND, OR, XOR). | 布尔运算符(AND,OR,XOR)的换行位置
+ * expressionWidth: maximum number of characters in parenthesized expressions to be kept on single line. | 带括号的表达式中保持在一行的最大字符数
+ * linesBetweenQueries: how many newlines to insert between queries. | 在查询之间插入的换行数
+ * denseOperators: packs operators densely without spaces. | 密集地封装运算符,没有空格
+ * newlineBeforeSemicolon: places semicolon on separate line. | 将分号放在单独的行上
+ * params: collection of values for placeholder replacement. | 占位符替换的值的集合
+ * paramTypes: specifies parameter placeholders types to support | 指定要支持的参数占位符类型
+ * register editor action + * @param editorInstance editor instance + */ +function registerEditorAction(editorInstance?: editor.IStandaloneCodeEditor) { + // 格式化所有代码 添加到 右键菜单 | format document + editorInstance?.addAction({ + id: 'format', + label: l('shortcut.key.format'), + keybindings: [KeyMod.CtrlCmd | KeyCode.Digit3], + contextMenuGroupId: 'custom', + contextMenuOrder: 1.5, + run: () => { + editorInstance?.trigger('anyString', 'editor.action.formatDocument', ''); + editorInstance?.setValue(format(editorInstance?.getValue(), { language: 'spark' })); + } + }); + // 格式化选定内容 添加到 右键菜单 | format selection + editorInstance?.addAction({ + id: 'formatSelection', + label: l('shortcut.key.formatSelection'), + keybindings: [KeyMod.CtrlCmd | KeyCode.Digit4], + contextMenuGroupId: 'custom', + contextMenuOrder: 1.5, + run: () => { + editorInstance?.trigger('anyString', 'editor.action.formatSelection', ''); + editorInstance?.setValue(format(editorInstance?.getValue(), { language: 'spark' })); + } + }); + // 注释该行 添加到 右键菜单 | comment line + editorInstance?.addAction({ + id: 'commentLine', + label: l('shortcut.key.notes'), + keybindings: [KeyMod.CtrlCmd | KeyCode.Slash], + contextMenuGroupId: 'custom', + contextMenuOrder: 1.5, + run: () => { + editorInstance?.trigger('anyString', 'editor.action.commentLine', ''); + } + }); + // 转为 大写 添加到 右键菜单 | to uppercase + editorInstance?.addAction({ + id: 'upperCase', + label: l('shortcut.key.upperCase'), + keybindings: [KeyMod.CtrlCmd | KeyCode.KeyU], + contextMenuGroupId: 'custom', + contextMenuOrder: 1.5, + run: () => { + editorInstance?.trigger('anyString', 'editor.action.transformToUppercase', ''); + } + }); + // 转为 小写 添加到 右键菜单 | to lowercase + editorInstance?.addAction({ + id: 'lowerCase', + label: l('shortcut.key.lowerCase'), + keybindings: [KeyMod.CtrlCmd | KeyCode.KeyL], + contextMenuGroupId: 'custom', + contextMenuOrder: 1.5, + run: () => { + editorInstance?.trigger('anyString', 'editor.action.transformToLowercase', ''); + } + }); +} + +/** + * register editor key binding and action | 注册编辑器快捷键和右键菜单 + * @param editorInstance + */ +export function registerEditorKeyBindingAndAction(editorInstance?: editor.IStandaloneCodeEditor) { + registerEditorKeyBinding(editorInstance); + registerEditorAction(editorInstance); +} + /** * get code edit theme by localStorage's theme * @constructor @@ -132,45 +246,49 @@ export function convertCodeEditTheme(editorInstance?: any) { if (!editorInstance) { editorInstance = editor; } - /** - * 定义亮色 覆盖vs主题,增加扩展规则 - */ - editorInstance.defineTheme(CODE_EDIT_THEME.VS, { - base: 'vs', // 指定基础主题 , 可选值: 'vs', 'vs-dark', 'hc-black' , base theme - inherit: true, // 是否继承主题配置 - rules: [ - // 注意,默认的不做修改 因为上边继承了父主题, 只添加自己定义的 , 否则会覆盖默认的 , 导致编辑器样式不一致 - { token: 'custom-info', foreground: '#808080' }, - { token: 'custom-thread', foreground: '#9fa19f' }, - { token: 'custom-class', foreground: '#1060d9' }, - { token: 'custom-error', foreground: '#ff0000', fontStyle: 'bold' }, - { token: 'custom-warning', foreground: '#FFA500', fontStyle: 'bold' }, - { token: 'custom-date', foreground: '#008800' }, - { token: 'custom-process', foreground: '#07f313' } - ], - colors: {}, - encodedTokensColors: [] - }); - - /** - * 定义暗色 覆盖vs-dark主题,增加扩展规则 - */ - editorInstance.defineTheme(CODE_EDIT_THEME.DARK, { - base: 'vs-dark', // 指定基础主题 , 可选值: 'vs', 'vs-dark', 'hc-black' , base theme - inherit: true, // 是否继承主题配置 - rules: [ - // 注意,默认的不做修改 因为上边继承了父主题, 只添加自己定义的 , 否则会覆盖默认的 , 导致编辑器样式不一致 - { token: 'custom-info', foreground: '#008800' }, - { token: 'custom-thread', foreground: '#9fa19f' }, - { token: 'custom-class', foreground: '#1060d9' }, - { token: 'custom-error', foreground: '#ff0000', fontStyle: 'bold' }, - { token: 'custom-warning', foreground: '#FFA500', fontStyle: 'bold' }, - { token: 'custom-date', foreground: '#008800' }, - { token: 'custom-process', foreground: '#07f313' } - ], - colors: {}, - encodedTokensColors: [] - }); + if (editorInstance === undefined) { + return CODE_EDIT_THEME.VS; + } else { + /** + * 定义亮色 覆盖vs主题,增加扩展规则 + */ + editorInstance?.defineTheme?.(CODE_EDIT_THEME.VS, { + base: 'vs', // 指定基础主题 , 可选值: 'vs', 'vs-dark', 'hc-black' , base theme + inherit: true, // 是否继承主题配置 + rules: [ + // 注意,默认的不做修改 因为上边继承了父主题, 只添加自己定义的 , 否则会覆盖默认的 , 导致编辑器样式不一致 + { token: 'custom-info', foreground: '#808080' }, + { token: 'custom-thread', foreground: '#9fa19f' }, + { token: 'custom-class', foreground: '#1060d9' }, + { token: 'custom-error', foreground: '#ff0000', fontStyle: 'bold' }, + { token: 'custom-warning', foreground: '#FFA500', fontStyle: 'bold' }, + { token: 'custom-date', foreground: '#008800' }, + { token: 'custom-process', foreground: '#07f313' } + ], + colors: {}, + encodedTokensColors: [] + }); + + /** + * 定义暗色 覆盖vs-dark主题,增加扩展规则 + */ + editorInstance?.defineTheme?.(CODE_EDIT_THEME.DARK, { + base: 'vs-dark', // 指定基础主题 , 可选值: 'vs', 'vs-dark', 'hc-black' , base theme + inherit: true, // 是否继承主题配置 + rules: [ + // 注意,默认的不做修改 因为上边继承了父主题, 只添加自己定义的 , 否则会覆盖默认的 , 导致编辑器样式不一致 + { token: 'custom-info', foreground: '#008800' }, + { token: 'custom-thread', foreground: '#9fa19f' }, + { token: 'custom-class', foreground: '#1060d9' }, + { token: 'custom-error', foreground: '#ff0000', fontStyle: 'bold' }, + { token: 'custom-warning', foreground: '#FFA500', fontStyle: 'bold' }, + { token: 'custom-date', foreground: '#008800' }, + { token: 'custom-process', foreground: '#07f313' } + ], + colors: {}, + encodedTokensColors: [] + }); + } const theme = getLocalTheme(); switch (theme) { @@ -183,48 +301,6 @@ export function convertCodeEditTheme(editorInstance?: any) { } } -/** - * use SSE build single data - * @param url - */ -export const useSSEBuildSingleData = (url: string) => { - const [data, setData] = useState(null); - - useEffect(() => { - const eventSource = new EventSource(url); - eventSource.onmessage = (event) => { - const newData = JSON.parse(event.data); - setData(newData); - }; - return () => { - eventSource.close(); - }; - }, [url]); - - return data; -}; - -/** - * use SSE build array data - * @param url - */ -export const useSSEBuildArrayData = (url: string) => { - const [data, setData] = useState([]); - - useEffect(() => { - const eventSource = new EventSource(url); - eventSource.onmessage = (event) => { - const newData = JSON.parse(event.data); - setData((prevData) => [...prevData, newData]); - }; - return () => { - eventSource.close(); - }; - }, [url]); - - return data; -}; - /** * get file icon by file type * @param type