grafana/public/app/features/explore/slate-plugins/prism/index.tsx
2020-10-20 07:35:45 -07:00

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;
},
};
}