polytmpl#
A polyglot Go-template language toolkit — syntax highlighting, diagnostics, and completion for Go templates embedded in any base language (YAML, JSON, TOML, HTML, …).
Why#
Go templates are widely used outside of Go's own text/template callers — for Helm charts, CI/CD pipelines, test fixtures, configuration generators, and more. Each of these embeds Go template syntax inside a different base language: YAML, JSON, TOML, INI, Dockerfile, SQL, shell scripts, etc.
JetBrains GoLand handles this well: you can associate file extensions like *.yaml.tmpl and *.json.tmpl with Go-templated variants of YAML and JSON, and the IDE highlights both layers correctly. No equivalent exists for VS Code or Zed today. A survey of existing extensions (existing-extensions.md) shows that:
- The most flavour-aware VS Code extension hard-codes its mappings (no user configuration).
- The most configurable VS Code extension is limited to a few base languages and applies its language list globally.
- The only Zed extension is described as "rudimentary" by its own author.
- No extension on either editor supports configurable delimiters or custom template function signatures.
- No project shares code between editors.
polytmpl aims to fill that gap.
Goals#
| Goal | Status | |
|---|---|---|
| A | Syntax highlighting for both the base language and the Go template parts | required |
| B | Configurable mapping between base language (flavour) and file extension or pattern | required |
| C | Compatible with both VS Code and Zed | extra |
| D | Configurable template delimiters (default {{ / }}; allow {%/%} or any custom pair, optionally bound to file pattern) |
extra |
| E | Configurable list of template functions and signatures, ideally loaded from a Go package, so the editor can flag unknown functions and bad call sites | extra |
| F | Highlighting for Go templates inside Markdown code blocks fenced as [base-language]-go-template (e.g. ```yaml-go-template); a bare go-template fence treats the block as plain text with template parts highlighted on top |
extra |
The required goals (A, B) define the MVP. The extras (C, D, E, F) define the long-term scope and shape the architecture from day one — especially D and E, which a grammar-only approach cannot deliver.
Architecture#
polytmpl uses a three-layered, decoupled architecture composed of standard Tree-Sitter grammars, a Go-based Language Server (LSP), and editor-specific adapter shells.
For the detailed design rationale, component responsibilities, and rendering paths, see:
- System Architecture Documentation — comprehensive description of the layers and parser pipelines.
- ADR 01: Core Architecture — the formal Architecture Decision Record explaining the "why" behind this technical layout.
Project structure#
The repository is structured as a single pnpm monorepo. Each directory corresponds to an architectural piece:
polytmpl/
├── README.md
├── mise.toml
├── docs/
├── scripts/ // repo maintenance scripts (pnpm workspace)
├── tree-sitter-gotmpl/ // the Go-template tree-sitter grammar
├── lsp/ // the polytmpl language server (Go)
├── vscode/ // VS Code extension shell (TypeScript)
└── zed/ // Zed extension shell (Rust → WebAssembly)
tree-sitter-gotmpl/— Standalone tree-sitter grammar for Go template syntax. Generates optimized C source code used as-is by Zed or compiled to WASM for browser playgrounds.lsp/— The language server, written in Go. Handles parsing custom delimiters (D), loading custom Go package functions (E), and emitting semantic tokens, completions, and diagnostics.vscode/— VS Code extension. A thin TypeScript shell that registers language configurations, launches the LSP, and manages settings. Seevscode/README.mdfor VS Code-specific docs: highlighting strategy, current state, build & test.zed/— Zed extension. A thin Rust adapter compiled to WASM that integrates the grammar and spawns the LSP binary. Seezed/README.mdfor Zed-specific docs: the inverted-injection pattern, build & test, the current language registry, and how to add a new flavour.scripts/— Repo maintenance scripts (pnpm workspacepolytmpl-scripts). Seescripts/README.md.
Configuration#
User-facing settings are standardized around base-language mapping (flavours), custom template delimiters, and function package paths.
Under the recommended naming convention, files use the base language extension first, followed by the .tmpl suffix (e.g., simple.html.tmpl, config.yaml.tmpl, data.json.tmpl).
{
"polytmpl.flavours": {
"yaml": { "patterns": ["**/*.yaml.tmpl", "**/*.yml.tmpl"] },
"json": { "patterns": ["**/*.json.tmpl"] },
"toml": { "patterns": ["**/*.toml.tmpl"] },
"html": { "patterns": ["**/*.html.tmpl"] },
"sql": { "patterns": ["**/*.sql.tmpl"] },
"dockerfile": { "patterns": ["**/Dockerfile.tmpl"] }
},
"polytmpl.delimiters": {
"default": { "left": "{{", "right": "}}"},
"overrides": [
{ "patterns": ["**/*.j2.tmpl"], "left": "{%", "right": "%}" }
]
},
"polytmpl.functions": {
"packages": ["./internal/templatefuncs"]
}
}
Build & Test#
This project uses mise to manage compilers and tools (Node, Go, Rust, Tree-sitter CLI). Run mise install once at the repository root to set up the toolchain.
1. Tree-sitter Grammar (tree-sitter-gotmpl/)#
To compile the grammar and run its unit tests:
# Compile grammar.js into C code
pnpm --filter tree-sitter-gotmpl run generate
# Run AST unit tests
pnpm --filter tree-sitter-gotmpl test
# Open interactive visual playground in browser
pnpm --filter tree-sitter-gotmpl run playground
2. Zed Extension#
See zed/README.md for the full Zed workflow — build & test, dev-extension installation, the inverted-injection pattern, the current language registry, and how to add a new flavour via the workspace generator in scripts/.
3. VS Code Extension#
See vscode/README.md for the full VS Code workflow — build & test, Extension Development Host launch, LSP binary discovery, and the current state of highlighting / diagnostics.
Status#
Active development. Grammar (Layer 1) is fully complete with 100% passing tests.
TODOs#
- Define a license
- Implement Go LSP (Layer 2)
- Publish to VS Code: then set the publisher in vscode/package.json, under
publisher. - Tag a release and set the correct reference in zed/extension.toml, under
commit