From face5b1890d9d89aa988c5267407c1918bff14c6 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 14 Sep 2018 16:32:54 +0200 Subject: [PATCH] Explore: Add multiline syntax highlighting to query field - the non-nested query field schema did not allow for multi-line highlighting - added explicit code schema and a `makeValue` function that enforces the nested structure - replaced vendored prism-slate adapter with official slate-prism package - renamed language to syntax --- package.json | 1 + .../app/containers/Explore/PromQueryField.tsx | 21 ++- public/app/containers/Explore/QueryField.tsx | 28 +--- public/app/containers/Explore/Value.ts | 41 ++++++ .../Explore/slate-plugins/prism/index.tsx | 123 ------------------ yarn.lock | 8 +- 6 files changed, 73 insertions(+), 149 deletions(-) create mode 100644 public/app/containers/Explore/Value.ts delete mode 100644 public/app/containers/Explore/slate-plugins/prism/index.tsx diff --git a/package.json b/package.json index 071d32992af..d3f2518197b 100644 --- a/package.json +++ b/package.json @@ -173,6 +173,7 @@ "rxjs": "^5.4.3", "slate": "^0.33.4", "slate-plain-serializer": "^0.5.10", + "slate-prism": "^0.5.0", "slate-react": "^0.12.4", "tether": "^1.4.0", "tether-drop": "https://github.com/torkelo/drop/tarball/master", diff --git a/public/app/containers/Explore/PromQueryField.tsx b/public/app/containers/Explore/PromQueryField.tsx index 0991f08429a..491e7005bd0 100644 --- a/public/app/containers/Explore/PromQueryField.tsx +++ b/public/app/containers/Explore/PromQueryField.tsx @@ -3,10 +3,11 @@ import moment from 'moment'; import React from 'react'; import { Value } from 'slate'; import Cascader from 'rc-cascader'; +import PluginPrism from 'slate-prism'; +import Prism from 'prismjs'; // dom also includes Element polyfills import { getNextCharacter, getPreviousCousin } from './utils/dom'; -import PluginPrism, { setPrismTokens } from './slate-plugins/prism/index'; import PrismPromql, { FUNCTIONS } from './slate-plugins/prism/promql'; import BracesPlugin from './slate-plugins/braces'; import RunnerPlugin from './slate-plugins/runner'; @@ -27,7 +28,7 @@ const HISTOGRAM_SELECTOR = '{le!=""}'; // Returns all timeseries for histograms const HISTORY_ITEM_COUNT = 5; const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h const METRIC_MARK = 'metric'; -const PRISM_LANGUAGE = 'promql'; +const PRISM_SYNTAX = 'promql'; export const RECORDING_RULES_GROUP = '__recording_rules__'; export const wrapLabel = (label: string) => ({ label }); @@ -36,6 +37,15 @@ export const setFunctionMove = (suggestion: Suggestion): Suggestion => { return suggestion; }; +// Syntax highlighting +Prism.languages[PRISM_SYNTAX] = PrismPromql; +function setPrismTokens(language, field, values, alias = 'variable') { + Prism.languages[language][field] = { + alias, + pattern: new RegExp(`(?:^|\\s)(${values.join('|')})(?:$|\\s)`), + }; +} + export function addHistoryMetadata(item: Suggestion, history: any[]): Suggestion { const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF; const historyForItem = history.filter(h => h.ts > cutoffTs && h.query === item.label); @@ -164,7 +174,10 @@ class PromQueryField extends React.Component node.type === 'code_block', + getSyntax: node => 'promql', + }), ]; this.state = { @@ -221,7 +234,7 @@ class PromQueryField extends React.Component { diff --git a/public/app/containers/Explore/QueryField.tsx b/public/app/containers/Explore/QueryField.tsx index 52bfbc7fed4..13364729e7e 100644 --- a/public/app/containers/Explore/QueryField.tsx +++ b/public/app/containers/Explore/QueryField.tsx @@ -1,7 +1,7 @@ import _ from 'lodash'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Block, Change, Document, Text, Value } from 'slate'; +import { Change, Value } from 'slate'; import { Editor } from 'slate-react'; import Plain from 'slate-plain-serializer'; @@ -9,6 +9,7 @@ import ClearPlugin from './slate-plugins/clear'; import NewlinePlugin from './slate-plugins/newline'; import Typeahead from './Typeahead'; +import { makeFragment, makeValue } from './Value'; export const TYPEAHEAD_DEBOUNCE = 300; @@ -16,22 +17,6 @@ function flattenSuggestions(s: any[]): any[] { return s ? s.reduce((acc, g) => acc.concat(g.items), []) : []; } -export const makeFragment = (text: string): Document => { - const lines = text.split('\n').map(line => - Block.create({ - type: 'paragraph', - nodes: [Text.create(line)], - }) - ); - - const fragment = Document.create({ - nodes: lines, - }); - return fragment; -}; - -export const getInitialValue = (value: string): Value => Value.create({ document: makeFragment(value) }); - export interface Suggestion { /** * The label of this completion item. By default @@ -113,6 +98,7 @@ interface TypeaheadFieldProps { onWillApplySuggestion?: (suggestion: string, state: TypeaheadFieldState) => string; placeholder?: string; portalPrefix?: string; + syntax?: string; } export interface TypeaheadFieldState { @@ -156,7 +142,7 @@ class QueryField extends React.Component { + const lines = text.split('\n').map(line => + Block.create({ + type: 'code_line', + nodes: [Text.create(line)], + }) + ); + + const block = Block.create({ + data: { + syntax, + }, + type: 'code_block', + nodes: lines, + }); + + return Document.create({ + nodes: [block], + }); +}; + +export const makeValue = (text: string, syntax?: string) => { + const fragment = makeFragment(text, syntax); + + return Value.create({ + document: fragment, + SCHEMA, + }); +}; diff --git a/public/app/containers/Explore/slate-plugins/prism/index.tsx b/public/app/containers/Explore/slate-plugins/prism/index.tsx deleted file mode 100644 index d185518790f..00000000000 --- a/public/app/containers/Explore/slate-plugins/prism/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import Prism from 'prismjs'; - -const TOKEN_MARK = 'prism-token'; - -export function setPrismTokens(language, field, values, 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 }) { - if (definition) { - // Don't override exising modified definitions - Prism.languages[language] = Prism.languages[language] || definition; - } - - return { - /** - * Render a Slate mark with appropiate CSS class names - * - * @param {Object} props - * @return {Element} - */ - - renderMark(props) { - const { children, mark } = props; - // Only apply spans to marks identified by this plugin - if (mark.type !== TOKEN_MARK) { - return undefined; - } - const className = `token ${mark.data.get('types')}`; - return {children}; - }, - - /** - * Decorate code blocks with Prism.js highlighting. - * - * @param {Node} node - * @return {Array} - */ - - decorateNode(node) { - if (node.type !== 'paragraph') { - return []; - } - - const texts = node.getTexts().toArray(); - const tstring = texts.map(t => t.text).join('\n'); - const grammar = Prism.languages[language]; - const tokens = Prism.tokenize(tstring, grammar); - const decorations = []; - let startText = texts.shift(); - let endText = startText; - let startOffset = 0; - let endOffset = 0; - let start = 0; - - function processToken(token, acc?) { - // 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 = { - anchorKey: startText.key, - anchorOffset: startOffset, - focusKey: endText.key, - focusOffset: endOffset, - marks: [{ 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; - }, - }; -} diff --git a/yarn.lock b/yarn.lock index 2b98ff32766..c0619897123 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9322,7 +9322,7 @@ pretty-format@^23.6.0: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -prismjs@^1.6.0: +prismjs@^1.13.0, prismjs@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9" optionalDependencies: @@ -10736,6 +10736,12 @@ slate-plain-serializer@^0.5.10, slate-plain-serializer@^0.5.17: dependencies: slate-dev-logger "^0.1.43" +slate-prism@^0.5.0: + version "0.5.0" + resolved "http://registry.npmjs.org/slate-prism/-/slate-prism-0.5.0.tgz#009eb74fea38ad76c64db67def7ea0884917adec" + dependencies: + prismjs "^1.13.0" + slate-prop-types@^0.4.34: version "0.4.61" resolved "https://registry.yarnpkg.com/slate-prop-types/-/slate-prop-types-0.4.61.tgz#141c109bed81b130dd03ab86dd7541b28d6d962a"