mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
131 lines
3.8 KiB
TypeScript
131 lines
3.8 KiB
TypeScript
import React from 'react';
|
|
import Prism from 'prismjs';
|
|
import { Decoration } from 'slate';
|
|
import { Editor } from '@grafana/slate-react';
|
|
|
|
const TOKEN_MARK = 'prism-token';
|
|
|
|
export function setPrismTokens(language: string, field: string | number, values: any, alias = 'variable') {
|
|
Prism.languages[language][field] = {
|
|
alias,
|
|
pattern: new RegExp(`(?:^|\\s)(${values.join('|')})(?:$|\\s)`),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Code-highlighting plugin based on Prism and
|
|
* https://github.com/ianstormtaylor/slate/blob/master/examples/code-highlighting/index.js
|
|
*
|
|
* (Adapted to handle nested grammar definitions.)
|
|
*/
|
|
|
|
export default function PrismPlugin({ definition, language }: { definition: any; language: string }) {
|
|
if (definition) {
|
|
// Don't override exising modified definitions
|
|
Prism.languages[language] = Prism.languages[language] || definition;
|
|
}
|
|
|
|
return {
|
|
/**
|
|
* Render a Slate mark with appropriate CSS class names
|
|
*
|
|
* @param {Object} props
|
|
* @returns {Element}
|
|
*/
|
|
|
|
renderDecoration(props: any, editor: Editor, next: () => any): JSX.Element {
|
|
const { children, decoration } = props;
|
|
// Only apply spans to marks identified by this plugin
|
|
if (decoration.type !== TOKEN_MARK) {
|
|
return next();
|
|
}
|
|
const className = `token ${decoration.data.get('types')}`;
|
|
return <span className={className}>{children}</span>;
|
|
},
|
|
|
|
/**
|
|
* Decorate code blocks with Prism.js highlighting.
|
|
*
|
|
* @param {Node} node
|
|
* @returns {Array}
|
|
*/
|
|
|
|
decorateNode(node: any, editor: Editor, next: () => any): any[] {
|
|
if (node.type !== 'paragraph') {
|
|
return [];
|
|
}
|
|
|
|
const texts = node.getTexts().toArray();
|
|
const tstring = texts.map((t: { text: any }) => t.text).join('\n');
|
|
const grammar = Prism.languages[language];
|
|
const tokens = Prism.tokenize(tstring, grammar);
|
|
const decorations: Decoration[] = [];
|
|
let startText = texts.shift();
|
|
let endText = startText;
|
|
let startOffset = 0;
|
|
let endOffset = 0;
|
|
let start = 0;
|
|
|
|
function processToken(token: any, acc?: string) {
|
|
// Accumulate token types down the tree
|
|
const types = `${acc || ''} ${token.type || ''} ${token.alias || ''}`;
|
|
|
|
// Add mark for token node
|
|
if (typeof token === 'string' || typeof token.content === 'string') {
|
|
startText = endText;
|
|
startOffset = endOffset;
|
|
|
|
const content = typeof token === 'string' ? token : token.content;
|
|
const newlines = content.split('\n').length - 1;
|
|
const length = content.length - newlines;
|
|
const end = start + length;
|
|
|
|
let available = startText.text.length - startOffset;
|
|
let remaining = length;
|
|
|
|
endOffset = startOffset + remaining;
|
|
|
|
while (available < remaining) {
|
|
endText = texts.shift();
|
|
remaining = length - available;
|
|
available = endText.text.length;
|
|
endOffset = remaining;
|
|
}
|
|
|
|
// Inject marks from up the tree (acc) as well
|
|
if (typeof token !== 'string' || acc) {
|
|
const range = node.createDecoration({
|
|
anchor: {
|
|
key: startText.key,
|
|
offset: startOffset,
|
|
},
|
|
focus: {
|
|
key: endText.key,
|
|
offset: endOffset,
|
|
},
|
|
type: TOKEN_MARK,
|
|
data: { types },
|
|
});
|
|
|
|
decorations.push(range);
|
|
}
|
|
|
|
start = end;
|
|
} else if (token.content && token.content.length) {
|
|
// Tokens can be nested
|
|
for (const subToken of token.content) {
|
|
processToken(subToken, types);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process top-level tokens
|
|
for (const token of tokens) {
|
|
processToken(token);
|
|
}
|
|
|
|
return decorations;
|
|
},
|
|
};
|
|
}
|