mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-06-22 04:58:40 +00:00
move highlight plugin into its own file
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
/* eslint max-lines: ["error", { "max": 400 }] */
|
/* eslint max-lines: ["error", { "max": 405 }] */
|
||||||
import './codeEditor.less';
|
import './codeEditor.less';
|
||||||
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||||
|
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
highlightActiveLine,
|
highlightActiveLine,
|
||||||
scrollPastEnd,
|
scrollPastEnd,
|
||||||
Decoration,
|
Decoration,
|
||||||
ViewPlugin,
|
|
||||||
drawSelection,
|
drawSelection,
|
||||||
dropCursor,
|
dropCursor,
|
||||||
rectangularSelection,
|
rectangularSelection,
|
||||||
@@ -18,15 +17,12 @@ import {
|
|||||||
} from '@codemirror/view';
|
} from '@codemirror/view';
|
||||||
import { EditorState, Compartment, StateEffect, StateField } from '@codemirror/state';
|
import { EditorState, Compartment, StateEffect, StateField } from '@codemirror/state';
|
||||||
import {
|
import {
|
||||||
foldAll as foldAllCmd,
|
|
||||||
unfoldAll as unfoldAllCmd,
|
unfoldAll as unfoldAllCmd,
|
||||||
foldGutter,
|
foldGutter,
|
||||||
foldKeymap,
|
foldKeymap,
|
||||||
foldEffect,
|
foldEffect,
|
||||||
foldState,
|
foldState,
|
||||||
syntaxHighlighting,
|
syntaxHighlighting,
|
||||||
syntaxTree,
|
|
||||||
ensureSyntaxTree
|
|
||||||
} from '@codemirror/language';
|
} from '@codemirror/language';
|
||||||
import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
|
import { defaultKeymap, history, undo, redo, undoDepth, redoDepth } from '@codemirror/commands';
|
||||||
import { languages } from '@codemirror/language-data';
|
import { languages } from '@codemirror/language-data';
|
||||||
@@ -49,123 +45,11 @@ const highlightCompartment = new Compartment();
|
|||||||
|
|
||||||
import { generalKeymap, markdownKeymap } from './extensions/customKeyMaps.js';
|
import { generalKeymap, markdownKeymap } from './extensions/customKeyMaps.js';
|
||||||
import foldOnPages from './extensions/customFolding.js';
|
import foldOnPages from './extensions/customFolding.js';
|
||||||
import { customHighlightStyle, tokenizeCustomMarkdown, tokenizeCustomCSS } from './extensions/customHighlight.js';
|
import { customHighlightPlugin, customHighlightStyle } from './extensions/customHighlight.js';
|
||||||
import { legacyCustomHighlightStyle, legacyTokenizeCustomMarkdown } from './extensions/legacyCustomHighlight.js';
|
import { legacyCustomHighlightStyle } from './extensions/legacyCustomHighlight.js';
|
||||||
|
|
||||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
|
|
||||||
function getUrl(node, doc) {
|
|
||||||
let url = null;
|
|
||||||
|
|
||||||
const cursor = node.node.cursor();
|
|
||||||
|
|
||||||
if(cursor.firstChild()) {
|
|
||||||
do {
|
|
||||||
if(cursor.name === 'URL') {
|
|
||||||
url = doc.sliceString(cursor.from, cursor.to);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (cursor.nextSibling());
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
const createHighlightPlugin = (renderer, tab)=>{
|
|
||||||
//this function takes the custom tokens created in the tokenize function in customhighlight files
|
|
||||||
//takes the tokens defined by that function and assigns classes to them
|
|
||||||
//it also creates page number and snippet number widgets
|
|
||||||
|
|
||||||
let tokenize;
|
|
||||||
|
|
||||||
if(tab === 'brewStyles') {
|
|
||||||
tokenize = tokenizeCustomCSS;
|
|
||||||
} else {
|
|
||||||
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());
|
|
||||||
let pageCount = 1;
|
|
||||||
let snippetCount = 0;
|
|
||||||
|
|
||||||
const tree = ensureSyntaxTree(view.state, view.state.doc.length, 50) || syntaxTree(view.state);
|
|
||||||
tree.iterate({
|
|
||||||
enter : (node)=>{
|
|
||||||
if(node.name === 'Image') {
|
|
||||||
const url = getUrl(node, view.state.doc);
|
|
||||||
|
|
||||||
if(!url) return;
|
|
||||||
|
|
||||||
decos.push(
|
|
||||||
Decoration.mark({
|
|
||||||
class : 'cm-image',
|
|
||||||
attributes : {
|
|
||||||
'style' : `--preview-img:url(${url});`
|
|
||||||
}
|
|
||||||
}).range(node.from, node.to)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tokens.forEach((token)=>{
|
|
||||||
const line = view.state.doc.line(token.line + 1);
|
|
||||||
|
|
||||||
if(token.from != null && token.to != null && token.from < token.to) {
|
|
||||||
const from = line.from + token.from;
|
|
||||||
const to = line.from + token.to;
|
|
||||||
|
|
||||||
const attrs = {};
|
|
||||||
if(token.type === 'Image' && token.url) {
|
|
||||||
|
|
||||||
attrs['data-url'] = token.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
decos.push(
|
|
||||||
Decoration.mark({
|
|
||||||
class : `cm-${token.type}`,
|
|
||||||
...(Object.keys(attrs).length
|
|
||||||
? { attributes: attrs }
|
|
||||||
: {})
|
|
||||||
}).range(from, to)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
decos.push(
|
|
||||||
Decoration.line({
|
|
||||||
class : `cm-${token.type}`
|
|
||||||
}).range(line.from)
|
|
||||||
);
|
|
||||||
if(token.type === 'pageLine' && tab === 'brewText') {
|
|
||||||
pageCount++;
|
|
||||||
if(line.from === 0) pageCount--;
|
|
||||||
decos.push(Decoration.line({ attributes: { 'data-page-number': pageCount } }).range(line.from));
|
|
||||||
}
|
|
||||||
if(token.type === 'snippetLine' && tab === 'brewSnippets') {
|
|
||||||
snippetCount++;
|
|
||||||
decos.push(Decoration.line({ attributes: { 'data-page-number': snippetCount } }).range(line.from));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
decos.sort((a, b)=>a.from - b.from || a.to - b.to);
|
|
||||||
return Decoration.set(decos);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ decorations: (v)=>v.decorations }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setProgrammaticCursorLine = StateEffect.define();
|
const setProgrammaticCursorLine = StateEffect.define();
|
||||||
|
|
||||||
const programmaticCursorLineField = StateField.define({
|
const programmaticCursorLineField = StateField.define({
|
||||||
@@ -272,8 +156,6 @@ const CodeEditor = forwardRef(
|
|||||||
? syntaxHighlighting(customHighlightStyle)
|
? syntaxHighlighting(customHighlightStyle)
|
||||||
: syntaxHighlighting(legacyCustomHighlightStyle);
|
: syntaxHighlighting(legacyCustomHighlightStyle);
|
||||||
|
|
||||||
const customHighlightPlugin = createHighlightPlugin(renderer, tab);
|
|
||||||
|
|
||||||
const languageExtension = language === 'css' ? css() : [markdown({ base: markdownLanguage, codeLanguages: languages }), html({ autoCloseTags: true })];
|
const languageExtension = language === 'css' ? css() : [markdown({ base: markdownLanguage, codeLanguages: languages }), html({ autoCloseTags: true })];
|
||||||
const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : themes[editorTheme] || themes['default'];
|
const themeExtension = Array.isArray(themes[editorTheme]) ? themes[editorTheme] : themes[editorTheme] || themes['default'];
|
||||||
|
|
||||||
@@ -296,7 +178,7 @@ const CodeEditor = forwardRef(
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
//highlights
|
//highlights
|
||||||
highlightCompartment.of([customHighlightPlugin, highlightExtension]),
|
highlightCompartment.of([customHighlightPlugin(renderer, tab), highlightExtension]),
|
||||||
themeCompartment.of(themeExtension),
|
themeCompartment.of(themeExtension),
|
||||||
highlightActiveLine(),
|
highlightActiveLine(),
|
||||||
highlightActiveLineGutter(),
|
highlightActiveLineGutter(),
|
||||||
@@ -437,10 +319,8 @@ const CodeEditor = forwardRef(
|
|||||||
? syntaxHighlighting(customHighlightStyle)
|
? syntaxHighlighting(customHighlightStyle)
|
||||||
: syntaxHighlighting(legacyCustomHighlightStyle);
|
: syntaxHighlighting(legacyCustomHighlightStyle);
|
||||||
|
|
||||||
const customHighlightPlugin = createHighlightPlugin(renderer, tab);
|
|
||||||
|
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
effects : highlightCompartment.reconfigure([customHighlightPlugin, highlightExtension]),
|
effects : highlightCompartment.reconfigure([customHighlightPlugin(renderer, tab), highlightExtension]),
|
||||||
});
|
});
|
||||||
}, [renderer, tab]);
|
}, [renderer, tab]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
/* eslint max-lines: ["error", { "max": 450 }] */
|
||||||
import { HighlightStyle } from '@codemirror/language';
|
import { HighlightStyle } from '@codemirror/language';
|
||||||
import { tags } from '@lezer/highlight';
|
import { tags } from '@lezer/highlight';
|
||||||
|
import { legacyTokenizeCustomMarkdown } from './legacyCustomHighlight';
|
||||||
|
import {
|
||||||
|
Decoration,
|
||||||
|
ViewPlugin,
|
||||||
|
} from '@codemirror/view';
|
||||||
|
import {
|
||||||
|
syntaxTree,
|
||||||
|
ensureSyntaxTree
|
||||||
|
} from '@codemirror/language';
|
||||||
|
|
||||||
// Making the tokens
|
// Making the tokens
|
||||||
const customTags = {
|
const customTags = {
|
||||||
@@ -23,7 +33,7 @@ const customTags = {
|
|||||||
variable : 'variable',
|
variable : 'variable',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function tokenizeCustomMarkdown(text) {
|
function tokenizeCustomMarkdown(text) {
|
||||||
const tokens = [];
|
const tokens = [];
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
|
|
||||||
@@ -86,8 +96,8 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
if(/\~/.test(lineText)) {
|
if(/\~/.test(lineText)) {
|
||||||
const strikethroughRegex = /~(?!\s)(.+?)(?<!\s)~/g;
|
const strikethroughRegex = /~(?!\s)(.+?)(?<!\s)~/g;
|
||||||
|
|
||||||
let match = strikethroughRegex.exec(lineText);
|
const match = strikethroughRegex.exec(lineText);
|
||||||
let type = customTags.strikethrough;
|
const type = customTags.strikethrough;
|
||||||
|
|
||||||
if(match) {
|
if(match) {
|
||||||
tokens.push({
|
tokens.push({
|
||||||
@@ -149,7 +159,6 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
if(!/^::/.test(lines[lineNumber]) && lineNumber + 1 < lines.length && /^::/.test(lines[lineNumber + 1])) {
|
if(!/^::/.test(lines[lineNumber]) && lineNumber + 1 < lines.length && /^::/.test(lines[lineNumber + 1])) {
|
||||||
const startLine = lineNumber;
|
const startLine = lineNumber;
|
||||||
const defs = [];
|
const defs = [];
|
||||||
let endLine = startLine;
|
|
||||||
|
|
||||||
// collect all following :: definitions
|
// collect all following :: definitions
|
||||||
for (let i = lineNumber + 1; i < lines.length; i++) {
|
for (let i = lineNumber + 1; i < lines.length; i++) {
|
||||||
@@ -158,7 +167,6 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
const defMatch = /^(::)(.+)$/.exec(nextLine);
|
const defMatch = /^(::)(.+)$/.exec(nextLine);
|
||||||
if(!onlyColonsMatch && defMatch) {
|
if(!onlyColonsMatch && defMatch) {
|
||||||
defs.push({ colons: defMatch[1], desc: defMatch[2], line: i });
|
defs.push({ colons: defMatch[1], desc: defMatch[2], line: i });
|
||||||
endLine = i;
|
|
||||||
} else break;
|
} else break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +210,7 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
if(lineText.includes('{') && lineText.includes('}')) {
|
if(lineText.includes('{') && lineText.includes('}')) {
|
||||||
const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gmd;
|
const injectionRegex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gmd;
|
||||||
let match;
|
let match;
|
||||||
|
|
||||||
while ((match = injectionRegex.exec(lineText)) !== null) {
|
while ((match = injectionRegex.exec(lineText)) !== null) {
|
||||||
tokens.push({
|
tokens.push({
|
||||||
line : lineNumber,
|
line : lineNumber,
|
||||||
@@ -249,7 +257,7 @@ export function tokenizeCustomMarkdown(text) {
|
|||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenizeCustomCSS(text) {
|
function tokenizeCustomCSS(text) {
|
||||||
const tokens = [];
|
const tokens = [];
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
|
|
||||||
@@ -309,4 +317,114 @@ export const customHighlightStyle = HighlightStyle.define([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
function getUrl(node, doc) {
|
||||||
|
let url = null;
|
||||||
|
|
||||||
|
const cursor = node.node.cursor();
|
||||||
|
|
||||||
|
if(cursor.firstChild()) {
|
||||||
|
do {
|
||||||
|
if(cursor.name === 'URL') {
|
||||||
|
url = doc.sliceString(cursor.from, cursor.to);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (cursor.nextSibling());
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
export function customHighlightPlugin(renderer, tab) {
|
||||||
|
//this function takes the custom tokens created in the tokenize function in customhighlight files
|
||||||
|
//takes the tokens defined by that function and assigns classes to them
|
||||||
|
//it also creates page number and snippet number widgets
|
||||||
|
|
||||||
|
let tokenize;
|
||||||
|
|
||||||
|
if(tab === 'brewStyles') {
|
||||||
|
tokenize = tokenizeCustomCSS;
|
||||||
|
} else {
|
||||||
|
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());
|
||||||
|
let pageCount = 1;
|
||||||
|
let snippetCount = 0;
|
||||||
|
|
||||||
|
const tree = ensureSyntaxTree(view.state, view.state.doc.length, 50) || syntaxTree(view.state);
|
||||||
|
tree.iterate({
|
||||||
|
enter : (node)=>{
|
||||||
|
if(node.name === 'Image') {
|
||||||
|
const url = getUrl(node, view.state.doc);
|
||||||
|
|
||||||
|
if(!url) return;
|
||||||
|
|
||||||
|
decos.push(
|
||||||
|
Decoration.mark({
|
||||||
|
class : 'cm-image',
|
||||||
|
attributes : {
|
||||||
|
'style' : `--preview-img:url(${url});`
|
||||||
|
}
|
||||||
|
}).range(node.from, node.to)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tokens.forEach((token)=>{
|
||||||
|
const line = view.state.doc.line(token.line + 1);
|
||||||
|
|
||||||
|
if(token.from != null && token.to != null && token.from < token.to) {
|
||||||
|
const from = line.from + token.from;
|
||||||
|
const to = line.from + token.to;
|
||||||
|
|
||||||
|
const attrs = {};
|
||||||
|
if(token.type === 'Image' && token.url) {
|
||||||
|
|
||||||
|
attrs['data-url'] = token.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
decos.push(
|
||||||
|
Decoration.mark({
|
||||||
|
class : `cm-${token.type}`,
|
||||||
|
...(Object.keys(attrs).length
|
||||||
|
? { attributes: attrs }
|
||||||
|
: {})
|
||||||
|
}).range(from, to)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
decos.push(
|
||||||
|
Decoration.line({
|
||||||
|
class : `cm-${token.type}`
|
||||||
|
}).range(line.from)
|
||||||
|
);
|
||||||
|
if(token.type === 'pageLine' && tab === 'brewText') {
|
||||||
|
pageCount++;
|
||||||
|
if(line.from === 0) pageCount--;
|
||||||
|
decos.push(Decoration.line({ attributes: { 'data-page-number': pageCount } }).range(line.from));
|
||||||
|
}
|
||||||
|
if(token.type === 'snippetLine' && tab === 'brewSnippets') {
|
||||||
|
snippetCount++;
|
||||||
|
decos.push(Decoration.line({ attributes: { 'data-page-number': snippetCount } }).range(line.from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
decos.sort((a, b)=>a.from - b.from || a.to - b.to);
|
||||||
|
return Decoration.set(decos);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ decorations: (v)=>v.decorations }
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user