LanguageService
Defined in: packages/engine/src/language/LanguageService.ts:155
Editor-agnostic “language server” for solve expressions: turns a line of
text into semantic token ranges, using the exact same lexer real
evaluation uses (so it only ever classifies what the engine’s grammar
actually recognizes, never a separate/duplicated tokenizer). No
knowledge of CSS, CodeMirror, VS Code, or any other rendering concept
lives here. See language/adapters/ for that.
Classification happens at the LEXER stage, before the normalizer runs
(normalization, phrase fusion, implicit multiply, and package-specific
rules, happens later, only on the real evaluation path). A package’s
lexer-level custom token types (e.g. a custom keyword) are recognized
here exactly as evaluation would see them. A package’s normalizer-fused
synthetic tokens (e.g. OSRS’s GAME_ITEM, built by fusing several
consecutive IDENT tokens against an item-name trie) are NOT. This
service still shows the pre-fusion IDENT tokens individually for those.
IEnginePackage.tokenCategories entries for normalizer-only token types
are still valid, correct registrations (queryable via getTokenCategory)
, they just won’t currently be reachable through this lexer-only
classification path. Folding normalization in would require running it
per keystroke on the highlighting path too, which needs its own careful
design (span recomputation for fused multi-token ranges, in particular)
rather than a quick addition here.
Lexing alone is NOT sufficient to decide “recognized”, though: a run of
plain-English words (“My name is ron”) lexes into a sequence of
individually-valid IDENT tokens with no grammar tying them together
every word “recognized” at the token level, but the line as a whole is
not something the engine would ever accept as an expression. Surfacing
per-token colors for that case looks like the editor mistook prose for
code. So a line’s tokens are only surfaced once the line as a whole
parses successfully (via ExpressionEngine.compileExpression, the same
parse pipeline, and the same bytecode cache, real evaluation uses; no
separate/duplicated grammar check). A single bare word (“hello”, a valid
variable reference) or a keyword-only line (“pi”) still parses and still
highlights, only genuinely ungrammatical text is suppressed, unless it’s
a known variable elsewhere in the document (see variableNameSource).
getCompletions() is the other half of this “language server”: unlike
getSemanticTokens(), it’s explicitly for incomplete, mid-typing text
, it deliberately does NOT gate on parse validity (a half-typed
expression almost never parses), using simple prefix matching instead.
Must be constructed with an already-configured ExpressionEngine (one
with all currently-relevant packages registered) rather than a bare
lexer, reusing an existing engine is both the fast path (no throwaway
lexer construction) and the correct one: a highlighting-only lexer
built independently of the evaluation engine would silently fail to
recognize plugin-contributed tokens (e.g. a package’s custom keywords)
unless it happened to have the identical packages registered.
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new LanguageService(engine?, options?): LanguageService;Defined in: packages/engine/src/language/LanguageService.ts:197
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
engine? | | ExpressionEngine | null |
options? | LanguageServiceOptions |
Returns
Section titled “Returns”LanguageService
Methods
Section titled “Methods”getCompletions()
Section titled “getCompletions()”getCompletions(lineText, cursorOffset): CompletionItem[];Defined in: packages/engine/src/language/LanguageService.ts:409
Completion candidates for the identifier prefix immediately before
cursorOffset on lineText. Deliberately simple prefix matching, not
parser-driven “what’s grammatically valid here” prediction, a
half-typed expression almost never parses, so gating on parse
validity (the way getSemanticTokens does) would suppress
completions almost always. This is the safest, fastest option that
still delivers real value.
Candidates come from three sources: keywords (which already include
function names. See ExpressionLexer.getKeywords()’s doc comment)
and units, both static per engine configuration and cached lazily;
package-contributed items (IEnginePackage.completionItems), same
cache; and variable names, read fresh from variableNameSource() on
every call since those change on every edit.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
lineText | string |
cursorOffset | number |
Returns
Section titled “Returns”getSemanticTokens()
Section titled “getSemanticTokens()”getSemanticTokens(lineText, lineNumber): SemanticToken[];Defined in: packages/engine/src/language/LanguageService.ts:309
Classify every recognized token on one line.
Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
lineText | string | The raw line text (may be a markdown-structural line the engine’s classifier skips, that’s handled by the underlying lexer, which returns no tokens for those). |
lineNumber | number | 1-based line number, used purely as a cache key. |
Returns
Section titled “Returns”invalidateCache()
Section titled “invalidateCache()”invalidateCache(): void;Defined in: packages/engine/src/language/LanguageService.ts:546
Full cache clear. Reserved for cases with no meaningful “which lines changed” (e.g. the document was swapped wholesale, or a package was registered/unregistered mid-session, changing what categories exist). Prefer invalidateLines for ordinary edits. Also rebuilds the lazily-cached keyword/unit/package-item completion candidates on next use, the only thing that can change that list mid-session.
Returns
Section titled “Returns”void
invalidateLines()
Section titled “invalidateLines()”invalidateLines(lineNumbers): void;Defined in: packages/engine/src/language/LanguageService.ts:532
Evict specific lines (e.g. the lines actually touched by a CodeMirror change set) instead of the whole cache, the surgical counterpart to invalidateCache, letting a single-line edit stay cheap even in a large document: every other cached line is untouched and still hits on the next call.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
lineNumbers | Iterable<number> |
Returns
Section titled “Returns”void