Editor support for .guitkx, the JSX-like markup of Reactive UI Toolkit — Godot — a React-style reactive UI library for Godot 4.x.
Features
- Syntax highlighting for the JSX-like markup and the embedded GDScript, via a self-contained TextMate grammar.
- Markup IntelliSense — completion and hover for host-element tags, structural/common attributes, control-flow directives (
@if/@elif/@else/@for/@while/@match/@case/@default), and per-element event handlers. - Embedded-GDScript IntelliSense — completion, hover, and go-to-definition inside
{expr}, setup, and control-flow conditions, analyzed in-process by the headless@gdscript-analyzer/coreso you get realV.*/Hooks.*/ Godot-API intelligence — no running Godot editor required, fully offline. - Diagnostics for structural problems.
Requirements
- None — the language server ships with the extension (a Node.js runtime is bundled). Embedded-GDScript intelligence runs offline; no Godot editor, no TCP/language-server connection required.
Changelog
[0.13.0] - 2026-07-28
- Reactive UI Toolkit rebrand (rides the library 0.13.0 — see MIGRATION-0.13.md): the repository is now github.com/reactive-ui-toolkit/ruitk-godot (listing links, READMEs, and the analyzer download URLs all point at the org; old GitHub URLs redirect), the product label in the descriptions is "Reactive UI Toolkit — Godot", and the bundled license is retitled Reactive UI Toolkit Community License 1.1 — terms unchanged except the binding Attribution clause, whose required credit line follows the product name and is now "Made with Reactive UI Toolkit" (or "Reactive UI Toolkit — Godot"): update an existing "Made with ReactiveUI" credits line when you upgrade. The language surface follows the library's class rename: the component classifier is
-> RuitkVNode, and the schema, TextMate grammar, virtual-document stubs, and language server all classify theRuitk*spellings. Extension IDs, display names, and theguitkxlanguage identity are unchanged — nothing to reinstall or reconfigure; run the library's one-command codemod in your project and the extensions pick the new spellings up.
[0.12.1] - 2026-07-25
- Null-only components (library 0.12.1, React semantics): a component whose body has a top-level
return nulland no markup return is now legal — it renders nothing. The live GUITKX2101 missing-return check exempts the shape (a nested-only guard with a fall-through path is still flagged), and Format Document re-emits such a body verbatim instead of falling back unformatted (it previously bailed; it never corrupted). Embedded-GDScript intelligence inside null-only bodies already worked and is unchanged.
[0.12.0] - 2026-07-18
- ES combined import forms (library 0.12.0):
import Def, { a, b as c } from "./file"andimport Def, * as X from "./file"— one declaration carrying the default binding plus the named/namespace surface. Every formerly-exclusive consumer now sees every part: click-through navigation, rename through import clauses, the virtual-doc import stubs (a combined form's bindings had no declaration in the embedded analysis), the TextMate grammar, and semantic tokens. - Import-brace completion: Ctrl+Space inside
import { | } from "./file"offers the target's exported declarations (kind-tagged), excluding names already listed. Works inside the combinedimport Def, { | }form too, where the default binding and the export it already binds are excluded as noise. - Imported bindings now color by the KIND of the export they bind — components like component tags, hooks/utils as functions, values as variables (star aliases as variables; a default alias takes its target declaration's kind) — resolved from each import's target file. Semantic tokens override the grammar's uniform type tint in every LSP client; unresolvable targets degrade silently.
- The analyzer's virtual mirror of a peer
.guitkx(used when the generated sibling.gdis not on disk) now declares NEW-MODE member exports — values asstatic var, utils/hooks/extra components as variadic static funcs. A member-only new-mode file used to mirror a near-empty class, so its exports never existed in the analysis while the real compiled.gddeclared them all. - Value-binding mirror (library 0.12.0): the analyzer's virtual library of a peer
.guitkxno longer registers a global class under a VALUE binding's name — the compiled.gdof a file whose first exported declaration is a value emits noclass_name(the value/class self-reference fix), and the mirror follows; its member statics still resolve through the preload path. - BREAKING (library 0.12.0): GUITKX2304 (unused import) is promoted Warning -> Error, family-wide and consistent with the other import diagnostics — an unused binding (named,
* as, default, or any part of a combined import) now fails the compile and squiggles red in the editor. The bundled vocabulary carries the new severity; findings span the whole binding token. Safe from false positives: the reference scan over-approximates "used" (every identifier outside the import lines counts, markup-text hazards fixed before the flip). - License: from this release the extensions ship under the ReactiveUI Community License 1.0 (previously PolyForm Shield 1.0.0) — free to use and to ship commercially for any company under US $250,000 trailing-12-month revenue; above that, shipping a product needs a commercial license ($2,000 per title or $2,500 per studio per year — LICENSE-COMMERCIAL.md in the repo). No functional changes: the VS Code extension's bundled LICENSE carries the new text, and the VS2022 VSIX now ships a LICENSE.txt (it previously carried no license file at all). Every previously published extension version keeps the license it shipped with.
[0.11.0] - 2026-07-18
- ES modules 0.11.0 (rides the library 0.11.0 — see MIGRATION-0.11.md): plain, signature-classified declarations replace the
component/hook/modulewrapper keywords —Name(p) -> RUIVNode {}is a component,use_x(p) {}a hook, any other callable a util,name := expra value export — and the import surface grows to the full ES set ({ a as b }renames,* as Xnamespaces, default imports/exports, deferredexport { … }lists). Highlighting, the tokenizer, the workspace/declaration index, outline, live header diagnostics, markup windows, and the virtual-doc analyzer all classify the plain forms; wrapper syntax keeps working for the deprecation window with a live GUITKX2320 warning, and the new 232x diagnostics (2321 cross-guard, 2323–2327 export/import-marker errors) surface live. The TextMate grammar and schema teach the new forms;as/default/fromjoin the keyword faces.
[0.10.3] - 2026-07-16
- Requirements corrected on both marketplace pages: there is nothing to install — VS Code runs the language server on its own built-in Node runtime, and the VS2022 VSIX bundles a Node runtime of its own. The "Node.js on your PATH" requirement was a stale pre-bundling claim.
- First upload since 0.10.0 (the 0.10.2 upload was rejected on the VS Marketplace's 280-character description cap): carries the full listing overhaul — the distinguishable display name, the structured page body, and the repo + Discord links in a description that now fits the cap.
[0.10.2] - 2026-07-16
- Extension descriptions now link the repo (github.com/reactive-ui-toolkit/ruitk-godot) and the community Discord (discord.gg/Knedqu4Wyv), so both marketplace listing pages point back to the project and its community from the very top.
[0.10.1] - 2026-07-16
- Marketplace listing overhaul: distinguishable display names —
GUITKX (Godot - VS Code)/GUITKX (Godot - VS2022)— and a structured page body (Title / Description / Features / Requirements / Changelog) on both marketplaces + Open VSX.
[0.10.0] - 2026-07-11
- 0.10.0 imports leg: the language server understands cross-file imports.
import/exportare first-class syntax (no red squiggles); imported names resolve in embedded GDScript analysis via synthesized const/member stubs; the declaration index and completion are export-aware; and the compiler's frozen GUITKX2300–2309 import diagnostics surface from the .guitkx.diags sidecar (schema v3). Imports are OPTIONAL syntax — projects without them stay green.
[0.9.0] - 2026-07-11
- 0.9.0 naming loyalty (BREAKING, rides the library 0.9.0 — see MIGRATION-0.9.md): the language vocabulary is now 1:1 the official Godot names. Tags = class names (shorthands like VBox/Panel removed; ANY instantiable ClassDB Node class is now a valid tag, validated against the bundled dump); factories = V.ClassName verbatim; events = on + PascalCase(signal) derived for EVERY signal from ClassDB (the React alias table is gone — completions, hover, signature help and semantic tokens all speak onPressed/onValueChanged); style-key completion = the exact Godot property/theme/StyleBoxFlat names. Removed pre-0.9 tags get a precise "renamed in 0.9.0: use <X>" diagnostic instead of a generic did-you-mean.
[0.8.8] - 2026-07-10
- Picks up the runtime compiler's markup brace-matching fix (see its CHANGELOG.md for full detail): a parenthetical comment split across two
#/##lines inside a component/directive body's setup code no longer desyncs delimiter matching -- previously it surfaced as a bogus GUITKX0304 "unclosed component body" anchored at the component's own{, far from the offending comment, in both editors' live diagnostics. Body content mode is now tracked per delimiter-stack level with per-line classification: setup statements (and their#comments) scan as GDScript, markup-shaped lines andreturn ( ... )windows keep markup lexis (a literal#in element text stays literal). The same fix covers keyword bait ("# early return (see docs") and#comments inside multi-line array literals. Also faster markup parsing on large files: the parser's per-element line lookup is now a binary search over a prebuilt line-start table instead of an O(n) scan per element -- this runs on every keystroke.
[0.8.7] - 2026-07-06
- Picks up a batch of compiler/formatter correctness fixes from the runtime addon (see its CHANGELOG.md for the full detail): markup-lexis brace/paren matching, so a literal
#in element text (e.g. "Score #3") or a markup comment (////* *//<!-- -->) containing}/)can no longer miscount the delimiters the compiler uses to find a component/directive body or areturn ( ... )window -- previously a silent miscompile risk, not just a parse error. The formatter no longer re-indents or collapses spaces inside a triple-quoted string's interior, and no longer drops interior blank lines when re-anchoring a directive body's prep code. A handful of smaller hardenings: a truncated closing tag at the very end of a window now fails cleanly instead of scanning past it; format() now reports when it fell back to verbatim (a syntax error) instead of looking identical to "nothing to format" -- surfaced as a one-time warning per file in both editors. - The embedded-GDScript reflow safety net (BUG-1) now treats comments as part of the token stream it compares before and after reflowing -- a gdscript-fmt bug that deleted or mangled a comment used to normalize identically to the original (both "nothing"), so the guard called it safe and applied the reflow anyway, silently deleting the comment. A dropped or mangled comment is now rejected exactly like any other structural change; trailing whitespace inside a comment still doesn't count.
- guitkx.* settings (enableEmbeddedAnalysis, useGdformat, enableGdscriptAnalysis) now apply live: the language server re-reads them on a workspace/didChangeConfiguration notification instead of only once at startup, so a toggle no longer needs a server restart to take effect. enableGdscriptAnalysis is also now enforced server-side across every request handler (VS2022 previously had no way to actually turn off plain-.gd analysis at all -- the option was persisted but silently unenforced; VS Code already gated it client-side by not registering the .gd document selector).
- A closed file's diagnostics no longer linger in the Problems panel until it's reopened. .guitkx typing defaults (both editors) now match the formatter's canonical spaces-at-2 style -- previously tabs/4, which meant every hand-typed line got rewritten by the next format-on-save. The shipped package no longer bundles the language server's compiled test code (pure size, never needed at runtime).
- Format-on-save no longer risks hanging Visual Studio: the request now times out after 2 seconds instead of blocking the UI thread indefinitely when the server is slow to respond (e.g. mid workspace scan). "Analyze plain .gd files" is now actually enforced (previously a persisted-but-ignored setting). guitkx.* option changes in Tools > Options > GUITKX now push to the running language server immediately, with no restart needed. "Restart Language Server" now warns when Visual Studio's one-time-per-session auto-restart budget is already spent instead of promising a restart that won't happen, and the server's stderr is now captured in a new "GUITKX Language Server" Output pane so a hang or crash is diagnosable without attaching a debugger.
[0.8.6] - 2026-07-06
- Repackaged onto the shared language server, jumping straight from 0.5.5 to 0.8.6 -- inheriting every VS Code release in between in one shot: camelCase hooks tooling (completion/hover/go-to-def all speak useState/useEffect/... now, 23 hooks), diagnostic codes renumbered onto the Unity-shared table, gdscript-analyzer 0.6.0 (Godot's own verbatim error messages, wrong-arity call errors, unreachable-code dimming), early/conditional markup returns with full live intelligence, the directive-bodies-are-code-blocks grammar (prep GDScript +
return ( <markup> ), GUITKX2103/2104), format-on-save enabled by default at the spaces-2 canonical style, the compiler-sidecar watch (post-save compiler-only errors now reach the editor), and live dangling-reference/duplicate-class detection (GUITKX2106/2107) including folder-delete handling. See ide-extensions/vscode/CHANGELOG.md for the full per-release detail this jump absorbs.
[0.5.5] - 2026-07-02
- Bundles gdscript-analyzer 0.5.4: a typo'd method on a built-in value —
sliced[1].casll(1)on auseStatepair,s.upper()on aString(a Godot-3 rename),v.zzzon aVector2— is now an error with a precise squiggle, exactly where Godot itself errors. Works through plain untypedvar s = useState(0)locals (the analyzer narrows single-assignment locals to their initializer's type), and Dictionaryd.keysugar now types as the value and can never false-flag.
[0.5.4] - 2026-07-02
- A typo'd hook call (
usseState(0)) is now flagged live as an error, mapped onto the typo. The bundled analyzer (0.5.3) proves a name is defined nowhere only when it holds the whole project: the server feeds every.gdat startup, keeps the view current through a file watcher (created/edited/deleted files and folders, plusproject.godotautoload changes), and re-checks open files once armed. Names declared only in a sibling.guitkx(whose generated.gdisn't on disk yet) are never false-flagged. GUITKX0108(a@for/@whilebody with more than one root element) andGUITKX0102(a component with noreturn ( ... )) now fire live while typing, precisely ranged — previously they appeared only after a save, pinned to line 1 via the compiler sidecar.useState/useReducer/useTransitionreturns are now typed pairs end-to-end:s := useState(0)makess[1]a checkableCallable(hover, inlay hints, and future setter-typo checking), via## @return-tuple(...)tags in hooks.gd and real wrapper-func hook stubs in the virtual document. Hook declarations also keep their parameters and-> Hintin the analysis view, so hovering or navigating a hook parameter works.- Fixed a formatter/compiler regression family around indentation anchoring: one outlier-shallow setup line, or an over-indented leading comment, no longer shifts the rest of the block (which produced an invalid generated
.gdand could dedent anifbody during Format Document). Module members are analyzed under their real names, so a sibling component's bare call to a module-local hook resolves.
[0.5.3] - 2026-07-02
- Formatting a
.guitkxfile now normalizes embedded GDScript indentation to real tabs by depth. A nested setup line — most visibly a lambda body — could previously come out as a tab followed by spaces (\t), which renders like two tabs but is a byte-level tab/space mix; the formatter now re-indents it to clean tabs, matching the compiler's_reindent_setup. Applies to component setup and hook bodies. - A misspelled declaration keyword no longer blacks out the rest of the file. The language server now recovers a near-miss
component/hook/moduleheader (e.g.comssponent Foo {) for analysis, so markup diagnostics, embedded-GDScript checks, tag highlighting, and completion keep working while you fix the typo — instead of every other error silently vanishing. Markup diagnostics are also resilient to a malformed tag now: one bad< a>no longer collapses the whole component's markup window. And a misspelled@class_namedirective (e.g.@clasaas_name) is flagged asGUITKX0300with a did-you-mean, instead of being silently ignored.
[0.5.2] - 2026-07-02
- A misspelled declaration keyword (e.g.
comssponentinstead ofcomponent) or an invalid@class_namevalue is now reported live —GUITKX0102/GUITKX0300— instead of silently blacking out the whole file. Previously the editor skipped ALL analysis whenever it could not find a validcomponent/hook/moduleheader, so one typo in the header made every other error (and hover/completion) vanish with no indication why. A<followed by whitespace inside markup is also flagged live now as an invalid tag name.
[0.5.1] - 2026-07-02
.guitkxfiles that mix tabs and spaces in a component's setup — a tab followed by spaces renders identically to tabs, so the difference is invisible in the editor — no longer trigger a spuriousMixed use of tabs and spaces/expected a declarationerror cascade. The embedded-GDScript virtual document now normalizes setup indentation by depth (a tab and the inferred space-unit each count as one level), matching the compiler.
[0.5.0] - 2026-07-01
- Hooks are now camelCase (React parity): completion, hover, go-to-definition, and the embedded-GDScript virtual document all use the camelCase names —
useState,useEffect,useRef,useMemo,useContext,createContext, … (23 hooks). Existing snake_case hook code must be migrated (a snake→camel rename over the 23 hook names). - Hovering a hook now shows its signature — e.g.
useState(initial) → [value, setter]— instead of a bareCallable. Host-element hover drops the internalV.*factory detail and keeps the Godot class reference. - Unreachable code after a component's
return (...)is now dimmed (faded) live, the same way dead code is shown for GDScript — via anUnnecessary-tagged diagnostic. - Duplicate-
keydetection now covers expression keys (key={ str(i) }), not only literalkey="x"— two siblings sharing the same key expression collide on every iteration. - The markup parser now reports a
<followed by whitespace (e.g.< a>) as an invalid tag name, instead of silently treating it as a fragment plus an attribute.
[0.4.0] - 2026-07-01
- Event handlers in
.guitkxnow use React-style camelCase names —onClick,onChange,onSubmit,onFocus,onBlur,onPointerDown/onPointerUp/onPointerEnter/onPointerLeave,onResize— and the editor understands them everywhere: completion offers the right events per control, hover shows the underlying Godot signal and its argument signature, signature help fires inside the handler, unknown-attribute validation accepts them, and they are semantically highlighted as events.onChangeis polymorphic — it is offered for (and binds to) whichever value/selection signal the control actually exposes (text_changed,value_changed,item_selected,tab_changed, ortoggled). The nativeon_<signal>spelling is still recognized as an escape hatch to any Godot signal. - Prop spread
{...obj}is now recognized in markup: it is highlighted, never flagged as an unknown attribute, and preserved (and kept idempotent) by the formatter — matching the compiler, which merges a spread with explicit props left-to-right (later wins), on both host elements and components.
[0.3.1] - 2026-07-01
- Hover, completion, and go-to-definition now work for embedded GDScript in a component's setup block (the lines before
return (...)). They were returning nothing — including hooks likeuse_state— because the setup region's source map was dropped on CRLF files or when the block ended in a blank line; it is now mapped line by line. - Hover in markup now resolves the full identifier under the cursor against host elements, your own components, and the host element's ClassDB properties and signals. Previously it only matched a host tag at the exact end of its name, so attributes (
text,separation,on_pressed, …) and component tags hovered to nothing. A component tag's hover also shows its parameter signature. - Completion now fires at a blank markup position — an empty child slot or inside an
@for/@ifbody — and offers your project's components as<Tag>suggestions. Those positions were being misread as embedded GDScript, so nothing was offered. - Go-to-definition, find-references, and rename now work from a component's
<Tag>usage (including with the cursor on the opening<), from thecomponentdeclaration name, AND from its@class_namedirective — previously F12 / ctrl+click on the declaration or the@class_namereported 'no definition found' and find-references came up empty. A GDScript comparison such asa < Nameis never mistaken for a tag. - Renaming a component that declares
@class_namenow rewrites the@class_namedirective, the declaration, and every<Tag>usage together. It previously left@class_namestale, so the renamed usages reported a danglingGUITKX0105'unknown element' error. - Embedded GDScript inside
.guitkxis now semantically highlighted by the analyzer (type-aware), the same way a real.gdfile is, instead of grammar-only colouring. - Embedded GDScript inside
.guitkxis now formatted by the bundled analyzer — the samegdscript-fmtthat formats plain.gdfiles — so a snippet formats identically in a.gdand in.guitkx. The optional externalgdformatdependency is gone.
[0.3.0] - 2026-06-30
- The extension now drives plain
.gdfiles through gdscript-analyzer, not just.guitkxmarkup: diagnostics, completion, hover, go-to-definition, project-wide find-references and rename, signature help, inlay hints, code actions, document symbols, formatting, and semantic highlighting, all headless with no running Godot editor. It is ON by default; the newguitkx.enableGdscriptAnalysissetting turns it off. Because it runs alongside the godot-tools extension, disable godot-tools' language server to avoid duplicate diagnostics on.gdfiles. - Embedded GDScript inside
.guitkxnow also gets find-references, project-wide rename (correct-or-refuse), signature help, inlay hints, and code actions, on top of the existing completion, hover, diagnostics, and go-to-definition. - Upgraded the bundled GDScript analyzer to 0.5.2, which exposes GDScript formatting and semantic highlighting, now wired for
.gdvia Format Document and semantic tokens.
[0.2.6] - 2026-06-28
- Go-to-definition now resolves across files fully offline (no running Godot editor): a library symbol such as a hook's
use_refnow jumps to its real implementation incore/hooks.gd, landing on the declaration in that file. Previously only same-file symbols resolved headlessly; cross-file navigation required a running Godot editor. - Upgraded the bundled GDScript analyzer to 0.4.0: navigation and diagnostics now cross the extension boundary as native objects, and each navigation target reports its file directly, making cross-file go-to-definition faster and more robust.
[0.2.5] - 2026-06-27
- Embedded-GDScript intelligence (completion, hover, and go-to-definition inside {expr}/setup blocks) is now analyzed in-process by gdscript-analyzer, with no running Godot editor or TCP connection required, so it works fully offline. Go-to-definition now resolves same-file symbols (the previous Godot-proxy path could not). The
guitkx.enableGodotProxyandguitkx.godotLanguageServerPortsettings are replaced by a singleguitkx.enableEmbeddedAnalysistoggle (the legacyenableGodotProxyvalue is still honored for back-compat). - Embedded-GDScript type and parse diagnostics (e.g. integer division, type mismatch, syntax errors) now surface as squiggles inside {expr}/setup blocks, mapped back into the .guitkx source. Unresolved library/cross-file symbols never warn (the analyzer treats them as the Unknown seam), so this adds real diagnostics with no false positives.
[0.2.4] - 2026-06-22
- Formatting: an authored blank line at the start or end of a component/hook setup block is now preserved (it was being stripped), and runs of 2+ spaces in embedded GDScript are collapsed to one outside strings/comments (e.g.
if x == nullbecomesif x == null). - Diagnostics: an unknown element (GUITKX0105) and an unknown host attribute (GUITKX0107) are now reported as Errors (a red squiggle) instead of a faint hint / a warning.
- Completion: style-dict keys (
bg_color,corner_radius,pad,separation,expand_h,font_size, the theme channels, the per-state slots, …) are offered inside astyle={ {…} }(or*_style) dictionary; common built-in constants (Color.WHITE,Vector2.ZERO, …) complete afterType.; and go-to-definition on an embedded GDScript symbol now forwards to Godot's language server (jumping to the library.gd, e.g.use_ref→ core/hooks.gd) when the editor is running.
[0.2.3] - 2026-06-22
- Formatting now always uses tab indentation for
.guitkx(the embedded GDScript requires tabs, and the compiler emits tabs), so the markup and the embedded setup no longer mix indentation units — previously, with an editor configured for spaces, a deeper setup line kept its authored tab and produced a "2 spaces + tab" indent. Diagnostics: unknown attributes on a host element are now flagged (e.g. a typo'dte/xton<Label>) with a did-you-mean suggestion, validated against the bundled Godot ClassDB property + signal data; component tags (which take arbitrary props) are not flagged, and the check is skipped when the ClassDB dump is unavailable so it never false-flags. - Formatter configuration: a project
guitkx.config.json(Prettier-style walk-up, the analogue of ReactiveUIToolKit'suitkx.config.json) now overrides the formatter —printWidth,indentStyle("tab" | "space"),indentSize,singleAttributePerLine,insertSpaceBeforeSelfClose. Tab indentation is the default when no config is present.
[0.2.1] - 2026-06-22
- Renamed the extension to just "GUITKX" (it was "GUITKX (ReactiveUI for Godot)", which truncated to "GUITKX (React..." in the editor UI) and added the Godot logo as the extension / marketplace icon.
[0.2.0] - 2026-06-22
- Formatter: components whose render is guarded by an early
return null(e.g.if not ready: return nullbefore the real markup return) are now formatted instead of being left verbatim, and the multi-attribute wrap path honors theinsertSpaceBeforeSelfCloseoption (it previously always inserted the space). - Language server: hover and completion inside a
hookbody now map to the correct embedded-GDScript offset (the body is no longer mis-mapped after re-indentation); a<>...</>fragment no longer raises a false GUITKX0104 duplicate-key warning across its siblings; and parameter completion is now noncode-aware, so a string default containing a comma or colon (e.g.label: String = "a, b") no longer drops the parameters declared after it. - find-references and rename now use the exact jsx-as-value keyword boundaries the compiler uses (return / else / and only), so they never edit a
<Namethat the compiler reads as a less-than comparison (e.g. afterif,in,not,await,yield, oror). - Control-flow inside an embedded expression that cannot be lowered to an expression (
@while/@match) now reports GUITKX0113 through the compiler-diagnostics sidecar;@if/@elif/@elseand@forinside a{expression}or a lambda now compile correctly (inline ternary / .map).
[0.1.0] - 2026-06-21
- Initial release. Syntax highlighting and language intelligence for .guitkx markup (ReactiveUI for Godot): a self-contained TextMate grammar that colors both the JSX-like markup and the embedded GDScript; markup completion and hover for host-element tags, structural/common attributes, control-flow directives (@if/@elif/@else/@for/@while/@match/@case/@default) and per-element event handlers, served from the bundled schema; and embedded-GDScript completion/hover forwarded to Godot's built-in GDScript language server (TCP, engine port 6005) through a synthetic virtual .gd document with a length-preserving source map. Structural diagnostics (unbalanced braces) ship today; the Godot proxy degrades gracefully when the editor is not running so the markup features always work.
- Visual Studio 2022 extension registers the TextMate grammar via .pkgdef (VS does not colorize over LSP) and drives the same Node language server through an ILanguageClient. Requires Node.js on PATH.