Skip to content

How to set up your editor for Hem

Install the VS Code extension, turn on format on save, use quick fixes, and connect other editors to the language server.

Updated View as Markdown

Editing Hem without a language server works, but diagnostics will report against generated output rather than authored code, and quick fixes will be unavailable.

VS Code

The extension is not on the Marketplace yet. Until it is, build and install the packaged .vsix from the repository:

pnpm --filter hem-vscode package
code --install-extension artifacts/hem-vscode.vsix

The extension registers .hem, activates when you open a Hem document, and starts its bundled language server. There is no separate server to install and nothing to put on your PATH.

Once it is running you get live syntax, semantic, and TypeScript diagnostics; document formatting; hover; go to definition, find references, and symbol rename across Hem and TypeScript; TypeScript-aware completions; and match-arm completions that fill in the arms an exhaustive match is missing.

Rename a symbol or find its uses

Use your editor’s Rename Symbol or Find All References command on an authored identifier. These operations follow the checked binding, so a shadowed binding has its own uses. Rename preserves import aliases and TypeScript shorthand property names. Constructor declarations, values, match arms, and Effect catch arms participate when the compiler can resolve their authored constructor binding. Renaming an unaliased imported constructor changes its actual declaration and tag consistently across the project.

Rename returns a single workspace edit with versions for open documents, including unsaved TypeScript buffers. The server refuses the complete edit if a location is generated scaffolding, ambiguous, stale, or in dependency/declaration files. It also refuses invalid identifiers and a new name already used elsewhere in an affected file, to avoid introducing capture or duplicate bindings. A refused rename leaves the source untouched; choose another name or edit manually. Unsupported programs and unproven constructor patterns do not receive speculative edits.

Constructor renames also change runtime identity: a variant member changes its _tag, and an error changes its _tag and Error.name (including the Data.TaggedError tag with Effect lowering). The server labels every constructor rename edit with the old and new tag and marks it as requiring confirmation. It does not migrate existing serialized values or external tag comparisons. This requires clients to advertise workspace.workspaceEdit.documentChanges, workspace.workspaceEdit.changeAnnotationSupport, and textDocument.rename.honorsChangeAnnotations. Other clients receive a refusal explaining how to use a compatible client or perform a manual migration; ordinary binding renames and references remain available.

Read signatures and navigate the outline

Signature help shows overloads, parameter documentation, and the current argument for calls into Hem and TypeScript modules. An unfinished call at the end of the document can be previewed by closing up to eight missing parentheses. Unfinished strings, missing brackets, and other parser errors are outside that recovery. The preview produces no source edits.

The document outline lists top-level bindings and declarations, plus variant and service members. Complete classes in a configured project also expose members when their names map exactly to authored source. It keeps authored source ranges and works in incomplete documents without a tsconfig.json. It does not list declarations inside functions.

VS Code highlights comments, strings, numbers, keywords, and types using Hem’s TextMate grammar. In complete, configured projects, type-aware semantic highlighting adds checked classifications for types, functions, parameters, variables, and properties, including imported symbols at their uses. It uses only unchanged, unambiguous authored spans; generated scaffolding, transformed names, and coarse mappings are omitted. The grammar remains the fallback for incomplete or unconfigured files and unmapped spans. The server supports full-document tokens, without range or delta requests.

Read an Effect contract

With hem.effectImportSource configured, hover an Effect value or an operation that returns one. Below the usual signature, Hem shows its checked channels:

Success: User
Error: DbError | UserNotFound
Requires: Users

Success is the result, Error is the typed failure channel, and Requires lists the services the caller must provide. never means that channel has no members. Existing documentation stays in the hover.

These types come from TypeScript’s checker and the configured Effect module. Hem leaves out the extra summary for unproven values and overloaded operations; their ordinary hover still works.

Add an import

After you type at least two characters, completions can add imports for exports already known to the TypeScript project, including sibling Hem and TypeScript files and packages referenced elsewhere in the project. The server offers an auto-import only when it can safely apply both the reference and import edits to the current Hem document. Discovering installed packages that no project file references is not supported yet.

Complete an Effect catch

On a blank line before a catch block’s closing brace, completion offers remaining typed errors and an explicit catch-all. Details show the input errors left after that arm; errors raised by the handler still belong to the result’s error channel. This also works while the block is empty, though an empty catch cannot compile.

Named suggestions currently require a closed error union and top-level error declarations in the same Hem file. Imported or nested constructors are not suggested by name. Unknown or mixed untagged error channels do not get speculative suggestions.

Provide missing services

At an explicit runner, HEM4102 or a TypeScript argument diagnostic can list the checked remaining services under Requires. HEM4102 also links to the operations that introduced them. Provide … with … adds explicit provision only when one Layer binding in scope covers every service, requires no additional services, and adds no incompatible errors. With no compatible Layer or multiple choices, the diagnostic explains why provision remains manual. The edit preserves namespace aliases and pipes and is tied to the current document version.

The automatic edit requires an exact authored argument span. A bound Effect value is supported; a complex call argument whose diagnostic maps to a larger statement may still need manual provision.

Format on save

The extension implements VS Code’s standard Format Document command, so enable formatting for the hem language:

{
  "[hem]": {
    "editor.formatOnSave": true
  }
}

Formatting is the same canonical output hem fmt produces. There is nothing to configure and no width setting, so a file formatted in your editor and a file formatted in CI are byte-identical.

Apply a quick fix

Some diagnostics carry a machine-applicable edit, offered as a quick fix on the diagnostic:

Diagnostic Fix
HEM2005 Insert the missing match arms
HEM2013 Insert mut on the binding you assigned to
HEM1036 Replace const with let
HEM1037 Replace var with let mut
HEM1035 Rewrite i++ as i += 1
HEM1032 Rewrite a generic arrow’s <T> as <T,>
HEM2003 Rewrite ??T as ?T

The inserted text is what hem fmt would print, so applying a fix never leaves the file unformatted.

Other editors

Any editor with an LSP client can talk to the server directly. It speaks LSP over stdin and stdout:

hem-lsp

Point your client at that command and associate it with the .hem extension. The server advertises incremental document sync, formatting, hover, go to definition, references, rename with preparation, quick-fix code actions, completion triggered on ., and workspace folders.

If diagnostics look wrong or stale

The server watches your source files and tsconfig*.json. A tsconfig change that it did not observe, such as one made by a script or a branch switch, can leave it holding a stale project. Restarting the language server picks up the new configuration.

If a diagnostic points at generated TypeScript rather than your Hem source, that is a bug worth reporting: every diagnostic is supposed to be remapped to authored coordinates before it reaches you.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close