From f131c2c724fddc209df854eacbae9be7c59f52bf Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 19:51:11 +0100 Subject: [PATCH 1/2] feat: added color rules for namespace and node positioning attributes --- .../frontend/app/routes/editor/editor.tsx | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/src/main/frontend/app/routes/editor/editor.tsx b/src/main/frontend/app/routes/editor/editor.tsx index e106a567..fc47c3a9 100644 --- a/src/main/frontend/app/routes/editor/editor.tsx +++ b/src/main/frontend/app/routes/editor/editor.tsx @@ -1,4 +1,4 @@ -import Editor, { type Monaco, type OnMount } from '@monaco-editor/react' +import Editor, { type Monaco, type OnMount, type BeforeMount } from '@monaco-editor/react' import XsdManager from 'monaco-xsd-code-completion/esm/XsdManager' import XsdFeatures from 'monaco-xsd-code-completion/esm/XsdFeatures' import 'monaco-xsd-code-completion/src/style.css' @@ -47,6 +47,64 @@ const SAVED_DISPLAY_DURATION = 2000 const ELEMENT_ERROR_RE = /[Ee]lement [\u2018\u2019'"'{]?([\w:.-]+)[\u2018\u2019'"'}]?/ const ATTRIBUTE_ERROR_RE = /[Aa]ttribute [\u2018\u2019'"'{]?([\w:.-]+)[\u2018\u2019'"'}]?/ +const FLOW_ATTR_PATTERN = /(?:xmlns:flow|flow:[\w-]+)/ +const FLOW_TOKENS = [ + 'flow-attribute', + 'flow-attribute-value', + 'flow-attribute-value', + 'flow-attribute-value', + 'flow-attribute-value', +] as const +const ATTR_TOKENS = ['attribute.name', 'delimiter', 'attribute.value', 'attribute.value', 'attribute.value'] as const + +const XML_MONARCH_GRAMMAR = { + defaultToken: '', + tokenPostfix: '.xml', + tokenizer: { + root: [ + [/[^<&]+/, ''], + [/&\w+;/, 'string.escape'], + [//, { token: 'delimiter.cdata', next: '@pop' }], + [/\]/, ''], + ], + xmlDecl: [ + [/\s+/, ''], + [/[\w:-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/"[^"]*"/, 'attribute.value'], + [/'[^']*'/, 'attribute.value'], + [/\?>/, { token: 'delimiter', next: '@pop' }], + ], + tag: [ + [/\s+/, ''], + [new RegExp(String.raw`(${FLOW_ATTR_PATTERN.source})(\s*=\s*)(")((?:[^"])*)(")`), FLOW_TOKENS], + [new RegExp(String.raw`(${FLOW_ATTR_PATTERN.source})(\s*=\s*)(')((?:[^'])*)(')`), FLOW_TOKENS], + [FLOW_ATTR_PATTERN, 'flow-attribute'], + [/([\w.:_-]+)(\s*=\s*)(")((?:[^"])*)(")/, ATTR_TOKENS], + [/([\w.:_-]+)(\s*=\s*)(')((?:[^']*))(')/, ATTR_TOKENS], + [/[\w.:_-]+/, 'attribute.name'], + [/=/, 'delimiter'], + [/\/>/, { token: 'delimiter', next: '@pop' }], + [/>/, { token: 'delimiter', next: '@pop' }], + ], + comment: [ + [/-->/, { token: 'comment', next: '@pop' }], + [/[^-]+/, 'comment'], + [/--/, 'comment'], + [/./, 'comment'], + ], + }, +} + function extractLocalName(name: string): string { return name.includes(':') ? name.split(':').pop()! : name } @@ -314,6 +372,32 @@ export default function CodeEditor() { .catch(console.error) }, [editorMounted]) + const handleEditorBeforeMount: BeforeMount = (monacoInstance) => { + monacoInstance.languages.setMonarchTokensProvider( + 'xml', + XML_MONARCH_GRAMMAR as Parameters[1], + ) + + monacoInstance.editor.defineTheme('vs-light', { + base: 'vs', + inherit: true, + rules: [ + { token: 'flow-attribute.xml', foreground: 'a8a8a8', fontStyle: 'italic' }, + { token: 'flow-attribute-value.xml', foreground: 'a8a8a8', fontStyle: 'italic' }, + ], + colors: {}, + }) + monacoInstance.editor.defineTheme('vs-dark', { + base: 'vs-dark', + inherit: true, + rules: [ + { token: 'flow-attribute.xml', foreground: '808080', fontStyle: 'italic' }, + { token: 'flow-attribute-value.xml', foreground: '808080', fontStyle: 'italic' }, + ], + colors: {}, + }) + } + const handleEditorMount: OnMount = (editor, monacoInstance) => { editorReference.current = editor monacoReference.current = monacoInstance @@ -547,6 +631,7 @@ export default function CodeEditor() { language="xml" theme={`vs-${theme}`} value={xmlContent} + beforeMount={handleEditorBeforeMount} onMount={handleEditorMount} onChange={(value) => { scheduleSave() From c1750c79b4a31e832e2697d8cd7a909debb73cb6 Mon Sep 17 00:00:00 2001 From: stijnpotters Date: Tue, 24 Mar 2026 20:07:34 +0100 Subject: [PATCH 2/2] feat: update flow attribute handling and define custom themes --- .../frontend/app/routes/editor/editor.tsx | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/main/frontend/app/routes/editor/editor.tsx b/src/main/frontend/app/routes/editor/editor.tsx index fc47c3a9..b657439a 100644 --- a/src/main/frontend/app/routes/editor/editor.tsx +++ b/src/main/frontend/app/routes/editor/editor.tsx @@ -47,16 +47,6 @@ const SAVED_DISPLAY_DURATION = 2000 const ELEMENT_ERROR_RE = /[Ee]lement [\u2018\u2019'"'{]?([\w:.-]+)[\u2018\u2019'"'}]?/ const ATTRIBUTE_ERROR_RE = /[Aa]ttribute [\u2018\u2019'"'{]?([\w:.-]+)[\u2018\u2019'"'}]?/ -const FLOW_ATTR_PATTERN = /(?:xmlns:flow|flow:[\w-]+)/ -const FLOW_TOKENS = [ - 'flow-attribute', - 'flow-attribute-value', - 'flow-attribute-value', - 'flow-attribute-value', - 'flow-attribute-value', -] as const -const ATTR_TOKENS = ['attribute.name', 'delimiter', 'attribute.value', 'attribute.value', 'attribute.value'] as const - const XML_MONARCH_GRAMMAR = { defaultToken: '', tokenPostfix: '.xml', @@ -86,11 +76,11 @@ const XML_MONARCH_GRAMMAR = { ], tag: [ [/\s+/, ''], - [new RegExp(String.raw`(${FLOW_ATTR_PATTERN.source})(\s*=\s*)(")((?:[^"])*)(")`), FLOW_TOKENS], - [new RegExp(String.raw`(${FLOW_ATTR_PATTERN.source})(\s*=\s*)(')((?:[^'])*)(')`), FLOW_TOKENS], - [FLOW_ATTR_PATTERN, 'flow-attribute'], - [/([\w.:_-]+)(\s*=\s*)(")((?:[^"])*)(")/, ATTR_TOKENS], - [/([\w.:_-]+)(\s*=\s*)(')((?:[^']*))(')/, ATTR_TOKENS], + // flow: namespace attributes — literal regexes, matched before generic rules + [/((?:xmlns:flow|flow:[\w-]+))(\s*=\s*(?:"[^"]*"|'[^']*'))/, ['flow-attribute', 'flow-attribute-value']], + [/(?:xmlns:flow|flow:[\w-]+)/, 'flow-attribute'], + // Regular attributes + [/([\w.:_-]+)(\s*=\s*(?:"[^"]*"|'[^']*'))/, ['attribute.name', 'attribute.value']], [/[\w.:_-]+/, 'attribute.name'], [/=/, 'delimiter'], [/\/>/, { token: 'delimiter', next: '@pop' }], @@ -378,7 +368,8 @@ export default function CodeEditor() { XML_MONARCH_GRAMMAR as Parameters[1], ) - monacoInstance.editor.defineTheme('vs-light', { + // Use non-conflicting names — 'vs-dark' is a Monaco built-in and can't safely be overridden + monacoInstance.editor.defineTheme('flow-vs-light', { base: 'vs', inherit: true, rules: [ @@ -387,7 +378,7 @@ export default function CodeEditor() { ], colors: {}, }) - monacoInstance.editor.defineTheme('vs-dark', { + monacoInstance.editor.defineTheme('flow-vs-dark', { base: 'vs-dark', inherit: true, rules: [ @@ -403,6 +394,8 @@ export default function CodeEditor() { monacoReference.current = monacoInstance setEditorMounted(true) + setTimeout(() => monacoInstance.editor.setTheme(`flow-vs-${theme}`), 0) + editor.addAction({ id: 'save-file', label: 'Save File', @@ -629,7 +622,7 @@ export default function CodeEditor() {