Skip to content

curist/fennel-indent.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fennel-indent.nvim

Neovim indent plugin for Fennel code using both indentexpr and formatexpr.

Installation

Lazy.nvim

{
  'curist/fennel-indent.nvim',
  ft = 'fennel',
  opts = {
    -- Optional: Configure semantic alignment (default shown)
    semantic_alignment = {
      'if', 'and', 'or', '..', '->', '->>', '-?>', '-?>>',
      '%', '*', '+', '/', '-', '>=', '//', '<=', '^', '>', '<', '=', 'not=',
    }
  }
}

Usage

The plugin automatically enables for .fnl files. Both approaches work seamlessly:

Line-by-line Indentation (indentexpr)

  • Insert mode: Automatic indentation while typing
  • ==: Indent current line
  • =ap: Indent around paragraph
  • Manual trigger: Any standard Vim indentation command

Format Commands (formatexpr)

  • gq: Format selection or motion
  • gqG: Format entire file (recommended for large files - up to 333x faster than gg=G)
  • gqap: Format around paragraph

Configuration

Default Configuration

require('fennel-indent').setup({
  -- Default semantic alignment for multi-token forms (vector format)
  semantic_alignment = {
    'if', 'and', 'or', '..', '->', '->>', '-?>', '-?>>',
    '%', '*', '+', '/', '-', '>=', '//', '<=', '^', '>', '<', '=', 'not=',
  }
})

Recommend mappings

augroup fennel_format_map
  autocmd!
  autocmd FileType fennel nnoremap <buffer> = gq
  autocmd FileType fennel xnoremap <buffer> = gq
  autocmd FileType fennel nnoremap <buffer> == gqq
augroup END

or

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,
})

Semantic Alignment Examples

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)

Indentation Rules

The plugin implements comprehensive indentation rules:

Tables & Vectors

{:key1 value1
 :key2 value2}        ; Anchor at opening brace + 1

[item1
 item2
 item3]               ; Vector elements align

Nested Structures

(let [binding1 value1
      binding2 {:nested :table
                :with :values}]
  body)

Comments

;; Top-level comments at column 0
(function
  ;; Inner comments follow context rules
  body)

For complete specification, see specs/fennel-indent-parser.md.

Performance

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: Calls indentexpr line-by-line (O(n²) behavior)
  • gqG: Uses formatexpr with range-based processing (O(n), single-pass)

Technical Details

Architecture

  • Core parser: Compiled from scripts/indent-parser.fnl to pure Lua with tokenization optimizations
  • Build system: Uses custom redbean-based test runner
  • Frame stack: Tracks nested contexts with precedence rules

Dual Implementation Benefits

  • indentexpr: Real-time indentation while typing, works with == and similar commands
  • formatexpr: Reliable formatting that bypasses known Vim/Neovim gg=G limitations
  • Consistent results: Both approaches use identical core logic

Known Limitations

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.

Development

Testing

make test          # Run all tests
make benchmark     # Run performance tests

Test-Driven Development

The 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

Contributing

  1. Follow TDD: Write tests first, then implement
  2. Run make preflight before commits
  3. All changes must pass the complete test suite
  4. Maintain spec compliance with specs/fennel-indent-parser.md

AI / LLM Usage

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

License

MIT License - see LICENSE file for details.

About

fennel indentation for neovim

Resources

License

Stars

3 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors