diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 7b718a1f4..2d1778811 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -8,6 +8,8 @@ import { highlightActiveLineGutter, highlightActiveLine, scrollPastEnd, + Decoration, + ViewPlugin, } from '@codemirror/view'; import { EditorState, Compartment } from '@codemirror/state'; import { foldGutter, foldKeymap, syntaxHighlighting } from '@codemirror/language'; @@ -19,8 +21,50 @@ import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import * as themes from '@uiw/codemirror-themes-all'; const themeCompartment = new Compartment(); -import { customHighlightPlugin, customHighlightStyle } from './customHighlight.js'; +const highlightCompartment = new Compartment(); + import { homebreweryFold, hbFolding } from './customFolding.js'; +import { customHighlightStyle, tokenizeCustomMarkdown } from './customHighlight.js'; +import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './legacyCustomHighlight.js'; //only makes highlight for + +const createHighlightPlugin = (renderer)=>{ + console.log(renderer); + const tokenize = renderer === 'V3' ? tokenizeCustomMarkdown : legacyTokenizeCustomMarkdown; + + return ViewPlugin.fromClass( + class { + constructor(view) { + this.decorations = this.buildDecorations(view); + } + update(update) { + if(update.docChanged) { + this.decorations = this.buildDecorations(update.view); + } + } + buildDecorations(view) { + const decos = []; + const tokens = tokenize(view.state.doc.toString()); + + tokens.forEach((tok)=>{ + const line = view.state.doc.line(tok.line + 1); + + if(tok.from != null && tok.to != null && tok.from < tok.to) { + decos.push( + Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to) + ); + } else { + decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); + } + }); + + decos.sort((a, b)=>a.from - b.from || a.to - b.to); + return Decoration.set(decos); + } + }, + { decorations: (v)=>v.decorations } + ); +}; + const CodeEditor = forwardRef( ( @@ -32,6 +76,7 @@ const CodeEditor = forwardRef( editorTheme = 'default', view, style, + renderer, ...props }, ref, @@ -79,11 +124,24 @@ const CodeEditor = forwardRef( { key: 'Mod-i', run: italicCommand }, ]); + const highlightExtension = renderer === 'V3' + ? syntaxHighlighting(customHighlightStyle) + : syntaxHighlighting(legacyCustomHighlightStyle); + + const customHighlightPlugin = createHighlightPlugin(renderer); + + const combinedHighlight = [ + customHighlightPlugin, + highlightExtension, + ]; + + const languageExtension = language === 'css' ? css() : markdown({ base: markdownLanguage, codeLanguages: languages }); const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : []; + return [ history(), keymap.of(defaultKeymap), @@ -107,7 +165,7 @@ const CodeEditor = forwardRef( highlightActiveLine(), highlightActiveLineGutter(), customHighlightPlugin, - syntaxHighlighting(customHighlightStyle), + highlightCompartment.of(combinedHighlight), ]; }; @@ -175,6 +233,21 @@ const CodeEditor = forwardRef( effects : themeCompartment.reconfigure(themeExtension), }); }, [editorTheme]); + useEffect(()=>{ + const view = viewRef.current; + if(!view) return; + + const highlightExtension = + renderer === 'V3' + ? syntaxHighlighting(customHighlightStyle) + : syntaxHighlighting(legacyCustomHighlightStyle); + + const customHighlightPlugin = createHighlightPlugin(renderer); + + view.dispatch({ + effects : highlightCompartment.reconfigure([customHighlightPlugin, highlightExtension]), + }); + }, [renderer]); useImperativeHandle(ref, ()=>({ getValue : ()=>viewRef.current.state.doc.toString(), diff --git a/client/components/codeEditor/customFolding.js b/client/components/codeEditor/customFolding.js index 778901371..cf6bea5c3 100644 --- a/client/components/codeEditor/customFolding.js +++ b/client/components/codeEditor/customFolding.js @@ -31,7 +31,6 @@ export const homebreweryFold = foldService.of((state, lineStart)=>{ if(endLine === startLine.number) return null; const widgetObject = { from: startLine.from, to: doc.line(endLine).to }; - console.log(widgetObject); return widgetObject; }); diff --git a/client/components/codeEditor/customHighlight.js b/client/components/codeEditor/customHighlight.js index 29df5576d..fda24a7ba 100644 --- a/client/components/codeEditor/customHighlight.js +++ b/client/components/codeEditor/customHighlight.js @@ -1,9 +1,5 @@ import { HighlightStyle } from '@codemirror/language'; import { tags } from '@lezer/highlight'; -import { - Decoration, - ViewPlugin, -} from '@codemirror/view'; // Making the tokens const customTags = { @@ -23,7 +19,7 @@ const customTags = { definitionColon : 'definitionColon', // .cm-definitionColon }; -function tokenizeCustomMarkdown(text) { +export function tokenizeCustomMarkdown(text) { const tokens = []; const lines = text.split('\n'); @@ -39,7 +35,18 @@ function tokenizeCustomMarkdown(text) { if(/\\snippet/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetBreak }); // --- Emoji --- - if(/:\w+?:/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.emoji }); + if(/:.\w+?:/.test(lineText)) { + const emojiRegex = /(:\w+?:)/g; + let match; + while ((match = emojiRegex.exec(lineText)) !== null) { + tokens.push({ + line : lineNumber, + type : customTags.emoji, + from : match.index, + to : match.index + match[0].length, + }); + } + } // --- Superscript / Subscript --- if(/\^/.test(lineText)) { @@ -243,41 +250,5 @@ export const customHighlightStyle = HighlightStyle.define([ { tag: customTags.definitionDesc, class: 'cm-dd', color: '#070' }, ]); -export const customHighlightPlugin = ViewPlugin.fromClass( - class { - constructor(view) { - this.decorations = this.buildDecorations(view); - } - update(update) { - if(update.docChanged) { - this.decorations = this.buildDecorations(update.view); - } - } - buildDecorations(view) { - const decos = []; - const tokens = tokenizeCustomMarkdown(view.state.doc.toString()); - tokens.forEach((tok)=>{ - const line = view.state.doc.line(tok.line + 1); - if(tok.from != null && tok.to != null && tok.from < tok.to) { - // inline decoration - decos.push( - Decoration.mark({ class: `cm-${tok.type}` }).range(line.from + tok.from, line.from + tok.to), - ); - } else { - // full-line decoration - decos.push(Decoration.line({ class: `cm-${tok.type}` }).range(line.from)); - } - }); - - // sort by absolute start position - decos.sort((a, b)=>a.from - b.from || a.to - b.to); - - return Decoration.set(decos); - } - }, - { - decorations : (v)=>v.decorations, - }, -); diff --git a/client/components/codeEditor/legacyCustomHighlight.js b/client/components/codeEditor/legacyCustomHighlight.js new file mode 100644 index 000000000..2777b3102 --- /dev/null +++ b/client/components/codeEditor/legacyCustomHighlight.js @@ -0,0 +1,34 @@ +import { HighlightStyle } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; + +// Making the tokens +const customTags = { + pageLine : 'pageLine', // .cm-pageLine + snippetLine : 'snippetLine', // .cm-snippetLine +}; + +export function legacyTokenizeCustomMarkdown(text) { + const tokens = []; + const lines = text.split('\n'); + + // Track multi-line blocks + const inBlock = false; + const blockStart = 0; + + lines.forEach((lineText, lineNumber)=>{ + // --- Page / snippet lines --- + if(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(lineText)) tokens.push({ line: lineNumber, type: customTags.pageLine }); + if(/^\\snippet\ .*$/.test(lineText)) tokens.push({ line: lineNumber, type: customTags.snippetLine }); + }); + + return tokens; +} + +export const legacyCustomHighlightStyle = HighlightStyle.define([ + { tag: tags.heading1, color: '#000', fontWeight: '700' }, + { tag: tags.keyword, color: '#07a' }, // example for your markdown headings + { tag: customTags.pageLine, color: '#f0a' }, + { tag: customTags.snippetLine, class: 'cm-snippetLine', color: '#0af' }, +]); + + diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 62cea09eb..535e3cbf5 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -447,6 +447,7 @@ const Editor = createReactClass({ value={this.props.brew.text} onChange={this.props.onBrewChange('text')} editorTheme={this.state.editorTheme} + renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} /> ; @@ -462,6 +463,7 @@ const Editor = createReactClass({ onChange={this.props.onBrewChange('style')} enableFolding={true} editorTheme={this.state.editorTheme} + renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} style={{ height: `calc(100% - ${this.state.snippetBarHeight}px)` }} /> ; @@ -492,6 +494,7 @@ const Editor = createReactClass({ onChange={this.props.onBrewChange('snippets')} enableFolding={true} editorTheme={this.state.editorTheme} + renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} style={{ height: `calc(100% -${this.state.snippetBarHeight}px)` }} /> ;