From 5269e76608d9b42f29764dd8aa900391fade8ee5 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:04:12 +1000 Subject: [PATCH 1/5] updates --- .github/{dependapot.yml => dependabot.yml} | 0 .github/workflows/check.yml | 14 +- .../1784851829193-codecompanion-revamp.md | 85 +++++++ .../plans/1784858731550-nix-neovim-review.md | 191 ++++++++++++++++ flake.lock | 34 +-- flake.nix | 10 +- ftplugin/quarto.lua | 35 +-- modules/module/settings/cat-packages.nix | 4 +- modules/module/settings/core.nix | 4 - modules/module/settings/env.nix | 7 +- modules/module/settings/hosts.nix | 10 - plugin/00_options.lua | 46 ++-- plugin/01_lib.lua | 4 +- plugin/10_keymap.lua | 23 +- plugin/20_startup.lua | 209 ++++-------------- plugin/21_datascience.lua | 30 ++- plugin/22_languages.lua | 6 +- plugin/23_editor.lua | 18 +- plugin/24_completion.lua | 199 +++++++++++++---- plugin/25_lsp.lua | 14 +- 20 files changed, 599 insertions(+), 344 deletions(-) rename .github/{dependapot.yml => dependabot.yml} (100%) create mode 100644 .kilo/plans/1784851829193-codecompanion-revamp.md create mode 100644 .kilo/plans/1784858731550-nix-neovim-review.md diff --git a/.github/dependapot.yml b/.github/dependabot.yml similarity index 100% rename from .github/dependapot.yml rename to .github/dependabot.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 637fb10..fcf1db9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -5,12 +5,20 @@ on: paths: - 'flake.lock' - 'flake.nix' - - 'modules' + - 'modules/**' + - 'plugin/**' + - 'lua/**' + - 'overlays/**' + - 'ftplugin/**' pull_request: paths: - 'flake.lock' - 'flake.nix' - - 'modules' + - 'modules/**' + - 'plugin/**' + - 'lua/**' + - 'overlays/**' + - 'ftplugin/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -35,5 +43,5 @@ jobs: authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' extraPullNames: rstats-on-nix, nix-community - run: nix build - - run: nix develop + - run: nix develop -c echo ok - run: nix flake check diff --git a/.kilo/plans/1784851829193-codecompanion-revamp.md b/.kilo/plans/1784851829193-codecompanion-revamp.md new file mode 100644 index 0000000..b5898e4 --- /dev/null +++ b/.kilo/plans/1784851829193-codecompanion-revamp.md @@ -0,0 +1,85 @@ +# CodeCompanion Revamp Plan + +## Context + +- Current: CodeCompanion **19.18.0** via nixpkgs `vimPlugins.codecompanion-nvim` (nixpkgs input lastModified ~2026-05-15). +- Latest: **19.20.0**. Key changes since 19.18.0: + - v19.19.0: `claude-sonnet-5` support, async/dynamic model fetching, copilot `top_p` fixes, inline orphaned-keymap fix, background command deregistration. + - v19.20.0: `gemini_interactions` adapter, PDF support for http adapters (`/file` on Anthropic/Copilot/OpenAI/OpenRouter), env vars from files, prompt-library items auto-receive default rule groups, copilot schema options removed. +- **Bug in current setup:** config sets `model = "claude-sonnet-5"` but 19.18.0 predates its support; the model name may not resolve. The nixpkgs bump fixes this. +- Files involved: + - `plugin/24_completion.lua` — `get_codecompanion_config()`, setup, blink integration. + - `plugin/10_keymap.lua` — `a*` keymaps (lines 69–84, contains duplicates/commented cruft). + - `overlays/plugins.nix` — codecompanion overlay (nvimSkipModules); likely unchanged. + - `modules/module/specs/plugins.nix` — plugin spec; unchanged. + +## Decisions (confirmed with user) + +1. **Version bump:** `nix flake update nixpkgs` (accepts wider plugin bump; do NOT override src). +2. **Adopt:** agent-mode keymaps, one workflow prompt, minor slash-command config (`/share` token). **No MCP.** +3. **Models:** `claude-sonnet-5` (copilot) for **chat only**; cheaper copilot model for **inline** and **background** (title generation) — use `gpt-5-mini` as placeholder; verify exact model id via `ga` model picker in a chat buffer after the bump and adjust. +4. **Codex (ChatGPT Edu) = heavy agent lane.** Copilot stays the default chat adapter; Codex ACP is launched explicitly for heavy autonomous tasks. Auth via ChatGPT login only (no API key) — user's existing `auth_method = "chatgpt"` is correct per the v19.20.0 adapter source (`"openai-api-key"|"codex-api-key"|"chatgpt"`; the docs page comment `"chat-gpt"` is stale). + +## Tasks + +1. **Bump nixpkgs** + - Run `nix flake update nixpkgs`. + - Verify: `nix eval --raw nixpkgs#vimPlugins.codecompanion-nvim.version` reports ≥ 19.20.0. + - Rebuild the wrapped Neovim per this repo's normal build (`nix build` / the repo's usual package target) and smoke-test that the editor starts. + +2. **`plugin/24_completion.lua` — `get_codecompanion_config()`** + - Keep `interactions.chat.adapter = { name = "copilot", model = "claude-sonnet-5" }`. + - Change `interactions.inline.adapter` to `{ name = "copilot", model = "gpt-5-mini" }` (cheap/fast). + - Add a background adapter so title generation doesn't use Sonnet: set the background chat adapter to copilot/`gpt-5-mini` (config lives under `interactions.background.chat`; confirm exact key against `:h codecompanion` / `:checkhealth codecompanion` after the bump — the docs page "Generating Titles" says an adapter must be configured for background interactions). + - Keep existing context_management, rules, display, shared keymaps unchanged. + - Add `interactions.chat.slash_commands["share"].opts.token = os.getenv("GITHUB_GIST_TOKEN")`. + - **Prompt library additions** (keep `expert`, `fixer`, `suggest`): + - `["agent"]`: interaction "chat", alias `agent`, first user prompt starts with `@{agent}` plus selected code block, so Copilot/Claude gets the file-editing tool group (read_file, insert_edit_into_file, grep_search, run_command, etc., with approvals). + - `["tdd"]` (name flexible): interaction "chat", `opts = { is_workflow = true, alias = "tdd" }` — 3-stage workflow: (1) plan/understand `#buffer`, (2) `@{agent}` implement, (3) `@{run_command}` run the test suite (leverages run_command's test-flag for agentic workflows). + +3. **`plugin/10_keymap.lua` — clean up + add** + - Remove duplicated/commented lines (70–71, 77, 81). + - Add: + - `nmap_leader("aa", "CodeCompanionChat", "Agent chat (use @{agent})")` or directly `CodeCompanion /agent`. + - `xmap_leader("aa", "CodeCompanion /agent", "Agent on selection")`. + - `nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan→implement→test")`. + - `nmap_leader("aC", "CodeCompanion /compact", "Compact chat")` (verify `aC` doesn't clash). + - Keep existing mappings unchanged. + +4. **No changes** to `overlays/plugins.nix` or `modules/module/specs/plugins.nix` unless the new version introduces new lazy-module load failures (re-run the build's check phase; `doCheck = false` is already set on the spec entry). + +5. **Codex ACP lane (ChatGPT Edu)** + - **Resolved prerequisite:** user has installed `codex-acp` at `~/.nix-profile/bin/codex-acp` (verified on PATH 2026-07-24). The preset adapter's default command (`codex-acp`) now works as-is — **no `commands` override in the Lua config**. Fallback only if the binary misbehaves: override with `commands = { default = { "codex", "acp" } }` (requires the codex CLI's built-in `acp` subcommand; verify with `codex acp --help`). + - **Config change (small):** none strictly required — the existing `extend("codex", { defaults = { auth_method = "chatgpt" } })` block in `plugin/24_completion.lua` is correct. Keep it. + - Auth: prereq is an active `codex login` session with the ChatGPT Edu account (`~/.codex/auth.json` exists — user confirms validity; if expired, re-run `codex login`, browser/ChatGPT-app flow, no API key). + - Keep codex on-demand (do NOT make it the default chat adapter): + - Keep `ak` (`:CodeCompanionChat adapter=codex`). + - Add comment documenting ACP-only slash commands for this lane: `/resume` (restore past codex session, fresh chat only), `/mode` (switch agent mode), `/command`, `/acp_session_options`, plus `\`-triggered ACP command completion in the chat buffer. + - Do not set a default codex model in config; pick per-session via `ga` / `/acp_session_options` (avoids hardcoding model ids that change with the Edu plan). + - PATH note: Neovim must inherit a PATH containing `~/.nix-profile/bin` so the spawned `codex-acp` resolves — true when nvim is launched from the user's normal shell; call it out if validation fails with "command not found". + +6. **Docs/habit notes** (add as comment block above the codecompanion setup, no separate docs file): `/compact`, `/fork`, `/symbols`, `/share`, `/resume`+`/mode` (codex ACP), `gm` (btw), `gty` (YOLO), `gba`/`gbd` (buffer sync), `gd` (debug window). + +## Validation + +- `nix flake update nixpkgs` then repo build succeeds. +- In Neovim: `:checkhealth codecompanion` clean. +- `:CodeCompanionChat`, press `ga` → copilot adapter lists `claude-sonnet-5`; send a trivial message and confirm response + auto title generation. +- In chat: type `@` → completion shows `agent`, `files`, `memory`, etc.; run `@{agent}` task on a scratch repo and confirm the approval prompt flow works. +- `:CodeCompanion /tdd` starts the workflow stages in order. +- Inline: visual-select code, `:CodeCompanion` prompt → confirm diff shows and `ga`/`gr` accept/reject still work on the cheap model. +- `:CodeCompanion /share` prompts/errors sensibly if `GITHUB_GIST_TOKEN` unset. +- **Codex lane:** `ak` opens a chat with the codex ACP adapter; the spawned `codex-acp` process initializes without auth errors (ChatGPT method), a trivial prompt gets a response, and `/mode` lists codex session modes. If it fails with command-not-found, check that Neovim inherited `~/.nix-profile/bin` in PATH (`:echo $PATH` inside nvim). + +## Risks + +- `nix flake update nixpkgs` bumps **all** vimPlugins and Neovim itself; other plugins may break. If the blast radius is too large, fall back to overriding codecompanion `src` to tag `v19.20.0` in `overlays/plugins.nix` (fetchFromGitHub, `doCheck = false`). +- Model ids (`claude-sonnet-5`, `gpt-5-mini`) must match the copilot adapter's choices post-bump; verify via `ga` picker and adjust literals. +- `gpt-5-mini` may not support tool use on copilot — fine, since inline/background don't use tools. +- `codex-acp` lives in the user's nix profile (outside this flake). If the profile is rebuilt/removed, the codex lane breaks — long-term consider adding `codex-acp` to this flake's runtime deps so it's pinned with the rest of the setup (optional follow-up, not required now). +- ChatGPT Edu accounts authenticate codex via browser/ChatGPT-app login; token expiry will surface as ACP auth errors → re-run `codex login`. + +## Out of scope + +- MCP server integration. +- Custom rules parsers, custom tools, extensions (mcphub/history/vectorcode). diff --git a/.kilo/plans/1784858731550-nix-neovim-review.md b/.kilo/plans/1784858731550-nix-neovim-review.md new file mode 100644 index 0000000..d0f0c63 --- /dev/null +++ b/.kilo/plans/1784858731550-nix-neovim-review.md @@ -0,0 +1,191 @@ +# Neovim + Nix setup review + +Scope: `flake.nix`, `overlays/`, `modules/`, `plugin/*.lua`, `lua/*`, `ftplugin/*`, CI/workflows, and cross-check against the pinned versions in `flake.lock` (nixpkgs weekly `241313f4`). + +## Pinned package versions (from current lock) +- neovim `0.12.2` +- blink-cmp `1.10.2` +- codecompanion-nvim `19.13.0` +- nvim-treesitter `0.10.0-unstable-2026-04-03` (main-branch rewrite) +- nvim-treesitter-textobjects `0-unstable-2026-04-07` (main-branch rewrite) +- copilot-lua `2.0.3` +- render-markdown-nvim `8.12.0-unstable-2026-05-07` +- zk-nvim `0.4.7-unstable-2026-03-13` +- mini.nvim `0.17.0-unstable-2026-05-12` +- quarto-nvim `2.1.0` +- otter-nvim `2.14.5` +- lspconfig `2.9.0` + +--- + +## CRITICAL: Treesitter is currently non-functional + +### Finding +The nixpkgs `nvim-treesitter` package is the **main-branch rewrite** — there is no `lua/nvim-treesitter/configs.lua`. The whole module in `plugin/20_startup.lua` lines 212-294 is dead code: + +```lua +local ok_configs, configs = pcall(require, "nvim-treesitter.configs") -- fails +``` + +The working tree already removed the fallback `vim.treesitter.start()` FileType autocmd (the "fixed treesitter" commit deleted it). Net result: **no treesitter highlighting, indentexpr, foldexpr, or textobjects are configured**. + +### Impact +- Syntax highlighting falls back to Neovim's regex-only engine. +- All textobject keymaps (`]a`, `[a`, `]f`, `[f`, `]e`/`[e`, `x`/`X`, lsp_interop `lm`) are inert. +- `foldexpr`/`indentexpr` based folding does nothing. + +### Fix direction +Rewrite `plugin/20_startup.lua` for main-branch API: +1. FileType autocmd → `vim.treesitter.start()` (and optionally `indentexpr`/`foldexpr`). +2. `require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true } })` + explicit keymaps using `move.goto_next_start(query, "textobjects")` and `swap.swap_next(...)`. +3. The non-Nix `ensure_installed` block should set `opts.ensure_installed` BEFORE the filter (line 278 reads it before line 286 defines it). + +Also remove duplicate `vim.treesitter.language.register("markdown", ...)` between `plugin/20_startup.lua` and `plugin/21_datascience.lua`. + +--- + +## CodeCompanion version mismatch + +### Finding +Config targets features absent in `19.13.0` (present only in `≥19.19.0`/main): + +| Config usage | Present in 19.13? | Present in main (`v19.20.0`) | +|---|---|---| +| `interactions.chat.opts.context_management.editing` | No (flat `trigger`/`enabled` only) | Yes | +| `interactions.chat.opts.context_management.compaction` | No | Yes | +| `aC` requiring `codecompanion.interactions.chat.context_management.compaction` | **RUNTIME ERROR** | Works | +| `slash_commands.share` (`opts.token = ...`) | No (absent from defaults) | Yes | +| `adapters.acp.codex.defaults.auth_method = "chatgpt"` | Yes | Yes | +| `interactions.chat.adapter = { name = ..., model = ... }` table form | Yes | Yes | + +### Risk +On today's lock (`19.13.0`): `/share` is inert, compaction-only keymap throws module-not-found, and `editing`/`compaction` tuning is silently ignored. On `≥19.19.0`/main: everything works. + +### Decision needed (BLOCKING) +Choose **exactly one**: + +- **A — Upgrade the plugin pin** (`overlays/plugins.nix` fetch to `v19.20.0` or `main`, bump the lock, and keep the current config). Riskiest change but matches what the config is written for. +- **B — Downgrade config call sites** to match `19.13.0`: remove `slash_commands.share`, remove `editing`/`compaction` keys (or collapse to flat `trigger: 0.75`), remove or gate `aC` on "newer" version. +- **C — Version-gate the config**: keep current code, read the installed version at startup and skip the new features when < 19.19. + +I recommend A (the config clearly intends to track upstream main-ish behavior; `nix flake update` already bumped it to `19.18.0` in a prior session and you only reverted because the nightly was still too old — `v19.20.0` is now available). + +--- + +## DEAD / DUPLICATE code + +- `plugin/20_startup.lua` — the entire `configs.setup(opts)` block is dead (see Treesitter finding above). +- `plugin/10_keymap.lua`: + - `nmap_leader('od', 'Neogen', ...)` — `neogen` plugin is **not shipped** in `specs/plugins.nix`. Mapping errors on press. + - `nmap_leader('fp', 'Pick projects', ...)` — `MiniExtra.pickers.projects` doesn't exist in mini.extra; would error. + - `nmap_leader('oS', 'lua Config.insert_section()', ...)` — `Config.insert_section` is never defined. + - `vim.lsp.buf.definition()` is bound to `grd`; Neovim 0.11+ defaults include `gr` aliases. Cosmetic, but `]d`/`[d` exist on modern LSP config and would be more idiomatic. +- `plugin/23_editor.lua`: + - `my_styler` formatter (calls `R -s -e styler::...`) is defined but never referenced — dead. +- `plugin/24_completion.lua`: + - `providers.cmp_r` is defined in the blink source list but never enabled in `default` or `per_filetype`; inert. + - `BLINK_VERSION = "v1.4.1"` — only consulted in the non-Nix install path; nixpkgs is `1.10.2`. Pin it to current or drop. + - `get_blink_fuzzy_setting().prebuilt_binary = { force_version = BLINK_VERSION }` — singular key is wrong; blink option is `fuzzy.prebuilt_binaries` (plural). Being skipped in-Nix anyway, but still wrong key. +- `modules/module/specs/plugins.nix` — `specs.utils-lazy` ships `nvim-dap*` and `nvim-lint` but there is zero config/tooling that references them in this repo. +- `ftplugin/quarto.lua`: + - Second top-level `require('quarto').setup()` with **no args** runs _after_ `21_datascience.lua`'s setup and **resets** it to defaults (no `lspFeatures`, no `codeRunner`). + - Top-level `require('quarto')` at FileType load also crashes if the quarto plugin isn't installed (e.g. cats off and a `.qmd`/`.quarto` file is opened): Neovim detects `quarto` ft natively in 0.10+. +- `plugin/00_options.lua`: + - Lines 146-163: two back-to-back FileType autocmds that both remove `r`/`o` from `formatoptions`. The second references an undefined `augroup` variable (nil → autocmd is global, happens to still work). + +--- + +## STALE / WRONG options + +- `plugin/10_keymap.lua` line 174: + ```lua + require("conform").format({ lsp_fallback = true }) + ``` + `lsp_fallback` is the **deprecated** boolean form of `conform.nvim`; should be `lsp_format = "fallback"` (the form already used in `plugin/23_editor.lua` and `ftplugin/python.lua`). In newer conform this may warn or error. +- `ftplugin/quarto.lua`: + - Sets `RDSendLine` and R-style keymaps on _all_ quarto buffers, including Python/Julia chunks. This collides with quarto-runner mappings set in `21_datascience.lua`. Should gate on `vim.bo.filetype == "r"` (the ftplugin already imports quarto.runner for python, but the R keys leak). +- `.github/dependapot.yml` — filename typo. GitHub expects `dependabot.yml`; Dependabot won't run. +- `.github/workflows/check.yml`: + - `nix develop` without a `-c` command just launches an interactive shell. In CI it does nothing useful (or hangs). Replace with `nix develop -c echo ok` or drop. + - Path filter `'modules'` only matches the root directory itself; should be `modules/**` (or `'modules/**'`). + - Changing `plugin/`, `lua/`, `overlays/`, `ftplugin/`, etc. does **not** trigger CI. +- `flake.nix` / `.envrc` shellHook: + ```sh + export R_LIBS_SITE=$(strings "$(command -v R)" | grep -oP '/nix/store/[^:]+/library' ...) + ``` + `grep -oP` (PCRE) is **not available in macOS BSD grep**. On `aarch64-darwin` this silently fails → `R_LIBS_SITE` is empty. Either depend on `ripgrep` regex (`strings ... | rg -o '...'`) or use `gsed` (GNU sed). +- `overlays/plugins.nix` — `zk-nvim` skip list contains `zk.pickers.fzf_lua`; current zk-nvim package likely doesn't load that regardless, but harmless. +- `modules/module/settings/core.nix` — `config.settings.nvim_lua_env` references `lp.tiktoken_core` but `tiktoken_core` is **not in `catPkgs.general` or anywhere else** in these files. If there's an extra Lua/tiktoken module, fine; otherwise this option is placeholder dead code. +- `modules/module/settings/hosts.nix` — host `m` (marimo) is defined with `enable = false` but also configured with `package`, `argv0`, `addFlag`. Dead block. + +--- + +## PORTABILITY / DARWIN issues + +- Shell hook `grep -oP` (above) fails on macOS. +- `mkdir -p "$R_LIBS_USER"` fine; but `command -v R` on macOS returns the wrapper; the wrapper path injection still works. + +--- + +## DEAD WEIGHT (shipped but unused) + +- `catPkgs.r` includes `pkgs.rnvimserver` — `rnvimserver` is needed by R.nvim only when using the _socket_ transport; `21_datascience.lua` only uses vim-slime. Acceptable, but `rnvimserver` adds to build time. Include if you actually use it? Currently not used. +- `specs.utils-lazy` ships `nvim-dap`, `nvim-dap-ui`, `nvim-dap-virtual-text`, `nvim-lint` — none of these are referenced anywhere in `plugin/` or `lua/`. Consider moving them to a devShell-only cat, or drop them. +- `.gitignore` `*.R` — prevents tracking any new `.R` files. `tests/test.R` is already committed so it isn't actively harmful, but it's surprising for a repo whose default cats include R. +- `.commandcode/` dir is listed in `.gitignore` but is in the worktree; fine, but worth cleaning up if it's an artifact. + +--- + +## IMPROVEMENTS given updated packages + +- `catPkgs.markdown` — add `marksman` (it's the LSP used by `render-markdown.nvim` wiki links and configured in LSP). Same binary is needed by `render-markdown` for wiki link ISP. +- `vim.lsp.enable` servers configured but binaries missing in PATH: + - `marksman` (LSP + render-markdown wiki integration). + - `r_ls` (new R language server — package name in nixpkgs is likely `r-languageserver` or `r_ls`; current `catPkgs.r` doesn't ship either). + - `clangd` — not required for this data-science setup; either drop or add to `external`. + - `julials` — require `LanguageServer.jl` in `settings.lang_packages.julia` for it to be useful. +- `blink.cmp` is `1.10.2` but config pins `BLINK_VERSION = "v1.4.1"` for the non-Nix path. Either drop the `BLINK_VERSION` constant (let MiniDeps track HEAD or the tag pinned in flake) or update it to `v1.10.2`. +- `conform.nvim` already uses `lsp_format = "fallback"` in `23_editor.lua` and `ftplugin/python.lua`, but `10_keymap.lua` still calls the deprecated `lsp_fallback = true`. Align to `lsp_format = "fallback"`. +- `.github/workflows/check.yml` — add path filters for `plugin/**`, `lua/**`, `overlays/**`, `ftplugin/**`, `modules/**`. +- `tests/init.lua` smoke-test and `tests/test.R` are present but **not wired into `nix flake check`**. Add a trivial check that runs `lua tests/init.lua` via `nix-shell -A ...`. + +--- + +## CONFLICTS + +- `up` (terminal bracketed paste toggle in `10_keymap.lua`) conflicts with `mini.basics.mappings.option_toggle_prefix = "u"` (paste toggle), which is set up later in `20_startup.lua` via `now()`. Result: the user's `up` mapping gets overwritten. Portable workaround: remap to `tp` (terminal namespace) and keep `tb` as alternative — both already mapped in 10_keymap. +- `ftplugin/quarto.lua` resets quarto-nvim config (kills `lspFeatures`/`codeRunner` set in `21_datascience.lua`) and injects R plug mappings into non-R quarto chunks. + +--- + +## CLEAN summary for implementer + +### Immediate (not version-dependent) +1. Restore treesitter highlighting + textobjects in `plugin/20_startup.lua` using main-branch API. +2. Remove duplicate `vim.treesitter.language.register` in `21_datascience.lua`. +3. Fix `plugin/10_keymap.lua`: remove/resolve `Neogen`, `Pick projects`, `Config.insert_section` dangling mappings. +4. Replace deprecated `lsp_fallback = true` with `lsp_format = "fallback"` in `plugin/10_keymap.lua`. +5. Gate `ftplugin/quarto.lua` to not reset config and to not leak R keys into non-R chunks; guard against missing `quarto` plugin. +6. Remove duplicate `formatoptions` autocmds (or at least dedupe). +7. Dedupe `RNVIM_COMPLDIR` / `TMPDIR` setup (do it in one place). +8. Rename `.github/dependapot.yml` to `.github/dependabot.yml`. +9. Remove or gate dead host `m` in `hosts.nix`. +10. Remove unused `rsplit`? no, irrelevant. +11. Add `marksman` to `catPkgs.markdown` and remove orphan LSP entries or ship the binaries. +12. Remove dead `conform` formatter `my_styler` and dead blink `cmp_r` provider, or wire them up. + +### Version-dependent (BLOCKED) +- **Reset the CodeCompanion version decision**: either pin to `v19.20.0` (keep config, fix `slash_commands.share`, drop the `aC` compatibility shim, keep `editing`/`compaction`) OR downgrade config to match `19.13.0` (remove `share`, collapse `context_management`). + +### CI / packaging +- Fix `nix develop` usage and path filters in `.github/workflows/check.yml`. +- Replace `grep -oP` in `flake.nix` shellHook with portable `rg -o` or add `gnused` to `catPkgs.always` and use `gsed`. +- Wire `tests/init.lua` into `flake check`. + +### Verification +- `nix flake check` on both `aarch64-darwin` and `x86_64-linux`. +- `nix build .#packages..default` succeeds (already does). +- Start nvim, verify :TSContext works, treesitter highlighting is on, and `]f`/`[f` textobjects move. +- Open a `.qmd` file with cats off → no ftplugin crash. +- Confirm `up` still toggles bracketed paste. +- Confirm `:Pick projects` and `:Neogen` and `Config.insert_section` no longer error (or are mapped to valid handlers). diff --git a/flake.lock b/flake.lock index 21dc5ef..17fe244 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1776413584, - "narHash": "sha256-xqqv46MTveuT4yJH2YihmbHGy5mdLnnLFDebVmUws/E=", + "lastModified": 1781337902, + "narHash": "sha256-QjXxUvpOBbnlpSskBOZJ9nUbpYyFRD4cBo4s1aHniGs=", "owner": "dwinkler1", "repo": "fran", - "rev": "da09626e4dd8f0f57078b3a04e0443a8c20defa1", + "rev": "30ab141871ede23b245a90afea58e076ef83b515", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778869304, - "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "lastModified": 1784796856, + "narHash": "sha256-wWFrV5/Qbm+lyt5x20E/bSbfJiGKMo4RCxZV8cl/WZI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "rev": "e2587caef70cea85dd97d7daab492899902dbf5d", "type": "github" }, "original": { @@ -60,16 +60,16 @@ "rnvimsrc": "rnvimsrc" }, "locked": { - "lastModified": 1781392459, - "narHash": "sha256-9CMG+trBd9AX7cWiGPrMr0eJwuZaql+pj2WVGyT417I=", + "lastModified": 1784867506, + "narHash": "sha256-5zXVJLG7+Gi9sgb4Dn7P6O/ywQQxpvcjjoHSJXZEuVo=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "03d7872d7702db37ede41f3c6e4d4ce4c129e862", + "rev": "1d26452e3c940153271fc57fbad0b295fcb0e04f", "type": "github" }, "original": { "owner": "dwinkler1", - "ref": "v0.99.5", + "ref": "v1.0.0", "repo": "r_nvim_nix", "type": "github" } @@ -93,16 +93,16 @@ "rnvimsrc": { "flake": false, "locked": { - "lastModified": 1780759435, - "narHash": "sha256-VxgKMOP1hseQre3cas2dmMXZu4PVyl05INla2OdHTU4=", + "lastModified": 1783264911, + "narHash": "sha256-FywUL3mV2+kfu+rO6uUFyUv80EflbdgSkYuSnW965UE=", "owner": "R-nvim", "repo": "R.nvim", - "rev": "6ca306191531c3e3d501ee1609c84a5d29059386", + "rev": "c56ebe0f8445e251673981c40ac2d74659ecd6ed", "type": "github" }, "original": { "owner": "R-nvim", - "ref": "v0.99.5", + "ref": "v1.0.0", "repo": "R.nvim", "type": "github" } @@ -124,11 +124,11 @@ ] }, "locked": { - "lastModified": 1779297405, - "narHash": "sha256-VFoBwH7ZjVxCnvZTb5ODRXt70sLtWMxstive0N+RS50=", + "lastModified": 1782135443, + "narHash": "sha256-vAmbArdCyjqpVW+37aCy/PMBOLIqukUXLQuEKLwUhA4=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "e7ed7a1205945befdf2e0d73ba7df91d935e5af1", + "rev": "6e7f66fa2cdf4d63162580b438f7fcf87c28a46f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d8c0018..a8584e0 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ rixpkgs.url = "github:dwinkler1/rixpkgs/nixpkgs"; r-nvim-nix = { - url = "github:dwinkler1/r_nvim_nix/v0.99.5"; + url = "github:dwinkler1/r_nvim_nix/v1.0.0"; inputs = { nixpkgs.follows = "rixpkgs"; }; @@ -69,7 +69,7 @@ '' + nixpkgs.lib.optionalString (config.cats.r or false) '' export R_HOME=$(R RHOME) - export R_LIBS_SITE=$(strings "$(command -v R)" | grep -oP '/nix/store/[^:]+/library' | sort -u | paste -sd: -) + export R_LIBS_SITE=$(strings "$(command -v R)" | rg -o '/nix/store/[^:]+/library' | sort -u | paste -sd: -) export R_LIBS_USER="$PWD/.r-libs" mkdir -p "$R_LIBS_USER" ''; @@ -224,6 +224,12 @@ fi echo "Downstream override assertions passed" > $out ''; + lua-test = pkgs.runCommand "lua-test" { + buildInputs = [ pkgs.neovim-unwrapped ]; + } '' + nvim --headless -u NONE -c "set runtimepath+=${./.}" -l ${./tests/init.lua} + touch $out + ''; } ); diff --git a/ftplugin/quarto.lua b/ftplugin/quarto.lua index 7ee10a0..b737644 100644 --- a/ftplugin/quarto.lua +++ b/ftplugin/quarto.lua @@ -1,23 +1,28 @@ -local quarto = require('quarto') -quarto.setup() -vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true }) +local quarto_ok, quarto = pcall(require, 'quarto') +if quarto_ok then + vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true, buffer = true }) +end -vim.keymap.set("n", "", "RDSendLine", { buffer = true }) -vim.keymap.set("v", "", "RSendSelection", { buffer = true }) +if vim.bo.filetype == "r" then + vim.keymap.set("n", "", "RDSendLine", { buffer = true }) + vim.keymap.set("v", "", "RSendSelection", { buffer = true }) --- Assignment operator (--) -vim.keymap.set("i", "--", "lua MiniTrailspace.trim()RInsertAssign", { buffer = true, noremap = true }) + -- Assignment operator (--) + vim.keymap.set("i", "--", "lua MiniTrailspace.trim()RInsertAssign", { buffer = true, noremap = true }) --- Pipe operator (;;) -vim.keymap.set("i", ";;", "lua MiniTrailspace.trim()RInsertPipe", { buffer = true, noremap = true }) + -- Pipe operator (;;) + vim.keymap.set("i", ";;", "lua MiniTrailspace.trim()RInsertPipe", { buffer = true, noremap = true }) +end -local runner = require("quarto.runner") -vim.keymap.set("n", "a", runner.run_cell, { desc = "run cell", silent = true }) -vim.keymap.set("n", "A", runner.run_all, { desc = "run all cells", silent = true }) -vim.keymap.set("n", "RA", function() - runner.run_all(true) -end, { desc = "run all cells of all languages", silent = true }) +local runner_ok, runner = pcall(require, "quarto.runner") +if runner_ok then + vim.keymap.set("n", "a", runner.run_cell, { desc = "run cell", silent = true, buffer = true }) + vim.keymap.set("n", "A", runner.run_all, { desc = "run all cells", silent = true, buffer = true }) + vim.keymap.set("n", "RA", function() + runner.run_all(true) + end, { desc = "run all cells of all languages", silent = true, buffer = true }) +end diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index b92c331..2709ca0 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -43,6 +43,7 @@ in python313Packages.pylatexenc quartoPkg zk + marksman ]); nix = maybe "nix" (with pkgs; [ @@ -99,13 +100,12 @@ in quartoPkg pkgs.air-formatter pkgs.yaml-language-server - pkgs.rnvimserver ]; # cats without packages get empty lists general = [ ]; gitPlugins = [ ]; - treesitterParsers = [ pkgs.tree-sitter ]; + treesitterParsers = [ ]; utils = [ ]; }; } diff --git a/modules/module/settings/core.nix b/modules/module/settings/core.nix index 822fc53..bebc487 100644 --- a/modules/module/settings/core.nix +++ b/modules/module/settings/core.nix @@ -14,10 +14,6 @@ # Enable RC wrapping (allows neovim to find the config) config.settings.wrapRc = lib.mkDefault true; - # Lua packages available to neovim (for :lua require()) - config.settings.nvim_lua_env = lp: - lib.optionals (config.cats.general or false) [ lp.tiktoken_core ]; - # Binary name for the wrapper config.binName = lib.mkDefault "vv"; diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index c7a1a8b..75ac273 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -11,10 +11,9 @@ UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON = pkgs.python.interpreter; }) - (lib.mkIf (config.cats.r or false) { - RNVIM_COMPLDIR = "$PWD/.r-compl"; - TMPDIR = "$PWD/.r-tmp"; - }) + # R.nvim v1.x owns its cache and temporary directories and exports + # RNVIM_COMPLDIR/RNVIM_TMPDIR during setup. Do not inject literal `$PWD` + # values into the wrapper environment. ]; # Environment variables with defaults (can be overridden by user) diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index aadd70f..2db1515 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -22,16 +22,6 @@ in nvim-host.flags."--neovim-bin" = "${builtins.placeholder "out"}/bin/${config.binName}"; }; - m = { - nvim-host.enable = false; - nvim-host.package = "${pkgs.uv}/bin/uv"; - nvim-host.argv0 = "uv"; - nvim-host.addFlag = [ - "run" - "marimo" - "edit" - ]; - }; } (lib.mkIf (config.cats.julia or false) { jl = { diff --git a/plugin/00_options.lua b/plugin/00_options.lua index e824333..16f3832 100644 --- a/plugin/00_options.lua +++ b/plugin/00_options.lua @@ -128,27 +128,20 @@ local diagnostic_opts = { -- Don't update diagnostics when typing update_in_insert = false, } -later(function() vim.diagnostic.config(diagnostic_opts) end) +later(function() + vim.diagnostic.config(diagnostic_opts) - --- Custom autocommands ======================================================== -local augroup = vim.api.nvim_create_augroup('CustomSettings', {}) - -vim.api.nvim_create_autocmd('FileType', { - pattern = { 'markdown' }, - group = augroup, - callback = function() - vim.diagnostic.config({ - signs = { - severity = { min = 'WARN', max = 'ERROR' } - }, - virtual_text = { - current_line = false, - severity = { min = 'HINT', max = 'ERROR' } - } - }) - end -}) + -- Relax diagnostics for markdown (many false positives) + vim.api.nvim_create_autocmd('FileType', { + pattern = { 'markdown' }, + callback = function() + vim.diagnostic.config({ + signs = { severity = { min = 'WARN', max = 'ERROR' } }, + virtual_text = { severity = { min = 'HINT', max = 'ERROR' }, current_line = false }, + }) + end, + }) +end) vim.api.nvim_create_autocmd("FileType", { desc = "remove formatoptions", @@ -157,17 +150,6 @@ vim.api.nvim_create_autocmd("FileType", { vim.b.minitrailspace_disable = true -- Don't highlight trailing space by default end, }) - -vim.api.nvim_create_autocmd('FileType', { - group = augroup, - callback = function() - -- Don't auto-wrap comments and don't insert comment leader after hitting 'o' - -- If don't do this on `FileType`, this keeps reappearing due to being set in - -- filetype plugins. - vim.cmd('setlocal formatoptions-=r formatoptions-=o') - end, - desc = [[Ensure proper 'formatoptions']], -}) -- Neovide ============================================== if vim.g.neovide then vim.g.neovide_cursor_vfx_mode = "pixiedust" @@ -176,7 +158,7 @@ if vim.g.neovide then vim.g.neovide_cursor_short_animation_length = 0 vim.g.neovide_font_hinting = 'none' vim.g.neovide_font_edging = 'subpixelantialias' - vim.o.guifont = 'JetBrainsMono Nerd Font,Symbols Nerd Font:h14:#e-subpixelantialias:#h-none' + vim.o.guifont = 'JetBrainsMono Nerd Font:h14:#e-subpixelantialias:#h-none' vim.g.neovide_floating_corner_radius = 0.35 vim.keymap.set("n", "nf", "NeovideFullscreen", { desc = "Toggle Neovide Fullscreen" }) end diff --git a/plugin/01_lib.lua b/plugin/01_lib.lua index 1197ee8..64bd37e 100644 --- a/plugin/01_lib.lua +++ b/plugin/01_lib.lua @@ -12,7 +12,7 @@ end Config.log = {} Config.log_buf_id = Config.log_buf_id or nil -Config.start_hrtime = Config.start_hrtime or vim.loop.hrtime() +Config.start_hrtime = Config.start_hrtime or vim.uv.hrtime() Config.log_print = function() if Config.log_buf_id == nil or not vim.api.nvim_buf_is_valid(Config.log_buf_id) then @@ -24,7 +24,7 @@ end Config.log_clear = function() Config.log = {} - Config.start_hrtime = vim.loop.hrtime() + Config.start_hrtime = vim.uv.hrtime() vim.cmd('echo "Cleared log"') end diff --git a/plugin/10_keymap.lua b/plugin/10_keymap.lua index 5682b93..8ae0a1c 100644 --- a/plugin/10_keymap.lua +++ b/plugin/10_keymap.lua @@ -66,21 +66,28 @@ nmap_leader('', 'bnext', 'Next buffer') nmap_leader('', 'bprev', 'Prev buffer') -- a is for 'AI' +nmap_leader("aa", "CodeCompanion /agent", "Agent chat (@{agent} tools)") nmap_leader("ac", "CodeCompanionChat Toggle", "Chat Toggle") --- nmap_leader("ae", "CodeCompanion /explain", "Explain Code") --- nmap_leader("af", "CodeCompanion /fix", "Fix Code") +nmap_leader("aC", function() + local chat = require("codecompanion").last_chat() + if not chat then + return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN) + end + require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 }) +end, "Compact chat") nmap_leader("ag", "CodeCompanion /commit", "Generate commit message") nmap_leader("ai", "CodeCompanionActions", "Chat Action") nmap_leader("al", "CodeCompanion /lsp", "Explain LSP Diagnostics") nmap_leader("an", "CodeCompanionChat Add", "Chat New") nmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") ---nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") +nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan, implement, test") nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") +xmap_leader("aa", "CodeCompanion /agent", "Agent on selection") xmap_leader("ae", "CodeCompanion /explain", "Explain Code") xmap_leader("af", "CodeCompanion /fix", "Fix Code") ---xmap_leader("ap", "CodeCompanion /expert", "Code Fixer") -xmap_leader("ap", "CodeCompanion /expert", "Code Fixer") +xmap_leader("ap", "CodeCompanion /expert", "Code Expert") xmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") +nmap_leader("ak", "CodeCompanionChat adapter=codex", "Chat with Codex") -- b is for 'buffer' nmap_leader('bb', 'b#', 'Alternate') @@ -123,7 +130,7 @@ nmap_leader('fm', 'Pick marks', 'Marks') nmap_leader('fn', 'ZkNotes', "Notes") nmap_leader('fk', 'Pick keymaps', 'Keymaps') nmap_leader('fR', 'Pick resume', 'Resume') -nmap_leader('fp', 'Pick projects', 'Projects') +nmap_leader('fp', 'Pick files', 'Files') nmap_leader('fq', 'Pick list scope="quickfix"', 'Quickfix') nmap_leader('fr', 'Pick lsp scope="references"', 'References (LSP)') nmap_leader('flr', 'Pick lsp scope="references"', 'References (LSP)') @@ -164,7 +171,7 @@ vim.keymap.set({ 'n' }, 'grk', 'lua vim.lsp.buf.hover()', { desc = 'Doc vim.keymap.set({ 'n' }, 'gre', 'lua vim.diagnostic.open_float()', { desc = 'Diagnostics' }) nmap_lsp("K", 'lua vim.lsp.buf.hover()', "Documentation") -local formatting_cmd = 'lua require("conform").format({ lsp_fallback = true })' +local formatting_cmd = 'lua require("conform").format({ lsp_format = "fallback" })' nmap_leader('la', 'lua vim.lsp.buf.code_action()', 'Actions') nmap_leader('le', 'lua vim.diagnostic.open_float()', 'Diagnostics popup') nmap_leader('lf', formatting_cmd, 'Format') @@ -189,11 +196,9 @@ nmap_leader('Lx', 'lua Config.execute_lua_line()', 'Execute `lua` line' -- o is for 'other' local trailspace_toggle_command = 'lua vim.b.minitrailspace_disable = not vim.b.minitrailspace_disable' -nmap_leader('od', 'Neogen', 'Document') nmap_leader('oh', 'normal gxiagxila', 'Move arg left') nmap_leader('ol', 'normal gxiagxina', 'Move arg right') nmap_leader('or', 'lua MiniMisc.resize_window()', 'Resize to default width') -nmap_leader('oS', 'lua Config.insert_section()', 'Section insert') nmap_leader('ot', 'lua MiniTrailspace.trim()', 'Trim trailspace') nmap_leader('oT', trailspace_toggle_command, 'Trailspace hl toggle') nmap_leader('oz', 'lua MiniMisc.zoom()', 'Zoom toggle') diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index 0404b2c..d17e2f9 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -208,112 +208,31 @@ now_if_args(function() on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching } - - local ok_configs, configs = pcall(require, "nvim-treesitter.configs") - - if ok_configs and configs.setup then - local opts = { - highlight = { enable = true }, - indent = { enable = false }, - textobjects = { - move = { - enable = true, - set_jumps = true, - goto_next_start = { - ["]a"] = "@parameter.inner", -- fixed typo - ["]f"] = "@function.outer", - ["]o"] = "@loop.*", - ["]s"] = { query = "@local.scope", desc = "Next scope" }, - ["]z"] = { query = "@fold", desc = "Next fold" }, - }, - goto_next_end = { - ["]M"] = "@function.outer", - ["]["] = "@class.outer", - }, - goto_previous_start = { - ["[a"] = "@parameter.inner", - ["[f"] = "@function.outer", - ["[o"] = "@loop.*", - ["[s"] = { query = "@local.scope", query_group = "locals", desc = "Prev. scope" }, - ["[z"] = { query = "@fold", query_group = "folds", desc = "Prev. fold" }, - }, - goto_previous_end = { - ["[M"] = "@function.outer", - ["[]"] = "@class.outer", - }, - goto_next = { - ["]e"] = "@conditional.outer", - }, - goto_previous = { - ["[e"] = "@conditional.outer", - }, - }, - swap = { - enable = true, - swap_next = { - ["x"] = "@parameter.inner", - }, - swap_previous = { - ["X"] = "@parameter.inner", - }, - }, - lsp_interop = { - enable = true, - border = "none", - floating_preview_opts = {}, - peek_definition_code = { - ["lm"] = "@function.outer", - ["lM"] = "@class.outer", - }, - }, - }, - } - - - -- Manual parser check for non-Nix users - if not Config.isNixCats then - local installed_check = function(lang) - return #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 - end - local to_install = vim.tbl_filter(installed_check, opts.ensure_installed) - if #to_install > 0 then - require("nvim-treesitter").install(to_install) - end - end - -- Environment-specific Overrides - if not Config.isNixCats then - opts.auto_install = true - opts.ensure_installed = Config.treesitter_helpers.default_parsers - else - opts.auto_install = false - -- Nix handles installation, so ensure_installed is skipped/empty - end - - - configs.setup(opts) - return - end - + local ts_filetypes = { + "c", "cpp", "lua", "nix", "python", "r", "markdown", "query", + "vim", "vimdoc", "yaml", "json", "toml", "rust", "go", + "javascript", "typescript", "tsx", "html", "css", "sql", + "julia", "rnoweb", "latex", "gitcommit", "gitignore", + "git_config", "git_rebase", "diff", "dockerfile", + "make", "xml", "zig", "regex", "csv", "bash", + "markdown_inline", "quarto", "rmd", "codecompanion", + } vim.api.nvim_create_autocmd("FileType", { - pattern = "*", - callback = function(args) - -- Use explicit buffer + filetype to avoid any ambiguity - local ok = pcall(vim.treesitter.start, args.buf, args.match) - vim.bo.syntax = 'on' - vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" - vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()' - vim.wo[0][0].foldmethod = 'expr' + pattern = ts_filetypes, + callback = function(ev) + local lang = ev.match + if vim.treesitter.language.get_lang then + lang = vim.treesitter.language.get_lang(lang) or lang + end + if #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 then + return + end + vim.treesitter.start(ev.buf, lang) + vim.bo[ev.buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" end, }) - -- Textobjects: require plugin and bail out quietly if missing - local ok_nto, nto = pcall(require, "nvim-treesitter-textobjects") - if not ok_nto then - return - end - - vim.g.no_plugin_maps = true - nto.setup({ + require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true, }, @@ -322,70 +241,36 @@ now_if_args(function() local move = require("nvim-treesitter-textobjects.move") local swap = require("nvim-treesitter-textobjects.swap") - -- Map motion function names to actual functions - local move_fns = { - goto_next_start = move.goto_next_start, - goto_next_end = move.goto_next_end, - goto_previous_start = move.goto_previous_start, - goto_previous_end = move.goto_previous_end, - goto_next = move.goto_next, - goto_previous = move.goto_previous, - } + vim.keymap.set("n", "]a", function() move.goto_next_start("@parameter.inner", "textobjects") end, { desc = "Next parameter" }) + vim.keymap.set("n", "[a", function() move.goto_previous_start("@parameter.inner", "textobjects") end, { desc = "Prev parameter" }) + vim.keymap.set("n", "]f", function() move.goto_next_start("@function.outer", "textobjects") end, { desc = "Next function" }) + vim.keymap.set("n", "[f", function() move.goto_previous_start("@function.outer", "textobjects") end, { desc = "Prev function" }) + vim.keymap.set("n", "]o", function() move.goto_next_start("@loop.*", "textobjects") end, { desc = "Next loop" }) + vim.keymap.set("n", "[o", function() move.goto_previous_start("@loop.*", "textobjects") end, { desc = "Prev loop" }) + vim.keymap.set("n", "]s", function() move.goto_next_start("@local.scope", "textobjects") end, { desc = "Next scope" }) + vim.keymap.set("n", "[s", function() move.goto_previous_start("@local.scope", "locals") end, { desc = "Prev scope" }) + vim.keymap.set("n", "]z", function() move.goto_next_start("@fold", "folds") end, { desc = "Next fold" }) + vim.keymap.set("n", "[z", function() move.goto_previous_start("@fold", "folds") end, { desc = "Prev fold" }) + vim.keymap.set("n", "]M", function() move.goto_next_end("@function.outer", "textobjects") end, { desc = "Next function end" }) + vim.keymap.set("n", "][", function() move.goto_next_end("@class.outer", "textobjects") end, { desc = "Next class end" }) + vim.keymap.set("n", "[M", function() move.goto_previous_end("@function.outer", "textobjects") end, { desc = "Prev function end" }) + vim.keymap.set("n", "[]", function() move.goto_previous_end("@class.outer", "textobjects") end, { desc = "Prev class end" }) + vim.keymap.set("n", "]e", function() move.goto_next("@conditional.outer", "textobjects") end, { desc = "Next conditional" }) + vim.keymap.set("n", "[e", function() move.goto_previous("@conditional.outer", "textobjects") end, { desc = "Prev conditional" }) - -- All motions defined in one place - -- spec = { query_or_list, query_group, desc } - local move_maps = { - goto_next_start = { - ["]a"] = { "@parameter.inner", "textobjects", "Next parameter" }, - ["]f"] = { "@function.outer", "textobjects", "Next function start" }, - ["]o"] = { { "@loop.inner", "@loop.outer" }, "textobjects", "Next loop" }, - ["]s"] = { "@local.scope", "locals", "Next scope" }, - ["]z"] = { "@fold", "folds", "Next fold" }, - }, - goto_next_end = { - ["]M"] = { "@function.outer", "textobjects", "Next function end" }, - ["]["] = { "@class.outer", "textobjects", "Next class end" }, - }, - goto_previous_start = { - ["[a"] = { "@parameter.inner", "textobjects", "Previous parameter" }, - ["[f"] = { "@function.outer", "textobjects", "Previous function start" }, - ["[o"] = { { "@loop.inner", "@loop.outer" }, "textobjects", "Previous loop" }, - ["[s"] = { "@local.scope", "locals", "Previous scope" }, - ["[z"] = { "@fold", "folds", "Previous fold" }, - }, - goto_previous_end = { - ["[M"] = { "@function.outer", "textobjects", "Previous function end" }, - ["[]"] = { "@class.outer", "textobjects", "Previous class end" }, - }, - goto_next = { - ["]e"] = { "@conditional.outer", "textobjects", "Next conditional" }, - }, - goto_previous = { - ["[e"] = { "@conditional.outer", "textobjects", "Previous conditional" }, - }, - } + vim.keymap.set("n", "x", function() swap.swap_next("@parameter.inner", "textobjects") end, { desc = "Swap parameter next" }) + vim.keymap.set("n", "X", function() swap.swap_previous("@parameter.inner", "textobjects") end, { desc = "Swap parameter prev" }) - -- Generate motion keymaps - for fn_name, maps in pairs(move_maps) do - local fn = move_fns[fn_name] - if fn then - for lhs, spec in pairs(maps) do - local query_or_list, group, desc = spec[1], spec[2], spec[3] - vim.keymap.set({ "n", "x", "o" }, lhs, function() - fn(query_or_list, group) - end, { desc = desc }) - end + if not Config.isNixCats then + local installed_check = function(lang) + return #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 + end + local default_parsers = Config.treesitter_helpers.default_parsers + local to_install = vim.tbl_filter(installed_check, default_parsers) + if #to_install > 0 then + require("nvim-treesitter").install(to_install) end end - - -- Swap keymaps (unchanged, but minimal) - vim.keymap.set("n", "x", function() - swap.swap_next("@parameter.inner") - end, { desc = "Swap with next parameter" }) - - vim.keymap.set("n", "X", function() - swap.swap_previous("@parameter.inner") - end, { desc = "Swap with previous parameter" }) end) -- zk diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index aeab360..cb496b9 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -5,18 +5,18 @@ local add = Config.add local nix = require('config.nix') if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add now(function() - m_add({ source = "R-nvim/R.nvim" }) + add({ source = "R-nvim/R.nvim" }) end) now_if_args(function() - m_add({ source = "jmbuhr/otter.nvim" }) + add({ source = "jmbuhr/otter.nvim" }) end) later(function() - m_add({ source = "jpalardy/vim-slime" }) + add({ source = "jpalardy/vim-slime" }) end) end @@ -44,12 +44,6 @@ end) -- r now(function() if nix.get_cat("r", false) then - local cwd = vim.fn.getcwd(-1) - vim.env.RNVIM_COMPLDIR = cwd .. "/.r-compl" - vim.env.R_LIBS_USER = (vim.env.R_LIBS_USER or ""):gsub("%$PWD", cwd) - vim.env.TMPDIR = cwd .. "/.r-tmp" - vim.fn.mkdir(vim.env.RNVIM_COMPLDIR, "p") - vim.fn.mkdir(vim.env.TMPDIR, "p") vim.g.rout_follow_colorscheme = true require("r").setup({ -- Create a table with the options to be passed to setup() @@ -62,6 +56,20 @@ now(function() rconsole_height = 20, nvimpager = "split_h", pdfviewer = "", + -- Use R.nvim's built-in rnvimserver-backed language server. Do not + -- configure the external R languageserver through plugin/25_lsp.lua. + r_ls = { + completion = true, + hover = true, + signature = true, + definition = true, + references = true, + implementation = true, + document_symbol = true, + workspace_symbol = true, + document_highlight = true, + rename = true, + }, }) end end) @@ -69,8 +77,6 @@ end) -- Quarto now(function() - vim.treesitter.language.register("markdown", { "quarto", "rmd" }) - if nix.get_cat({ "r", "markdown" }, false) then vim.api.nvim_create_autocmd("FileType", { pattern = { "quarto" }, diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index ed4682a..70bec23 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -4,10 +4,10 @@ local later = MiniDeps.later local nix = require('config.nix') if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add later(function() - m_add({ source = "Bilal2453/luvit-meta" }) - m_add({ source = "folke/lazydev.nvim" }) + add({ source = "Bilal2453/luvit-meta" }) + add({ source = "folke/lazydev.nvim" }) end) end diff --git a/plugin/23_editor.lua b/plugin/23_editor.lua index d4669d8..4345e2d 100644 --- a/plugin/23_editor.lua +++ b/plugin/23_editor.lua @@ -2,10 +2,10 @@ local later = MiniDeps.later local add = Config.add if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add later(function() - m_add("stevearc/conform.nvim") + add("stevearc/conform.nvim") end) end @@ -23,20 +23,6 @@ later(function() rmd = { "injected" }, quarto = { "injected" }, }, - - default_format_opts = { - lsp_format = "fallback", - }, - - formatters = { - my_styler = { - command = "R", - -- A list of strings, or a function that returns a list of strings - -- Return a single string instead of a list to run the command in a shell - args = { "-s", "-e", "styler::style_file(commandArgs(TRUE)[1])", "--args", "$FILENAME" }, - stdin = false, - }, - }, }) end) diff --git a/plugin/24_completion.lua b/plugin/24_completion.lua index 3e0f00c..f6516a1 100644 --- a/plugin/24_completion.lua +++ b/plugin/24_completion.lua @@ -4,7 +4,7 @@ local now = MiniDeps.now local now_if_args = Config.now_if_args -- Constants -local BLINK_VERSION = "v1.4.1" +local BLINK_VERSION = "v1.10.2" -- Plugin sources configuration local PLUGIN_SOURCES = { @@ -26,26 +26,16 @@ local PLUGIN_ADDS = { -- Helper functions local function create_system_prompt(role_description) return function(context) - return "I want you to act as a senior " .. context.filetype .. " developer. " .. role_description + local lang = context.filetype or "programmer" + return "I want you to act as a senior " .. lang .. " developer. " .. role_description end end local function get_code_block(context) - local text = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line) + local text = require("codecompanion.helpers.code").get_code(context.start_line, context.end_line) return "```" .. context.filetype .. "\n" .. text .. "\n```" end -local function create_common_opts(mapping, short_name) - return { - mapping = mapping, - modes = { "v" }, - short_name = short_name, - auto_submit = true, - stop_context_insertion = true, - user_prompt = true, - } -end - local function get_mini_icons_highlight(ctx) local _, hl, _ = require("mini.icons").get("lsp", ctx.kind) return hl @@ -57,7 +47,7 @@ local function get_blink_fuzzy_setting() } if not Config.isNixCats then - setting.prebuilt_binary = { force_version = BLINK_VERSION } + setting.prebuilt_binaries = { force_version = BLINK_VERSION } end return setting @@ -65,10 +55,10 @@ end -- Plugin loading if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add now_if_args(function() - m_add({ + add({ source = "saghen/blink.cmp", depends = { "rafamadriz/friendly-snippets" }, checkout = BLINK_VERSION, @@ -77,38 +67,91 @@ if not Config.isNixCats then later(function() for _, source in ipairs(PLUGIN_SOURCES) do - m_add({ source = source }) + add({ source = source }) end end) end local function get_codecompanion_config() return { + adapters = { + acp = { + -- Codex = heavy agent lane (ChatGPT Edu login via `codex login`; ~/.codex/auth.json). + -- Requires `codex-acp` on PATH (~/.nix-profile/bin). ACP-only slash commands in the + -- chat buffer: /resume (restore a past session, fresh chat only), /mode (switch agent + -- mode), /command, /acp_session_options (e.g. model per session); `\` triggers ACP + -- command completion (1-5s delay after chat open). + codex = function() + return require("codecompanion.adapters").extend("codex", { + defaults = { + auth_method = "chatgpt", + }, + }) + end, + }, + }, interactions = { chat = { adapter = { name = "copilot", - model = "gemini-3.1-pro-preview", + model = "claude-sonnet-5", + }, + slash_commands = { + ["share"] = { + opts = { + token = os.getenv("GITHUB_GIST_TOKEN"), + }, + }, }, opts = { completion_provider = "blink", + context_management = { + editing = { + trigger = 0.65, + keep_cycles = 3, + exclude_tools = { "memory" }, + }, + compaction = { + trigger = 0.85, + min_token_savings = 10000, + }, + }, }, }, inline = { adapter = { name = "copilot", - model = "gemini-3.1-pro-preview", - } - }, - keymaps = { - accept_change = { - modes = { n = "ga" }, - description = "Accept the suggested change", + model = "gpt-5-mini", }, - reject_change = { - modes = { n = "gr" }, - opts = { nowait = true }, - description = "Reject the suggested change", + }, + shared = { + keymaps = { + accept_change = { + modes = { n = "ga" }, + description = "Accept the suggested change", + }, + reject_change = { + modes = { n = "gr" }, + opts = { nowait = true }, + description = "Reject the suggested change", + }, + }, + }, + background = { + adapter = { + name = "copilot", + model = "gpt-5-mini", + }, + chat = { + callbacks = { + ["on_ready"] = { + actions = { "interactions.background.builtin.chat_make_title" }, + enabled = true, + }, + }, + opts = { + enabled = true, + }, }, }, }, @@ -121,12 +164,38 @@ local function get_codecompanion_config() height = 0.33, }, }, + diff = { + enabled = true, + threshold_for_chat = 6, + }, + }, + rules = { + default = { + description = "Collection of common files for all projects", + files = { + ".clinerules", + ".cursorrules", + ".rules", + ".github/copilot-instructions.md", + "AGENT.md", + "AGENTS.md", + { path = "CLAUDE.md", parser = "claude" }, + { path = "CLAUDE.local.md", parser = "claude" }, + { path = "~/.claude/CLAUDE.md", parser = "claude" }, + }, + }, + opts = { + chat = { + autoload = "default", + enabled = true, + }, + }, }, prompt_library = { ["expert"] = { interaction = "chat", description = "Get expert advice from an LLM", - --opts = create_common_opts("ae", "expert"), + opts = { alias = "expert" }, prompts = { { role = "system", @@ -146,7 +215,7 @@ local function get_codecompanion_config() ["fixer"] = { interaction = "chat", description = "Fix code errors with expert guidance", - --opts = create_common_opts("af", "afixer"), + opts = { alias = "fixer" }, prompts = { { role = "system", @@ -166,7 +235,7 @@ local function get_codecompanion_config() ["suggest"] = { interaction = "chat", description = "Suggest improvements to the buffer", - --opts = create_common_opts("as", "suggest"), + opts = { alias = "suggest" }, prompts = { { role = "system", @@ -188,7 +257,53 @@ local function get_codecompanion_config() }, }, }, - } + ["agent"] = { + interaction = "chat", + description = "Agentic coding with the @{agent} tool group (read/edit/grep/run)", + opts = { alias = "agent" }, + prompts = { + { + role = "user", + content = function(context) + return "@{agent} Work on the following code:\n\n" .. get_code_block(context) .. "\n\n" + end, + opts = { contains_code = true }, + }, + }, + }, + ["tdd"] = { + interaction = "chat", + description = "Workflow: plan the buffer change, implement it, run the tests", + opts = { alias = "tdd", is_workflow = true }, + prompts = { + { + { + role = "user", + content = function(context) + return "Let's work test-driven. First, study #buffer and the relevant parts of the codebase, then propose a concise implementation plan (no code yet).\n\nThe code under discussion:\n\n" + .. get_code_block(context) + .. "\n\nThe task: " + end, + opts = { contains_code = true }, + }, + }, + { + { + role = "user", + content = "Implement the plan now, writing or updating tests alongside the code. @{agent}", + opts = { auto_submit = true }, + }, + }, + { + { + role = "user", + content = "Run the project's test suite with @{run_command} and fix any failures until it passes.", + opts = { auto_submit = true }, + }, + }, + }, + }, + }, } end @@ -244,6 +359,13 @@ later(function() end) +-- CodeCompanion habit notes (chat buffer unless stated): +-- /compact compact history, keep summary /fork fork the conversation +-- /symbols insert symbols for a file /share export chat to a GitHub gist +-- gm toggle "btw" ephemeral message gty YOLO: approve all tool calls +-- gba/gbd buffer sync add/drop gd debug window (adapter/tools info) +-- Codex (ACP) lane: /resume (fresh chat only), /mode, /command, /acp_session_options, +-- `\` ACP command completion. Prompt library: /expert /fixer /suggest /agent /tdd (workflow). later(function() add("codecompanion.nvim") @@ -316,7 +438,10 @@ now_if_args(function() }, snippets = { preset = "mini_snippets" }, sources = { - default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot", "codecompanion" }, + per_filetype = { + codecompanion = { "codecompanion" }, + }, + default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot" }, providers = { path = { score_offset = 50, @@ -335,10 +460,6 @@ now_if_args(function() score_offset = 10, opts = { cmp_name = "cmdline" } }, - cmp_r = { - name = "cmp_r", - module = "blink.compat.source", - }, copilot = { name = "copilot", module = "blink-copilot", diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index f3c4c50..8c40915 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -8,24 +8,14 @@ if not Config.isNixCats then end now_if_args(function() + -- R.nvim owns the R `r_ls` client and starts its bundled rnvimserver from + -- its own plugin directory. Keep R out of this generic server registry. local servers = { - clangd = {}, basedpyright = {}, ruff = {}, marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, - r_ls = { - filetypes = { 'r', 'rmd', 'rmarkdown' }, - settings = { - ['r_ls'] = { - lsp = { - rich_documentation = true, - enable = true, - }, - }, - } - }, julials = { settings = { julia = { From 6542de2212e7aaadc7101b616339f2583753464c Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:08:08 +1000 Subject: [PATCH 2/5] updates --- flake.nix | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index a8584e0..ce3082d 100644 --- a/flake.nix +++ b/flake.nix @@ -233,15 +233,19 @@ } ); - nixosModules.default = wrappers.lib.mkInstallModule { + nixosModules.default = wrappers.lib.getInstallModule { name = "vModule"; value = module; }; - homeModules.default = wrappers.lib.mkInstallModule { + homeModules.default = wrappers.lib.getInstallModule { name = "vModule"; - value = module; - loc = ["home" "packages"]; + value = [ + module + { + config.install.optionLocation = [ "home" "packages" ]; + } + ]; }; }; } From 509442f0b471f654c9faeca587ad7ee3a59b7145 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:27:32 +1000 Subject: [PATCH 3/5] testing --- flake.nix | 7 +------ plugin/20_startup.lua | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/flake.nix b/flake.nix index ce3082d..db14f58 100644 --- a/flake.nix +++ b/flake.nix @@ -240,12 +240,7 @@ homeModules.default = wrappers.lib.getInstallModule { name = "vModule"; - value = [ - module - { - config.install.optionLocation = [ "home" "packages" ]; - } - ]; + value = module; }; }; } diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index d17e2f9..e8d6693 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -217,21 +217,27 @@ now_if_args(function() "make", "xml", "zig", "regex", "csv", "bash", "markdown_inline", "quarto", "rmd", "codecompanion", } + local function start_treesitter(buf, filetype) + local lang = vim.treesitter.language.get_lang(filetype) or filetype + if #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 then + return + end + vim.treesitter.start(buf, lang) + vim.bo[buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" + end + vim.api.nvim_create_autocmd("FileType", { pattern = ts_filetypes, callback = function(ev) - local lang = ev.match - if vim.treesitter.language.get_lang then - lang = vim.treesitter.language.get_lang(lang) or lang - end - if #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 then - return - end - vim.treesitter.start(ev.buf, lang) - vim.bo[ev.buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" + start_treesitter(ev.buf, vim.bo[ev.buf].filetype) end, }) + -- FileType may have fired before this deferred setup ran. + if vim.bo.filetype ~= "" then + start_treesitter(0, vim.bo.filetype) + end + require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true, From cd1e524eb8a61dac3ba2c1c5ed0f7447ab4a5923 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:35:40 +1000 Subject: [PATCH 4/5] testing --- modules/module/specs/plugins.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ad464b8..695e6bd 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -4,6 +4,7 @@ lib, ... }: { + config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or false) { data = []; }; From 0ea9b249edad34daffa043dc97b3575ebcd1547b Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 16:28:07 +1000 Subject: [PATCH 5/5] Fixed syntax highlighting --- modules/module/specs/plugins.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 695e6bd..63d32d0 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -111,13 +111,17 @@ nvim-lspconfig nvim-treesitter-context nvim-treesitter-textobjects + { + data = pkgs.vimPlugins.nvim-treesitter; + pname = "nvim-treesitter"; + } { data = pkgs.codecompanion-nvim.overrideAttrs (old: { doCheck = false; }); pname = "codecompanion"; } - ]; + ] ++ builtins.attrValues pkgs.vimPlugins.nvim-treesitter.queries; }; config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or false) {