D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
alerting
/
unified
/
components
/
Filename :
Tokenize.tsx
back
Copy
import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Badge, useStyles2 } from '@grafana/ui'; import { HoverCard } from './HoverCard'; import { keywords as KEYWORDS, builtinFunctions as FUNCTIONS } from './receivers/editor/language'; const VARIABLES = ['$', '.', '"']; interface TokenizerProps { input: string; delimiter?: [string, string]; } function Tokenize({ input, delimiter = ['{{', '}}'] }: TokenizerProps) { const styles = useStyles2(getStyles); const [open, close] = delimiter; /** * This RegExp uses 2 named capture groups, text that comes before the token and the token itself * * <before> open <token> close * ───────── ── ─────────── ── * Some text {{ $labels.foo }} */ const regex = new RegExp(`(?<before>.*?)(${open}(?<token>.*?)${close}|$)`, 'gm'); const lines = input.split('\n'); const output: React.ReactElement[] = []; lines.forEach((line, lineIndex) => { const matches = Array.from(line.matchAll(regex)); matches.forEach((match, matchIndex) => { const before = match.groups?.before; const token = match.groups?.token?.trim(); if (before) { output.push(<span key={`${lineIndex}-${matchIndex}-before`}>{before}</span>); } if (token) { const type = tokenType(token); const description = type === TokenType.Variable ? token : ''; const tokenContent = `${open} ${token} ${close}`; output.push( <Token key={`${lineIndex}-${matchIndex}-token`} content={tokenContent} type={type} description={description} /> ); } }); output.push(<br key={`${lineIndex}-newline`} />); }); return <span className={styles.wrapper}>{output}</span>; } enum TokenType { Variable = 'variable', Function = 'function', Keyword = 'keyword', Unknown = 'unknown', } interface TokenProps { content: string; type?: TokenType; description?: string; } function Token({ content, description, type }: TokenProps) { const styles = useStyles2(getStyles); const disableCard = Boolean(type) === false; return ( <HoverCard placement="top-start" disabled={disableCard} content={ <div className={styles.hoverTokenItem}> <Badge tabIndex={0} text={<>{type}</>} color={'blue'} /> {description && <code>{description}</code>} </div> } > <span> <Badge tabIndex={0} className={styles.token} text={content} color={'blue'} /> </span> </HoverCard> ); } function isVariable(input: string) { return VARIABLES.some((character) => input.startsWith(character)); } function isKeyword(input: string) { return KEYWORDS.some((keyword) => input.startsWith(keyword)); } function isFunction(input: string) { return FUNCTIONS.some((functionName) => input.startsWith(functionName)); } function tokenType(input: string) { let tokenType; if (isVariable(input)) { tokenType = TokenType.Variable; } else if (isKeyword(input)) { tokenType = TokenType.Keyword; } else if (isFunction(input)) { tokenType = TokenType.Function; } else { tokenType = TokenType.Unknown; } return tokenType; } const getStyles = (theme: GrafanaTheme2) => ({ wrapper: css` white-space: pre-wrap; `, token: css` cursor: default; font-family: ${theme.typography.fontFamilyMonospace}; `, popover: css` border-radius: ${theme.shape.borderRadius()}; box-shadow: ${theme.shadows.z3}; background: ${theme.colors.background.primary}; border: 1px solid ${theme.colors.border.medium}; padding: ${theme.spacing(1)}; `, hoverTokenItem: css` display: flex; flex-direction: row; align-items: center; gap: ${theme.spacing(1)}; `, }); export { Tokenize, Token };