- Rust 62.8%
- Shell 23.1%
- Tree-sitter Query 14.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github/workflows | ||
| languages/twig | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| extension.toml | ||
| LICENSE | ||
| README.md | ||
| rust-toolchain.toml | ||
| twig.svg | ||
Twig for Zed
Twig language support for Zed.
This extension combines a Tree-sitter grammar with the Twiggy language server. It supports Twig syntax highlighting, embedded HTML, completions, hover information, definitions, signatures, inlay hints, and document outlines for blocks and macros.
Framework-aware language features come from Twiggy. Twiggy supports Symfony, Craft CMS, and standalone Twig projects. The exact results depend on the selected framework and the PHP environment in the project.
Install the development version
Until this fork is published, install it as a development extension:
- Clone this repository.
- In Zed, run
zed: extensionsfrom the command palette. - Select
Install Dev Extension. - Choose this repository's root directory.
Zed compiles the Rust extension and grammar, then starts Twiggy when a Twig file opens. Files ending in .twig, .html.twig, or .twig.html are detected as Twig.
Twiggy needs PHP for framework indexing, diagnostics, and formatting. Make sure php is available in the shell environment inherited by Zed, or set phpExecutable to the executable used by the project.
Install Zed's Emmet extension for Emmet completions in Twig HTML. Its current manifest maps Twig buffers to the twig language ID, so this extension does not bundle a second Emmet server.
Configure Twiggy
When framework is omitted, Twiggy checks the root composer.json for symfony/twig-bundle or craftcms/cms in require. Set the framework explicitly for monorepos, standalone Twig projects, dependencies listed only in require-dev, or projects without a root composer.json:
{
"lsp": {
"twiggy-language-server": {
"settings": {
"twiggy": {
"framework": "craft",
"phpExecutable": "php"
}
}
}
}
}
Supported framework values are craft, symfony, twig, and ignore. Omitting the value enables detection. ignore keeps language-server features that do not require a framework environment.
Other Twiggy settings can be placed in the same object:
{
"lsp": {
"twiggy-language-server": {
"settings": {
"twiggy": {
"autoInsertSpaces": true,
"diagnostics": {
"twigCsFixer": false
},
"inlayHints": {
"block": true,
"macro": true,
"macroArguments": true
},
"phpExecutable": "php",
"symfonyConsolePath": "bin/console",
"vanillaTwigEnvironmentPath": ""
}
}
}
}
}
The extension leaves Twig CS Fixer disabled by default. Twiggy uses the same switch for diagnostics and formatting, so both remain disabled until diagnostics.twigCsFixer is true. Enable it only when the project has vincentlanglet/twig-cs-fixer installed and its PHP executable can run from Zed.
Use a local Twiggy binary
An explicit binary.path takes priority. If it is absent, the extension uses twiggy-language-server from the worktree's PATH, then falls back to its managed npm copy. The extension supplies --stdio when binary.arguments is omitted.
To select an exact binary:
{
"lsp": {
"twiggy-language-server": {
"binary": {
"path": "/absolute/path/to/twiggy-language-server",
"arguments": ["--stdio"]
}
}
}
}
If neither override is available, Zed installs the latest twiggy-language-server npm package in the extension work directory. A working installed copy remains available if a later update check or version read fails, but the extension retries the check after the language server restarts.
What is covered
- Twig expressions, filters, functions, tests, named arguments, arrow functions, and whitespace-control delimiters
- Twig blocks including
if,for,block,macro,set,apply,embed, andverbatim - HTML injection, including HTML's CSS and JavaScript injections
- Structural selection through complete Twig control-flow nodes, including the body and closing tag of
forandifblocks - Matching Twig delimiters and indentation for Twig bodies, arrays, objects, and argument lists
- Outline entries for blocks, macros, and captured
setblocks - Twiggy completions, definitions, hover information, signatures, and inlay hints where the project supports them
- Twig CS Fixer diagnostics and formatting when
diagnostics.twigCsFixeris enabled
Known limits
- Symfony UX Twig component name and prop completion depends on Twiggy and is not complete.
- Twiggy's automatic framework detection only checks the root
composer.jsonand itsrequireobject. Configureframeworkexplicitly when that does not describe the project. - Twig test names such as
definedandemptyparse as variables in the pinned grammar, so their highlighting follows variable highlighting. - Twig delimiter matching may cross-pair adjacent
elseifandelsetags because the grammar does not expose a query-safe delimiter wrapper for each branch. - Twig expressions placed directly inside HTML tag syntax can expose gaps between the Twig and injected HTML parsers.
- Zed's comment and bracket behavior inside injected HTML can differ from behavior inside a Twig expression.
- Syntax selection inside combined injected HTML can skip an otherwise valid parent HTML element. The combined HTML tree contains the element, but Zed's current expansion behavior does not always visit it.
- Emmet comes from Zed's separate Emmet extension and is unavailable when that extension is disabled or missing.
Development
Run the checks without starting Zed:
cargo fmt --check
cargo test
cargo clippy --target wasm32-wasip2 -- -D warnings
cargo check --target wasm32-wasip2
TWIG_GRAMMAR_DIR=grammars/twig ./scripts/check-grammar.sh
scripts/check-grammar.sh clones the pinned grammar when TWIG_GRAMMAR_DIR is omitted. Point it at the ignored local grammar checkout to avoid a network install. The script parses every fixture, compiles every language query, runs the selection regression query, and checks the body-indent and named-endblock captures.
Grammar changes still need a manual pass with tests/fixtures/kitchen-sink.twig. Install this repository as a Zed development extension, open the fixture, and inspect syntax highlighting, the syntax tree, delimiter matching, indentation, and the outline.
tests/fixtures/issue-1-selection.twig reproduces the selection-expansion case from upstream issue #1. With the caret on product.title, repeated editor::SelectLargerSyntaxNode actions should select the output expression and then the complete for block before reaching the outer template. editor::SelectSmallerSyntaxNode should retrace the same selections.
tests/fixtures/selection-html-boundary.twig covers nested HTML split by Twig output. Starting with the inner <span>, selection expansion must reach the complete outer <span>...</span> rather than stop at the raw HTML chunk before {{ recurrenceLabel }}.
The extension pins corneliusio/tree-sitter-twig to a full commit SHA. That fork keeps raw template chunks anonymous so they remain available for HTML injection without interrupting syntax selection.
See CONTRIBUTING.md for the acceptance checklist.
Credits
This extension uses kaermorchen/tree-sitter-twig and Twiggy. Both projects are licensed under MPL-2.0. This repository's existing copyright notices remain in LICENSE.
The extension is available under the MIT License.