Neovim indent plugin for Fennel code using both indentexpr and formatexpr.
{
'curist/fennel-indent.nvim',
ft = 'fennel',
opts = {
-- Optional: Configure semantic alignment (default shown)
semantic_alignment = {
'if', 'and', 'or', '..', '->', '->>', '-?>', '-?>>',
'%', '*', '+', '/', '-', '>=', '//', '<=', '^', '>', '<', '=', 'not=',
}
}
}The plugin automatically enables for .fnl files. Both approaches work seamlessly:
- Insert mode: Automatic indentation while typing
==: Indent current line=ap: Indent around paragraph- Manual trigger: Any standard Vim indentation command
gq: Format selection or motiongqG: Format entire file (recommended for large files - up to 333x faster thangg=G)gqap: Format around paragraph
require('fennel-indent').setup({
-- Default semantic alignment for multi-token forms (vector format)
semantic_alignment = {
'if', 'and', 'or', '..', '->', '->>', '-?>', '-?>>',
'%', '*', '+', '/', '-', '>=', '//', '<=', '^', '>', '<', '=', 'not=',
}
})augroup fennel_format_map
autocmd!
autocmd FileType fennel nnoremap <buffer> = gq
autocmd FileType fennel xnoremap <buffer> = gq
autocmd FileType fennel nnoremap <buffer> == gqq
augroup ENDor
vim.api.nvim_create_autocmd("FileType", {
pattern = "fennel",
callback = function()
vim.keymap.set({"n", "x"}, "=", "gq", { buffer = true })
vim.keymap.set("n", "==", "gqq", { buffer = true })
end,
})With semantic alignment enabled:
;; Aligned to first argument
(if condition-here
then-clause
else-clause)
;; Threading macros align consistently
(-> data
(map transform)
(filter predicate)
(reduce combine))
;; Boolean operators align
(and condition1
condition2
condition3)With semantic alignment disabled:
;; Structural indentation (base + 2)
(if condition-here
then-clause
else-clause)The plugin implements comprehensive indentation rules:
{:key1 value1
:key2 value2} ; Anchor at opening brace + 1
[item1
item2
item3] ; Vector elements align(let [binding1 value1
binding2 {:nested :table
:with :values}]
body);; Top-level comments at column 0
(function
;; Inner comments follow context rules
body)For complete specification, see specs/fennel-indent-parser.md.
Real-world benchmarks comparing different formatting approaches:
| Lines | gg=G (indentexpr) |
gqG (formatexpr) |
Performance Difference |
|---|---|---|---|
| 100 | 41ms (2.5/ms) | 34ms (2.9/ms) | 1.2x faster |
| 500 | 203ms (2.5/ms) | 37ms (13.5/ms) | 5.5x faster |
| 1000 | 719ms (1.4/ms) | 38ms (26.5/ms) | 19.0x faster |
| 2000 | 2.9s (0.7/ms) | 41ms (49.2/ms) | 70.3x faster |
| 5000 | 18.5s (0.3/ms) | 53ms (94.0/ms) | 348x faster |
Key Takeaway: Use gqG instead of gg=G for whole-file formatting on large files.
Why the difference?
gg=G: Callsindentexprline-by-line (O(n²) behavior)gqG: Usesformatexprwith range-based processing (O(n), single-pass)
- Core parser: Compiled from
scripts/indent-parser.fnlto pure Lua with tokenization optimizations - Build system: Uses custom redbean-based test runner
- Frame stack: Tracks nested contexts with precedence rules
indentexpr: Real-time indentation while typing, works with==and similar commandsformatexpr: Reliable formatting that bypasses known Vim/Neovimgg=Glimitations- Consistent results: Both approaches use identical core logic
Performance Note: The gg=G command calls indentexpr line-by-line, resulting in O(n²) behavior for large files. Use gqG (formatexpr) for significantly better performance on files >500 lines.
make test # Run all tests
make benchmark # Run performance testsThe project follows strict TDD methodology with comprehensive test coverage:
- Unit tests: 19 tests covering all indentation rules
- Integration tests: 6 tests using headless Neovim
- Performance tests: Benchmarking on large files
- Spec compliance: All tests implement fennel-indent-parser.md
- Follow TDD: Write tests first, then implement
- Run
make preflightbefore commits - All changes must pass the complete test suite
- Maintain spec compliance with
specs/fennel-indent-parser.md
This project was developed with assistance from large language models (LLMs).
LLMs were used to help with:
- Drafting and iterating on code implementations
- Generating boilerplate and repetitive structures
- Refining documentation and comments
MIT License - see LICENSE file for details.