OTUI / OTML (OTClient UI)
Editor support for OTUI/OTML (.otui / .otmod / .otfont), the UI markup language of the
OTClient game client.
Works in VS Code and in the VS Code–compatible editors — Cursor, Antigravity,
Windsurf and VSCodium — which install extensions from Open VSX.
This extension contains no language logic. It launches the
otui-lsp server binary and speaks LSP to it over stdio;
all the intelligence lives in the server.
Install
Install OTUI Language Support from your editor's extension panel. The published packages are
platform-specific: each one bundles the otui-lsp binary for your OS and CPU, so there is
nothing else to install and no Rust toolchain required.
| Editor |
Registry |
| VS Code |
Visual Studio Marketplace |
| Cursor, Antigravity, Windsurf, VSCodium |
Open VSX |
Open a folder, not a single file — the server indexes .otui files across the workspace, which
is what lets references, rename and type hierarchy resolve into files you have not opened.
Using your own server build
If you build otui-lsp yourself, point the extension at it:
// settings.json
"otui.server.path": "~/workspace/otui-lsp/target/release/otui-lsp"
The binary is resolved in this order, and an explicit otui.server.path that does not exist is a
hard error rather than a silent fallback:
otui.server.path
- the binary bundled inside the extension
otui-lsp on your PATH
Features
Diagnostics, completion (properties / $state / anchors / @events), hover, go-to-definition,
type definition / implementation / type hierarchy (the Name < Base graph), find references,
rename, document & workspace symbols, semantic highlighting, color swatches, clickable asset
links (image-source:), inlay hints, folding, document/range formatting, and document highlight —
all resolving workspace-wide across closed files.
A style that others derive from carries a code lens — 3 widget(s) inherit this style — and
clicking it peeks the inheriting widgets.
Syntax highlighting comes from two layers: a TextMate grammar colors the file the moment it opens,
and the server's semantic tokens refine it once the language server attaches. Embedded Lua in
@event:, !expr: and &alias: values is highlighted with the editor's Lua grammar.
Settings
| Setting |
Default |
Description |
otui.server.path |
(unset) |
Absolute path to an otui-lsp binary. Supports ~ and ${workspaceFolder}. |
otui.lua.enable |
true |
Attach the server to .lua files for the Lua→OTUI go-to-definition bridge (see below). |
otui.trace.server |
off |
Trace LSP traffic for debugging (off / messages / verbose). |
The Lua ↔ OTUI bridge
An OTClient module is two files: foo.otui draws the UI and names elements (id: closeButton),
foo.lua drives it (getChildById('closeButton')). With the bridge on, Go to Definition on a
getChildById('id') in a .lua jumps to the matching id: in the .otui — including ids the
widget inherits from a base style in another file (e.g. a MiniWindow's closeButton).
The server serves only definition on Lua files — never diagnostics, completion or hover, which
stay with your Lua language server. Both go-to-definition results (the Lua target and the .otui
declaration) show side by side; that is the feature, not a conflict. Runtime-built ids
('row_' .. i) have no static target and intentionally resolve to nothing.
Turn the bridge off with "otui.lua.enable": false to skip indexing the workspace's Lua files.
Commands
| Command |
Description |
| OTUI: Restart Language Server |
Restart otui-lsp without reloading the window. |
| OTUI: Show Language Server Output |
Open the server's log channel. |
Troubleshooting
When something looks wrong, the first move is OTUI: Show Language Server Output — the log names
which binary was launched, where it came from, and whether the server handshake completed. For a
deeper look, set "otui.trace.server": "verbose" to see the LSP traffic itself.
| Symptom |
Cause & fix |
| Nothing lights up — no diagnostics, no completion. |
You opened a single file. The server indexes across the workspace and the extension only activates on a folder that contains a .otui. Open the folder (File → Open Folder), not the file. |
| "Could not find the otui-lsp language server." |
No bundled binary and none on PATH. Either install a platform-specific package (which bundles one) or set otui.server.path to a binary you built. A path that is set but missing is a hard error by design — it never silently falls back. |
| Colors look flat or washed-out once the file finishes loading. |
You have "editor.semanticHighlighting.enabled": false in your settings. That flag turns off the server's semantic tokens, leaving only the coarser TextMate layer — which reads as "the colors got worse". Remove the flag; the two layers are meant to work together. |
| On Windows the extension installs but never loads. |
code --install-extension foo.vsix from the CLI can register the package without activating it. Install through the UI instead: Extensions → ⋯ → Install from VSIX. |
| Everything is disabled and the editor mentions "Restricted Mode". |
The extension launches a native binary, so it declares itself unsupported in untrusted workspaces and stays off until you trust the folder (the banner's Trust button, or Workspaces: Manage Workspace Trust). |
The log shows [error] otui-lsp: indexed 221 workspace .otui file(s). |
Not an error. The server writes progress to stderr on purpose (it is shutdown-safe), and the client surfaces any stderr at error level. A line like this means indexing succeeded. |
Language behavior itself — a wrong diagnostic, a missing completion, a go-to-definition that lands
in the wrong place — is the server's domain, not this client's. Those belong in the
otui-lsp tracker. ARCHITECTURE
explains where the line between the two repos falls.
Contributing
Development setup, the F5 dev loop, testing, the deliberate version pins and the release process
live in CONTRIBUTING. ARCHITECTURE explains the
client/server split — what belongs here versus in the server.
License
Dual-licensed under either MIT or Apache-2.0, at your option —
matching the server.