From 9f25569788b6f360e907d4b333b4d5e0f6f71eae Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 29 Jan 2026 15:05:35 -0500 Subject: [PATCH] Initial commit: Neovim config with Fennel via nfnl --- .gitignore | 5 + .nfnl.fnl | 1 + CLAUDE.md | 78 ++++++++ README.md | 461 +++++++++++++++++++++++++++++++++++++++++++ fnl/config/init.fnl | 139 +++++++++++++ fnl/plugins/init.fnl | 74 +++++++ init.lua | 26 +++ lua/bootstrap.lua | 54 +++++ 8 files changed, 838 insertions(+) create mode 100644 .gitignore create mode 100644 .nfnl.fnl create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 fnl/config/init.fnl create mode 100644 fnl/plugins/init.fnl create mode 100644 init.lua create mode 100644 lua/bootstrap.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..592dfa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +undodir + +# Generated Lua files (compiled from Fennel by nfnl) +lua/config/ +lua/plugins/ diff --git a/.nfnl.fnl b/.nfnl.fnl new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.nfnl.fnl @@ -0,0 +1 @@ +{} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1e4d331 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# Claude Code Context + +This is a Neovim configuration using Fennel (a Lisp that compiles to Lua) via nfnl. + +## Key Concepts + +- **nfnl** compiles `.fnl` files to `.lua` on save +- **lazy.nvim** is the plugin manager +- Bootstrap files are in Lua; user config is in Fennel + +## File Structure + +``` +init.lua - Entry point (Lua, do not convert to Fennel) +lua/bootstrap.lua - Core plugins in Lua (nfnl, treesitter, telescope) +lua/config/ - AUTO-GENERATED from fnl/config/ - do not edit +lua/plugins/ - AUTO-GENERATED from fnl/plugins/ - do not edit +fnl/config/ - User config in Fennel (options, keymaps, autocmds) +fnl/plugins/ - Plugin specs in Fennel +.nfnl.fnl - nfnl configuration +``` + +## Editing Rules + +1. **Never edit files in `lua/config/` or `lua/plugins/`** - they are auto-generated +2. **Edit Fennel files in `fnl/`** - they compile to `lua/` on save +3. **`init.lua` and `lua/bootstrap.lua` stay as Lua** - they bootstrap nfnl + +**IMPORTANT FOR CLAUDE:** After editing any `.fnl` file, compile all Fennel files to Lua: +```bash +cd ~/.config/nvim && nvim --headless -c "NfnlCompileAllFiles" -c "qa" +``` + +## Fennel Syntax Quick Reference + +```fennel +;; Set vim option +(set vim.opt.number true) + +;; Define keymap +(vim.keymap.set :n "x" ":cmd" {:desc "Description"}) + +;; Local variable +(local foo (require :foo)) + +;; Let binding +(let [x 1 y 2] (+ x y)) + +;; Function +(fn my-func [arg] (print arg)) + +;; Lambda +(fn [x] (* x 2)) + +;; Plugin spec (lazy.nvim format) +{:1 "author/plugin" + :ft ["fennel" "lua"] + :config (fn [] (setup-code))} +``` + +## Common Tasks + +### Add a plugin +Edit `fnl/plugins/init.fnl`, add spec to the vector, save. + +### Add a keymap +Edit `fnl/config/init.fnl`, add `(vim.keymap.set ...)`, save. + +### Add an option +Edit `fnl/config/init.fnl`, add `(set vim.opt.foo value)`, save. + +### Force recompile all Fennel +Run `:NfnlCompileAllFiles` in Neovim. + +## Leader Keys + +- Leader: `` +- Local leader: `` (same as leader, used by Conjure and paredit) diff --git a/README.md b/README.md new file mode 100644 index 0000000..824c04d --- /dev/null +++ b/README.md @@ -0,0 +1,461 @@ +# Neovim Fennel/Clojure Configuration + +A Neovim configuration optimized for Lisp development (Fennel, Clojure, Scheme, Racket) using [Fennel](https://fennel-lang.org/) as the configuration language. + +## Prerequisites + +- **Neovim 0.11+** (uses built-in LSP configuration) +- **Git** (for plugin installation) + +### Optional but Recommended + +Install language servers for full IDE features: + +```bash +# Clojure LSP (for Clojure/ClojureScript) +brew install clojure-lsp/brew/clojure-lsp + +# Fennel Language Server (for Fennel) +cargo install fennel-language-server + +# Lua Language Server (for Lua/Neovim plugin development) +brew install lua-language-server +``` + +Or use Mason inside Neovim: `:MasonInstall clojure-lsp lua-language-server` + +## Quick Start + +1. Clone this repo to `~/.config/nvim` +2. Open Neovim - plugins install automatically on first launch +3. Open a `.fnl` or `.clj` file and start editing + +## Understanding Lisp Editing + +If you're new to Lisp, here are the key concepts that make editing different from other languages. + +### S-Expressions (Sexps) + +All Lisp code is made of nested parenthesized expressions called **s-expressions** or **sexps**: + +```clojure +(defn greet [name] ; <- this whole thing is a "form" or "sexp" + (str "Hello, " name)) ; <- this is a nested form +``` + +- **Form**: A complete s-expression (anything in balanced parens/brackets/braces) +- **Element**: A single item - either an atom (`name`, `42`, `:keyword`) or a form + +### Structural Editing + +Instead of editing text character-by-character, Lisp programmers edit **structures**. The paredit plugin keeps your parentheses balanced automatically and lets you manipulate code by its structure. + +**Example - Slurping:** +```clojure +;; Before: cursor inside the (+ 1 2) form +(foo (+ 1 2) 3 4) + +;; After pressing >) (slurp forward) - pulls 3 into the form +(foo (+ 1 2 3) 4) +``` + +**Example - Barfing:** +```clojure +;; Before: cursor inside the (+ 1 2 3) form +(foo (+ 1 2 3) 4) + +;; After pressing <) (barf forward) - pushes 3 out of the form +(foo (+ 1 2) 3 4) +``` + +### Interactive Development (REPL) + +Lisp development is **interactive**. You connect to a running program (REPL) and evaluate code as you write it. Conjure handles this - you can evaluate any expression and see results immediately. + +--- + +## Keybindings Reference + +**Leader key: `Space`** + +Press `Space` and wait to see available options via which-key. + +### General Navigation + +| Key | Action | +|-----|--------| +| `Ctrl-h/j/k/l` | Move between windows | +| `Space f f` | Find files (Telescope) | +| `Space f g` | Live grep in project | +| `Space f b` | List open buffers | +| `Space f h` | Search help tags | +| `Space b n` | Next buffer | +| `Space b p` | Previous buffer | +| `Space b d` | Delete buffer | + +### General Editing + +| Key | Action | +|-----|--------| +| `jk` | Escape insert mode | +| `Space w` | Save file | +| `Space f s` | Save all files | +| `Space q` | Quit | +| `Esc` | Clear search highlight | + +### LSP (Language Server) + +Available when editing files with LSP support (Clojure, Fennel, Lua): + +| Key | Action | +|-----|--------| +| `gd` | Go to definition | +| `gD` | Go to declaration | +| `gr` | Find references | +| `gi` | Go to implementation | +| `K` | Show hover documentation | +| `Space r n` | Rename symbol | +| `Space c a` | Code actions | +| `Space e` | Show diagnostic popup | +| `Space F` | Format buffer | +| `[d` | Previous diagnostic | +| `]d` | Next diagnostic | + +**Commands:** `:LspInfo`, `:LspStart`, `:LspStop`, `:LspRestart`, `:LspLog` + +--- + +## Conjure - REPL Integration + +Conjure connects your editor to a running REPL, allowing you to evaluate code interactively. + +### Starting a REPL + +**Clojure (deps.edn):** +```bash +clj -M:repl +``` + +**Clojure (Leiningen):** +```bash +lein repl +``` + +**Fennel:** +```bash +fennel --repl +``` + +Conjure auto-connects when you open a Lisp file and a REPL is running. + +### Evaluation Keybindings + +All Conjure bindings start with `Space` (local leader): + +| Key | Action | +|-----|--------| +| `Space e e` | Evaluate form under cursor | +| `Space e r` | Evaluate root (top-level) form | +| `Space e b` | Evaluate entire buffer | +| `Space e f` | Evaluate file | +| `Space e !` | Evaluate form and replace with result | +| `Space e w` | Evaluate word under cursor | + +### Log Buffer + +Results appear in a floating HUD window. To interact with the full log: + +| Key | Action | +|-----|--------| +| `Space l v` | Open log in vertical split | +| `Space l s` | Open log in horizontal split | +| `Space l t` | Open log in new tab | +| `Space l q` | Close log windows | +| `Space l r` | Reset/clear the log | + +### Documentation + +| Key | Action | +|-----|--------| +| `K` | Show documentation for symbol under cursor | + +### Tip: Learn Interactively + +Run `:ConjureSchool` for an interactive tutorial inside Neovim. + +--- + +## nvim-paredit - Structural Editing + +Paredit keeps parentheses balanced and provides structural editing commands. + +**All paredit operations are dot-repeatable.** Press `.` to repeat the last structural edit: + +```clojure +;; Start with cursor inside (+ 1 2) +(foo (+ 1 2) a b c d e) + +;; >) slurp forward +(foo (+ 1 2 a) b c d e) + +;; . repeat slurp +(foo (+ 1 2 a b) c d e) + +;; . repeat again +(foo (+ 1 2 a b c) d e) +``` + +### Navigation + +Move by **elements** (atoms or forms) rather than words: + +| Key | Action | +|-----|--------| +| `W` | Next element head | +| `B` | Previous element head | +| `E` | Next element tail | +| `gE` | Previous element tail | +| `(` | Jump to parent form's opening paren | +| `)` | Jump to parent form's closing paren | +| `T` | Jump to top-level form's head | + +### Slurp and Barf + +**Slurp**: Pull the next/previous element INTO the current form +**Barf**: Push the last/first element OUT OF the current form + +| Key | Action | +|-----|--------| +| `>)` | Slurp forward (pull next element in) | +| `<(` | Slurp backward (pull previous element in) | +| `<)` | Barf forward (push last element out) | +| `>(` | Barf backward (push first element out) | + +**Visual example - forward operations:** +```clojure +;; Cursor inside: (+ 1 2) +(foo (+ 1 2) 3 4) + +;; >) slurp forward - pull 3 into the form +(foo (+ 1 2 3) 4) + +;; >) again - pull 4 in +(foo (+ 1 2 3 4)) + +;; <) barf forward - push 4 back out +(foo (+ 1 2 3) 4) +``` + +**Visual example - backward operations:** +```clojure +;; Cursor inside: (+ 1 2) +(a b (+ 1 2)) + +;; <( slurp backward - pull b into the form +(a (b + 1 2)) + +;; >( barf backward - push b back out +(a b (+ 1 2)) +``` + +### Dragging Elements + +Move elements/forms left and right within their parent: + +| Key | Action | +|-----|--------| +| `>e` | Drag element right | +| `f` | Drag form right | +| `e drag element right - swap a and b +(foo b a c) +``` + +### Wrapping + +Wrap an element in delimiters: + +| Key | Action | +|-----|--------| +| `cse(` or `cse)` | Wrap element in `()` | +| `cse[` or `cse]` | Wrap element in `[]` | +| `cse{` or `cse}` | Wrap element in `{}` | + +### Splice (Unwrap) + +| Key | Action | +|-----|--------| +| `dsf` | Splice - delete surrounding form, keeping contents | + +**Example:** +```clojure +;; Before (cursor inside the when form) +(when true (println "hello")) + +;; dsf - splice, removing the surrounding parens +when true (println "hello") +``` + +### Raise + +Replace the parent form with the current form/element: + +| Key | Action | +|-----|--------| +| `Space o` | Raise form (replace parent with current form) | +| `Space O` | Raise element (replace parent with current element) | + +**Example:** +```clojure +;; Before: cursor on (inner value) +(outer (inner value)) + +;; Space o - raise form, replacing parent with current form +(inner value) +``` + +### Text Objects + +Use with operators like `d`, `c`, `y`, `v`: + +| Text Object | Meaning | +|-------------|---------| +| `af` | Around form (including parens) | +| `if` | Inside form (excluding parens) | +| `aF` | Around top-level form | +| `iF` | Inside top-level form | +| `ae` | Around element | +| `ie` | Inside element | + +**Examples:** +- `daf` - Delete around form (delete entire form including parens) +- `cif` - Change inside form (replace form contents) +- `yae` - Yank around element + +--- + +## Workflow Examples + +### Editing a Clojure Function + +1. Start your REPL: `clj` in terminal +2. Open your `.clj` file +3. Write a function: + ```clojure + (defn add [a b] + (+ a b)) + ``` +4. `Space e r` to evaluate the top-level form +5. Test it: type `(add 1 2)` and `Space e e` to evaluate +6. See `3` appear in the HUD + +### Refactoring with Paredit + +Transform `(if test a b)` to `(when test a)`: + +1. Position cursor on `if` +2. `ciw` to change word, type `when` +3. Move to `b`, `dae` to delete the element +4. Done: `(when test a)` + +### Wrapping Code in a Let + +```clojure +;; Start with: +(+ x y) + +;; Position on the form, then: cse( +((+ x y)) + +;; Now you have wrapped parens. Type your let: +(let [z 1] (+ x y)) +``` + +--- + +## File Structure + +``` +~/.config/nvim/ +├── init.lua # Entry point (Lua) +├── lua/ +│ ├── bootstrap.lua # Core plugins (nfnl, treesitter, telescope) +│ ├── config/ # AUTO-GENERATED from fnl/config/ +│ └── plugins/ # AUTO-GENERATED from fnl/plugins/ +└── fnl/ + ├── config/init.fnl # Your settings, keymaps, LSP config + └── plugins/init.fnl # Plugin specifications +``` + +**Important:** Edit files in `fnl/`, not `lua/config/` or `lua/plugins/` - those are auto-generated. + +--- + +## Customization + +### Add a Plugin + +Edit `fnl/plugins/init.fnl`, add a spec to the vector: + +```fennel +{:1 "author/plugin-name" + :config (fn [] (setup-code-here))} +``` + +### Add a Keymap + +Edit `fnl/config/init.fnl`: + +```fennel +(vim.keymap.set :n "x" ":SomeCommand" {:desc "Do something"}) +``` + +### Change the Colorscheme + +Edit the tokyonight config in `fnl/plugins/init.fnl`, or add a different colorscheme plugin. + +--- + +## Troubleshooting + +### LSP not working? + +1. Check if the server is installed: `:LspInfo` +2. Check the log: `:LspLog` +3. Ensure you're in a project with root markers (`.git`, `deps.edn`, etc.) + +### Conjure not connecting? + +1. Make sure your REPL is running before opening the file +2. For Clojure: ensure nREPL is exposed (default port 7888 or `.nrepl-port` file) +3. Check `:ConjureLog` for connection errors + +### Paredit feels wrong? + +Structural editing takes practice. Start with just `>)` and `<)` for slurp/barf, then gradually add more commands. The `:help nvim-paredit` documentation is excellent. + +--- + +## Learning Resources + +- **Conjure Interactive Tutorial**: `:ConjureSchool` +- **Fennel Language**: https://fennel-lang.org/tutorial +- **Clojure for the Brave and True**: https://www.braveclojure.com/ +- **Paredit Guide** (Emacs, but concepts apply): https://www.emacswiki.org/emacs/ParEdit + +--- + +## Credits + +- [nfnl](https://github.com/Olical/nfnl) - Fennel to Lua compiler for Neovim +- [Conjure](https://github.com/Olical/conjure) - Interactive REPL environment +- [nvim-paredit](https://github.com/julienvincent/nvim-paredit) - Structural editing +- [lazy.nvim](https://github.com/folke/lazy.nvim) - Plugin manager +- [tokyonight.nvim](https://github.com/folke/tokyonight.nvim) - Colorscheme diff --git a/fnl/config/init.fnl b/fnl/config/init.fnl new file mode 100644 index 0000000..1dc7071 --- /dev/null +++ b/fnl/config/init.fnl @@ -0,0 +1,139 @@ +;; Main Fennel configuration +;; This file is compiled to lua/config/init.lua by nfnl + +;; Options +(set vim.opt.number true) +(set vim.opt.relativenumber true) +(set vim.opt.tabstop 2) +(set vim.opt.shiftwidth 2) +(set vim.opt.expandtab true) +(set vim.opt.smartindent true) +(set vim.opt.wrap false) +(set vim.opt.ignorecase true) +(set vim.opt.smartcase true) +(set vim.opt.termguicolors true) +(set vim.opt.signcolumn "yes") +(set vim.opt.updatetime 250) +(set vim.opt.undofile true) +(set vim.opt.clipboard "unnamedplus") + +;; Keymaps +(local keymap vim.keymap.set) + +;; Window navigation +(keymap :n "" "h" {:desc "Move to left window" + :silent true}) +(keymap :n "" "j" {:desc "Move to lower window" + :silent true}) +(keymap :n "" "k" {:desc "Move to upper window" + :silent true}) +(keymap :n "" "l" {:desc "Move to right window" + :silent true}) + +;; Buffer navigation +(keymap :n "bn" ":bnext" {:desc "Next buffer"}) +(keymap :n "bp" ":bprevious" {:desc "Previous buffer"}) +(keymap :n "bd" ":bdelete" {:desc "Delete buffer"}) + +;; Clear search highlight +(keymap :n "" ":noh" {:desc "Clear search highlight"}) + +;; Better escape +(keymap :i "jk" "" {:desc "Escape insert mode"}) + +;; Save file +(keymap :n "w" ":w" {:desc "Save file"}) +(keymap :n "fs" ":wa" {:desc "Save all files" :silent true}) + +;; Quit +(keymap :n "q" ":q" {:desc "Quit"}) + +;; Autocommands +(local augroup vim.api.nvim_create_augroup) +(local autocmd vim.api.nvim_create_autocmd) + +;; Highlight on yank +(let [yank-group (augroup "YankHighlight" {:clear true})] + (autocmd "TextYankPost" {:group yank-group + :callback (fn [] (vim.highlight.on_yank))})) + +;; Remove trailing whitespace on save +(let [whitespace-group (augroup "TrimWhitespace" {:clear true})] + (autocmd "BufWritePre" {:group whitespace-group + :pattern "*" + :command "%s/\\s\\+$//e"})) + +;; LSP Configuration (Neovim 0.11+ built-in) +;; Configure servers using vim.lsp.config +(vim.lsp.config :clojure_lsp + {:cmd [:clojure-lsp] + :filetypes [:clojure :edn] + :root_markers [:deps.edn :project.clj :build.boot :shadow-cljs.edn :.git]}) + +(vim.lsp.config :lua_ls + {:cmd [:lua-language-server] + :filetypes [:lua] + :root_markers [:.luarc.json :.luarc.jsonc :.git] + :settings {:Lua {:runtime {:version :LuaJIT} + :workspace {:library (vim.api.nvim_get_runtime_file "" true)}}}}) + +(vim.lsp.config :fennel_language_server + {:cmd [:fennel-language-server] + :filetypes [:fennel] + :root_markers [:.nfnl.fnl :fnl :.git] + :settings {:fennel {:workspace {:library (vim.api.nvim_get_runtime_file "" true)} + :diagnostics {:globals [:vim]}}}}) + +;; Enable the configured LSP servers +(vim.lsp.enable [:clojure_lsp :lua_ls :fennel_language_server]) + +;; LSP keymaps (set on attach) +(autocmd "LspAttach" + {:callback (fn [ev] + (local opts {:buffer ev.buf}) + (keymap :n "gd" vim.lsp.buf.definition opts) + (keymap :n "gD" vim.lsp.buf.declaration opts) + (keymap :n "gr" vim.lsp.buf.references opts) + (keymap :n "gi" vim.lsp.buf.implementation opts) + (keymap :n "K" vim.lsp.buf.hover opts) + (keymap :n "rn" vim.lsp.buf.rename opts) + (keymap :n "ca" vim.lsp.buf.code_action opts) + (keymap :n "[d" (fn [] (vim.diagnostic.jump {:count -1})) opts) + (keymap :n "]d" (fn [] (vim.diagnostic.jump {:count 1})) opts) + (keymap :n "e" vim.diagnostic.open_float opts) + (keymap :n "F" vim.lsp.buf.format opts))}) + +;; LSP commands (lspconfig-style) +(local usercmd vim.api.nvim_create_user_command) + +(usercmd "LspInfo" + (fn [] (vim.cmd "checkhealth vim.lsp")) + {:desc "Show LSP info"}) + +(usercmd "LspStart" + (fn [opts] + (if (and opts.args (> (length opts.args) 0)) + (vim.lsp.enable opts.args) + (vim.lsp.enable [:clojure_lsp :lua_ls :fennel_language_server]))) + {:nargs "?" + :complete (fn [] [:clojure_lsp :lua_ls :fennel_language_server]) + :desc "Start LSP server"}) + +(usercmd "LspStop" + (fn [] + (each [_ client (ipairs (vim.lsp.get_clients))] + (vim.lsp.stop_client client.id))) + {:desc "Stop all LSP clients"}) + +(usercmd "LspRestart" + (fn [] + (each [_ client (ipairs (vim.lsp.get_clients))] + (vim.lsp.stop_client client.id)) + (vim.defer_fn (fn [] (vim.cmd "edit")) 100)) + {:desc "Restart LSP clients"}) + +(usercmd "LspLog" + (fn [] (vim.cmd (.. "edit " (vim.lsp.get_log_path)))) + {:desc "Open LSP log file"}) + +{} diff --git a/fnl/plugins/init.fnl b/fnl/plugins/init.fnl new file mode 100644 index 0000000..5377e2e --- /dev/null +++ b/fnl/plugins/init.fnl @@ -0,0 +1,74 @@ +;; Plugin specs in Fennel +;; This file is compiled to lua/plugins/init.lua by nfnl + +(local repo 1) ;; lazy.nvim spec index for the git repo (e.g., "folke/plugin") +(local lhs 1) ;; which-key spec index for the key sequence (see which-key below) + +[;; Tokyonight - Colorscheme + {repo "folke/tokyonight.nvim" + :lazy false + :priority 1000 + :config (fn [] + (let [tokyonight (require :tokyonight)] + (tokyonight.setup + {:style "night" ; storm, moon, night, or day + :transparent false + :terminal_colors true})) + (vim.cmd.colorscheme "tokyonight"))} + + ;; Conjure - Interactive REPL for Fennel, Clojure, Lisp, etc. + {repo "Olical/conjure" + :ft ["fennel" "clojure" "lisp" "scheme" "racket" "lua"] + :config (fn [] + ;; Enable HUD floating window + (set vim.g.conjure#log#hud#enabled true))} + + ;; nvim-paredit - Structural editing for Lisp + ;; Default keybindings: >)/<) slurp/barf forward, <(/>( slurp/barf backward + ;; >e/f/), <), <(, >(, etc.) + (paredit.setup {}) + + ;; Additional vim-sexp compatible bindings + (local keymap vim.keymap.set) + (local api paredit.api) + + ;; dsf - Splice (delete surrounding form) + (keymap :n "dsf" api.unwrap_form_under_cursor {:desc "Splice (delete surrounding form)"}) + + ;; cse( cse) - Wrap in parens + (keymap :n "cse(" #(api.wrap_element_under_cursor "(" ")") {:desc "Wrap element in ()"}) + (keymap :n "cse)" #(api.wrap_element_under_cursor "(" ")") {:desc "Wrap element in ()"}) + + ;; cse[ cse] - Wrap in brackets + (keymap :n "cse[" #(api.wrap_element_under_cursor "[" "]") {:desc "Wrap element in []"}) + (keymap :n "cse]" #(api.wrap_element_under_cursor "[" "]") {:desc "Wrap element in []"}) + + ;; cse{ cse} - Wrap in braces + (keymap :n "cse{" #(api.wrap_element_under_cursor "{" "}") {:desc "Wrap element in {}"}) + (keymap :n "cse}" #(api.wrap_element_under_cursor "{" "}") {:desc "Wrap element in {}"}))} + + ;; Mason - Package manager for LSP servers, DAP servers, linters, formatters + ;; Run :MasonInstall clojure_lsp lua_ls to install servers + {repo "williamboman/mason.nvim" + :cmd ["Mason" "MasonInstall" "MasonUpdate"] + :build ":MasonUpdate" + :opts {:ui {:border "rounded"}}} + + ;; which-key - Shows keybinding hints + ;; lhs constant defined at top - which-key specs use index [1] for the key sequence + {repo "folke/which-key.nvim" + :event "VeryLazy" + :opts {} + :config (fn [_ opts] + (local wk (require :which-key)) + (wk.setup opts) + (wk.add + [{lhs "f" :group "find"} + {lhs "b" :group "buffer"} + {lhs "r" :group "refactor"} + {lhs "c" :group "code"}]))}] diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..d778857 --- /dev/null +++ b/init.lua @@ -0,0 +1,26 @@ +-- Set leader keys before lazy loads +vim.g.mapleader = " " +vim.g.maplocalleader = " " + +-- Bootstrap lazy.nvim +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", + lazypath, + }) +end +vim.opt.rtp:prepend(lazypath) + +-- Setup lazy.nvim +require("lazy").setup({ + { import = "bootstrap" }, + { import = "plugins" }, +}) + +-- Load Fennel config (compiled from fnl/config/init.fnl) +pcall(require, "config.init") diff --git a/lua/bootstrap.lua b/lua/bootstrap.lua new file mode 100644 index 0000000..f0c89d4 --- /dev/null +++ b/lua/bootstrap.lua @@ -0,0 +1,54 @@ +-- Core plugins that must be in Lua (nfnl compiles Fennel, so it can't be in Fennel) +return { + { + "Olical/nfnl", + lazy = false, + config = function() + -- Create global commands that work without opening a fennel file first + local api = require("nfnl.api") + vim.api.nvim_create_user_command("NfnlCompileAllFiles", function(opts) + api["compile-all-files"](opts.args ~= "" and opts.args or nil) + end, { desc = "Compile all Fennel files", nargs = "?", complete = "dir" }) + vim.api.nvim_create_user_command("NfnlCompileFile", function(opts) + api["compile-file"]({ path = opts.args ~= "" and opts.args or nil }) + end, { desc = "Compile current or specified Fennel file", nargs = "?", complete = "file" }) + end, + }, + { + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + config = function() + local langs = { "clojure", "fennel", "lua", "vim", "vimdoc", "query" } + + -- Install parsers asynchronously on first load + vim.schedule(function() + local installed = require("nvim-treesitter").get_installed() + for _, lang in ipairs(langs) do + if not vim.list_contains(installed, lang) then + require("nvim-treesitter").install(lang) + end + end + end) + + -- Enable treesitter highlighting for supported filetypes + -- This is required for nvim-paredit to work (needs vim.treesitter.get_node()) + vim.api.nvim_create_autocmd("FileType", { + pattern = { "clojure", "fennel", "lua", "vim", "query", "scheme", "lisp" }, + callback = function() + pcall(vim.treesitter.start) + end, + }) + end, + }, + { + "nvim-telescope/telescope.nvim", + tag = "0.1.8", + dependencies = { "nvim-lua/plenary.nvim" }, + keys = { + { "ff", "Telescope find_files", desc = "Find files" }, + { "fg", "Telescope live_grep", desc = "Live grep" }, + { "fb", "Telescope buffers", desc = "Buffers" }, + { "fh", "Telescope help_tags", desc = "Help tags" }, + }, + }, +}