# Editor setup

> The VS Code extension, what the language server provides, and wiring the LSP into any editor.

Source: https://docs.astra-void.xyz/vela-rbxts/guides/editor-setup/

Vela ships a language server, `vela-rbxts-lsp`, and a VS Code extension that drives it. The server
reuses the compiler crate directly, so what the editor tells you about a class name is what the
compiler would do with it. The same parser, the same theme resolution, the same diagnostic codes.

## VS Code

Install **Vela LSP** from the marketplace, id `astra-void.vela-rbxts-lsp`. It requires VS Code
`^1.91.0`.

It activates on TypeScript and TypeScript React documents with a `file:` scheme. There is nothing to
enable per project — open a `.tsx` file and the server starts.

### Settings

The extension contributes three settings and **no commands** — changing any of them restarts the
client for you.

| Prop | Type | Description |
| --- | --- | --- |
| `velaRbxts.lsp.serverPath` | `string` | Explicit path to a server binary. A relative path is resolved against the workspace root. Defaults to "", meaning auto-resolve. |
| `velaRbxts.lsp.trace.server` | `"off" \| "messages" \| "verbose"` | LSP protocol trace level written to the extension's output channel. Defaults to "off". |
| `velaRbxts.lsp.enabled` | `boolean` | Whether to start the language client at all. Defaults to true. |
| `velaRbxts.inlayHints.enabled` | `boolean` | Show what each class lowers to as an inlay hint after the class. Added in 0.13.0. Defaults to false. |

### How the server binary is resolved

`velaRbxts.lsp.serverPath`, if set, is used directly (resolved against the workspace root when
relative). Otherwise the extension launches the server through `process.execPath` with
`require.resolve("@vela-rbxts/lsp")`, gated on the matching platform package being present.

> **Two platforms have no prebuilt binary**
>
> The VSIX bundles the same platforms npm publishes: **darwin** arm64 and x64, **linux x64** gnu and musl, **linux arm64 gnu**, and **win32 x64**. Linux arm64 musl and Windows on ARM have no binary on either channel — build the server from source and point `velaRbxts.lsp.serverPath` at the result.

There is no `cargo run` fallback. Developing the server itself, build it and set `serverPath`
explicitly.

## What the language server provides

The declared capability set is small and deliberate.

**Completions.** Offered only when the compiler reports the cursor inside a `className` context, on
the trigger characters `-`, `:`, `"`, `'` and space. There is no resolve step, so every item arrives
complete, and a completion rewrites the segment under the cursor and nothing else.
`placeholder-transparent` is not offered on a `textbox`, which the compiler turns down. Every other
family taking the `transparent` keyword still offers it.

> **A class value the compiler has to walk into counts as that context**
>
> Every capability on this page reads the class strings out of the shapes a `className` is really written in. Chief among them a **function**, which is how a [Vide](https://docs.astra-void.xyz/vela-rbxts/guides/vide.md) project writes a dynamic class value. Also a template's interpolations, `as const`, `satisfies`, string concatenation, and an object's computed keys and spreads.

**Hover.** Shows what a class token resolves to. A viewport-scaled offset reads as its rem value
first — `` Sets `UIPadding` to `1rem` (16px at the base viewport). `` — in hovers and completion
docs alike. A config that
[pins rem](https://docs.astra-void.xyz/vela-rbxts/guides/theming.md#pinning-offsets-back-to-literal-pixels) gets plain pixel
wording, matching the emit it produces.

**Diagnostics.** Push-based via `publish_diagnostics`, debounced 200 ms and gated on the document
version. There is no pull-diagnostics provider.

**Document colors and color presentations.** Color utilities get a swatch in the gutter and can be edited through the editor's color picker.

**Code actions.** Quickfixes driven by diagnostics whose source is `vela-rbxts`, each offering up to
three ranked replacements plus a "remove token" action. There is also the **source action**
`source.sortVelaClasses` below.

**Document highlight.** Highlights other occurrences of the token under the cursor.

**Inlay hints, since 0.13.0.** The editor can show what each class lowers to, after the class:

```tsx
<frame className="p-4 rounded-l-lg" />
//                 ↑ UIPadding.PaddingTop/Right/Bottom/Left
//                      ↑ UICorner.TopLeftRadius, BottomLeftRadius
```

They are **off by default**, behind `velaRbxts.inlayHints.enabled`. The labels come from the
compiler's own lowering, read back through an editor API.

That is the complete list. The server does **not** provide go-to-definition, references, rename,
formatting, semantic tokens or signature help. Your editor falls back to its TypeScript language
service for them.

### Sorting class names

The compiler exposes a canonical class order, offered as the source action `source.sortVelaClasses`.
It rewrites every `className` in the document at once — layout, sizing, spacing, colours, radius,
typography, with variant-prefixed tokens last:

```tsx title="before"
<frame className="text-lg bg-blue-600 flex p-4 hover:bg-blue-700 rounded-lg w-full" />
```

```tsx title="after"
<frame className="flex w-full p-4 bg-blue-600 rounded-lg text-lg hover:bg-blue-700" />
```

**The sort never changes what your classes do.** Utilities that can write the same Roblox property
are treated as one group and keep their relative order within it. A `px-4` written after `p-2` stays
after it. A template keeps the whitespace around each interpolation.

**It does not change how they are laid out either.** Whitespace between tokens is carried over as
written. A class list broken across several lines stays that way. An arbitrary payload containing a
space, such as `w-[calc(100% - 4px)]`, moves as the single class it is. A value whose bracket never
closes is left alone entirely.

Run it from the editor's source-action menu, or on save:

```json title=".vscode/settings.json"
{
  "editor.codeActionsOnSave": {
    "source.sortVelaClasses": "explicit"
  }
}
```

Any LSP client supporting source actions can invoke it by that kind. The underlying API is
`sortClassNames` on `@vela-rbxts/compiler`. It takes `{ source, fileName }` and returns
`{ edits: [{ range: { start, end }, text }] }`. Those are the same offsets the server turns into
workspace edits.

Text synchronization is incremental and the position encoding is UTF-16.

> **unsupported-host-utility is editor-only**
>
> The rule that `text-*` belongs on `textlabel`/`textbutton`/`textbox`, `image-*` on `imagelabel`/`imagebutton` and `placeholder-*` on `textbox` is enforced **only** in the editor. `<frame className="text-red-500" />` shows a squiggle in VS Code and compiles without complaint, emitting `TextColor3` onto a Frame. Treat it as a lint.
>
> Inside a component's `className` the rule is dropped rather than merely unenforced. The editor cannot know which host element `<Panel />` renders, so it completes and hovers the full utility set there and never raises `unsupported-host-utility`.

The editor also suppresses `unknown-theme-key` while the payload typed so far is still a prefix of a
real key. `bg-sla` never flashes a warning on the way to `bg-slate-800`, while `bg-nope` warns
immediately.

> **A config the extension cannot read now says so**
>
> A `vela.config.ts` the extension fails to load raises a notification naming the file and the reason, and a config that loads on a later save clears it. Without that the session stays silently on the default theme, reporting every key the project defines as unknown.

## Other editors

The server speaks LSP over stdio and has no VS Code dependency. Spawn it with:

```bash
npx --package @vela-rbxts/lsp vela-rbxts-lsp
```

The binary is named `vela-rbxts-lsp` but owned by the `@vela-rbxts/lsp` package. `vela-rbxts-lsp` on
its own is the VS Code extension id, published as a VSIX, so a bare `npx vela-rbxts-lsp` resolves
nothing.

**The server does not read `vela.config.ts` from disk.** It has no file loader and no config
discovery. The client evaluates each config and hands over the result. Without that, the server
falls back to the built-in default theme and every custom key reports `unknown-theme-key`.

Pass the configs in `initializationOptions`:

```json title="initializationOptions"
{
  "workspaceRoot": "/abs/path/to/project",
  "configs": [
    { "dir": "/abs/path/to/project", "json": "{\"theme\":{\"colors\":{ }}}" }
  ]
}
```

`workspaceRoot` and `configs` are top-level siblings. Get the shape wrong and there is no error. The
server deserializes with `from_value(...).ok().unwrap_or_default()`, so anything unparseable becomes
zero configs and you are back on the default theme.

Each entry is a directory plus the resolved config serialized as JSON. The server matches a file to the nearest containing `dir`, which is how per-package configs in a monorepo work.

To update configs after startup, send the custom method `vela-rbxts/setConfigs`. Its payload carries
only `configs`. That is what the VS Code extension does. It watches `**/vela.config.{ts,json}`,
evaluates each match through `@vela-rbxts/rbxtsc-host/project-config`, and pushes `{ dir, json }`
pairs at startup and on every change.

A client that cannot evaluate TypeScript can build the same JSON any way it likes — the server only
cares that the shape matches the resolved `TailwindConfig`.

### Platform packages

The `vela-rbxts-lsp` launcher resolves one of six platform-specific packages:

- `@vela-rbxts/lsp-darwin-arm64`
- `@vela-rbxts/lsp-darwin-x64`
- `@vela-rbxts/lsp-linux-arm64-gnu`
- `@vela-rbxts/lsp-linux-x64-gnu`
- `@vela-rbxts/lsp-linux-x64-musl`
- `@vela-rbxts/lsp-win32-x64-msvc`

Those six are what the release workflow publishes and what the VSIX bundles. The launcher's lookup
table also names `@vela-rbxts/lsp-linux-arm64-musl` and `@vela-rbxts/lsp-win32-arm64-msvc`, but
nothing builds them — on those two, build from source.

## See also

- [Configuration](https://docs.astra-void.xyz/vela-rbxts/reference/config.md) — the config shape the server expects.
- [Diagnostics](https://docs.astra-void.xyz/vela-rbxts/reference/diagnostics.md) — every code the editor can surface, and which ones also appear in a build.
- [Dynamic class names](https://docs.astra-void.xyz/vela-rbxts/guides/dynamic-class-names.md) — the case the editor cannot help you with, because nothing is reported at all.
