- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
slsls
[WARN] This is AI-slopped experimental project
A minimal Language Server Protocol (LSP) server for SaltStack .sls files.
Three validation passes run on each open/change:
- Jinja block-closure checking — flags unclosed opening blocks (
{% if %}without{% endif %}), closing blocks without a matching opener ({% endif %}alone), and mismatched closers ({% if %}closed by{% endfor %}). Tracks nesting, handles Jinja whitespace-control delimiters ({%- ... -%},{%+ ... +%}), recognizes keywords without surrounding whitespace ({% if(x) %}), ignores block-looking content inside{% raw %}blocks, and never treats tag delimiters found inside Jinja string literals as real tags. - Jinja expression checking (minijinja) — when the block structure is sound,
the whole document is compiled as a Jinja template; compile failures are
reported as
jinja-invalid-expression. Salt-only tags such as{% import_yaml %}and{% load_yaml as x %}...{% endload %}blocks anddostatements with inline conditionals are understood and not flagged. Jinja2 implicit tuples ({% for x in 'a','b' %},{% if v in 'a','b' %}) are accepted as well. - YAML syntax checking — Jinja tags are masked out before parsing so the YAML parser never sees them; syntax errors are reported with their locations.
No other LSP language features are implemented.
Build
cargo build --release
The resulting executable is target/release/sls-lsp.
Neovim setup
Neovim 0.11+ with the built-in LSP client:
vim.filetype.add({
extension = {
sls = "sls",
},
})
vim.lsp.config("sls_lsp", {
cmd = { "sls-lsp" },
filetypes = { "sls" },
root_markers = { ".git" },
})
vim.lsp.enable("sls_lsp")
Verify with:
:set filetype?
:checkhealth vim.lsp
:lua =vim.lsp.get_clients({ bufnr = 0 })
Diagnostics
| Code | Description |
|---|---|
jinja-unclosed-block |
Opening block without its closer. |
jinja-unexpected-closing-block |
Closing block with no matching opener. |
jinja-mismatched-closing-block |
Closing block does not match the top of the stack. |
jinja-invalid-expression |
Jinja template compile error (minijinja). |
yaml-syntax-error |
YAML parse error (Jinja tags are masked first). |
All diagnostics share the source sls-jinja-blocks, use error severity, and
report positions as zero-based line numbers and UTF-16 code unit columns.
When a Jinja tag is malformed (an unterminated {%, {{ or {#), only the
Jinja error is reported; YAML checking is skipped for that revision because
the masked view of the file would not be faithful.
Quick fixes are offered per Jinja closure issue and only for diagnostics overlapping the requested range: add a missing closer (inserted at the end of the document, keeping the opener's indentation; when deeper blocks are still open, their closers are appended first so nested fixes compose in one shot), remove an unexpected closer, or replace a mismatched closer. Expression errors offer no automated fix.
Supported messages
initialize, initialized, shutdown, exit, textDocument/didOpen,
textDocument/didChange (full document sync), textDocument/didClose,
textDocument/codeAction (quick fixes per Jinja issue).
Development
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
cargo build --release