Add hemx-lsp for stdio LSP diagnostics, completion, and hover while preserving HTML editor tooling for .heml files. Teach hemx-build to expose generated target and derive-known template context facts, including simple h-for locals, so editor help comes from build-owned facts instead of editor-only parsers. Wire VS Code/Cursor and Neovim documentation and extend the Workout exemplar with a real h-for plan loop for end-to-end proof. req: diag/004 req: diag/005 req: diag/006
4.9 KiB
.heml editor support
.heml authoring should feel like HTML first: keep normal HTML highlighting,
formatting, tag matching, and tree-sitter queries, then layer hemx compiler
feedback on top. The shared authority is hemx-build diagnostics plus
docs/hemplate-syntax.md; editors must not carry separate parser rules for the
hemplate language. req: diagnostics/004 req: diagnostics/005
Shared language service
hemx-lsp owns editor protocol behavior; hemx-xtask stays a project workflow
runner, not the language-service home.
From the repo, run the stdio language service:
cargo run -p hemx-lsp -- lsp
Or install the same binary and run it directly:
cargo install --path hemx-lsp
hemx-lsp lsp
It speaks standard LSP framing over stdin/stdout. Today it supports open/change/save
text synchronization, compiler-backed textDocument/publishDiagnostics, and
small completion/hover entries for documented .heml constructs from
docs/hemplate-syntax.md. Generated targets discovered by hemx-build in an
open document are offered as ui::target completions. For derive-known template
contexts, self. field completion/hover and simple h-for locals such as
exercise in &self.plan come from hemx-owned Rust struct facts, not an editor
parser or rust-analyzer proxy. It intentionally does not format templates, parse
JavaScript, parse arbitrary Rust expressions, or replace HTML tooling.
For scripts and editor wrappers that only need one-shot diagnostics, run:
cargo run -p hemx-lsp -- diagnostics path/to/file.heml
The one-shot command prints a JSON object shaped like LSP
textDocument/publishDiagnostics parameters:
{
"uri": "file:///absolute/path/to/file.heml",
"diagnostics": [
{
"range": { "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 0 } },
"severity": 1,
"source": "hemx-build",
"code": "unkeyed-generated-target",
"message": "data-hemx-slot=\"todo_row\" is inside h-for=\"todo in &self.todos\" without h-key",
"data": {
"directive": "data-hemx-slot",
"target": "todo_row",
"expected": "a stable template h-key on h-for=\"todo in &self.todos\" so generated keyed helpers such as ui::todo_row.replace(row) can target this partial",
"repair": "add h-key=\"todo.id\" to that h-for; dynamic +data-key on the child is rendered HTML, not the template fact hemx uses for generated targets"
}
}
]
}
The diagnostic payload comes from hemx-build; editor integrations should display
it as-is instead of recreating the rule.
VS Code and Cursor
Use the shared repo extension in editors/vscode-hemx for VS Code and Cursor.
It sets .heml to the built-in HTML language mode, starts hemx-lsp, and maps
LSP diagnostics/completion/hover into the editor without adding a separate grammar.
req: diagnostics/005
When the workspace root is this repository, the extension starts:
cargo run -p hemx-lsp -- lsp
In app workspaces, install hemx-lsp and the extension starts:
hemx-lsp lsp
If you do not use the extension, keep the same HTML association manually so HTML syntax highlighting, completion, folding, and tag matching keep working:
{
"files.associations": {
"*.heml": "html"
}
}
Use the one-shot diagnostics command only as a fallback task if your editor cannot launch a stdio LSP server. Do not copy hemplate syntax into a VS Code/Cursor-only grammar.
Neovim
Use HTML filetype and tree-sitter HTML highlighting for .heml:
vim.filetype.add({ extension = { heml = "html" } })
If you use nvim-treesitter, this keeps .heml on the HTML parser. Start the
shared LSP service with Neovim's built-in client:
vim.lsp.start({
name = "hemx-heml",
cmd = { "cargo", "run", "-p", "hemx-lsp", "--", "lsp" },
root_dir = vim.fs.root(0, { "Cargo.toml", ".git" }) or vim.fn.getcwd(),
})
Use cargo run -p hemx-lsp -- diagnostics % only as a fallback if LSP is
unavailable. Do not add a separate .heml tree-sitter grammar unless HTML
injection can no longer represent the documented syntax in
docs/hemplate-syntax.md.
Known limits and boundary
If hemx-lsp is missing, crashes, or cannot be started by the editor, .heml
files should still open as HTML and keep normal highlighting/tag tooling; use the
one-shot diagnostics command until the service is available.
This foundation intentionally supports diagnostics, completion, and hover/help.
It does not yet implement broad go-to-definition/reference navigation, formatting,
refactoring, semantic Rust analysis, arbitrary Rust expression parsing, or a
.heml tree-sitter parser fork.
Editor support may add startup glue, diagnostics display, completion, hover/help,
and navigation over documented .heml facts. It must not add a second template
language, editor-owned formatter, selector targeting model, JavaScript expression
layer, or editor-specific diagnostics that disagree with hemx-build.