XCL Twee X-Lowe (VS Code Extension)
Language support for X-Change Life Twee files written in X-Lowe, the game's Harlowe-derived story format. Version 2.0 is a rebuild around the current engine (X-Lowe 6.1), the real corpus (8,000+ passages), JavaScript macros, work-in-progress drafts, and mods.
.twee and .twee.draft files get the same treatment. Drafts are indexed so their passages resolve while you write, and shipped files that reference a draft-only passage are flagged, because the game build ignores drafts.
Highlights
- Grammar written against the engine's own lexer: macros by category, every operator (
is defined, is a, does not contain…), lambdas, -type signatures and unions, spread, bind, time units, ordinals, colours, verbatim backticks, inline styles, headings, lists, aligners, links, indented headers, (js:[…]) and <script after> as embedded JavaScript.
- Macro handbook that knows your workspace: X-Lowe 6.1.2 engine docs generated from the engine source, the X-Change Life and Goblin's Pet catalogs, keyword docs, plus everything discovered in the workspace:
(macro:) definitions with their typed parameters, JavaScript registrations (Harlowe.macro, XLowe.macro, GE.registerGuardedMacro, registerMacroOnce) and generated stat accessors (get_, set_, gain_ and the clamp_ helpers for every skill in statsMapping, plus the tertiary meters in statRegistry and their gain_<stat>_msg macros). Hover shows where a macro is defined; F12 jumps there.
- Semantic colouring: macros tinted by provenance (engine, game, Twee-defined, generated, unknown, deprecated), missing or draft-only passage references, insertion-point tags, overriding passages.
- Navigation for everything: passages, hooks (
|name>[, ]<name|, ?name), story and temp variables, custom macros, tags. Definition, references, rename, highlight, outline, folding, CodeLens, document links, passage graph.
- Diagnostics that match the game: the game's own
tweelint when present, media paths, missing passages (with Create), draft-only references, unknown and deprecated macros (with replacement quick fix), legacy ($cs:)/($nx:)/$next navigation, (display:) of layout passages, underscore datamap keys, registry datamap contracts, # meta blocks, and X-Lowe's migration linter on save.
- Game development: tasks for build / dev server / tests / doctor, open the dev server, copy an
XLowe.goto() for the current passage, changelog reminder, xlowe-macro-types.json export, cSpell setup for prose.
- Modding: Insertion Points view (every
($passage_tags:) call site and what plugs into it), new-mod scaffold, .meta YAML schema, package as .zip/.xcl, launch with mods, JavaScript typings for GE.*/XLowe.*, override and [around] CodeLens with Compare and Convert, release-change warnings from xcl-profile.json.
- 15 colour themes, all regenerated for every scope and semantic token. The default, XCL Launcher, reproduces the palette of the passage inspector inside the XCL launcher, down to the exact hex values it uses for each token class.
XCL Launcher theme
The launcher's passage inspector maps this extension's own TextMate scopes to its token classes, so the theme is a reproduction rather than a homage. Every colour comes from platform/launcher/assets/xcl-theme.css in the game repository.
| Token |
Colour |
Launcher variable |
| Passage names, tags, strings, audio macros |
#d8bfd8 |
--theme-highlight-color |
| Generic and custom macros |
#ffb2d3 |
--glow-color |
| Navigation macros, control flow, operators, links |
#7fc4b5 |
--syntax-flow-color |
| Hooks, UI macros, HTML |
#c9b6ea |
--syntax-hook-color |
| Story variables |
#6edb6e |
--buff-color |
| Temp variables and identifiers |
#a9e5a9 |
--syntax-temp-color |
| Numbers, datatypes, unclosed constructs |
#eac97e |
--syntax-literal-color |
| Verbatim and embedded code |
#b6d2c2 |
--syntax-embedded-color |
| Brackets and sigils |
#9882b8 |
--theme-button-color3 |
| Editor background |
#20162a |
--launcher-code-background |
The workbench follows the launcher's own chrome: the purple status bar, the darker activity bar and side bar, pink focus glows, and the green/red buff and debuff colours for added and deleted lines.
Keyboard shortcuts
All shortcuts are chords under Ctrl+K (Cmd+K on Mac) so they never fight VS Code's own bindings. Disable them all with xcl.keybindings.enabled: false.
| Action |
Windows / Linux |
Mac |
| Passage picker |
Ctrl+K G |
Cmd+K G |
| Next / previous passage |
Ctrl+K Ctrl+Alt+↓ / ↑ |
Cmd+K Cmd+Alt+↓ / ↑ |
| Go to current passage header |
Ctrl+K H |
Cmd+K H |
| Validate media in file |
Ctrl+K Ctrl+Alt+M |
Cmd+K Cmd+Alt+M |
| Passage graph |
Ctrl+K Ctrl+Alt+G |
Cmd+K Cmd+Alt+G |
| Search macro handbook |
Ctrl+K Ctrl+Alt+H |
Cmd+K Cmd+Alt+H |
| Rename passage / hook / variable / custom macro |
F2 |
F2 |
| Go to definition · references |
F12 · Shift+F12 |
F12 · Shift+F12 |
Click the XCL icon in the Activity Bar.
- Passages: passages of the active file with tags; drag to reorder, sort A→Z, validate media, open the graph. Icons mark drafts,
[around] advice, passages that override the base game, and passages inserted through a tag.
- Insertion Points: every tag the base game exposes through
($passage_tags:"tag"), with its description from passage tags.twee, the call sites, and the passages currently plugged into it.
- Macros: the handbook grouped by kind (Twee-defined, JavaScript, generated accessors, X-Change Life, X-Lowe engine, Harlowe, Goblin's Pet, keywords). Counts show how often each macro is used in the workspace. Click for full docs; use the search button for a quick pick.
- Palettes: preview and apply the bundled themes.
Diagnostics and quick fixes
Every rule has its own severity setting under xcl.diagnostics.* (off, hint, information, warning, error).
| Source |
What it reports |
xcl-structural |
Unclosed macros, hooks and collapsed blocks. Uses the workspace's scripts/tweescript/tweelint.js when present so results equal npm run build. |
xcl-media |
(pic:), (vid:), (media:) and media-variable paths that do not exist under the media directories. Quick fixes offer similar files or a picker. |
xcl |
Passage not found (Create quick fix), draft-only passage, unknown macro, deprecated macro or stub passage (replacement quick fix), legacy navigation (($cs:) → (cs:) quick fix), (display:) of a layout passage, underscore datamap keys, unset story variables, register_* datamap contracts, # meta blocks. |
xcl-mod |
A mod passage overrides a base passage (Convert to [around]), or touches a passage changed in the current release. |
xlowe-migration |
X-Lowe's comparison-elision migration linter, run on save when xlowe-migration-linter.js is found (build it in the x-lowe repo with make migration-linter, or set xcl.migration.linterPath). |
Macro documentation sources
| File |
Contents |
macros/xcl-macros.engine.json |
Generated from X-Lowe's /*d: doc comments (npm run catalog:engine with an ../x-lowe checkout). |
macros/xcl-macros.xlowe.json |
X-Change Life macros (hand-written prose). |
macros/xcl-macros.harlowe.json |
Harlowe 3.3 basics (kept for prose and examples). |
macros/xcl-macros.tgp.json |
The Goblin's Pet macros. |
macros/xcl-keywords.json |
Operators, identifiers and datatypes. |
Catalogs never override each other. When several document the same name, xcl.macroDocs.kindPriority decides which wins (workspace definitions first by default). xcl.macroDocsFile accepts one file or a list to replace the bundled set.
Working with the game checkout
Open the xchange-life clone (alone or together with mod folders). The extension detects it from project/twee/00-meta/compiler-options.twee, or set xcl.modding.baseGameRoot.
- Run tasks (
Terminal → Run Task → xcl): build, dev server with live reload, unit tests, doctor.
- Open the current passage in the running game: copies
XLowe.goto("…") and opens the dev server. Paste into the browser console. The dev server only relays reloads to the page, so the jump itself has to run in the page.
- Changelog reminder: saving Twee in the checkout while
CHANGELOG.md has no uncommitted changes shows a status bar reminder.
- Export macro types: writes
xlowe-macro-types.json for the migration linter from the handbook and discovered macros.
Modding
- New Mod Scaffold creates a folder with a
.twee (using a passage tag and an [initialize] passage), a .meta file validated by the bundled schema (needs the Red Hat YAML extension for live validation), a script that registers a macro with GE.registerMacroOnce, img/ and aud/ folders, and JavaScript typings.
- Insertion Points shows where a mod can add content without overwriting anything.
- Passage headers that duplicate a base passage get a CodeLens with Compare (diff of the two bodies) and Convert to [around], which adds the tag and a
(display:_around) line.
- Package streams a
.zip or .xcl with the layout the loader expects (drafts and typings excluded). Large media packs use ZIP64 when needed; cancelling leaves an existing package untouched.
- Test this mod saves your edits, imports your mod folder into the desktop launcher, and applies and plays it. The launcher remembers the folder: use Refresh mod and reload or
Ctrl/Cmd+Shift+R for the next edit. Install the game checkout and desktop dependencies first.
- Release-change advice comes from the selected game checkout's matching
xcl-profile.json. With multiple game checkouts open, set xcl.modding.baseGameRoot; use xcl.modding.releaseProfile to select an explicit Full ZIP profile.
Settings
See the Settings UI for the full list (search "xcl"). The ones most people touch:
| Setting |
Default |
Description |
xcl.indexGlobs |
["**/*.twee", "**/*.twee.draft"] |
Files to index (prefix ! to exclude). |
xcl.indexExclude |
dist, build, out, .cache, … |
Folders never indexed. A built game holds a compiled copy of every passage and installed mods extract beside it, so dist/ would double every definition. |
xcl.passageReferenceMacros / xcl.passageReferenceParams |
see defaults |
Which macros and argument positions hold passage names. |
xcl.mediaDirectories |
[] (→ dist) |
Folders containing img/. |
xcl.macroDiscovery.* |
on |
Twee, JavaScript and stat-accessor discovery. |
xcl.macroDocs.kindPriority |
twee, js, generated, xlowe, engine, harlowe, tgp |
Which documentation wins per name. |
xcl.semanticTokens.enabled |
true |
Provenance colouring. |
xcl.diagnostics.* |
per rule |
Severity per diagnostic. |
xcl.lint.preferWorkspaceLinter |
true |
Use the game's linter. |
xcl.migration.enabled |
true |
Run the migration linter on save when available. |
xcl.keybindings.enabled |
true |
Chord shortcuts. |
Development
npm install
npm run compile # type-check + esbuild bundle → dist/extension.js
npm test # grammar snapshot + scanner tests (set XCL_CORPUS=/path/to/project/twee for a full sweep)
npm run themes # regenerate theme rules for new scopes
npm run catalog:engine # regenerate the engine catalog from ../x-lowe
npm run package # build the .vsix
Press F5 in VS Code to launch an Extension Development Host.
src/
core/ scan.ts (single-pass structural scanner), index.ts (workspace index), macros.ts (registry + discovery), media.ts, lint.ts, tagdocs.ts
features/ hover, completion, signature, navigation, semantic tokens, lenses, code actions, diagnostics, scope
views/ passage tree, handbook, insertion points, palette gallery, passage graph
game/ tasks, dev server, changelog nudge, macro-types export, migration linter runner
modding/ scaffold, packaging (zip), typings, launch, release profile
syntaxes/ xcl-twee.tmLanguage.json
macros/ documentation catalogs
schemas/ xcl-mod-meta.schema.json
snippets/ xcl-twee.json
typings/ xcl-mod-api.d.ts
test/ grammar and scanner tests
Repository
License
MIT
| |