Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | 1x 1x 1x 259x 259x 6x 259x 5x 259x 259x 259x 259x 259x 4x 259x 259x 259x 259x 259x 49x 259x 5x 259x 71x 259x 259x 259x 259x 259x 259x 259x 259x 259x 26x 259x 259x 259x 259x 259x 259x 259x 259x 259x 11x 259x 259x 259x 32x 259x 259x 259x 259x 259x 259x 259x 259x 259x 259x 259x 259x 259x 45x 259x 259x 259x 259x 3x 259x 1x 259x 1x 259x 259x 1x 141x 141x 1x 143x 143x 143x 143x 143x 143x 258x 79x 79x 79x 79x 79x 2x 2x 2x 2x 2x 77x 77x 79x 246x 246x 246x 70x 70x 27x 27x 27x 27x 27x 27x 27x 70x 7x 7x 7x 7x 7x 7x 7x 70x 212x 212x 77x 257x 246x 246x 242x 242x 242x 242x 4x 4x 14x 14x 14x 258x 143x 143x | /**
* SpEL grammar parser for CodeMirror 6 — StreamLanguage adapter.
*
* Uses @agentix-e/spel-ts Tokenizer for precise token-level parsing
* mapped to CM6 StreamParser tokens. Provides accurate syntax highlighting
* matching spel-ts v1.1.0 lexer behavior without a full Lezer grammar build step.
*
* For incremental parsing and richer editor features, a full Lezer grammar
* can be added later via @lezer/generator build pipeline.
*/
import { type StringStream, StreamLanguage } from '@codemirror/language';
import { Tokenizer as SpelTokenizer, TokenKind } from '@agentix-e/spel-ts';
/**
* Map spel-ts TokenKind to CM6 highlight style.
* Exported for testing.
*/
export function tokenKindToStyle(kind: TokenKind): string {
switch (kind) {
// Keywords
case TokenKind.LITERAL_NULL:
return 'keyword';
case TokenKind.LITERAL_BOOLEAN:
return 'bool';
case TokenKind.MATCHES:
case TokenKind.BETWEEN:
case TokenKind.INSTANCEOF:
case TokenKind.MOD:
case TokenKind.NEW:
return 'keyword';
// Literals
case TokenKind.LITERAL_INT:
case TokenKind.LITERAL_LONG:
case TokenKind.LITERAL_FLOAT:
case TokenKind.LITERAL_DOUBLE:
case TokenKind.LITERAL_HEX:
return 'number';
case TokenKind.LITERAL_STRING:
return 'string';
// Variables
case TokenKind.IDENTIFIER:
// Could be a variable reference if preceded by #, handled below
return 'variableName';
// Operators
case TokenKind.PLUS:
case TokenKind.MINUS:
case TokenKind.STAR:
case TokenKind.SLASH:
case TokenKind.PERCENT:
case TokenKind.POWER:
case TokenKind.INC:
case TokenKind.DEC:
case TokenKind.ASSIGN:
return 'operator';
// Comparison
case TokenKind.EQ:
case TokenKind.NE:
case TokenKind.LT:
case TokenKind.LE:
case TokenKind.GT:
case TokenKind.GE:
case TokenKind.AND:
case TokenKind.OR:
case TokenKind.NOT:
return 'operator';
// Type/Bean references
case TokenKind.HASH:
case TokenKind.AT:
case TokenKind.AMP_AT:
return 'typeName';
// Punctuation
case TokenKind.LPAREN:
case TokenKind.RPAREN:
case TokenKind.LBRACKET:
case TokenKind.RBRACKET:
case TokenKind.LBRACE:
case TokenKind.RBRACE:
case TokenKind.COMMA:
case TokenKind.COLON:
case TokenKind.DOT:
case TokenKind.SAFE_NAV:
case TokenKind.QMARK:
case TokenKind.ELVIS:
case TokenKind.DOTDOT:
return 'punctuation';
// Selection/Projection
case TokenKind.PROJECTION:
case TokenKind.SELECTION:
case TokenKind.SELECT_FIRST:
case TokenKind.SELECT_LAST:
return 'operatorKeyword';
// Type
case TokenKind.TYPE_START:
return 'typeName';
default:
return '';
}
}
/** Create a StreamLanguage-based SpEL language for CodeMirror 6 */
export function createSpelStreamParser() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return StreamLanguage.define(createTokenParser() as any);
}
/**
* Create the token parser spec for StreamLanguage.define.
* Exported for testing — allows direct invocation of the token function.
*/
export function createTokenParser(): {
startState: () => null;
token: (stream: StringStream) => string | null;
} {
let _tokenizer: SpelTokenizer | null = null;
let _tokens: Array<{ from: number; to: number; style: string }> = [];
let _tokenIndex = 0;
const startState = (): null => null;
const token = (stream: StringStream): string | null => {
// Tokenize entire input on first call
if (!_tokenizer || _tokenIndex === 0) {
_tokenizer = new SpelTokenizer(stream.string);
let rawTokens: ReturnType<SpelTokenizer['tokenize']>;
try {
rawTokens = _tokenizer.tokenize();
} catch {
// Tokenizer threw on invalid input (e.g. unterminated string) —
// treat entire content as unhighlighted plain text
_tokens = [];
_tokenIndex = 0;
stream.skipToEnd();
return null;
}
_tokens = [];
_tokenIndex = 0;
for (let i = 0; i < rawTokens.length - 1; i++) {
// Skip EOF
const tok = rawTokens[i]!;
const style = tokenKindToStyle(tok.kind);
// Handle special cases
if (tok.kind === TokenKind.IDENTIFIER) {
const prevTok = i > 0 ? rawTokens[i - 1] : null;
if (prevTok?.kind === TokenKind.HASH) {
// #variableName — highlight the variable name
_tokens.push({
from: tok.startPos,
to: tok.endPos,
style: 'variableName',
});
continue;
}
if (prevTok?.kind === TokenKind.DOT || prevTok?.kind === TokenKind.SAFE_NAV) {
// .property or ?.property
_tokens.push({
from: tok.startPos,
to: tok.endPos,
style: 'propertyName',
});
continue;
}
}
_tokens.push({ from: tok.startPos, to: tok.endPos, style });
}
}
// Return tokens in order
while (_tokenIndex < _tokens.length) {
const t = _tokens[_tokenIndex]!;
if (t.from >= stream.pos) {
stream.pos = t.to;
_tokenIndex++;
return t.style || null;
}
_tokenIndex++;
}
// Skip to end
stream.skipToEnd();
_tokenIndex = 0; // Reset for next parse
return null;
};
return { startState, token };
}
|