Quilon for VS Code
Syntax highlighting and editor tasks for the Quilon programming
language — a statically-typed, symbol-based language (no keywords) that
compiles to native code via LLVM. Files use the .ql extension.
Features
- Syntax highlighting for the full symbol set:
- Comments (
~ to end of line), strings ("…" with escapes), numbers, true/false, wildcard _.
- The entry point
^, module import << (with the imported path), and export marker >>.
- Operators:
|> (pipe), := (mutable bind) vs = (immutable bind), :: (type annotation),
=> (function body / match arm), -> (return type), <- (inclusive range),
? / | (pattern matching), arithmetic + - * / %, comparison == != < <= > >=,
logical && || !. Each multi-character operator (=>, ->, :=, |>,
<-, ::, ==, !=, <=, >=, &&, ||) is highlighted as a single
token — never split into its first character colored separately from the rest.
< > block delimiters — a line-final < (opens a block) and a line-final
> (the block close: the last token on its line) share one block-punctuation
scope, so both delimiters color identically; a </> used mid-line stays the
less-than / greater-than comparison operator.
- Built-in types
Num / Text / Bool, and the unit type/value $ ($ is
both the type, as in -> $, and its sole value — highlighted like the other
built-in types).
- Capitalized identifiers are highlighted as types / sum-type constructors
(
Ok, NotOk, Color, Circle); lowercase names followed by ( as function calls.
- Bracket matching & auto-closing for
< >, { }, [ ], ( ), and ".
- Inline diagnostics — type/parse/lex errors from the compiler appear as
editor squiggles (see Diagnostics).
- Editor tasks & commands to run the compiler on the active file.
- CodeLens — ▶ Run and ▶ Debug actions appear above each
^
entry-point definition (see Running the compiler).
- Debugging — set breakpoints in
.ql source and step through a native
build under CodeLLDB
(see Debugging).
Install / run locally
The extension is written in TypeScript (src/extension.ts), compiled to
out/extension.js by tsc. It uses pnpm as its package
manager (the pinned version lives in the packageManager field of
package.json; corepack enable will provision it automatically). Install
dependencies once before building or debugging:
cd editors/vscode
pnpm install
This extension is not published to the Marketplace. To try it from this checkout:
Option A — Extension Development Host (recommended)
- Open the
editors/vscode/ folder in VS Code.
- Press
F5 ("Run Extension"). The compile preLaunchTask builds the
TypeScript, then a new "Extension Development Host" window opens.
- Open any
.ql file (e.g. one from examples/) — highlighting is active.
Use pnpm run watch for incremental recompiles while iterating.
Option B — install as a .vsix
cd editors/vscode
pnpm install # if you haven't already
pnpm run package # compiles + produces quilon-0.1.0.vsix (via vsce)
code --install-extension quilon-0.1.0.vsix
Development
The extension is TypeScript (strict). It is linted with oxlint and formatted
with oxfmt (the Oxc toolchain) — not ESLint/Prettier:
pnpm run compile # tsc: type-check + emit out/extension.js
pnpm test # compile, then run the unit tests (node --test)
pnpm run lint # oxlint (fails on any finding)
pnpm run lint:fix # oxlint --fix (auto-fix what it can)
pnpm run fmt # oxfmt --write (format in place)
pnpm run fmt:check # oxfmt --check (verify formatting; CI gate)
CI runs lint, fmt:check, compile, test, and package on every PR that
touches editors/vscode/** (see Publishing).
Tests & manual verification
Unit tests (pnpm test) cover three things, all kept free of any vscode
import so they run under plain Node:
- the diagnostic-output parser (
src/diagnostics.ts ↔ src/diagnostics.test.ts);
- the entry-point detector behind the CodeLens (
src/entryPoints.ts ↔
src/entryPoints.test.ts);
- grammar tokenization (
src/grammar.test.ts) — it loads the real
syntaxes/quilon.tmLanguage.json and asserts each multi-character operator
(=>, ->, :=, |>, <-, ::, ==, !=, <=, >=, &&, ||)
tokenizes to a single scope, plus regression guards for < / >, =,
$, comments, strings, and numbers. src/grammar.ts is a tiny dependency-free
re-implementation of TextMate's ordered first-match-wins rule (the behaviour
the operator ordering relies on), so no native engine is needed.
To verify the inline diagnostics end-to-end manually:
- Set
quilon.command to a working compiler (e.g. "cargo run --" from a
checkout, or "quilon" if it's on your PATH).
- Launch the Extension Development Host (
F5) and open a .ql file with a
type error (e.g. examples/type_error.ql) — a red squiggle should appear at
the reported span, with the message in the Problems panel.
- Fix the error and save — the squiggle clears.
- Point
quilon.command at a non-existent binary and reopen a .ql file — a
single warning notification appears (it does not repeat).
To verify $ highlighting, open examples/unit.ql: both the -> $ return
type and the $ value are colored like the built-in types (Num/Text/Bool).
Running the compiler from the editor
Two commands are contributed (open the Command Palette, Ctrl/Cmd+Shift+P):
- Quilon: Check Current File → runs
quilon check <file>
- Quilon: Run Current File → runs
quilon run <file>
They run in an integrated terminal named "Quilon". By default they invoke a
quilon binary on your PATH. If you are working from a checkout of the
compiler instead, set:
// settings.json
"quilon.command": "cargo run --"
The bundled .vscode/tasks.json also provides quilon: check current file
and quilon: run current file tasks (Terminal → Run Task…).
CodeLens above the entry point
Every executable Quilon program defines a top-level ^ entry point (its
main). Above each ^ definition the extension shows two clickable CodeLens
actions:
- ▶ Run — invokes Quilon: Run Current File (
quilon run <file>).
- ▶ Debug — builds the file with
quilon build --debug and launches it
under CodeLLDB, so breakpoints set in the .ql source are hit (see
Debugging).
Both act on the file containing the lens.
The quilon run subcommand must exist in your toolchain. Depending on your
build it may instead be compile + manual llc/link — see the repo's
CLAUDE.md / LANGUAGE.md.
Diagnostics
- Inline diagnostics (squiggles). When you open or save a
.ql file, the
extension runs <quilon.command> check <file> in the background and surfaces
any compile errors as editor squiggles in the Problems panel. The compiler
reports errors as path:line:col: error: <message> with a caret underline;
the extension parses those, converts the 1-based line/column to a VS Code
range (using the caret run for the span width, falling back to the token at
the column), and publishes them against the file. Diagnostics clear when the
file checks clean. If the configured command can't be found, the extension
warns once (set quilon.command, e.g. to cargo run --) and stays quiet
thereafter.
Debugging
Source-level debugging is delegated to CodeLLDB,
declared as an extension dependency so VS Code installs it alongside Quilon.
The quilon debug type does two things when a session starts:
- Builds the active
.ql with <quilon.command> build --debug <file> -o <tmp>,
which emits DWARF line info into the native binary.
- Launches that binary under CodeLLDB (
type: "lldb").
Because the binary's DWARF line table references the .ql source, breakpoints
you set in the source and single-stepping both work. Start a session with the
▶ Debug CodeLens above ^, the Quilon: Debug Current File command, or a
launch.json entry:
{
"type": "quilon",
"request": "launch",
"name": "Quilon: Debug current file",
"program": "${file}",
"args": []
}
Value inspection. The lldb formatter the session loads
(formatters/quilon.py) renders Quilon values against the distinct DWARF types
the compiler emits: a Text shows as its string (not a {data, byte_len}
struct), and a []T expands to an indexed list of its elements, each keeping its
own type — so a [][]Text expands to a list of inner []Text arrays, each of
its own Text values. Long arrays cap the default expansion and note the
remaining count in the summary (an explicit arr[i] past the cap still works).
Records and sum types currently fall back to lldb's default struct rendering.
Publishing
CI/CD for this extension lives in
.github/workflows/vscode-extension.yml:
- PR gate (
validate). Every pull request and main push that touches
editors/vscode/** validates the manifest/grammar/config JSON, type-checks
and compiles the TypeScript (pnpm run compile), and runs
pnpm exec vsce package to prove the extension still builds into a .vsix.
- Release (
publish). Pushing a tag matching vscode-v* packages the
.vsix, attaches it to a GitHub Release for that tag, and — if the
maintainer secrets are set — publishes to the VS Code Marketplace and
Open VSX.
Cutting a release
Bump version in package.json — this is the version
that gets published (vsce reads it from the manifest, not from the tag). Use
a matching vscode-v<version> tag so the GitHub Release name lines up.
Tag and push, e.g. for version 0.1.0:
git tag vscode-v0.1.0
git push origin vscode-v0.1.0
The publish job builds the .vsix and creates the GitHub Release with the
.vsix attached. This part needs no secrets — it always runs.
Marketplace / Open VSX publishing (maintainer setup)
Publishing to the registries is opt-in and gated on repo secrets, so the
workflow succeeds for forks/contributors without credentials:
- VS Code Marketplace — set a
VSCE_PAT repository secret (a
Personal Access Token
for your Azure DevOps publisher). The publisher field in package.json is
currently the placeholder quilon; replace it with your real, registered
Marketplace publisher id before the first publish, since the PAT must belong
to that publisher.
- Open VSX — set an
OVSX_PAT repository secret
(Open VSX access token).
Before the first publish, create the namespace once (otherwise ovsx publish
fails): pnpm dlx ovsx create-namespace quilon -p "$OVSX_PAT" (use your real
publisher id).
If a secret is absent the matching publish step is skipped and the run still
passes (release-only). Add either or both at
Settings → Secrets and variables → Actions.
Note: vsce package warns that no LICENSE file is found inside the
extension folder (the canonical license is LICENSE.md at the repo root).
This is non-fatal. To surface a license on the Marketplace page, add a
LICENSE/LICENSE.md under editors/vscode/.
License
See LICENSE.md at the repo root.