diff --git a/.Rprofile b/.Rprofile deleted file mode 100644 index 7617f61..0000000 --- a/.Rprofile +++ /dev/null @@ -1,5 +0,0 @@ -if (Sys.getenv("RNVIM_TMPDIR") == "") { - options(defaultPackages = c("utils", "grDevices", "graphics", "stats", "methods")) -} else { - options(defaultPackages = c("utils", "grDevices", "graphics", "stats", "methods", "nvimcom")) -} diff --git a/.github/dependabot.yml b/.github/dependapot.yml similarity index 100% rename from .github/dependabot.yml rename to .github/dependapot.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e96a388..637fb10 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -5,20 +5,12 @@ on: paths: - 'flake.lock' - 'flake.nix' - - 'modules/**' - - 'plugin/**' - - 'lua/**' - - 'overlays/**' - - 'ftplugin/**' + - 'modules' pull_request: paths: - 'flake.lock' - 'flake.nix' - - 'modules/**' - - 'plugin/**' - - 'lua/**' - - 'overlays/**' - - 'ftplugin/**' + - 'modules' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -29,7 +21,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v4 - uses: wimpysworld/nothing-but-nix@main if: runner.os == 'Linux' with: @@ -37,11 +29,11 @@ jobs: - uses: cachix/install-nix-action@v31 with: github_access_token: ${{ secrets.GH_TOKEN }} - - uses: cachix/cachix-action@v17 + - uses: cachix/cachix-action@v14 with: name: rde authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' extraPullNames: rstats-on-nix, nix-community - run: nix build - - run: nix develop -c echo ok + - run: nix develop - run: nix flake check diff --git a/.github/workflows/flakehub-publish-rolling.yml b/.github/workflows/flakehub-publish-rolling.yml index 50ccb47..57250d0 100644 --- a/.github/workflows/flakehub-publish-rolling.yml +++ b/.github/workflows/flakehub-publish-rolling.yml @@ -10,7 +10,7 @@ jobs: id-token: "write" contents: "read" steps: - - uses: "actions/checkout@v7" + - uses: "actions/checkout@v5" with: persist-credentials: false - uses: "DeterminateSystems/determinate-nix-action@v3" diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 3bd7493..a65fbdc 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -10,13 +10,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@v4 - uses: wimpysworld/nothing-but-nix@main with: hatchet-protocol: 'carve' - name: Install Determinate Nix uses: DeterminateSystems/determinate-nix-action@v3 - - uses: cachix/cachix-action@v17 + - uses: cachix/cachix-action@v14 with: name: rde authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' diff --git a/.gitignore b/.gitignore index 6e33f00..8474af7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,3 @@ *.R .Rlibs .nvimcom -.commandcode -.commandcode/ -.r-* diff --git a/.kilo/plans/1784851829193-codecompanion-revamp.md b/.kilo/plans/1784851829193-codecompanion-revamp.md deleted file mode 100644 index b5898e4..0000000 --- a/.kilo/plans/1784851829193-codecompanion-revamp.md +++ /dev/null @@ -1,85 +0,0 @@ -# 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 deleted file mode 100644 index d0f0c63..0000000 --- a/.kilo/plans/1784858731550-nix-neovim-review.md +++ /dev/null @@ -1,191 +0,0 @@ -# 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/.letta/settings.local.json b/.letta/settings.local.json deleted file mode 100644 index 7656883..0000000 --- a/.letta/settings.local.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "lastAgent": "agent-local-096f2785-09c8-4487-b71b-f8ac2b81aaaf", - "sessionsByServer": { - "api.letta.com": { - "agentId": "agent-21b21fd6-49e4-4d5c-a1b7-1c6dcd8dacc9", - "conversationId": "default" - }, - "local:/Users/daniel/.letta/lc-local-backend": { - "agentId": "agent-local-096f2785-09c8-4487-b71b-f8ac2b81aaaf", - "conversationId": "default" - } - }, - "lastSession": { - "agentId": "agent-local-096f2785-09c8-4487-b71b-f8ac2b81aaaf", - "conversationId": "default" - } -} \ No newline at end of file diff --git a/.omp/tools/nvim_buffers.mjs b/.omp/tools/nvim_buffers.mjs deleted file mode 100644 index 82e9ce6..0000000 --- a/.omp/tools/nvim_buffers.mjs +++ /dev/null @@ -1,185 +0,0 @@ -// omp custom tool: read this machine's running Neovim buffers over its RPC -// socket. Read-only and on-demand — the model calls it when it needs buffer -// content; nothing is injected into prompts automatically. -// -// Install options -// 1. Project scope (this project): keep the file in .omp/tools/ — omp picks it -// up when a session's cwd is inside this repository. -// 2. All projects: mkdir -p ~/.omp/agent/tools -// ln -s "$PWD/.omp/tools/nvim_buffers.mjs" ~/.omp/agent/tools/ -// then restart `omp` (custom tools are loaded at session bootstrap). -// -// Socket contract (must match lua/nvim_omp/init.lua): -// path = $NVIM_OMP_SOCKET or /nvim/omp.sock -// The nvim instance running this config starts the listener at startup. -// Only one instance can own the socket; a second instance never replaces a -// live owner. Remove a stale socket manually only after confirming no nvim -// process owns it. - -import os from "node:os"; -import path from "node:path"; - -// Safe default cap so a large buffer cannot flood the conversation. -const DEFAULT_MAX_LINES = 2000; - -export default function (pi) { - // Resolve socket path with the same rule as lua/nvim_omp/init.lua. - const socketPath = () => { - const env = process.env.NVIM_OMP_SOCKET; - if (env) return env; - const state = - process.env.XDG_STATE_HOME || - path.join(os.homedir(), ".local", "state"); - return path.join(state, "nvim", "omp.sock"); - }; - - // Evaluate expr in the remote nvim via `nvim --server`; returns parsed JSON. - const rpc = async (expr, signal) => { - const bin = process.env.NVIM_BIN || "nvim"; - const { code, stdout, stderr, killed } = await pi.exec( - bin, - ["--server", socketPath(), "--remote-expr", expr], - { signal }, - ); - if (killed) throw new Error("cancelled"); - if (code !== 0) { - const why = String(stderr || "").trim(); - throw new Error( - why.includes("E247") - ? `No Neovim RPC socket at ${socketPath()}. ` + - "Start nvim (or vv) first — the 31_nvim_omp.lua plugin binds the socket at startup." - : `nvim RPC failed (${code}): ${why}`, - ); - } - const text = String(stdout ?? "").trim(); - if (!text) throw new Error("empty response from nvim RPC"); - return JSON.parse(text); - }; - - // Resolve a buffer argument (number, string, or partial path) to a buffer - // number. Returns null when nothing matches. - const resolveBuf = async (buffer, signal) => { - if (buffer === undefined || buffer === null || buffer === "") { - return rpc("json_encode(nvim_get_current_buf())", signal); - } - if (typeof buffer === "number") return buffer; - const bufnum = Number(buffer); - if (Number.isInteger(bufnum) && bufnum > 0) return bufnum; - const list = await rpc( - "json_encode(map(nvim_list_bufs(), {i, v -> " + - "{'bufnr': v, 'name': nvim_buf_get_name(v)}}))", - signal, - ); - const q = String(buffer); - const hit = - list.find( - (b) => b.name === q || b.name.endsWith("/" + q) || b.name.endsWith(q), - ) || - list.find((b) => b.name.includes(q)); - return hit ? hit.bufnr : null; - }; - - const tools = []; - - // List open buffers ----------------------------------------------- - tools.push({ - name: "nvim_buffers", - label: "Neovim Buffers", - description: - "List the buffers currently open in the user's running Neovim " + - "(number, name, current/loaded state, modified). Use before " + - "nvim_buffer to pick a buffer.", - parameters: pi.arktype({}), - async execute(_id, _params, _onUpdate, _ctx, _signal) { - const list = await rpc( - "json_encode(map(nvim_list_bufs(), {i, v -> {'nr': v, " + - "'name': nvim_buf_get_name(v), " + - "'current': v == nvim_get_current_buf(), " + - "'loaded': nvim_buf_is_loaded(v), " + - "'modified': getbufvar(v, '&modified')}}))", - ); - if (!list.length) { - return { content: [{ type: "text", text: "No buffers open." }] }; - } - const lines = list.map( - (b) => - `${b.nr}\t${b.name || "[no name]"}\t` + - `${b.current ? "current " : ""}` + - `${b.loaded ? "" : "unloaded "}` + - `${b.modified ? "modified" : ""}`.trim(), - ); - return { - content: [ - { - type: "text", - text: `Open buffers (${list.length}):\n` + lines.join("\n"), - }, - ], - }; - }, - }); - - // Read a single buffer, line-capped ------------------------------- - tools.push({ - name: "nvim_buffer", - label: "Neovim Buffer", - description: - "Read lines of a Neovim buffer. `buffer` accepts the current buffer " + - "(default), a buffer number from nvim_buffers, or a file name/path " + - "open in Neovim. `maxLines` caps the returned lines (default 2000); " + - "pass a larger value explicitly to read more of a long buffer.", - parameters: pi.arktype({ - buffer: "string? | number?", - maxLines: "number?", - }), - async execute(_id, params, _onUpdate, _ctx, signal) { - const buf = await resolveBuf(params.buffer, signal); - if (buf === null) { - return { - content: [ - { - type: "text", - text: `No buffer matches '${params.buffer}'. List open buffers with nvim_buffers.`, - }, - ], - }; - } - const maxLines = - Number.isInteger(params.maxLines) && params.maxLines > 0 - ? Math.min(params.maxLines, 100000) - : DEFAULT_MAX_LINES; - - const expr = - `json_encode({'name': nvim_buf_get_name(${buf}), ` + - `'total': nvim_buf_line_count(${buf}), ` + - `'lines': nvim_buf_get_lines(${buf}, 0, ${maxLines}, 0)})`; - const { name, total, lines } = await rpc(expr, signal); - if (!total) { - return { - content: [ - { type: "text", text: `Buffer ${buf} (${name}) is empty.` }, - ], - }; - } - const truncated = - total > lines.length - ? `\n... ${total - lines.length} more lines ` + - `(raise maxLines to read them)` - : ""; - const numbered = lines.map((l, i) => `${i + 1}: ${l}`).join("\n"); - return { - content: [ - { - type: "text", - text: - `Buffer ${buf} (${name}), ${total} lines:\n` + - numbered + - truncated, - }, - ], - }; - }, - }); - - return tools; -} \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 068d704..0000000 --- a/README.md +++ /dev/null @@ -1,341 +0,0 @@ -# nvimConfig - -Modular Neovim wrapper config. Defines a wrapped `vv` binary with per-category packages, plugins, and environment variables. Available as NixOS module, Home Manager module, or single-starflake import. - -## Quick start (imported into another flake) - -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; - fran = { - url = "github:dwinkler1/fran"; - inputs.nixpkgs.follows = "rixpkgs"; - }; - nvimConfig = { - url = "github:dwinkler1/nvimConfig"; - inputs = { - nixpkgs.follows = "nixpkgs"; - rixpkgs.follows = "rixpkgs"; - fran.follows = "fran"; - }; - }; - }; - - outputs = { self, nixpkgs, nvimConfig, ... } @ inputs: let - systems = ["aarch64-darwin" "x86_64-linux" "aarch64-linux"]; - forAllSystems = nixpkgs.lib.genAttrs systems; - in { - packages = forAllSystems (system: let - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - overlays = [ nvimConfig.overlays.dependencies ]; - }; - evalResult = nvimConfig.lib.eval { - inherit pkgs; - modules = [ projectSettings ]; # see below - }; - in { - default = evalResult.config.wrap { inherit pkgs; }; - }); - - devShells = forAllSystems (system: let - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - overlays = [ nvimConfig.overlays.dependencies ]; - }; - evalResult = nvimConfig.lib.eval { - inherit pkgs; - modules = [ projectSettings ]; - }; - nv = evalResult.config.wrap { inherit pkgs; }; - in { - default = pkgs.mkShell { - packages = [ nv ] ++ nvimConfig.lib.devShellPackages evalResult.config; - shellHook = nvimConfig.lib.shellHook evalResult.config; - }; - }); - }; -} -``` - -## Home Manager module - -```nix -{ inputs, ... }: { - home.packages = [ - (inputs.nvimConfig.homeModules.default { inherit pkgs; }) - # Or via the overlay: - # pkgs.vv - ]; -} -``` - -## NixOS module - -```nix -{ inputs, ... }: { - environment.systemPackages = [ - (inputs.nvimConfig.nixosModules.default { inherit pkgs; }) - ]; -} -``` - -## Overriding settings - -Define a `projectSettings` attrset and pass it to `nvimConfig.lib.eval` or `nvimConfig.lib.mkWrapper`. The helper injects your overlaid `pkgs` automatically, so downstream consumers do not need to wire `specialArgs` themselves. Every option in `nvimConfig.wrapperConfigs.default` can be overridden here. Local `packages.default`, local `devShells.default`, downstream helper usage, and `pkgs.vv` all resolve the same module defaults unless you override them. - -### Basic downstream flake example - -This example enables a few cats, adds runtime tools to the shared wrapper/devShell package set, and appends language libraries to the default Python and R environments. - -```nix -{ - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; - fran = { - url = "github:dwinkler1/fran"; - inputs.nixpkgs.follows = "rixpkgs"; - }; - nvimConfig = { - url = "github:dwinkler1/nvimConfig"; - inputs = { - nixpkgs.follows = "nixpkgs"; - rixpkgs.follows = "rixpkgs"; - fran.follows = "fran"; - }; - }; - }; - - outputs = { nixpkgs, nvimConfig, ... }: let - system = "aarch64-darwin"; - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - overlays = [ nvimConfig.overlays.dependencies ]; - }; - projectSettings = { - cats = { - python = true; - r = true; - nix = true; - markdown = true; - optional = true; - }; - - settings.lang_packages = { - python = with pkgs.python3Packages; [ - pandas - pyarrow - ]; - r = with pkgs.rpkgs.rPackages; [ - fixest - modelsummary - ]; - }; - - catPkgs = { - python = [ - (pkgs.python3.withPackages (ps: - ps - ++ (with ps; [ - pandas - pyarrow - ]))) - pkgs.ruff - pkgs.basedpyright - pkgs.uv - pkgs.nodejs - ]; - nix = [ - pkgs.alejandra - pkgs.nil - ]; - optional = [ - pkgs.git - pkgs.fd - pkgs.ripgrep - ]; - }; - }; - evalResult = nvimConfig.lib.eval { - inherit pkgs; - modules = [ projectSettings ]; - }; - nv = evalResult.config.wrap { inherit pkgs; }; - in { - packages.${system}.default = nv; - - devShells.${system}.default = pkgs.mkShell { - packages = [ nv ] ++ nvimConfig.lib.devShellPackages evalResult.config; - shellHook = nvimConfig.lib.shellHook evalResult.config; - }; - }; -} -``` - -### Categories - -Enable only the cats you need. Defaults are listed in `modules/module/settings/cats.nix`. - -```nix -projectSettings = { - cats = { - python = true; # enable Python tooling - r = false; # disable R - nix = true; - lua = false; - markdown = false; - optional = false; - gitPlugins = false; - }; -}; -``` - -### Language packages (module defaults) - -Add Python/R/Julia libraries that get appended to the module defaults. The built-in defaults are: - -- Python: `duckdb`, `polars` -- R: `arrow`, `broom`, `data_table`, `janitor`, `styler` -- Julia: `DataFramesMeta`, `QuackIO` - -```nix -projectSettings = { - settings.lang_packages = { - python = with pkgs.python3Packages; [ pandas numpy ]; - r = with pkgs.rpkgs.rPackages; [ fixest data_table ]; - julia = [ "StatsBase" "Plots" ]; - }; -}; -``` - -Use `lib.mkForce` to replace rather than append: - -```nix -projectSettings = { - settings.lang_packages = { - python = pkgs.lib.mkForce (with pkgs.python3Packages; [ polars duckdb ]); - }; -}; -``` - -### Runtime dependencies (per-cat packages) - -Override `catPkgs` to change the full runtime tool lists that appear in both the wrapper PATH and the devShell. Use normal assignments to merge list values, or `lib.mkForce` to replace them. - -```nix -projectSettings = { - catPkgs = { - nix = [ pkgs.nil pkgs.nixfmt ]; - python = [ - (pkgs.python3.withPackages (ps: [ ps.pandas ps.duckdb ])) - pkgs.ruff - ]; - }; -}; -``` - -### Direct runtimePkgs override (always-on) - -Add packages to `catPkgs.always` for tools that should always be available regardless of other cat toggles. - -```nix -projectSettings = { - catPkgs = { - always = [ pkgs.git pkgs.curl pkgs.pre-commit ]; - }; -}; -``` - -### Runtime env vars - -```nix -projectSettings = { - env = { - MY_PROJECT_VAR = "some-value"; - }; - envDefault = { - EDITOR = "vv"; - }; -}; -``` - -`env` values are forced; `envDefault` values can be overridden by the user's shell. - -### Choosing the right override surface - -- Use `cats` to enable or disable a whole language or tooling category. -- Use `settings.lang_packages` to add or replace Python, R, or Julia libraries within a language environment. -- Use `catPkgs` to change the full runtime tool list for a category. `config.runtimePkgs` drives wrapper PATH composition, while `nvimConfig.lib.devShellPackages config` returns the package-only list suitable for `mkShell`. -- Use `env` or `envDefault` for wrapper environment variables. -- Use `specs` for plugin additions or custom runtime behavior. - -### Neovim settings - -```nix -projectSettings = { - settings = { - colorscheme = "kanagawa"; - background = "dark"; - wrapRc = true; - config_directory = ./nvim; # path to init.lua + lua/ dir - aliases = [ "v" "nvim" ]; # extra binary names (default: [ "vvim" ]) - }; - binName = "nv"; # binary name (default: vv) -}; -``` - -## Adding plugins - -```nix -projectSettings = { - specs = { - extraPlugins = { - data = with pkgs.vimPlugins; [ - telescope-nvim - which-key-nvim - ]; - }; - - extraLazy = { - lazy = true; - data = with pkgs.vimPlugins; [ dashboard-nvim ]; - }; - - # Inject Lua config at startup - extraLua = { - data = pkgs.writeText "extra.lua" '' - vim.opt.number = true - vim.opt.relativenumber = true - ''; - before = ["INIT_MAIN"]; - }; - }; -}; -``` - -## Architecture - -``` -settings/lang-packages.nix ──► settings.lang_packages - │ -cats.nix ───────────────► config.cats │ - │ │ - ▼ ▼ - cat-packages.nix ───────► config.catPkgs. - │ │ - ├──────────────┐ │ - ▼ ▼ ▼ - deps.nix PATH wrapper.config.wrap devShells.default -``` - -- **Overlays** (`overlays/`) inject dependency package sets into nixpkgs (`rpkgs`, `baseRPackages`, `basePythonPackages`, plugins). Top-level `rWrapper` and `quarto` are compatibility conveniences, but `pkgs.rpkgs` is the canonical R surface for downstream configuration. -- **cat-packages.nix** is the single source of truth for per-category packages. Each list is gated by its cat toggle. -- **deps.nix** wires `catPkgs` into the wrapper's runtime PATH. -- **settings.lang_packages** holds the shared defaults used by local outputs and downstream module consumers. -- **flake.nix** exports `lib.eval`, `lib.mkWrapper`, `lib.devShellPackages`, and `lib.shellHook` as the canonical downstream helpers, and builds `packages.default` and `devShells.default` from the same module config. diff --git a/flake.lock b/flake.lock index 195ab40..78a5bd8 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1781337902, - "narHash": "sha256-QjXxUvpOBbnlpSskBOZJ9nUbpYyFRD4cBo4s1aHniGs=", + "lastModified": 1776413584, + "narHash": "sha256-xqqv46MTveuT4yJH2YihmbHGy5mdLnnLFDebVmUws/E=", "owner": "dwinkler1", "repo": "fran", - "rev": "30ab141871ede23b245a90afea58e076ef83b515", + "rev": "da09626e4dd8f0f57078b3a04e0443a8c20defa1", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1785967620, - "narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=", + "lastModified": 1776169885, + "narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436", + "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", "type": "github" }, "original": { @@ -36,22 +36,6 @@ "type": "github" } }, - "plugins-bloocky": { - "flake": false, - "locked": { - "lastModified": 1786042339, - "narHash": "sha256-8QcoC1bS8pRcwBkW3mqD2vgP9Mu0aCdIy6umZO6mUGM=", - "owner": "atiladefreitas", - "repo": "bloocky", - "rev": "a052c3b1a8126e04b194bb3db3c4c6ca641c0e4e", - "type": "github" - }, - "original": { - "owner": "atiladefreitas", - "repo": "bloocky", - "type": "github" - } - }, "plugins-cmp-pandoc-references": { "flake": false, "locked": { @@ -68,51 +52,29 @@ "type": "github" } }, - "plugins-dooing": { + "plugins-r": { "flake": false, "locked": { - "lastModified": 1785971574, - "narHash": "sha256-GtEC5kWcH0DmiYXkzxzNjjfYLfyEb7zpTLQWdi7n3zs=", - "owner": "atiladefreitas", - "repo": "dooing", - "rev": "6748316bc6b4601797fb952a83694cc0a5ef6da2", + "lastModified": 1776340770, + "narHash": "sha256-o/8UZIc/Bq9dWTjA+MpSR5uMUpE7KHTErk+TwWID8Ww=", + "owner": "R-nvim", + "repo": "R.nvim", + "rev": "b9cfffeb9b4e484aa9e13f01c0eb80230aada455", "type": "github" }, "original": { - "owner": "atiladefreitas", - "repo": "dooing", - "type": "github" - } - }, - "r-nvim-nix": { - "inputs": { - "nixpkgs": [ - "rixpkgs" - ], - "rnvimsrc": "rnvimsrc" - }, - "locked": { - "lastModified": 1784867506, - "narHash": "sha256-5zXVJLG7+Gi9sgb4Dn7P6O/ywQQxpvcjjoHSJXZEuVo=", - "owner": "dwinkler1", - "repo": "r_nvim_nix", - "rev": "1d26452e3c940153271fc57fbad0b295fcb0e04f", - "type": "github" - }, - "original": { - "owner": "dwinkler1", - "ref": "v1.0.0", - "repo": "r_nvim_nix", + "owner": "R-nvim", + "repo": "R.nvim", "type": "github" } }, "rixpkgs": { "locked": { - "lastModified": 1785776890, - "narHash": "sha256-CzzgB1teVbroR/QhGAnsQToUYCDXEZZtFTcISAooASM=", + "lastModified": 1771303851, + "narHash": "sha256-tgveHozOJ2D/mi3LxVy/FcmLFDlM5XKZxsNB2XpvzaM=", "owner": "dwinkler1", "repo": "rixpkgs", - "rev": "a54080a58b57d5785dc85901850b817bef2aaf83", + "rev": "af2dd3f7b4b172077747c0869d4e30702fb71b0e", "type": "github" }, "original": { @@ -122,31 +84,12 @@ "type": "github" } }, - "rnvimsrc": { - "flake": false, - "locked": { - "lastModified": 1783264911, - "narHash": "sha256-FywUL3mV2+kfu+rO6uUFyUv80EflbdgSkYuSnW965UE=", - "owner": "R-nvim", - "repo": "R.nvim", - "rev": "c56ebe0f8445e251673981c40ac2d74659ecd6ed", - "type": "github" - }, - "original": { - "owner": "R-nvim", - "ref": "v1.0.0", - "repo": "R.nvim", - "type": "github" - } - }, "root": { "inputs": { "fran": "fran", "nixpkgs": "nixpkgs", - "plugins-bloocky": "plugins-bloocky", "plugins-cmp-pandoc-references": "plugins-cmp-pandoc-references", - "plugins-dooing": "plugins-dooing", - "r-nvim-nix": "r-nvim-nix", + "plugins-r": "plugins-r", "rixpkgs": "rixpkgs", "wrappers": "wrappers" } @@ -158,11 +101,11 @@ ] }, "locked": { - "lastModified": 1782135443, - "narHash": "sha256-vAmbArdCyjqpVW+37aCy/PMBOLIqukUXLQuEKLwUhA4=", + "lastModified": 1776375800, + "narHash": "sha256-/SSAR77Brr9fbapsh1cb2K47JXCbvwS1GjM4yyDxle8=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "6e7f66fa2cdf4d63162580b438f7fcf87c28a46f", + "rev": "f11469ca69068bac13d9e163b2bd268cc06dff57", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 00ad477..cce0107 100644 --- a/flake.nix +++ b/flake.nix @@ -1,23 +1,17 @@ -# Copyright (c) 2026 BirdeeHub & Daniel +# Copyright (c) 2026 BirdeeHub # Licensed under the MIT license { description = "Daniel's NixCats"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + #nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; wrappers = { url = "github:BirdeeHub/nix-wrapper-modules"; inputs.nixpkgs.follows = "nixpkgs"; }; rixpkgs.url = "github:dwinkler1/rixpkgs/nixpkgs"; - r-nvim-nix = { - url = "github:dwinkler1/r_nvim_nix/v1.0.0"; - inputs = { - nixpkgs.follows = "rixpkgs"; - }; - }; - fran = { url = "github:dwinkler1/fran"; inputs = { @@ -25,20 +19,15 @@ }; }; + "plugins-r" = { + url = "github:R-nvim/R.nvim"; + flake = false; + }; + "plugins-cmp-pandoc-references" = { url = "github:jmbuhr/cmp-pandoc-references"; flake = false; }; - - "plugins-bloocky" = { - url = "github:atiladefreitas/bloocky"; - flake = false; - }; - - "plugins-dooing" = { - url = "github:atiladefreitas/dooing"; - flake = false; - }; }; outputs = { @@ -47,42 +36,46 @@ wrappers, ... } @ inputs: let - devShellCatOrder = [ - "always" - "clickhouse" - "external" - "julia" - "lua" - "markdown" - "nix" - "optional" - "python" - "r" - "treesitterParsers" - ]; - evalWithPkgs = pkgs: extraModules: - wrappers.lib.evalModules { - specialArgs = { - inherit pkgs; + wrapperSettings = pkgs: let + def = pkgs.lib.mkDefault; + in + wrapper.config.wrap { + inherit pkgs; + cats = { + clickhouse = def false; + gitPlugins = def true; + julia = def false; + lua = def true; + markdown = def false; + nix = def true; + optional = def false; + python = def false; + r = def false; }; - modules = - [ - module - ] - ++ extraModules; + + settings = { + lang_packages = { + python = with pkgs.python3Packages; [ + duckdb + polars + ]; + + r = with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + ]; + + julia = ["DataFramesMeta" "QuackIO"]; + }; + colorscheme = def "cyberdream"; + background = def "dark"; + wrapRc = def true; + }; + binName = def "vv"; }; - mkDevShellPackages = config: - builtins.concatLists (map (name: config.catPkgs.${name} or []) devShellCatOrder); - mkShellHook = config: - '' - echo 'I am a NixShell' - '' - + nixpkgs.lib.optionalString (config.cats.r or false) '' - export R_HOME=$(R RHOME) - 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" - ''; systems = [ "aarch64-darwin" @@ -92,49 +85,54 @@ forAllSystems = nixpkgs.lib.genAttrs systems; + extra_pkg_config = { + # allowUnfree = true; + }; + overlayDefs = import ./overlays inputs; + dependencyOverlays = overlayDefs.dependencyOverlays; + + dependencyOverlay = overlayDefs.dependencyOverlay; + mkPkgs = system: import nixpkgs { inherit system; - config = {allowUnfree = true;}; - overlays = [ - overlayDefs.dependencyOverlay - ]; + config = extra_pkg_config; + overlays = [dependencyOverlay]; }; - module = (import ./modules/neovim.nix) inputs; + module = nixpkgs.lib.modules.importApply ./modules/neovim.nix inputs; + wrapper = wrappers.lib.evalModule module; in { - lib = { - eval = {pkgs, modules ? []}: evalWithPkgs pkgs modules; - mkWrapper = {pkgs, modules ? []}: (evalWithPkgs pkgs modules).config.wrap {inherit pkgs;}; - devShellPackages = config: mkDevShellPackages config; - shellHook = config: mkShellHook config; - }; - overlays = { - # overlay `vv` wraps the module with default settings only. - # It is evaluated against the final package set so module defaults can - # depend on overlays such as rixpkgs-backed `pkgs.rpkgs`. default = nixpkgs.lib.composeManyExtensions [ - overlayDefs.dependencyOverlay + dependencyOverlay (final: prev: { - vv = (evalWithPkgs final []).config.wrap {pkgs = final;}; + vv = wrapper.config.wrap {pkgs = final;}; }) ]; - dependencies = overlayDefs.dependencyOverlay; + dependencies = dependencyOverlay; + vv = self.overlays.default; }; - wrapperModules.default = module; - wrapperConfigs.default = {pkgs, modules ? []}: (self.lib.eval {inherit pkgs modules;}).config; + wrapperModules = { + default = module; + neovim = self.wrapperModules.default; + }; + + wrappers = { + default = wrapper.config; + neovim = self.wrappers.default; + }; packages = forAllSystems ( system: let pkgs = mkPkgs system; + nvimPkg = wrapperSettings pkgs; in { - default = self.lib.mkWrapper { - inherit pkgs; - }; + default = nvimPkg; + vv = nvimPkg; } ); @@ -148,13 +146,14 @@ devShells = forAllSystems ( system: let pkgs = mkPkgs system; - config = (self.lib.eval {inherit pkgs;}).config; - nvimPkg = config.wrap {inherit pkgs;}; + nvimPkg = wrapperSettings pkgs; in { default = pkgs.mkShell { name = "vShell"; - packages = [nvimPkg] ++ self.lib.devShellPackages config; - shellHook = mkShellHook config; + packages = [nvimPkg]; + nativeBuildInputs = with pkgs; [] ++ (pkgs.lib.optionals self.wrappers.default.cats.optional [devenv]); + inputsFrom = []; + shellHook = ""; }; } ); @@ -162,34 +161,20 @@ checks = forAllSystems ( system: let pkgs = mkPkgs system; - defaultConfig = (self.lib.eval {inherit pkgs;}).config; - defaultNvimPkg = defaultConfig.wrap {inherit pkgs;}; - defaultShellHook = mkShellHook defaultConfig; - overrideConfig = - (self.lib.eval { - inherit pkgs; - modules = [ - { - cats = { - r = false; - python = true; - }; - settings.lang_packages.python = nixpkgs.lib.mkForce (with pkgs.python3Packages; [ - pandas - ]); - catPkgs.nix = nixpkgs.lib.mkForce [ - pkgs.alejandra - ]; - } - ]; - }).config; - overrideShellHook = mkShellHook overrideConfig; + nvimPkg = wrapperSettings pkgs; in { - default = pkgs.runCommand "check-vv" {} '' - BINARY_PATH="${defaultNvimPkg}/bin/vv" + default = nvimPkg; + module-eval = let + _ = wrapper.config; + in + pkgs.runCommand "check-module-eval" {} '' + echo "Module evaluation successful" > $out + ''; + package-build = pkgs.runCommand "check-vv" {} '' + BINARY_PATH="${nvimPkg}/bin/vv" if [ ! -x "$BINARY_PATH" ]; then - echo "Error: Binary not found or not executable" + echo "Error: Binary n not found or not executable" exit 1 fi @@ -202,67 +187,18 @@ cat version_output.txt >> $out fi ''; - module-eval = let - _ = (self.lib.eval {inherit pkgs;}).config; - in - pkgs.runCommand "check-module-eval" {} '' - echo "Module evaluation successful" > $out - ''; - downstream-overrides = let - overrideNix = builtins.map (p: p.pname or p.name) overrideConfig.catPkgs.nix; - defaultAssertions = [ - (defaultConfig.cats.r or false) - (builtins.match ".*R RHOME.*" defaultShellHook != null) - (builtins.length (self.lib.devShellPackages defaultConfig) > 0) - ]; - overrideAssertions = [ - (!(overrideConfig.cats.r or false)) - (builtins.length overrideNix == 1) - ((builtins.head overrideNix) == "alejandra") - (builtins.match ".*R RHOME.*" overrideShellHook == null) - ]; - in - pkgs.runCommand "check-downstream-overrides" { - pass = - if builtins.all (x: x) (defaultAssertions ++ overrideAssertions) - then "1" - else ""; - } '' - if [ -z "$pass" ]; then - echo "Downstream override assertions failed" >&2 - exit 1 - 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 - ''; - - smoke-test = pkgs.runCommand "smoke-test" {} '' - # The Nix build sandbox has a read-only HOME; point XDG dirs at a - # writable location so vim.lsp/shaDa can write state headlessly. - export XDG_CONFIG_HOME=$TMPDIR/xdg-config - export XDG_STATE_HOME=$TMPDIR/xdg-state - export XDG_CACHE_HOME=$TMPDIR/xdg-cache - export XDG_DATA_HOME=$TMPDIR/xdg-data - BINARY_PATH="${defaultNvimPkg}/bin/vv" - "$BINARY_PATH" --headless -c "luafile ${./tests/smoke.lua}" -c "qa!" - touch $out - ''; } ); - nixosModules.default = wrappers.lib.getInstallModule { + nixosModules.default = wrappers.lib.mkInstallModule { name = "vModule"; value = module; }; - homeModules.default = wrappers.lib.getInstallModule { + homeModules.default = wrappers.lib.mkInstallModule { name = "vModule"; value = module; + loc = ["home" "packages"]; }; }; } diff --git a/ftplugin/markdown.lua b/ftplugin/markdown.lua index db316fc..b673b60 100644 --- a/ftplugin/markdown.lua +++ b/ftplugin/markdown.lua @@ -1,35 +1,32 @@ -- Add the key mappings only for Markdown files in a zk notebook. -local nix = require("config.nix") -if nix.get_cat("markdown", false) then - if require("zk.util").notebook_root(vim.fn.expand('%:p')) ~= nil then - local map = vim.keymap.set - -- Open the link under the caret. - map("n", "", "lua vim.lsp.buf.definition()", { noremap = true, silent = false, buffer = true }) +if require("zk.util").notebook_root(vim.fn.expand('%:p')) ~= nil then + local map = vim.keymap.set + -- Open the link under the caret. + map("n", "", "lua vim.lsp.buf.definition()", { noremap = true, silent = false, buffer = true }) - -- Create a new note after asking for its title. - -- This overrides the global `zn` mapping to create the note in the same directory as the current buffer. - map("n", "zhn", "ZkNew { dir = vim.fn.expand('%:p:h'), title = vim.fn.input('Title: ') }", - { noremap = true, silent = false, buffer = true, desc = "Note (here)" }) - -- Create a new note in the same directory as the current buffer, using the current selection for title. - map("v", "zhnt", ":'<,'>ZkNewFromTitleSelection { dir = vim.fn.expand('%:p:h') }", - { noremap = true, silent = false, buffer = true, desc = "Note from selection (title)" }) - -- Create a new note in the same directory as the current buffer, using the current selection for note content and asking for its title. - map("v", "zhnc", - ":'<,'>ZkNewFromContentSelection { dir = vim.fn.expand('%:p:h'), title = vim.fn.input('Title: ') }", - { noremap = true, silent = false, buffer = true, desc = "Note from selection (content)" }) + -- Create a new note after asking for its title. + -- This overrides the global `zn` mapping to create the note in the same directory as the current buffer. + map("n", "zhn", "ZkNew { dir = vim.fn.expand('%:p:h'), title = vim.fn.input('Title: ') }", + { noremap = true, silent = false, buffer = true, desc = "Note (here)" }) + -- Create a new note in the same directory as the current buffer, using the current selection for title. + map("v", "zhnt", ":'<,'>ZkNewFromTitleSelection { dir = vim.fn.expand('%:p:h') }", + { noremap = true, silent = false, buffer = true, desc = "Note from selection (title)" }) + -- Create a new note in the same directory as the current buffer, using the current selection for note content and asking for its title. + map("v", "zhnc", + ":'<,'>ZkNewFromContentSelection { dir = vim.fn.expand('%:p:h'), title = vim.fn.input('Title: ') }", + { noremap = true, silent = false, buffer = true, desc = "Note from selection (content)" }) - -- Open notes linking to the current buffer. - map("n", "zb", "ZkBacklinks", { noremap = true, silent = false, buffer = true, desc = "Backlinks" }) - -- Alternative for backlinks using pure LSP and showing the source context. - --map('n', 'zb', 'lua vim.lsp.buf.references()', opts) - -- Open notes linked by the current buffer. - map("n", "zL", "ZkLinks", { noremap = true, silent = false, buffer = true, desc = "Links" }) - map("n", "zi", "ZkInsertLink", { noremap = true, silent = false, buffer = true, desc = "Insert link" }) + -- Open notes linking to the current buffer. + map("n", "zb", "ZkBacklinks", { noremap = true, silent = false, buffer = true, desc = "Backlinks" }) + -- Alternative for backlinks using pure LSP and showing the source context. + --map('n', 'zb', 'lua vim.lsp.buf.references()', opts) + -- Open notes linked by the current buffer. + map("n", "zL", "ZkLinks", { noremap = true, silent = false, buffer = true, desc = "Links" }) + map("n", "zi", "ZkInsertLink", { noremap = true, silent = false, buffer = true, desc = "Insert link" }) - -- Preview a linked note. - -- Open the code actions for a visual selection. - map("v", "za", ":'<,'>lua vim.lsp.buf.range_code_action()", - { noremap = true, silent = false, buffer = true, desc = "Code actions" }) + -- Preview a linked note. + -- Open the code actions for a visual selection. + map("v", "za", ":'<,'>lua vim.lsp.buf.range_code_action()", + { noremap = true, silent = false, buffer = true, desc = "Code actions" }) - end end diff --git a/ftplugin/quarto.lua b/ftplugin/quarto.lua index b737644..7ee10a0 100644 --- a/ftplugin/quarto.lua +++ b/ftplugin/quarto.lua @@ -1,28 +1,23 @@ -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 +local quarto = require('quarto') +quarto.setup() +vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true }) -if vim.bo.filetype == "r" then - vim.keymap.set("n", "", "RDSendLine", { buffer = true }) - vim.keymap.set("v", "", "RSendSelection", { buffer = true }) +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 }) -end +-- Pipe operator (;;) +vim.keymap.set("i", ";;", "lua MiniTrailspace.trim()RInsertPipe", { buffer = true, noremap = 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 +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 }) diff --git a/init.lua b/init.lua index 11b9975..8db9da5 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,7 @@ -local Config = require('config') -_G.Config = Config -- keep global alias for keymaps/backward compatibility +_G.Config = {} +local nix = require('config.nix').init { non_nix_value = true } +Config.isNixCats = nix.is_nix +Config.nixConfig = nix require('lze').register_handlers(require('nixCatsUtils.lzUtils').for_cat) @@ -10,11 +12,8 @@ if not Config.isNixCats then local mini_path = path_package .. 'pack/deps/start/mini.nvim' if not vim.uv.fs_stat(mini_path) then vim.cmd('echo "Installing `mini.nvim`" | redraw') - -- Pin to the stable branch for reproducible non-Nix installs. - -- Change the tag/branch here if you need a newer version. - local mini_nvim_tag = 'stable' local clone_cmd = { - 'git', 'clone', '--filter=blob:none', '--branch=' .. mini_nvim_tag, + 'git', 'clone', '--filter=blob:none', 'https://github.com/echasnovski/mini.nvim', mini_path } vim.fn.system(clone_cmd) diff --git a/lua/config/init.lua b/lua/config/init.lua deleted file mode 100644 index 60b80e5..0000000 --- a/lua/config/init.lua +++ /dev/null @@ -1,10 +0,0 @@ ---- Shared configuration / state table. ---- Plugin submodules attach their helpers to this table at runtime. -local M = {} - --- Detect whether this Neovim was launched via nixCats. -local nix = require('config.nix').init { non_nix_value = true } -M.isNixCats = nix.is_nix -M.nixConfig = nix - -return M diff --git a/lua/keymap/core.lua b/lua/keymap/core.lua deleted file mode 100644 index 8c0abe3..0000000 --- a/lua/keymap/core.lua +++ /dev/null @@ -1,44 +0,0 @@ -local Config = require('config') - --- Basic mappings ============================================================= --- NOTE: Most basic mappings come from 'mini.basics' --- Shorter version of the most frequent way of going outside of terminal window -vim.keymap.set('t', '', [[h]]) --- Select all --- vim.keymap.set({ "n", "v", "x" }, "", "gg3vG$", { noremap = true, silent = true, desc = "Select all" }) --- Escape deletes highlights -vim.keymap.set("n", "", "nohlsearch") --- Paste before/after linewise -local cmd = vim.fn.has('nvim-0.12') == 1 and 'iput' or 'put' -vim.keymap.set({ 'n', 'x' }, '[p', 'exe "' .. cmd .. '! " . v:register', { desc = 'Paste Above' }) -vim.keymap.set({ 'n', 'x' }, ']p', 'exe "' .. cmd .. ' " . v:register', { desc = 'Paste Below' }) - -vim.keymap.set({ "n", "v", "x" }, "p", '"+p', { noremap = true, silent = true, desc = "Paste from clipboard" }) -vim.keymap.set({ "n", "v", "x" }, "y", '"+y', { noremap = true, silent = true, desc = "Copy toclipboard" }) - --- Create global tables with information about clue group in certain modes --- Structure of tables is taken to be compatible with 'mini.clue'. -Config.leader_group_clues = { - { mode = 'n', keys = 'a', desc = '+AI' }, - { mode = 'n', keys = 'b', desc = '+Buffer' }, - { mode = 'n', keys = 'e', desc = '+Explore' }, - { mode = 'n', keys = 'f', desc = '+Find' }, - { mode = 'n', keys = 'fl', desc = '+LSP' }, - { mode = 'n', keys = 'fa', desc = '+Git' }, - { mode = 'n', keys = 'g', desc = '+Git' }, - { mode = 'n', keys = 'l', desc = '+LSP' }, - { mode = 'n', keys = 'L', desc = '+Lua/Log' }, - { mode = 'n', keys = 'o', desc = '+Other' }, - { mode = 'n', keys = 'r', desc = '+R' }, - { mode = 'n', keys = 's', desc = '+Send' }, - { mode = 'n', keys = 'd', desc = '+Debug' }, - { mode = 'n', keys = 't', desc = '+Terminal' }, - { mode = 'n', keys = 'u', desc = '+UI' }, - { mode = 'n', keys = 'v', desc = '+Visits' }, - { mode = 'n', keys = 'w', desc = '+Windows' }, - { mode = 'x', keys = 'l', desc = '+LSP' }, - { mode = 'x', keys = 'r', desc = '+R' }, - { mode = 'n', keys = 'z', desc = '+ZK' }, - { mode = 'n', keys = 'zr', desc = '+Reviews' }, - { mode = 'x', keys = 'a', desc = '+AI' }, -} diff --git a/lua/keymap/helpers.lua b/lua/keymap/helpers.lua deleted file mode 100644 index 10cdaa2..0000000 --- a/lua/keymap/helpers.lua +++ /dev/null @@ -1,25 +0,0 @@ -local M = {} - ----Create a normal-mode `` mapping. -function M.nmap_leader(suffix, rhs, desc, opts) - opts = opts or {} - opts.desc = desc - vim.keymap.set('n', '' .. suffix, rhs, opts) -end - ----Create a visual-mode `` mapping. -function M.xmap_leader(suffix, rhs, desc, opts) - opts = opts or {} - opts.desc = desc - vim.keymap.set('x', '' .. suffix, rhs, opts) -end - ----Create a normal-mode LSP keymap with an "(LSP)" suffix in the description. -function M.nmap_lsp(keys, func, desc) - if desc then - desc = desc .. "(LSP)" - end - vim.keymap.set("n", keys, func, { desc = desc }) -end - -return M diff --git a/lua/keymap/leader.lua b/lua/keymap/leader.lua deleted file mode 100644 index e741c81..0000000 --- a/lua/keymap/leader.lua +++ /dev/null @@ -1,293 +0,0 @@ -local Config = require('config') -local helpers = require('keymap.helpers') -local nmap_leader = helpers.nmap_leader -local xmap_leader = helpers.xmap_leader -local nmap_lsp = helpers.nmap_lsp - --- stylua: ignore start - --- Switch buffers -nmap_leader('', 'bnext', 'Next buffer') -nmap_leader('', 'bprev', 'Prev buffer') - --- a is for 'AI' -nmap_leader("aa", function() - require("opencode").ask("@this: ") -end, "Ask OpenCode about this") -nmap_leader("ab", function() - require("opencode").prompt("Analyse @buffer") -end, "Analyse buffer") -nmap_leader("aB", function() - require("opencode").prompt("Analyse @buffers") -end, "Analyse open buffers") -nmap_leader("ad", function() - require("opencode").prompt("Explain @diagnostics and fix the underlying issue") -end, "Explain/fix diagnostics") -nmap_leader("an", function() - require("opencode").command("session.new") -end, "New OpenCode session") -nmap_leader("ap", function() - require("opencode").ask("@") -end, "Ask OpenCode with context") -nmap_leader("as", function() - require("opencode").select() -end, "Select OpenCode action") -xmap_leader("aa", function() - require("opencode").ask("@this: ") -end, "Ask OpenCode about selection") -xmap_leader("af", function() - require("opencode").prompt("Fix the selected code and preserve its surrounding API:\n@this") -end, "Fix selection") -xmap_leader("ap", function() - require("opencode").ask("@") -end, "Ask OpenCode with context") -xmap_leader("as", function() - require("opencode").select() -end, "Select OpenCode action") - --- b is for 'buffer' -nmap_leader('bb', 'b#', 'Alternate') -nmap_leader('bd', 'lua MiniBufremove.delete()', 'Delete') -nmap_leader('bD', 'lua MiniBufremove.delete(0, true)', 'Delete!') -nmap_leader('bs', 'lua Config.new_scratch_buffer()', 'Scratch') -nmap_leader('bw', 'lua MiniBufremove.wipeout()', 'Wipeout') -nmap_leader('bW', 'lua MiniBufremove.wipeout(0, true)', 'Wipeout!') -nmap_leader('bq', 'qall', 'Quit all') - --- e is for 'explore' and 'edit' -nmap_leader('ed', 'lua MiniFiles.open()', 'Directory') -nmap_leader('ef', 'lua Config.try_opendir()', 'File directory') -nmap_leader('es', 'lua MiniSessions.select()', 'Sessions') -nmap_leader('eq', 'lua Config.toggle_quickfix()', 'Quickfix') -nmap_leader('ez', 'lua MiniFiles.open(os.getenv("ZK_NOTEBOOK_DIR"))', 'Notes directory') - --- f is for 'fuzzy find' -nmap_leader('f/', 'Pick history scope="/"', '"/" history') -nmap_leader('f:', 'Pick history scope=":"', '":" history') -nmap_leader('f,', 'Pick visit_labels', 'Visit labels') -nmap_leader('faa', 'Pick git_hunks scope="staged"', 'Added hunks (all)') -nmap_leader('faA', 'Pick git_hunks path="%" scope="staged"', 'Added hunks (current)') -nmap_leader('fb', 'Pick buffers', 'Buffers') -nmap_leader(',', 'Pick buffers', 'Buffers') -nmap_leader('fac', 'Pick git_commits', 'Commits (all)') -nmap_leader('faC', 'Pick git_commits path="%"', 'Commits (current)') -nmap_leader('fd', 'Pick diagnostic scope="all"', 'Diagnostic workspace') -nmap_leader('fD', 'Pick diagnostic scope="current"', 'Diagnostic buffer') -nmap_leader('ff', 'Pick files', 'Files') -nmap_leader('fg', 'Pick grep_live', 'Grep live') -nmap_leader('fG', 'Pick grep pattern=""', 'Grep current word') -nmap_leader('fh', 'Pick help', 'Help tags') -nmap_leader('fH', 'Pick hl_groups', 'Highlight groups') -nmap_leader('fj', 'Pick buf_lines scope="all"', 'Lines (all)') -nmap_leader('fJ', 'Pick buf_lines scope="current"', 'Lines (current)') -nmap_leader('fam', 'Pick git_hunks', 'Modified hunks (all)') -nmap_leader('faM', 'Pick git_hunks path="%"', 'Modified hunks (current)') -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 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)') -nmap_leader('fS', 'Pick lsp scope="workspace_symbol"', 'Symbols workspace (LSP)') -nmap_leader('flS', 'Pick lsp scope="workspace_symbol"', 'Symbols workspace (LSP)') -nmap_leader('fs', 'Pick lsp scope="document_symbol"', 'Symbols buffer (LSP)') -nmap_leader('fls', 'Pick lsp scope="document_symbol"', 'Symbols buffer (LSP)') -nmap_leader('fld', 'Pick lsp scope="definition"', 'Definition (LSP)') -nmap_leader('flD', 'Pick lsp scope="declaration"', 'Declaration (LSP)') -nmap_leader('flt', 'Pick lsp scope="type_definition"', 'Type Definition (LSP)') -nmap_leader('fv', 'Pick visit_paths cwd=""', 'Visit paths (all)') -nmap_leader('fV', 'Pick visit_paths', 'Visit paths (cwd)') - --- g is for git -local git_log_cmd = [[Git log --pretty=format:\%h\ \%as\ │\ \%s --topo-order]] - -nmap_leader('gc', 'Git commit', 'Commit') -nmap_leader('gC', 'Git commit --amend', 'Commit amend') -nmap_leader('gd', 'Git diff', 'Diff') -nmap_leader('gD', 'Git diff -- %', 'Diff buffer') -nmap_leader("gg", "Neogit", "Open Neogit UI") -nmap_leader('gl', '' .. git_log_cmd .. '', 'Log') -nmap_leader('gL', '' .. git_log_cmd .. ' --follow -- %', 'Log buffer') -nmap_leader('go', 'lua MiniDiff.toggle_overlay()', 'Toggle overlay') -nmap_leader('gp', 'Git pull', 'Pull') -nmap_leader('gP', 'Git push', 'Push') -nmap_leader('gs', 'lua MiniGit.show_at_cursor()', 'Show at cursor') - -xmap_leader('gs', 'lua MiniGit.show_at_cursor()', 'Show at selection') - --- j/k navigate quickfix -nmap_leader("j", 'cnextzz', "Quickfix next") -nmap_leader("k", 'cprevzz', "Quickfix prev") - --- l is for 'LSP' (Language Server Protocol) -vim.keymap.set({ 'n' }, 'grd', 'lua vim.lsp.buf.definition()', { desc = 'Definition' }) -vim.keymap.set({ 'n' }, 'grk', 'lua vim.lsp.buf.hover()', { desc = 'Documentation' }) -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_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') -nmap_leader('lk', 'lua vim.lsp.buf.hover()', 'Documentation') -nmap_leader('li', 'lua vim.lsp.buf.implementation()', 'Information') --- use ]d and [d ---nmap_leader('lj', 'lua vim.diagnostic.goto_next()', 'Next diagnostic') ---nmap_leader('lk', 'lua vim.diagnostic.goto_prev()', 'Prev diagnostic') -nmap_leader('lR', 'lua vim.lsp.buf.references()', 'References') -nmap_leader('lr', 'lua vim.lsp.buf.rename()', 'Rename') -nmap_leader('ls', 'lua vim.lsp.buf.definition()', 'Source definition') - -xmap_leader('lf', formatting_cmd, 'Format selection') - --- L is for 'Lua' -nmap_leader('Lc', 'lua Config.log_clear()', 'Clear log') -nmap_leader('LL', 'luafile %echo "Sourced lua"', 'Source buffer') -nmap_leader('Ls', 'lua Config.log_print()', 'Show log') -nmap_leader('Lx', 'lua Config.execute_lua_line()', 'Execute `lua` line') - --- m is free - --- o is for 'other' -local trailspace_toggle_command = 'lua vim.b.minitrailspace_disable = not vim.b.minitrailspace_disable' -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('ot', 'lua MiniTrailspace.trim()', 'Trim trailspace') -nmap_leader('oT', trailspace_toggle_command, 'Trailspace hl toggle') -nmap_leader('oz', 'lua MiniMisc.zoom()', 'Zoom toggle') -nmap_leader('ow', - "lua MiniSessions.write(vim.fn.input('Session name: ', string.match(vim.fn.getcwd(), \"[^/]+$\") .. '-session.vim'))", - 'Write session') - --- r is for 'R' -nmap_leader('rc', 'RSend devtools::check()', 'Check') -nmap_leader('rC', 'RSend devtools::test_coverage()', 'Coverage') -nmap_leader('rd', 'RSend devtools::document()', 'Document') -nmap_leader('ri', 'RSend devtools::install(keep_source=TRUE)', 'Install') -nmap_leader('rk', 'RSend quarto::quarto_preview("%")', 'Knit file') -nmap_leader('rl', 'RSend devtools::load_all()', 'Load all') -nmap_leader('rL', 'RSend devtools::load_all(recompile=TRUE)', 'Load all recompile') -nmap_leader('rm', 'RSend Rcpp::compileAttributes()', 'Run examples') -nmap_leader('rT', 'RSend testthat::test_file("%")', 'Test file') -nmap_leader('rt', 'RSend devtools::test()', 'Test') - --- Visual `r` bindings (dispatched by the filetype-aware REPL --- runner in `lua/keymap/repl.lua`). Previously the visual clue group was --- empty, so this restores parity between the visual and normal `r`-group. -xmap_leader('rr', function() require('keymap.repl').send_selection() end, 'Send selection to REPL') --- - Copy to clipboard and make reprex (which itself is loaded to clipboard) -xmap_leader('rx', '"+y :RSend reprex::reprex()', 'Reprex selection') - --- s is for 'send' (Send text to the active REPL/runner) -nmap_leader('s', function() require('keymap.repl').send_line() end, 'Send to REPL') -xmap_leader('s', function() require('keymap.repl').send_selection() end, 'Send selection to REPL') - --- d is for 'debug' (nvim-dap) -nmap_leader('db', 'lua require("dap").toggle_breakpoint()', 'Toggle breakpoint') -nmap_leader('dB', 'lua require("dap").set_breakpoint(vim.fn.input("Condition: "))', 'Conditional breakpoint') -nmap_leader('dc', 'lua require("dap").continue()', 'Continue') -nmap_leader('do', 'lua require("dap").step_over()', 'Step over') -nmap_leader('di', 'lua require("dap").step_into()', 'Step into') -nmap_leader('dO', 'lua require("dap").step_out()', 'Step out') -nmap_leader('dr', 'lua require("dap").repl.open()', 'Open DAP REPL') -nmap_leader('du', 'lua require("dapui").toggle()', 'Toggle DAP UI') -nmap_leader('dK', 'lua require("dapui").eval()', 'Evaluate expression') - --- u is for UI -nmap_leader('ut', 'TSContext toggle', 'Toggle TScontext') -nmap_leader('ua', 'Copilot toggle', 'Toggle AI completion') - --- v is for 'visits' -nmap_leader('vv', 'lua MiniVisits.add_label("core")', 'Add "core" label') -nmap_leader('vV', 'lua MiniVisits.remove_label("core")', 'Remove "core" label') -nmap_leader('vl', 'lua MiniVisits.add_label()', 'Add label') -nmap_leader('vL', 'lua MiniVisits.remove_label()', 'Remove label') - -local map_pick_core = function(keys, cwd, desc) - local rhs = function() - local sort_latest = MiniVisits.gen_sort.default({ recency_weight = 1 }) - MiniExtra.pickers.visit_paths({ - cwd = cwd, - filter = 'core', - sort = sort_latest - }, { source = { name = desc } }) - end - nmap_leader(keys, rhs, desc) -end -map_pick_core('vc', '', 'Core visits (all)') -map_pick_core('vC', nil, 'Core visits (cwd)') - --- w is for 'windows' -nmap_leader("wh", "h", "Go to Left Window", { remap = true }) -nmap_leader("wj", "j", "Go to Lower Window", { remap = true }) -nmap_leader("wk", "k", "Go to Upper Window", { remap = true }) -nmap_leader("wl", "l", "Go to Right Window", { remap = true }) -nmap_leader("w>", "vertical resize +5", "Increase Window Width") -nmap_leader("w", "vertical resize -5", "Decrease Window Width") - -local function set_window_width_percent(percent) - return function() - local target_width = math.max(1, math.floor(vim.o.columns * percent / 100 + 0.5)) - vim.api.nvim_win_set_width(0, target_width) - end -end - -for i = 1, 9 do - local percent = i * 10 - nmap_leader( - "w" .. i, - set_window_width_percent(percent), - string.format("Set Window Width to %d%%", percent) - ) -end - -nmap_leader("_", "s", "Split Window Below", { remap = true }) -nmap_leader("|", "v", "Split Window Right", { remap = true }) -nmap_leader("wd", "c", "Delete Window", { remap = true }) -nmap_leader("wo", "o", "Delete Other Windows", { remap = true }) - --- z is for 'ZettelKasten' -nmap_leader("zo", 'ZkNotes', "Notes") -nmap_leader("zt", 'ZkTags', "Tags") - -nmap_leader( - "zrd", - 'ZkNew { group = "dreviews" }', - "Daily Review" -) -nmap_leader( - "zrw", - 'ZkNew { group = "wreviews" }', - "Weekly Review" -) -nmap_leader( - "zn", - 'ZkNew { group = "inbox", title = vim.fn.input("Title: ") }', - "New" -) -nmap_leader( - "zp", - "ZkNew { group = 'permanent', title = vim.fn.input('Title: ') }", - "Permanent" -) - -nmap_leader( - "zl", - "ZkNew { group = 'literature', title = vim.fn.input('Title: '), extra.author = vim.fn.input('Author: '), extra.year = vim.fn.input('Year: ') }", - "Literature" -) - -nmap_leader( - "zd", - "ZkNew { group = 'dashboard', title = vim.fn.input('Title: ') }", - "Dashboard" -) -nmap_leader( - "zP", - "ZkNew { group = 'project', title = vim.fn.input('Title: ')}", - "Project" -) --- stylua: ignore end diff --git a/lua/keymap/repl.lua b/lua/keymap/repl.lua deleted file mode 100644 index 35425aa..0000000 --- a/lua/keymap/repl.lua +++ /dev/null @@ -1,101 +0,0 @@ ---- Filetype-aware REPL/runner dispatcher. ---- Keeps the existing terminal setup intact; it only decides *which* runner ---- to use for the current buffer/filetype. - -local M = {} - ---- Resolve the filetype to use for dispatching. ---- Quarto buffers report `quarto`; R buffers report `r`. Fallback to the ---- actual filetype if no special handling is needed. -local function dispatch_ft() - local ft = vim.bo.filetype - if ft == "quarto" or ft == "rmd" or ft == "markdown" then - return "quarto" - end - return ft -end - ---- Send the current line to the active REPL/runner. -function M.send_line() - local ft = dispatch_ft() - - if ft == "r" then - -- R.nvim v1+ exposes a Lua API; try the modern `r.send` module first, - -- then fall back to the older `r.run` module, and finally to . - local ok, rmod = pcall(require, "r.send") - if not ok or not rmod then - ok, rmod = pcall(require, "r.run") - end - if ok and rmod then - if type(rmod.line) == "function" then - rmod.line() - return - elseif type(rmod.send_line) == "function" then - rmod.send_line() - return - end - end - vim.api.nvim_feedkeys( - vim.api.nvim_replace_termcodes("RDSendLine", true, false, true), - "m", - false - ) - return - end - - if ft == "quarto" then - local ok, runner = pcall(require, "quarto.runner") - if ok and runner then - if runner.run_line then - runner.run_line() - elseif runner.run_cell then - runner.run_cell() - end - return - end - end - - -- Default: vim-slime (terminal). - local line = vim.api.nvim_get_current_line() - vim.fn["slime#send"](line .. "\n") - -- Move to the next line, matching the previous behaviour. - vim.cmd("normal! j") -end - ---- Send the current visual selection to the active REPL/runner. -function M.send_selection() - local ft = dispatch_ft() - - if ft == "r" then - -- Prefer R.nvim v1+ Lua API; try the modern `r.send` module first, - -- then fall back to the older `r.run` module, and finally to . - local ok, rmod = pcall(require, "r.send") - if not ok or not rmod then - ok, rmod = pcall(require, "r.run") - end - if ok and rmod then - if type(rmod.selection) == "function" then - rmod.selection() - return - elseif type(rmod.send_selection) == "function" then - rmod.send_selection() - return - end - end - vim.api.nvim_feedkeys( - vim.api.nvim_replace_termcodes("RSendSelection", true, false, true), - "m", - false - ) - return - end - - -- For Quarto/others, fall back to vim-slime's visual send. - vim.api.nvim_feedkeys( - vim.api.nvim_replace_termcodes("SlimeRegionSend", true, false, true), - "m", - false - ) -end - -return M diff --git a/lua/keymap/terminal.lua b/lua/keymap/terminal.lua deleted file mode 100644 index bb89596..0000000 --- a/lua/keymap/terminal.lua +++ /dev/null @@ -1,17 +0,0 @@ -local Config = require('config') -local helpers = require('keymap.helpers') -local nmap_leader = helpers.nmap_leader - --- Exit terminal insert mode with -vim.keymap.set("t", "", [[]], { desc = "Exit terminal mode" }) - --- t is for 'terminal' -nmap_leader("tc", 'lua Config.terminal.open_clickhouse_client()', 'Open Clickhouse client') -nmap_leader("tl", 'lua Config.terminal.open_clickhouse_local()', 'Open Clickhouse local') -nmap_leader("tp", 'lua Config.terminal.open_python()', 'Open Python') -nmap_leader("tj", 'lua Config.terminal.open_julia()', 'Open Julia') -nmap_leader("td", 'lua Config.terminal.open_duckdb();Config.terminal.toggle_bracket()', 'Open DuckDB') -nmap_leader("tx", 'lua Config.terminal.open_in_terminal()', 'Terminal Command') -nmap_leader("tt", 'lua Config.terminal.open_shell()', 'Terminal') -nmap_leader("tb", 'lua Config.terminal.toggle_bracket()', 'Toggle bracketed paste') -nmap_leader("up", 'lua Config.terminal.toggle_bracket()', 'Toggle bracketed paste') diff --git a/lua/nix_smart_send.lua b/lua/nix_smart_send.lua index 51e9611..c03af4c 100644 --- a/lua/nix_smart_send.lua +++ b/lua/nix_smart_send.lua @@ -1,12 +1,5 @@ local M = {} --- Define comment node types as constants -local COMMENT_TYPES = { - comment = true, - block_comment = true, - line_comment = true, -} - -- Helper function to check if value exists in list (optimized with early return) local function is_in_list(list, value) if not list or not value then @@ -21,153 +14,183 @@ local function is_in_list(list, value) return false end --- Safely get the Tree-sitter node under the cursor. +-- Define comment node types as constants +local COMMENT_TYPES = { + comment = true, + block_comment = true, + line_comment = true, +} + function M.get_current_node() - local ok, node = pcall(vim.treesitter.get_node, { ignore_injections = false }) - return ok and node or nil + local ts_utils = require('nvim-treesitter.ts_utils') + local cur_win = vim.api.nvim_get_current_win() + return ts_utils.get_node_at_cursor(cur_win, true) end --- Detect the root node type of the current buffer's Tree-sitter tree. function M.detect_global_node() local cur_node = M.get_current_node() local root if not cur_node then - local ok, parser = pcall(vim.treesitter.get_parser) - if not ok or not parser then + -- print("No node detected") + local parser = vim.treesitter.get_parser() + if not parser then return nil end - local trees = parser:parse() - if not trees or not trees[1] then - return nil - end - root = trees[1]:root() + root = parser:parse()[1]:root() else root = cur_node:root() end - return root and root:type() or nil -end - --- Ascend the tree from the current node until we hit a node whose parent is a --- "global" node (or the root). This is the unit of code we want to send. -local function get_target_node(global_nodes) - local root_type = M.detect_global_node() - global_nodes = global_nodes or {} - - local node = M.get_current_node() - if not node then + if not root then return nil end - while node do - local parent = node:parent() - if not parent then - break - end - - local p_type = parent:type() - if is_in_list(global_nodes, p_type) or p_type == root_type then - break - end - node = parent - end - - return node + return root:type() end --- Move the cursor to the next named sibling that is not a comment. --- Operates on the Tree-sitter AST instead of scanning lines, so it is fast and --- language-agnostic. -function M.move_to_next_non_empty_line(current_node) - local node = current_node - if not node then - node = get_target_node({}) - end - if not node then +function M.move_to_next_non_empty_line() + local ts_utils = require('nvim-treesitter.ts_utils') + -- Search for the next non-empty line + local line_num = vim.fn.search("[^;\\s]", "W") + + if line_num <= 0 then + -- print("No non-empty line found below the current position") return false end - -- Walk up the tree until we find a node with a next named sibling, - -- so we escape nested blocks when we are on the last statement. - while node and not node:next_named_sibling() do - node = node:parent() - end + -- Get the line content and find first non-whitespace character + local line_content = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1] + local first_non_ws = line_content:find("%S") or 1 + vim.api.nvim_win_set_cursor(0, { line_num, first_non_ws - 1 }) - node = node:next_named_sibling() - while node do - if not COMMENT_TYPES[node:type()] then - local start_row, start_col = node:range() - pcall(vim.api.nvim_win_set_cursor, 0, { start_row + 1, start_col }) - return true, node - end - node = node:next_named_sibling() - end - - return false -end - -function M.vselect_node(node) - if not node then + local node = M.get_current_node() + if not node or not node:type() then + -- print("No node found") return false end - local start_row, _, end_row, _ = node:range() + local global_node_type = M.detect_global_node() - vim.api.nvim_win_set_cursor(0, { start_row + 1, 0 }) - vim.cmd("normal! V") - vim.api.nvim_win_set_cursor(0, { end_row + 1, 0 }) + -- Skip comments and global nodes + while node and (COMMENT_TYPES[node:type()] or node:type() == global_node_type) do + line_num = line_num + 1 + local max_lines = vim.api.nvim_buf_line_count(0) + + if line_num > max_lines then + -- print("Reached end of buffer") + return false + end + + -- Get the line content and find first non-whitespace character + line_content = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1] + first_non_ws = line_content:find("%S") or 1 + vim.api.nvim_win_set_cursor(0, { line_num, first_non_ws - 1 }) + node = ts_utils.get_node_at_cursor() + end return true end +function M.vselect_node(node) + local ts_utils = require('nvim-treesitter.ts_utils') + if not node then + return false + end + + local cur_buf = vim.api.nvim_get_current_buf() + ts_utils.update_selection(cur_buf, node, "V") + return true +end + function M.select_until_global(global_nodes) - local target = get_target_node(global_nodes) - if not target then + local ts_utils = require('nvim-treesitter.ts_utils') + local root_node = M.detect_global_node() + if not root_node and global_nodes then + root_node = global_nodes[1] + end + + -- Use empty table if no global nodes provided + global_nodes = global_nodes or {} + + local node = ts_utils.get_node_at_cursor() + if not node then + -- print("No syntax node found at cursor position") return nil end - if M.vselect_node(target) then - return target + local node_type = node:type() + + if node_type == root_node then + -- print("Cursor is on the root " .. root_node .. " node or in an empty area.") + return nil + end + + -- Check if current node is a global + if is_in_list(global_nodes, node_type) then + if M.vselect_node(node) then + return node + end + end + + -- Traverse up the tree until we find a global node or reach the root + local parent = node:parent() + local parent_type = parent:type() or "" + if parent and is_in_list(global_nodes, parent_type) then + if M.vselect_node(node) then + return node + end + end + while parent and not is_in_list(global_nodes, parent:type()) do + node = parent + parent = node:parent() + end + + if M.vselect_node(node) then + return node end return nil end function M.slime_send_region() - vim.api.nvim_feedkeys( - vim.api.nvim_replace_termcodes("SlimeRegionSend", true, false, true), - "m", - false - ) + -- Check if slime plugin is available + if not vim.fn.exists('*slime#send_op') then + vim.notify("slime plugin not available", vim.log.levels.ERROR) + return + end + + local slime_command = ":call slime#send_op(visualmode(), 1)" + local termcodes = vim.api.nvim_replace_termcodes(slime_command, true, true, true) + + vim.api.nvim_feedkeys(termcodes, "x", true) end function M.send_repl(global_nodes) - local target_node = get_target_node(global_nodes) - if not target_node then - return - end + local ts_utils = require('nvim-treesitter.ts_utils') + local cur_node = M.get_current_node() - -- If sitting on a comment, step forward first so we don't send comments. - if COMMENT_TYPES[target_node:type()] then - local moved, next_node = M.move_to_next_non_empty_line(target_node) - if not moved or not next_node then - return + if not cur_node then + M.move_to_next_non_empty_line() + else + local cur_type = cur_node:type() + if COMMENT_TYPES[cur_type] or is_in_list(global_nodes, cur_type) then + M.move_to_next_non_empty_line() end - target_node = next_node end - -- Extract node text and send directly to avoid visual-mode/feedkeys races. - local ok, text = pcall(vim.treesitter.get_node_text, target_node, 0) - if not ok or not text then - vim.notify("Could not extract code from Tree-sitter node", vim.log.levels.WARN) + local sel_node = M.select_until_global(global_nodes) + if not sel_node then + -- print("No node selected for REPL") return end - vim.fn["slime#send"](text .. "\n") + -- Send the selected text to the terminal using vim-slime + M.slime_send_region() - -- Jump to the next relevant AST node instead of scanning lines - M.move_to_next_non_empty_line(target_node) + -- Move cursor and continue + ts_utils.goto_node(sel_node, true) + M.move_to_next_non_empty_line() end return M diff --git a/lua/nvim_omp/init.lua b/lua/nvim_omp/init.lua deleted file mode 100644 index 4400d49..0000000 --- a/lua/nvim_omp/init.lua +++ /dev/null @@ -1,55 +0,0 @@ --- omp bridge: expose an RPC socket so the omp harness can read this Neovim --- instance's buffers on demand. Read-only on the agent side; this module only --- owns the socket lifecycle. --- --- Contract --- * Socket path: $NVIM_OMP_SOCKET if set, else /omp.sock --- (~/.local/state/nvim/omp.sock on macOS, ~/.local/state/nvim/omp.sock --- elsewhere). The omp-side tool in .omp/tools/nvim_buffers.mjs resolves the --- *same* path, so the two sides agree without configuration. --- * The agent reads buffers by evaluating read-only nvim API expressions over --- the socket with `nvim --server --remote-expr 'json_encode(...)'`. --- Nothing here writes buffers or executes model-supplied commands. -local M = {} - -local SOCKET_NAME = "omp.sock" - --- Resolve the deterministic socket path. Copies the rule on the omp side; keep --- the two files in sync when changing the fallback or env override. -function M.socket_path() - local env = vim.env.NVIM_OMP_SOCKET - if env and env ~= "" then - return env - end - return vim.fn.stdpath("state") .. "/" .. SOCKET_NAME -end - --- Start the RPC listener. Returns the live socket path, or nil. --- A second instance cannot bind the same address. This function deliberately --- never removes an existing socket file: a failed liveness probe must not --- disconnect another live Neovim instance. --- If a crash leaves a stale socket, remove it manually only after confirming --- that no Neovim process owns the path, then restart vv. -function M.start() - if vim.v.headless == 1 then - -- Headless runs (tests, CI) get no socket; nothing should depend on one. - return nil - end - - local path = M.socket_path() - local ok, res = pcall(vim.fn.serverstart, path) - if ok and type(res) == "string" and res ~= "" then - -- serverstart returns the bound address string (e.g. "/tmp/omp.sock"). - vim.notify("nvim_omp: RPC socket ready at " .. path, vim.log.levels.INFO) - return path - end - - vim.notify( - "nvim_omp: could not bind RPC socket at " .. path - .. "; another instance may own it or a stale socket needs manual cleanup.", - vim.log.levels.WARN - ) - return nil -end - -return M diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix deleted file mode 100644 index dbdba3f..0000000 --- a/modules/module/settings/cat-packages.nix +++ /dev/null @@ -1,120 +0,0 @@ -{ - config, - pkgs, - lib, - ... -}: -let - # Include packages from a category only if that category is enabled. - # NOTE: Package list expressions are lazily evaluated, and derivations are - # not built until needed, so keep side-effecting expressions out of these - # lists. - maybe = cat: pkgsList: - lib.optionals (config.cats.${cat} or false) pkgsList; - rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; - rWrapperPackages = rPackages; - quartoPkg = - if config.cats.r or false - then pkgs.rpkgs.quarto.override { extraRPackages = rPackages; } - else pkgs.quarto; -in -{ - options.catPkgs = lib.mkOption { - type = lib.types.attrsOf (lib.types.listOf lib.types.package); - description = "Per-cat package lists, gated by cat toggles. Single source of truth for runtimeDeps and devShells."; - }; - - config.catPkgs = { - always = maybe "always" (with pkgs; [ - ripgrep - ]); - - clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]); - - external = maybe "external" (with pkgs; [ - nodejs - perl - ruby - shfmt - sqlfluff - tree-sitter - ]); - - julia = maybe "julia" [ - (pkgs.julia-bin.withPackages config.settings.lang_packages.julia) - ]; - - lua = maybe "lua" (with pkgs; [ lua-language-server ]); - - markdown = maybe "markdown" (with pkgs; [ - python3Packages.pylatexenc - quartoPkg - zk - marksman - texlab - imagemagick - harper - ]); - - nix = maybe "nix" (with pkgs; [ - alejandra - nix-doc - nixd - ]); - - optional = maybe "optional" (with pkgs; [ - bat - broot - devenv - dust - fd - fzf - gawk - gh - git - hunspell - hunspellDicts.de-at - hunspellDicts.en-us - ispell - jq - just - lazygit - man - ncdu - pigz - poppler - ripgrep - tokei - wget - yq - ]); - - python = maybe "python" (let - python_packages_fn = - if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python - else _: config.settings.lang_packages.python; - python_with_packages = pkgs.python3.withPackages python_packages_fn; - in - with pkgs; [ - python_with_packages - ruff - basedpyright - uv - ]); - - r = maybe "r" [ - (pkgs.rpkgs.rWrapper.override { packages = rWrapperPackages; }) - pkgs.rpkgs.radianWrapper - quartoPkg - pkgs.air-formatter - pkgs.yaml-language-server - ]; - - # cats without packages get empty lists - general = [ ]; - gitPlugins = [ ]; - treesitterParsers = [ ]; - utils = [ ]; - }; -} diff --git a/modules/module/settings/cats.nix b/modules/module/settings/cats.nix index e1895f7..22b3638 100644 --- a/modules/module/settings/cats.nix +++ b/modules/module/settings/cats.nix @@ -13,8 +13,8 @@ Set a category to `false` to skip its dependency/plugin specs. Available categories: - - always: always-on packages (not gated by a toggle) - clickhouse: Clickhouse client and tools + - customPlugins: local plugin specs - external: external tools and integrations - general: core Neovim plugins/features - gitPlugins: git-related plugins @@ -25,24 +25,26 @@ - optional: optional tools and utilities - python: Python tooling and plugins - r: R tooling and plugins + - test: test-only tooling (disabled by default) - treesitterParsers: Treesitter parsers - utils: general utilities ''; }; config.cats = { - always = lib.mkDefault true; clickhouse = lib.mkDefault false; + customPlugins = lib.mkDefault true; external = lib.mkDefault true; general = lib.mkDefault true; gitPlugins = lib.mkDefault true; julia = lib.mkDefault false; lua = lib.mkDefault true; - markdown = lib.mkDefault true; + markdown = lib.mkDefault false; nix = lib.mkDefault true; optional = lib.mkDefault false; python = lib.mkDefault false; - r = lib.mkDefault true; + r = lib.mkDefault false; + test = lib.mkDefault false; treesitterParsers = lib.mkDefault true; utils = lib.mkDefault true; }; diff --git a/modules/module/settings/core.nix b/modules/module/settings/core.nix index bebc487..42e9e11 100644 --- a/modules/module/settings/core.nix +++ b/modules/module/settings/core.nix @@ -14,6 +14,10 @@ # 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"; @@ -21,17 +25,11 @@ config.settings.block_normal_config = true; # Don't symlink the config (we wrap it instead) - config.settings.dont_link = lib.mkDefault false; + config.settings.dont_link = false; # Create additional aliases for the binary - config.settings.aliases = lib.mkDefault [ "vvim" ]; + config.settings.aliases = [ "vvim" ]; # Enable wrapper handling of spec runtimeDeps (template pattern). config.settings.autowrapRuntimeDeps = true; - - # The wrapper library currently emits runtime PATH additions via `suffixVar`, - # which lets host-level tools in `/usr/local/bin` win inside `nix run`. - # Mirror those additions into `prefixVar` so wrapped Neovim resolves the - # Nix-provided toolchain first while preserving existing wrapper behavior. - config.prefixVar = lib.mkAfter config.suffixVar; } diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index 75ac273..2ae4b9c 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -11,11 +11,12 @@ UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON = pkgs.python.interpreter; }) - # 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) - config.envDefault = lib.mkMerge [ ]; + config.envDefault = lib.mkMerge [ + (lib.mkIf (config.cats.r or false) { + R_LIBS_USER = "./.Rlibs"; + }) + ]; } diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index 2db1515..0743979 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -4,10 +4,6 @@ lib, ... }: -let - rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; - rWrapperPkg = pkgs.rpkgs.rWrapper.override { packages = rPackages ++ [pkgs.nvimcom]; }; -in { config.hosts = lib.mkMerge [ { @@ -19,11 +15,21 @@ in nvim-host.enable = true; nvim-host.package = "${pkgs.neovide}/bin/neovide"; nvim-host.argv0 = "neovide"; - nvim-host.flags."--neovim-bin" = "${builtins.placeholder "out"}/bin/${config.binName}"; + nvim-host.flags."--neovim-bin" = "${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) { + (lib.mkIf (config.cats.julia or true) { jl = { nvim-host.enable = true; nvim-host.package = "${pkgs.julia-bin}/bin/julia"; @@ -33,13 +39,13 @@ in ]; }; }) - (lib.mkIf (config.cats.python or false) { + (lib.mkIf (config.cats.python or true) { python3.nvim-host.enable = true; }) - (lib.mkIf (config.cats.r or false) { + (lib.mkIf (config.cats.r or true) { r = { nvim-host.enable = true; - nvim-host.package = "${rWrapperPkg}/bin/R"; + nvim-host.package = "${pkgs.rWrapper}/bin/R"; nvim-host.argv0 = "R"; nvim-host.addFlag = [ "--no-save" diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index 3be7a9a..3315363 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -1,6 +1,5 @@ { config, - pkgs, lib, ... }: @@ -27,33 +26,14 @@ }; default = { }; description = '' - Language-specific package defaults and downstream overrides appended to each - language spec's runtime packages. + Language-specific package overrides appended to each language spec's extraPackages. + Intended for flake.nix overrides via wrapper.config.wrap. ''; }; config.settings.lang_packages = { - python = lib.mkDefault (with pkgs.python3Packages; [ - duckdb - polars - ]); - r = lib.mkDefault ( - (with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - styler - # vscDebugger is not on CRAN/Bioconductor, so it is not available in - # pkgs.rpkgs.rPackages. Install it manually in your R library if you - # want to use the nvim-dap R adapter (see plugin/26_dap.lua). - # vscDebugger - lintr - ]) - ); - julia = lib.mkDefault [ - "DataFramesMeta" - "QuackIO" - ]; + python = lib.mkDefault [ ]; + r = lib.mkDefault [ ]; + julia = lib.mkDefault [ ]; }; } diff --git a/modules/module/settings/runtime-path.nix b/modules/module/settings/runtime-path.nix new file mode 100644 index 0000000..96c5d0f --- /dev/null +++ b/modules/module/settings/runtime-path.nix @@ -0,0 +1,35 @@ +{ + config, + lib, + ... +}: +let + collect_runtime_packages = runtime_deps_type: + config.specCollect + (acc: spec: + let + is_enabled = if spec ? enable then spec.enable else true; + has_runtime_deps = (spec.runtimeDeps or false) == runtime_deps_type; + packages = spec.extraPackages or [ ]; + in + acc ++ lib.optionals (is_enabled && has_runtime_deps) packages + ) + [ ]; + + prefix_packages = collect_runtime_packages "prefix"; + suffix_packages = collect_runtime_packages "suffix"; + + to_path_specs = packages: [ + { + data = [ + "PATH" + ":" + "${lib.makeBinPath packages}" + ]; + } + ]; +in +{ + config.prefixVar = lib.optionals (prefix_packages != [ ]) (to_path_specs prefix_packages); + config.suffixVar = lib.optionals (suffix_packages != [ ]) (to_path_specs suffix_packages); +} diff --git a/modules/module/specs/cats-enable.nix b/modules/module/specs/cats-enable.nix new file mode 100644 index 0000000..f5a9826 --- /dev/null +++ b/modules/module/specs/cats-enable.nix @@ -0,0 +1,51 @@ +{ config, lib, ... }: +{ + # This module implements category-based enabling of specs. + # It runs early (order 200) so other specMaps can see the enable flags. + # + # How it works: + # 1. For each spec, extract its name (removing -lazy suffix if present) + # 2. Check if there's a corresponding cats. toggle + # 3. Set spec.value.enable based on the cats toggle (default: true) + # 4. This allows specs to be conditionally included based on config.cats settings + # + # Example: If config.cats.python = false, then specs.python.enable = false + + config.specMaps = lib.mkOrder 200 [ + { + name = "CATS_ENABLE"; + data = + list: + map ( + v: + if v.type == "spec" || v.type == "parent" then + let + # Extract spec name, handling lazy specs (remove -lazy suffix) + specName = + if v.name == null then + null + else if lib.hasSuffix "-lazy" v.name then + lib.removeSuffix "-lazy" v.name + else + v.name; + + # Check if this spec has a corresponding cat toggle + catEnabled = + if specName != null && builtins.hasAttr specName config.cats then + config.cats.${specName} + else + true; # Default to enabled if no cat toggle exists + in + v + // { + value = v.value // { + # Use explicit enable if set, otherwise use cat toggle + enable = if v.value ? enable then v.value.enable else catEnabled; + }; + } + else + v + ) list; + } + ]; +} diff --git a/modules/module/specs/deps.nix b/modules/module/specs/deps.nix index 5cbfe63..a8bfaf6 100644 --- a/modules/module/specs/deps.nix +++ b/modules/module/specs/deps.nix @@ -5,78 +5,184 @@ wlib, ... }: { + # ============================================================================ + # SPEC MODULE DEFAULTS + # ============================================================================ + # Define default options available to all specs + config.specMods = {parentSpec ? null, ...}: { - options.runtimePkgs = lib.mkOption { + options.extraPackages = lib.mkOption { type = lib.types.listOf wlib.types.stringable; default = []; - description = "a runtimePkgs spec field to put packages to suffix to the PATH"; + description = "a extraPackages spec field to put packages to suffix to the PATH"; }; }; - config.specs.always = lib.mkIf (config.cats.always or false) { - data = lib.mkDefault null; - runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.always; - }; + # ============================================================================ + # EXTERNAL TOOLS SPEC + # ============================================================================ + # Core system tools and utilities - config.specs.external = lib.mkIf (config.cats.external or false) { + config.specs.external = { data = lib.mkDefault null; before = ["INIT_MAIN"]; config = '' vim.o.shell = "${pkgs.zsh}/bin/zsh" ''; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.external; + extraPackages = with pkgs; [ + perl + ruby + shfmt + sqlfluff + tree-sitter + ]; }; - config.specs.optional = lib.mkIf (config.cats.optional or false) { + # ============================================================================ + # OPTIONAL TOOLS SPEC + # ============================================================================ + + config.specs.optional = lib.mkIf (config.cats.optional or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; before = ["INIT_MAIN"]; - runtimePkgs = config.catPkgs.optional; + extraPackages = with pkgs; [ + bat + broot + devenv + dust + fd + fzf + gawk + gh + git + hunspell + hunspellDicts.de-at + hunspellDicts.en-us + ispell + jq + just + lazygit + man + ncdu + pigz + poppler + ripgrep + tokei + wget + yq + zathura + ]; }; - config.specs.markdown = lib.mkIf (config.cats.markdown or false) { + # ============================================================================ + # MARKDOWN SPEC + # ============================================================================ + + config.specs.markdown = lib.mkIf (config.cats.markdown or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.markdown; + extraPackages = with pkgs; [ + python313Packages.pylatexenc + quarto + zk + ]; }; - config.specs.nix = lib.mkIf (config.cats.nix or false) { + # ============================================================================ + # NIX SPEC + # ============================================================================ + + config.specs.nix = lib.mkIf (config.cats.nix or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.nix; + extraPackages = with pkgs; [ + alejandra + nix-doc + nixd + ]; }; - config.specs.lua = lib.mkIf (config.cats.lua or false) { + # ============================================================================ + # LUA SPEC + # ============================================================================ + + config.specs.lua = lib.mkIf (config.cats.lua or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.lua; + extraPackages = with pkgs; [ + lua-language-server + ]; }; - config.specs.python = lib.mkIf (config.cats.python or false) { + # ============================================================================ + # PYTHON SPEC + # ============================================================================ + + config.specs.python = lib.mkIf (config.cats.python or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.python; + extraPackages = let + python_packages_fn = + if pkgs ? basePythonPackages + then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python + else _: config.settings.lang_packages.python; + python_with_packages = pkgs.python3.withPackages python_packages_fn; + in + with pkgs; [ + python_with_packages + nodejs + ruff + basedpyright + uv + ]; }; - config.specs.r = lib.mkIf (config.cats.r or false) { + # ============================================================================ + # R SPEC + # ============================================================================ + + config.specs.r = lib.mkIf (config.cats.r or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.r; + extraPackages = let + r_packages = (pkgs.baseRPackages or []) ++ config.settings.lang_packages.r; + in + with pkgs; [ + (rWrapper.override {packages = r_packages;}) + radianWrapper + (quarto.override {extraRPackages = r_packages;}) + air-formatter + yaml-language-server + updateR + ]; }; - config.specs.julia = lib.mkIf (config.cats.julia or false) { + # ============================================================================ + # JULIA SPEC + # ============================================================================ + + config.specs.julia = lib.mkIf (config.cats.julia or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.julia; + extraPackages = let + julia_with_packages = + pkgs.julia-bin.withPackages config.settings.lang_packages.julia; + in [julia_with_packages]; }; - config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or false) { + # ============================================================================ + # CLICKHOUSE SPEC + # ============================================================================ + + config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = config.catPkgs.clickhouse; + extraPackages = with pkgs; [ + clickhouse-lts + ]; }; - config.runtimePkgs = config.specCollect (acc: v: acc ++ (v.runtimePkgs or [])) []; + config.extraPackages = config.specCollect (acc: v: acc ++ (v.extraPackages or [])) []; } diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index babb1ba..ded8195 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -4,59 +4,14 @@ lib, ... }: -let - parserList = [ - "bash" - "bibtex" - "c" - "cpp" - "csv" - "diff" - "dockerfile" - "git_config" - "git_rebase" - "gitattributes" - "gitcommit" - "gitignore" - "html" - "javascript" - "json" - "julia" - "latex" - "lua" - "luadoc" - "make" - "markdown" - "markdown_inline" - "matlab" - "nix" - "python" - "query" - "r" - "rnoweb" - "regex" - "sql" - "toml" - "vim" - "vimdoc" - "xml" - "yaml" - "zig" - ]; -in { - options.settings.treesitter_parsers = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = parserList; - description = "Tree-sitter parser names to install when the treesitterParsers category is enabled."; +{ + config.specs.gitPlugins = { + data = [ ]; }; - config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or false) { - data = []; - }; - - config.specs.r = lib.mkIf (config.cats.r or false) { + config.specs.r = { data = with pkgs.vimPlugins; [ - pkgs.r-nvim + config.nvim-lib.neovimPlugins.r quarto-nvim { data = otter-nvim; @@ -65,23 +20,19 @@ in { ]; }; - config.specs.markdown-lazy = lib.mkIf (config.cats.markdown or false) { + config.specs.markdown-lazy = { lazy = true; data = [ config.nvim-lib.neovimPlugins.cmp-pandoc-references - pkgs.vimPlugins.image-nvim ]; }; - config.specs.general = lib.mkIf (config.cats.general or false) { + config.specs.general = { data = with pkgs.vimPlugins; [ lze lzextras plenary-nvim - vimtex neogit - config.nvim-lib.neovimPlugins.bloocky - config.nvim-lib.neovimPlugins.dooing { data = mini-nvim; pname = "mini.nvim"; @@ -129,7 +80,7 @@ in { ]; }; - config.specs.lua = lib.mkIf (config.cats.lua or false) { + config.specs.lua = { data = with pkgs.vimPlugins; [ luvit-meta { @@ -139,7 +90,7 @@ in { ]; }; - config.specs.markdown = lib.mkIf (config.cats.markdown or false) { + config.specs.markdown = { data = with pkgs.vimPlugins; [ quarto-nvim render-markdown-nvim @@ -154,28 +105,59 @@ in { ]; }; - config.specs.utils = lib.mkIf (config.cats.utils or false) { + config.specs.utils = { data = with pkgs.vimPlugins; [ blink-cmp nvim-lspconfig nvim-treesitter-context nvim-treesitter-textobjects { - data = pkgs.vimPlugins.nvim-treesitter; - pname = "nvim-treesitter"; + data = pkgs.codecompanion-nvim; + pname = "codecompanion"; } - { - data = pkgs.vimPlugins.opencode-nvim; - pname = "opencode.nvim"; - } - ] ++ builtins.attrValues pkgs.vimPlugins.nvim-treesitter.queries; + ]; }; - config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or false) { - data = map (name: pkgs.vimPlugins.nvim-treesitter-parsers.${name}) config.settings.treesitter_parsers; + config.specs.treesitterParsers = { + data = with pkgs.vimPlugins.nvim-treesitter-parsers; [ + bash + c + cpp + csv + diff + dockerfile + git_config + git_rebase + gitattributes + gitcommit + gitignore + html + javascript + json + julia + latex + lua + luadoc + make + markdown + markdown_inline + nix + python + query + r + rnoweb + regex + sql + toml + vim + vimdoc + xml + yaml + zig + ]; }; - config.specs.utils-lazy = lib.mkIf (config.cats.utils or false) { + config.specs.utils-lazy = { lazy = true; data = with pkgs.vimPlugins; [ blink-compat @@ -184,26 +166,16 @@ in { colorful-menu-nvim conform-nvim copilot-lua + nvim-dap + nvim-dap-ui + nvim-dap-virtual-text nvim-lint vim-slime ]; }; - # Lazy-loaded plugins needed when the `r` cat is on. Kept separate from - # `utils-lazy` so users with `r=true` and `utils=false` still get the - # nvim-dap R adapter and in-buffer image rendering for plots. - config.specs.r-lazy = lib.mkIf (config.cats.r or false) { + config.specs.gitPlugins-lazy = { lazy = true; - data = with pkgs.vimPlugins; [ - nvim-dap - nvim-dap-ui - nvim-dap-virtual-text - image-nvim - ]; - }; - - config.specs.gitPlugins-lazy = lib.mkIf (config.cats.gitPlugins or false) { - lazy = true; - data = []; + data = [ ]; }; } diff --git a/modules/neovim.nix b/modules/neovim.nix index f4ca40f..ba0361c 100644 --- a/modules/neovim.nix +++ b/modules/neovim.nix @@ -7,18 +7,29 @@ inputs: ... }: { + # ============================================================================ + # IMPORTS + # ============================================================================ + # Import the base neovim wrapper module and all configuration modules + imports = [ wlib.wrapperModules.neovim - ./module/settings/cat-packages.nix ./module/specs/deps.nix ./module/specs/plugins.nix + ./module/specs/cats-enable.nix ./module/settings/core.nix ./module/settings/cats.nix ./module/settings/env.nix ./module/settings/hosts.nix ./module/settings/lang-packages.nix + ./module/settings/runtime-path.nix ]; + # ============================================================================ + # HELPER FUNCTIONS + # ============================================================================ + # Utilities for working with plugin inputs + options.nvim-lib.neovimPlugins = lib.mkOption { readOnly = true; type = lib.types.attrsOf wlib.types.stringable; @@ -47,6 +58,11 @@ inputs: ]; }; + # ============================================================================ + # CONFIGURATION + # ============================================================================ + # Pass cats configuration to neovim and expose metadata + config.settings.cats = config.cats; config.info.cats = config.cats; config.info.nixCats_config_location = config.settings.config_directory; diff --git a/nvim.log b/nvim.log deleted file mode 100644 index 4eb4ddf..0000000 --- a/nvim.log +++ /dev/null @@ -1,13 +0,0 @@ -WRN 2026-07-31T16:36:01.037 ?.50508 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/TN25Kv/nvim.50508.0 -WRN 2026-07-31T16:36:38.634 ?.50644 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/6jT8As/nvim.50644.0 -WRN 2026-07-31T16:46:12.752 ?.54395 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/zY36Oi/nvim.54395.0 -WRN 2026-07-31T17:01:43.138 ?.58656 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/nDisRA/nvim.58656.0 -WRN 2026-07-31T17:01:50.641 ?.58696 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/okDyqb/nvim.58696.0 -WRN 2026-07-31T17:02:04.773 ?.58741 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/5wxfRu/nvim.58741.0 -WRN 2026-07-31T17:02:29.644 ?.58854 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/OMTfpB/nvim.58854.0 -WRN 2026-07-31T17:02:29.644 ?.58852 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/QZvLy2/nvim.58852.0 -WRN 2026-07-31T17:14:31.289 ?.64329 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/zh9tLm/nvim.64329.0 -WRN 2026-07-31T17:14:40.017 ?.64399 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/pGaDez/nvim.64399.0 -WRN 2026-07-31T17:17:48.228 ?.64921 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/ryMpX0/nvim.64921.0 -WRN 2026-07-31T17:17:52.664 ?.64953 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/X7RmXh/nvim.64953.0 -WRN 2026-07-31T17:18:00.691 ?.64989 server_start:197: Failed to start server: operation not permitted: /tmp/nvim.daniel/C6njjq/nvim.64989.0 diff --git a/overlays/default.nix b/overlays/default.nix index 5a4cc6c..821ff88 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -3,33 +3,39 @@ let lib = nixpkgs.lib; rOverlay = import ./r.nix {inherit inputs;}; - rNvimNixOverlay = inputs.r-nvim-nix.overlays.default; - pythonOverlay = import ./python.nix {inherit inputs;}; - pluginsOverlay = import ./plugins.nix {inherit inputs;}; + franOverlay = inputs.fran.overlays.default; + pythonOverlay = import ./python.nix inputs; + pluginsOverlay = import ./plugins.nix inputs; dependencyOverlays = [ rOverlay - rNvimNixOverlay pythonOverlay pluginsOverlay ]; dependencyOverlay = lib.composeManyExtensions dependencyOverlays; - - # franOverlay provides R-specific tooling (radianWrapper, air-formatter). - # It is scoped to rixpkgs (via overlays/r.nix) rather than the global - # package set, since it only applies to R package derivations. - franOverlay = inputs.fran.overlays.default; in { inherit rOverlay - rNvimNixOverlay franOverlay pythonOverlay pluginsOverlay dependencyOverlays dependencyOverlay; + # Named exports for downstream composition. default = dependencyOverlay; dependencies = dependencyOverlays; + + overlays = { + inherit + rOverlay + franOverlay + pythonOverlay + pluginsOverlay + dependencyOverlays + dependencyOverlay; + default = dependencyOverlay; + dependencies = dependencyOverlays; + }; } diff --git a/overlays/plugins.nix b/overlays/plugins.nix index 17d7c5f..91c01aa 100644 --- a/overlays/plugins.nix +++ b/overlays/plugins.nix @@ -1,6 +1,22 @@ { ... }: final: prev: { + codecompanion-nvim = prev.vimPlugins.codecompanion-nvim.overrideAttrs { + checkInputs = with prev.vimPlugins; [ + blink-cmp + mini-nvim + ]; + dependencies = [ prev.vimPlugins.plenary-nvim ]; + nvimSkipModules = [ + "codecompanion.actions.static" + "codecompanion.actions.init" + "minimal" + "codecompanion.providers.actions.fzf_lua" + "codecompanion.providers.completion.cmp.setup" + "codecompanion.providers.actions.telescope" + "codecompanion.providers.actions.snacks" + ]; + }; zk-nvim = prev.vimPlugins.zk-nvim.overrideAttrs { nvimSkipModules = [ "zk.pickers.fzf_lua" diff --git a/overlays/r.nix b/overlays/r.nix index f03d07e..2fb71c8 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -1,11 +1,52 @@ -{inputs, ...}: final: prev: let +# R packages overlay (rix) +# +# This overlay provides access to R packages from rstats-on-nix. +# +# rstats-on-nix maintains snapshots of CRAN packages built with Nix: +# - Provides reproducible R package versions +# - Ensures binary cache availability for faster builds +# - Maintained by the rstats-on-nix community +# +# Available attributes after applying this overlay: +# - pkgs.rpkgs: R packages from rstats-on-nix +# - pkgs.rpkgs.rPackages: All CRAN packages +# - pkgs.rpkgs.quarto: Quarto publishing system +# - pkgs.rpkgs.rWrapper: R with package management +# - pkgs.rWrapper: R wrapper with standard packages pre-configured +# - pkgs.quarto: Quarto with R integration and standard packages +# +# Custom R packages and tools (radianWrapper, air-formatter) come from +# the fran overlay which should be applied separately. +# +# To use specific R packages, reference them via: +# with pkgs.rpkgs.rPackages; [ package1 package2 ] +# +# Update the R snapshot date in flake.nix inputs section: +# rixpkgs.url = "github:rstats-on-nix/nixpkgs/YYYY-MM-DD" +{ + inputs, + ... +}: final: prev: let + # R packages from rstats-on-nix for the current system rpkgs = import inputs.rixpkgs { system = prev.stdenv.hostPlatform.system; - overlays = [inputs.fran.overlays.default inputs.r-nvim-nix.overlays.default]; - }; + overlays = [inputs.fran.overlays.default]; + }; # rixpkgs.legacyPackages.${prev.stdenv.hostPlatform.system}; + + # Standard R packages used by default in rWrapper and quarto + reqPkgs = with rpkgs.rPackages; [ + # languageserver + ]; in { inherit rpkgs; - baseRPackages = [rpkgs.nvimcom rpkgs.rPackages.btw]; - rWrapper = rpkgs.rWrapper.override {packages = [];}; - quarto = rpkgs.quarto.override {extraRPackages = [];}; + baseRPackages = reqPkgs; + + # R wrapper with standard packages + rWrapper = rpkgs.rWrapper.override {packages = reqPkgs;}; + + # Quarto with R integration + quarto = rpkgs.quarto.override {extraRPackages = reqPkgs;}; + + # Update helper for rix + updateR = import ../scripts/updater.nix {pkgs = final;}; } diff --git a/plugin/00_options.lua b/plugin/00_options.lua index 16f3832..2b4485b 100644 --- a/plugin/00_options.lua +++ b/plugin/00_options.lua @@ -128,20 +128,27 @@ local diagnostic_opts = { -- Don't update diagnostics when typing update_in_insert = false, } -later(function() - vim.diagnostic.config(diagnostic_opts) +later(function() vim.diagnostic.config(diagnostic_opts) 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) + +-- 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 +}) vim.api.nvim_create_autocmd("FileType", { desc = "remove formatoptions", @@ -150,6 +157,17 @@ 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" @@ -158,7 +176,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:h14:#e-subpixelantialias:#h-none' + vim.o.guifont = 'Iosevka Nerd Font,Symbols 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 988905c..1197ee8 100644 --- a/plugin/01_lib.lua +++ b/plugin/01_lib.lua @@ -1,5 +1,3 @@ -local Config = require('config') - -- Global Functions Config.new_scratch_buffer = function() vim.api.nvim_win_set_buf(0, vim.api.nvim_create_buf(true, true)) end @@ -14,7 +12,7 @@ end Config.log = {} Config.log_buf_id = Config.log_buf_id or nil -Config.start_hrtime = Config.start_hrtime or vim.uv.hrtime() +Config.start_hrtime = Config.start_hrtime or vim.loop.hrtime() Config.log_print = function() if Config.log_buf_id == nil or not vim.api.nvim_buf_is_valid(Config.log_buf_id) then @@ -26,7 +24,7 @@ end Config.log_clear = function() Config.log = {} - Config.start_hrtime = vim.uv.hrtime() + Config.start_hrtime = vim.loop.hrtime() vim.cmd('echo "Cleared log"') end @@ -34,7 +32,7 @@ end Config.execute_lua_line = function() local line = 'lua ' .. vim.api.nvim_get_current_line() vim.api.nvim_command(line) - vim.notify(line, vim.log.levels.INFO) + print(line) vim.api.nvim_input('') end diff --git a/plugin/02_startup.lua b/plugin/02_startup.lua index b4d1c7e..c4fa2a9 100644 --- a/plugin/02_startup.lua +++ b/plugin/02_startup.lua @@ -1,5 +1,3 @@ -local Config = require('config') - local M = {} -- Helper function to normalize input to a list diff --git a/plugin/03_terminal.lua b/plugin/03_terminal.lua index 43a53a8..95d25b1 100644 --- a/plugin/03_terminal.lua +++ b/plugin/03_terminal.lua @@ -1,9 +1,3 @@ -local Config = require('config') - --- vim-slime target: use Neovim's built-in terminal. --- Must be set before any slime send happens. -vim.g.slime_target = "neovim" - local M = {} -- Configuration @@ -18,7 +12,7 @@ local defaults = { duckdb = "duckdb", julia = "julia", python = "ipython", - shell = "echo 'Hello " .. (vim.env.USER or "user") .. "!'", + shell = "echo 'Hello " .. vim.env.USER .. "!'", } -- Registry of terminal commands @@ -37,15 +31,11 @@ function M.split_and_open_terminal() vim.cmd("resize " .. math.floor(vim.fn.winheight(0) * 0.9)) local term_buf = vim.api.nvim_win_get_buf(vim.api.nvim_get_current_win()) M.opt_term = term_buf - + -- Set buffer-local variables for vim-slime local job_id = vim.b[term_buf].terminal_job_id - if not job_id then - vim.notify("Terminal job id not available", vim.log.levels.WARN) - return term_buf - end vim.b[term_buf].slime_config = { jobid = job_id } - + return M.opt_term end @@ -54,45 +44,26 @@ function M.open_in_terminal(cmd) local command = cmd or "" local current_window = vim.api.nvim_get_current_win() local code_buf = vim.api.nvim_get_current_buf() - - if not vim.api.nvim_buf_is_valid(code_buf) then - vim.notify("Code buffer is not valid", vim.log.levels.ERROR) - return - end - + -- Open terminal and get buffer local term_buf = M.split_and_open_terminal() - - if not term_buf or not vim.api.nvim_buf_is_valid(term_buf) then - vim.notify("Failed to open terminal buffer", vim.log.levels.ERROR) - return - end - + -- Send command if provided - local job_id = vim.b[term_buf].terminal_job_id if command ~= "" then - if not job_id then - vim.notify("Terminal job not ready, cannot send command", vim.log.levels.WARN) - else - local ok, err = pcall(vim.api.nvim_chan_send, job_id, command .. "\r") - if not ok then - vim.notify("Failed to send command to terminal: " .. tostring(err), vim.log.levels.ERROR) - end + -- We can use standard slime sending if needed, or direct chan_send for initialization + local job_id = vim.b[term_buf].terminal_job_id + if job_id then + vim.api.nvim_chan_send(job_id, command .. "\r") end end - + -- Configure slime for the ORIGINAL code buffer to point to this new terminal -- This makes "Send to Terminal" work immediately - if job_id then - local slime_config = { jobid = job_id } - - -- Fix: Set the variable on the captured code buffer, not the current (terminal) buffer - local ok, err = pcall(vim.api.nvim_buf_set_var, code_buf, "slime_config", slime_config) - if not ok then - vim.notify("Failed to set slime_config on code buffer: " .. tostring(err), vim.log.levels.ERROR) - end - end - + local slime_config = { jobid = vim.b[term_buf].terminal_job_id } + + -- Fix: Set the variable on the captured code buffer, not the current (terminal) buffer + vim.api.nvim_buf_set_var(code_buf, "slime_config", slime_config) + -- Switch back to code buffer vim.api.nvim_set_current_win(current_window) end diff --git a/plugin/04_treesitter.lua b/plugin/04_treesitter.lua index 88d3cb7..5a8d315 100644 --- a/plugin/04_treesitter.lua +++ b/plugin/04_treesitter.lua @@ -1,5 +1,3 @@ -local Config = require('config') - local M = {} -- Default parsers list moved from startup config @@ -95,12 +93,12 @@ end function M.get_type() local cur_node = smart_send.get_current_node() if not cur_node then - vim.notify("No Tree-sitter node under cursor", vim.log.levels.WARN) + print("Not a node") return nil end local node_type = cur_node:type() - vim.notify("Node type: " .. node_type, vim.log.levels.INFO) + print("Node type: " .. node_type) return node_type end @@ -121,17 +119,11 @@ function M.setup_keybindings(global_nodes) vim.keymap.set('n', 'a', function() smart_send.send_repl(current_global_nodes) end, { noremap = true, silent = true, desc = "Send node to REPL", buffer = true }) - -- Both `` and `` were removed from `M.setup_keybindings`. They - -- were hard, implicit overrides that clobbered Vim/filetype defaults and - -- the user's snippet + insert-mode workflows (see C2 + H5 in the PR - -- review). To opt back in for a specific filetype, override per-buffer - -- from a `ftplugin/.lua`: - -- - -- -- e.g. ftplugin/r.lua or ftplugin/quarto.lua - -- vim.keymap.set('n', '', function() - -- require('config').treesitter_helpers.setup_keybindings(global_nodes) - -- require('nix_smart_send').send_repl(global_nodes) - -- end, { buffer = true, desc = 'Send node to REPL' }) + vim.keymap.set({ 'n', 'i' }, '', function() smart_send.send_repl(current_global_nodes) end, + { noremap = true, silent = true, desc = "Send node to REPL", buffer = true }) + + vim.keymap.set('n', '', function() smart_send.send_repl(current_global_nodes) end, + { noremap = true, silent = true, desc = "Send node to REPL", buffer = true }) vim.keymap.set('n', 'n', function() current_global_nodes = M.add_global_node(current_global_nodes) end, @@ -142,8 +134,8 @@ function M.setup_keybindings(global_nodes) { noremap = true, silent = true, desc = "Remove node under cursor from globals", buffer = true }) vim.keymap.set('n', 'o', function() - local pout = table.concat(global_nodes, ', ') - vim.notify("global_nodes: " .. pout, vim.log.levels.INFO) + pout = table.concat(global_nodes, ', ') .. "" + print(pout) end, { noremap = true, silent = true, desc = "Print globals", buffer = true }) vim.keymap.set('n', 'p', function() M.get_type() end, @@ -152,46 +144,4 @@ end Config.treesitter_helpers = M --- Tree-sitter text objects: functions, calls, and assignments are especially --- useful when editing R/tidyverse pipelines (e.g. `df |> mutate(...)`). -local ts_ok, treesitter = pcall(require, "nvim-treesitter.configs") -if ts_ok then - treesitter.setup({ - textobjects = { - select = { - enable = true, - lookahead = true, - keymaps = { - ["af"] = "@function.outer", - ["if"] = "@function.inner", - ["ac"] = "@call.outer", - ["ic"] = "@call.inner", - ["aa"] = "@assignment.outer", - ["ia"] = "@assignment.inner", - }, - selection_modes = { - ["@function.outer"] = "V", - ["@function.inner"] = "V", - ["@call.outer"] = "v", - ["@call.inner"] = "v", - ["@assignment.outer"] = "v", - ["@assignment.inner"] = "v", - }, - }, - move = { - enable = true, - set_jumps = true, - goto_next_start = { - ["]f"] = "@function.outer", - ["]c"] = "@call.outer", - }, - goto_previous_start = { - ["[f"] = "@function.outer", - ["[c"] = "@call.outer", - }, - }, - }, - }) -end - return M diff --git a/plugin/10_keymap.lua b/plugin/10_keymap.lua index 98074fb..ebbebf0 100644 --- a/plugin/10_keymap.lua +++ b/plugin/10_keymap.lua @@ -1,11 +1,318 @@ -local Config = require('config') +-- Basic mappings ============================================================= +-- NOTE: Most basic mappings come from 'mini.basics' +-- Shorter version of the most frequent way of going outside of terminal window +vim.keymap.set('t', '', [[h]]) +-- Select all +-- vim.keymap.set({ "n", "v", "x" }, "", "gg3vG$", { noremap = true, silent = true, desc = "Select all" }) +-- Escape deletes highlights +vim.keymap.set("n", "", "nohlsearch") +-- Paste before/after linewise +local cmd = vim.fn.has('nvim-0.12') == 1 and 'iput' or 'put' +vim.keymap.set({ 'n', 'x' }, '[p', 'exe "' .. cmd .. '! " . v:register', { desc = 'Paste Above' }) +vim.keymap.set({ 'n', 'x' }, ']p', 'exe "' .. cmd .. ' " . v:register', { desc = 'Paste Below' }) --- Domain-specific keymap modules. Core must load first because it defines the --- leader clue groups used by the mini.clue setup in the startup plugins. -require('keymap.core') -require('keymap.leader') -require('keymap.terminal') +vim.keymap.set({ "n", "v", "x" }, "p", '"+p', { noremap = true, silent = true, desc = "Paste from clipboard" }) +vim.keymap.set({ "n", "v", "x" }, "y", '"+y', { noremap = true, silent = true, desc = "Copy toclipboard" }) +-- Leader mappings ============================================================ +-- stylua: ignore start --- `_G.Config` is set by `init.lua:2` once at start-up; no need to re-export --- here. The single source of truth is `init.lua` so future refactors don't --- have to chase which file currently publishes the alias. +-- Create global tables with information about clue groups in certain modes +-- Structure of tables is taken to be compatible with 'mini.clue'. +_G.Config.leader_group_clues = { + { mode = 'n', keys = 'a', desc = '+AI' }, + { mode = 'n', keys = 'b', desc = '+Buffer' }, + { mode = 'n', keys = 'e', desc = '+Explore' }, + { mode = 'n', keys = 'f', desc = '+Find' }, + { mode = 'n', keys = 'fl', desc = '+LSP' }, + { mode = 'n', keys = 'fa', desc = '+Git' }, + { mode = 'n', keys = 'g', desc = '+Git' }, + { mode = 'n', keys = 'l', desc = '+LSP' }, + { mode = 'n', keys = 'L', desc = '+Lua/Log' }, + { mode = 'n', keys = 'o', desc = '+Other' }, + { mode = 'n', keys = 'r', desc = '+R' }, + { mode = 'n', keys = 't', desc = '+Terminal' }, + { mode = 'n', keys = 'u', desc = '+UI' }, + { mode = 'n', keys = 'v', desc = '+Visits' }, + { mode = 'n', keys = 'w', desc = '+Windows' }, + { mode = 'x', keys = 'l', desc = '+LSP' }, + { mode = 'x', keys = 'r', desc = '+R' }, + { mode = 'n', keys = 'z', desc = '+ZK' }, + { mode = 'n', keys = 'zr', desc = '+Reviews' }, + { mode = 'x', keys = 'a', desc = '+AI' }, +} + +-- Create `` mappings +local nmap_leader = function(suffix, rhs, desc, opts) + opts = opts or {} + opts.desc = desc + vim.keymap.set('n', '' .. suffix, rhs, opts) +end +local xmap_leader = function(suffix, rhs, desc, opts) + opts = opts or {} + opts.desc = desc + vim.keymap.set('x', '' .. suffix, rhs, opts) +end +-- Other mappings +local nmap_lsp = function(keys, func, desc) + if desc then + desc = desc .. "(LSP)" + end + + vim.keymap.set("n", keys, func, { desc = desc }) +end + +-- Switch buffers +nmap_leader('', 'bnext', 'Next buffer') +nmap_leader('', 'bprev', 'Prev buffer') + +-- a is for 'AI' +nmap_leader("ac", "CodeCompanionChat Toggle", "Chat Toggle") +-- nmap_leader("ae", "CodeCompanion /explain", "Explain Code") +-- nmap_leader("af", "CodeCompanion /fix", "Fix Code") +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("ax", "CodeCompanion /fixer", "Code Fixer") +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("as", "CodeCompanion /suggest", "Suggest Improvements") + +-- b is for 'buffer' +nmap_leader('bb', 'b#', 'Alternate') +nmap_leader('bd', 'lua MiniBufremove.delete()', 'Delete') +nmap_leader('bD', 'lua MiniBufremove.delete(0, true)', 'Delete!') +nmap_leader('bs', 'lua Config.new_scratch_buffer()', 'Scratch') +nmap_leader('bw', 'lua MiniBufremove.wipeout()', 'Wipeout') +nmap_leader('bW', 'lua MiniBufremove.wipeout(0, true)', 'Wipeout!') +nmap_leader('bq', 'qall', 'Quit all') + +-- e is for 'explore' and 'edit' +nmap_leader('ed', 'lua MiniFiles.open()', 'Directory') +nmap_leader('ef', 'lua Config.try_opendir()', 'File directory') +nmap_leader('es', 'lua MiniSessions.select()', 'Sessions') +nmap_leader('eq', 'lua Config.toggle_quickfix()', 'Quickfix') +nmap_leader('ez', 'lua MiniFiles.open(os.getenv("ZK_NOTEBOOK_DIR"))', 'Notes directory') + +-- f is for 'fuzzy find' +nmap_leader('f/', 'Pick history scope="/"', '"/" history') +nmap_leader('f:', 'Pick history scope=":"', '":" history') +nmap_leader('f,', 'Pick visit_labels', 'Visit labels') +nmap_leader('faa', 'Pick git_hunks scope="staged"', 'Added hunks (all)') +nmap_leader('faA', 'Pick git_hunks path="%" scope="staged"', 'Added hunks (current)') +nmap_leader('fb', 'Pick buffers', 'Buffers') +nmap_leader(',', 'Pick buffers', 'Buffers') +nmap_leader('fac', 'Pick git_commits', 'Commits (all)') +nmap_leader('faC', 'Pick git_commits path="%"', 'Commits (current)') +nmap_leader('fd', 'Pick diagnostic scope="all"', 'Diagnostic workspace') +nmap_leader('fD', 'Pick diagnostic scope="current"', 'Diagnostic buffer') +nmap_leader('ff', 'Pick files', 'Files') +nmap_leader('fg', 'Pick grep_live', 'Grep live') +nmap_leader('fG', 'Pick grep pattern=""', 'Grep current word') +nmap_leader('fh', 'Pick help', 'Help tags') +nmap_leader('fH', 'Pick hl_groups', 'Highlight groups') +nmap_leader('fj', 'Pick buf_lines scope="all"', 'Lines (all)') +nmap_leader('fJ', 'Pick buf_lines scope="current"', 'Lines (current)') +nmap_leader('fam', 'Pick git_hunks', 'Modified hunks (all)') +nmap_leader('faM', 'Pick git_hunks path="%"', 'Modified hunks (current)') +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('fq', 'Pick list scope="quickfix"', 'Quickfix') +nmap_leader('fr', 'Pick lsp scope="references"', 'References (LSP)') +nmap_leader('flr', 'Pick lsp scope="references"', 'References (LSP)') +nmap_leader('fS', 'Pick lsp scope="workspace_symbol"', 'Symbols workspace (LSP)') +nmap_leader('flS', 'Pick lsp scope="workspace_symbol"', 'Symbols workspace (LSP)') +nmap_leader('fs', 'Pick lsp scope="document_symbol"', 'Symbols buffer (LSP)') +nmap_leader('fls', 'Pick lsp scope="document_symbol"', 'Symbols buffer (LSP)') +nmap_leader('fld', 'Pick lsp scope="definition"', 'Definition (LSP)') +nmap_leader('flD', 'Pick lsp scope="declaration"', 'Declaration (LSP)') +nmap_leader('flt', 'Pick lsp scope="type_definition"', 'Type Definition (LSP)') +nmap_leader('fv', 'Pick visit_paths cwd=""', 'Visit paths (all)') +nmap_leader('fV', 'Pick visit_paths', 'Visit paths (cwd)') + +-- g is for git +local git_log_cmd = [[Git log --pretty=format:\%h\ \%as\ │\ \%s --topo-order]] + +nmap_leader('gc', 'Git commit', 'Commit') +nmap_leader('gC', 'Git commit --amend', 'Commit amend') +nmap_leader('gd', 'Git diff', 'Diff') +nmap_leader('gD', 'Git diff -- %', 'Diff buffer') +nmap_leader('gg', 'lua require("neogit").open()', 'Git tab') +nmap_leader('gl', '' .. git_log_cmd .. '', 'Log') +nmap_leader('gL', '' .. git_log_cmd .. ' --follow -- %', 'Log buffer') +nmap_leader('go', 'lua MiniDiff.toggle_overlay()', 'Toggle overlay') +nmap_leader('gp', 'Git pull', 'Pull') +nmap_leader('gP', 'Git push', 'Push') +nmap_leader('gs', 'lua MiniGit.show_at_cursor()', 'Show at cursor') + +xmap_leader('gs', 'lua MiniGit.show_at_cursor()', 'Show at selection') + +-- j/k navigate quickfix +nmap_leader("j", 'cnextzz', "Quickfix next") +nmap_leader("k", 'cprevzz', "Quickfix prev") + +-- l is for 'LSP' (Language Server Protocol) +vim.keymap.set({ 'n' }, 'grd', 'lua vim.lsp.buf.definition()', { desc = 'Definition' }) +vim.keymap.set({ 'n' }, 'grk', 'lua vim.lsp.buf.hover()', { desc = 'Documentation' }) +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 })' +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') +nmap_leader('lk', 'lua vim.lsp.buf.hover()', 'Documentation') +nmap_leader('li', 'lua vim.lsp.buf.implementation()', 'Information') +-- use ]d and [d +--nmap_leader('lj', 'lua vim.diagnostic.goto_next()', 'Next diagnostic') +--nmap_leader('lk', 'lua vim.diagnostic.goto_prev()', 'Prev diagnostic') +nmap_leader('lR', 'lua vim.lsp.buf.references()', 'References') +nmap_leader('lr', 'lua vim.lsp.buf.rename()', 'Rename') +nmap_leader('ls', 'lua vim.lsp.buf.definition()', 'Source definition') + +xmap_leader('lf', formatting_cmd, 'Format selection') + +-- L is for 'Lua' +nmap_leader('Lc', 'lua Config.log_clear()', 'Clear log') +nmap_leader('LL', 'luafile %echo "Sourced lua"', 'Source buffer') +nmap_leader('Ls', 'lua Config.log_print()', 'Show log') +nmap_leader('Lx', 'lua Config.execute_lua_line()', 'Execute `lua` line') + +-- m is free + +-- 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') +nmap_leader('ow', + "lua MiniSessions.write(vim.fn.input('Session name: ', string.match(vim.fn.getcwd(), \"[^/]+$\") .. '-session.vim'))", + 'Write session') + +-- r is for 'R' +nmap_leader('rc', 'RSend devtools::check()', 'Check') +nmap_leader('rC', 'RSend devtools::test_coverage()', 'Coverage') +nmap_leader('rd', 'RSend devtools::document()', 'Document') +nmap_leader('ri', 'RSend devtools::install(keep_source=TRUE)', 'Install') +nmap_leader('rk', 'RSend quarto::quarto_preview("%")', 'Knit file') +nmap_leader('rl', 'RSend devtools::load_all()', 'Load all') +nmap_leader('rL', 'RSend devtools::load_all(recompile=TRUE)', 'Load all recompile') +nmap_leader('rm', 'RSend Rcpp::compileAttributes()', 'Run examples') +nmap_leader('rT', 'RSend testthat::test_file("%")', 'Test file') +nmap_leader('rt', 'RSend devtools::test()', 'Test') + +-- - Copy to clipboard and make reprex (which itself is loaded to clipboard) +xmap_leader('rx', '"+y :RSend reprex::reprex()', 'Reprex selection') + +-- s is for 'send' (Send text to neoterm buffer) +nmap_leader('s', 'SlimeSendCurrentLinej', 'Send to terminal') + +-- - In simple visual mode send text and move to the last character in +-- selection and move to the right. Otherwise (like in line or block visual +-- mode) send text and move one line down from bottom of selection. +xmap_leader('s', 'SlimeRegionSend', 'Send to terminal') + +-- t is for 'terminal' +vim.keymap.set("t", "", [[]], { desc = "Exit terminal mode" }) +vim.keymap.set("n", "tc", 'lua Config.terminal.open_clickhouse_client()', + { desc = "Open Clickhouse client" }) +vim.keymap.set("n", "tl", 'lua Config.terminal.open_clickhouse_local()', + { desc = "Open Clickhouse local" }) +vim.keymap.set("n", "tp", 'lua Config.terminal.open_python()', { desc = "Open Python" }) +vim.keymap.set("n", "tj", 'lua Config.terminal.open_julia()', { desc = "Open Julia" }) +vim.keymap.set("n", "td", 'lua Config.terminal.open_duckdb();Config.terminal.toggle_bracket()', + { desc = "Open DuckDB" }) +vim.keymap.set("n", "tx", 'lua Config.terminal.open_in_terminal()', { desc = "Terminal Command" }) +vim.keymap.set("n", "tt", 'lua Config.terminal.open_shell()', { desc = "Terminal" }) +nmap_leader("tb", 'lua Config.terminal.toggle_bracket()', "Toggle bracketed paste") +nmap_leader("up", 'lua Config.terminal.toggle_bracket()', "Toggle bracketed paste") + +-- u is for UI +nmap_leader('ut', 'TSContext toggle', 'Toggle TScontext') +nmap_leader('ua', 'Copilot toggle', 'Toggle AI completion') + +-- v is for 'visits' +nmap_leader('vv', 'lua MiniVisits.add_label("core")', 'Add "core" label') +nmap_leader('vV', 'lua MiniVisits.remove_label("core")', 'Remove "core" label') +nmap_leader('vl', 'lua MiniVisits.add_label()', 'Add label') +nmap_leader('vL', 'lua MiniVisits.remove_label()', 'Remove label') + +local map_pick_core = function(keys, cwd, desc) + local rhs = function() + local sort_latest = MiniVisits.gen_sort.default({ recency_weight = 1 }) + MiniExtra.pickers.visit_paths({ + cwd = cwd, + filter = 'core', + sort = sort_latest + }, { source = { name = desc } }) + end + nmap_leader(keys, rhs, desc) +end +map_pick_core('vc', '', 'Core visits (all)') +map_pick_core('vC', nil, 'Core visits (cwd)') + +-- w is for 'windows' +nmap_leader("wh", "h", "Go to Left Window", { remap = true }) +nmap_leader("wj", "j", "Go to Lower Window", { remap = true }) +nmap_leader("wk", "k", "Go to Upper Window", { remap = true }) +nmap_leader("wl", "l", "Go to Right Window", { remap = true }) + +nmap_leader("_", "s", "Split Window Below", { remap = true }) +nmap_leader("|", "v", "Split Window Right", { remap = true }) +nmap_leader("wd", "c", "Delete Window", { remap = true }) +nmap_leader("wo", "o", "Delete Other Windows", { remap = true }) + +-- z is for 'ZettelKasten' +nmap_leader("zo", 'ZkNotes', "Notes") +nmap_leader("zt", 'ZkTags', "Tags") + +nmap_leader( + "zrd", + 'ZkNew { group = "dreviews" }', + "Daily Review" +) +nmap_leader( + "zrw", + 'ZkNew { group = "wreviews" }', + "Weekly Review" +) +nmap_leader( + "zn", + 'ZkNew { group = "inbox", title = vim.fn.input("Title: ") }', + "New" +) +nmap_leader( + "zp", + "ZkNew { group = 'permanent', title = vim.fn.input('Title: ') }", + "Permanent" +) + +nmap_leader( + "zl", + "ZkNew { group = 'literature', title = vim.fn.input('Title: '), extra.author = vim.fn.input('Author: '), extra.year = vim.fn.input('Year: ') }", + "Literature" +) + +nmap_leader( + "zd", + "ZkNew { group = 'dashboard', title = vim.fn.input('Title: ') }", + "Dashboard" +) +nmap_leader( + "zP", + "ZkNew { group = 'project', title = vim.fn.input('Title: ')}", + "Project" +) +-- stylua: ignore end diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index f5cc9e8..ca18bd8 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -1,4 +1,3 @@ -local Config = require('config') local now = MiniDeps.now local later = MiniDeps.later local now_if_args = Config.now_if_args @@ -191,7 +190,7 @@ end) -- Treesitter now_if_args(function() - vim.treesitter.language.register("markdown", { "markdown", "rmd", "quarto" }) + vim.treesitter.language.register("markdown", { "markdown", "codecompanion", "rmd", "quarto" }) require 'treesitter-context'.setup { enable = true, @@ -209,37 +208,112 @@ now_if_args(function() on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching } - 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", - } - 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 + + 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 - vim.treesitter.start(buf, lang) - vim.bo[buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" + -- 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 vim.api.nvim_create_autocmd("FileType", { - pattern = ts_filetypes, - callback = function(ev) - start_treesitter(ev.buf, vim.bo[ev.buf].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' end, }) - -- FileType may have fired before this deferred setup ran. - if vim.bo.filetype ~= "" then - start_treesitter(0, vim.bo.filetype) + -- 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 - require("nvim-treesitter-textobjects").setup({ + vim.g.no_plugin_maps = true + nto.setup({ move = { set_jumps = true, }, @@ -248,59 +322,91 @@ now_if_args(function() local move = require("nvim-treesitter-textobjects.move") local swap = require("nvim-treesitter-textobjects.swap") - 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" }) + -- 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", "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" }) + -- 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" }, + }, + } - 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) + -- 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 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 now_if_args(function() - if nix.get_cat("markdown", false) then - require("zk").setup({ - picker = "minipick", - lsp = { - -- `config` is passed to `vim.lsp.start_client(config)` - config = { - cmd = { "zk", "lsp" }, - name = "zk", - -- on_attach = ... - -- etc, see `:h vim.lsp.start_client()` - }, - - -- automatically attach buffers in a zk notebook that match the given filetypes - auto_attach = { - enabled = true, - filetypes = { "markdown" }, - }, - + require("zk").setup({ + picker = "minipick", + lsp = { + -- `config` is passed to `vim.lsp.start_client(config)` + config = { + cmd = { "zk", "lsp" }, + name = "zk", + -- on_attach = ... + -- etc, see `:h vim.lsp.start_client()` }, - }) - end + + -- automatically attach buffers in a zk notebook that match the given filetypes + auto_attach = { + enabled = true, + filetypes = { "markdown" }, + }, + + }, + }) end) diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index 807d8ed..118ee20 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -5,23 +5,24 @@ local add = Config.add local nix = require('config.nix') if not Config.isNixCats then - local add = MiniDeps.add + local m_add = MiniDeps.add now(function() - add({ source = "R-nvim/R.nvim" }) + m_add({ source = "R-nvim/R.nvim" }) end) now_if_args(function() - add({ source = "jmbuhr/otter.nvim" }) + m_add({ source = "jmbuhr/otter.nvim" }) end) later(function() - add({ source = "jpalardy/vim-slime" }) + m_add({ source = "jpalardy/vim-slime" }) end) end -- terminal later(function() + vim.g.slime_target = "neovim" vim.g.slime_no_mappings = true add("vim-slime") vim.g.slime_cell_delimiter = vim.g.slime_cell_delimiter or "# %%" @@ -54,21 +55,7 @@ now(function() min_editor_width = 80, 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, - }, + pdfviewer = "zathura", }) end end) @@ -76,45 +63,43 @@ end) -- Quarto now(function() - if nix.get_cat({ "r", "markdown" }, false) then - vim.api.nvim_create_autocmd("FileType", { - pattern = { "quarto" }, - callback = function() - require("otter").activate() - end, - }) + vim.treesitter.language.register("markdown", { "quarto", "rmd" }) - require("otter").setup({ - lsp = { - diagnostic_update_events = { "BufWritePost", "InsertLeave" }, - }, - buffers = { - set_filetype = true, - write_to_disk = true, - }, - }) - end + vim.api.nvim_create_autocmd("FileType", { + pattern = { "quarto" }, + callback = function() + require("otter").activate() + end, + }) + + require("otter").setup({ + lsp = { + diagnostic_update_events = { "BufWritePost", "InsertLeave" }, + }, + buffers = { + set_filetype = true, + write_to_disk = true, + }, + }) end) later(function() - if nix.get_cat({ "r", "markdown" }, false) then - require("quarto").setup({ - lspFeatures = { + require("quarto").setup({ + lspFeatures = { + enabled = true, + chunks = "curly", + languages = { "r", "python", "julia" }, + diagnostics = { enabled = true, - chunks = "curly", - languages = { "r", "python", "julia" }, - diagnostics = { - enabled = true, - triggers = { "BufWritePost" }, - }, - completion = { - enabled = true, - }, + triggers = { "BufWritePost" }, }, - codeRunner = { + completion = { enabled = true, - default_method = "slime", }, - }) - end + }, + codeRunner = { + enabled = true, + default_method = "slime", + }, + }) end) diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index 3a6282e..1983b25 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -1,99 +1,29 @@ -local Config = require('config') - local add = Config.add local now_if_args = Config.now_if_args local later = MiniDeps.later -local nix = require('config.nix') if not Config.isNixCats then - local add = MiniDeps.add + local m_add = MiniDeps.add later(function() - add({ source = "Bilal2453/luvit-meta" }) - add({ source = "folke/lazydev.nvim" }) + m_add({ source = "Bilal2453/luvit-meta" }) + m_add({ source = "folke/lazydev.nvim" }) end) end -- lua later(function() - if nix.get_cat("lua", false) then - add("luvit-meta") - add("lazydev") - require("lazydev").setup({ - library = { - -- See the configuration section for more details - -- Load luvit types when the `vim.uv` word is found - "lua", - "mini.nvim", - "MiniDeps", - { path = "luvit-meta/library", words = { "vim%.uv" } }, - { path = "${3rd}/luv/library", words = { "vim%.uv" } }, - }, - }) - end -end) - --- Linting (via nvim-lint) -later(function() - Config.add("nvim-lint") - local lint_ok, lint = pcall(require, "lint") - if not lint_ok then - return - end - - -- R code style via lintr (must be available in the R runtime). - -- lintr::lint() returns a "lints" object; format() turns it into the - -- standard "file:line:col: severity: message" lines. - lint.linters.lintr = { - cmd = "Rscript", - stdin = false, - args = { - "-e", - "args <- commandArgs(trailingOnly=TRUE); l <- lintr::lint(args[1]); if (length(l) > 0) cat(paste(format(l), collapse='\\n'), '\\n')", + add("luvit-meta") + add("lazydev") + require("lazydev").setup({ + library = { + -- See the configuration section for more details + -- Load luvit types when the `vim.uv` word is found + "lua", + "mini.nvim", + "MiniDeps", + { path = "luvit-meta/library", words = { "vim%.uv" } }, + { path = "${3rd}/luv/library", words = { "vim%.uv" } }, }, - append_fname = true, - stream = "both", - ignore_exitcode = true, - parser = function(output, bufnr, linter_cwd) - local diagnostics = {} - -- Expected format: /path/file.R:10:5: style: Some message - local severity_map = { - style = vim.diagnostic.severity.INFO, - warning = vim.diagnostic.severity.WARN, - error = vim.diagnostic.severity.ERROR, - } - for line in output:gmatch("[^\r\n]+") do - -- Capture the file path as well so lnum/col line up with the numbers. - local path, lnum, col, severity, message = line:match("^(.-):(%d+):(%d+):%s*(%w+):%s*(.+)$") - if path and lnum and col and severity then - local line_num = tonumber(lnum) - local col_num = tonumber(col) - table.insert(diagnostics, { - bufnr = bufnr, - lnum = math.max(0, line_num - 1), - col = math.max(0, col_num - 1), - end_lnum = line_num - 1, - end_col = col_num, - severity = severity_map[severity:lower()] or vim.diagnostic.severity.WARN, - message = message or "lintr issue", - source = "lintr", - }) - end - end - return diagnostics - end, - } - - lint.linters_by_ft = { - r = { "lintr" }, - rmd = { "lintr" }, - quarto = { "lintr" }, - } - - vim.api.nvim_create_autocmd({ "BufReadPost", "BufWritePost", "InsertLeave" }, { - group = vim.api.nvim_create_augroup("LintOnEvents", { clear = true }), - callback = function() - lint.try_lint() - end, }) end) @@ -102,7 +32,7 @@ now_if_args(function() add("render-markdown.nvim") require('render-markdown').setup({ -- completions = { blink = { enabled = true } }, - file_types = { 'markdown', }, + file_types = { 'markdown', 'codecompanion', }, link = { wiki = { body = function(ctx) diff --git a/plugin/23_editor.lua b/plugin/23_editor.lua index 362333d..d4669d8 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 add = MiniDeps.add + local m_add = MiniDeps.add later(function() - add("stevearc/conform.nvim") + m_add("stevearc/conform.nvim") end) end @@ -23,6 +23,20 @@ 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) @@ -37,12 +51,7 @@ later(function() end) later(function() - require("mini.align").setup({ - mappings = { - start = "gA", - start_with_preview = "g", - }, - }) + require("mini.align").setup() end) later(function() @@ -218,8 +227,6 @@ later(function() local minikeymap = require("mini.keymap") minikeymap.setup() local map_multistep = minikeymap.map_multistep - -- blink_next/blink_prev/blink_accept are blink.cmp actions. - -- Direct blink keymaps (C-space/C-l) are set in 24_completion.lua. local tab_steps = { "blink_next", "pmenu_next", diff --git a/plugin/24_completion.lua b/plugin/24_completion.lua index 9e70b27..3e0f00c 100644 --- a/plugin/24_completion.lua +++ b/plugin/24_completion.lua @@ -1,9 +1,11 @@ -local Config = require('config') local add = Config.add local later = MiniDeps.later local now = MiniDeps.now local now_if_args = Config.now_if_args +-- Constants +local BLINK_VERSION = "v1.4.1" + -- Plugin sources configuration local PLUGIN_SOURCES = { "hrsh7th/cmp-cmdline", @@ -11,7 +13,7 @@ local PLUGIN_SOURCES = { "zbirenbaum/copilot.lua", "jmbuhr/cmp-pandoc-references", "fang2hou/blink-copilot", - "nickjvandyke/opencode.nvim", + "olimorris/codecompanion.nvim" } local PLUGIN_ADDS = { @@ -21,36 +23,175 @@ local PLUGIN_ADDS = { "cmp-pandoc-references", } +-- 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 + end +end + +local function get_code_block(context) + local text = require("codecompanion.helpers.actions").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 end local function get_blink_fuzzy_setting() - return { - sorts = { "exact", "score", "sort_text" }, - use_proximity = true, + local setting = { + sorts = { "exact", "score", "sort_text" } } + + if not Config.isNixCats then + setting.prebuilt_binary = { force_version = BLINK_VERSION } + end + + return setting end -- Plugin loading if not Config.isNixCats then - local add = MiniDeps.add + local m_add = MiniDeps.add now_if_args(function() - add({ + m_add({ source = "saghen/blink.cmp", depends = { "rafamadriz/friendly-snippets" }, + checkout = BLINK_VERSION, }) end) later(function() for _, source in ipairs(PLUGIN_SOURCES) do - add({ source = source }) + m_add({ source = source }) end end) end +local function get_codecompanion_config() + return { + interactions = { + chat = { + adapter = { + name = "copilot", + model = "gemini-3.1-pro-preview", + }, + opts = { + completion_provider = "blink", + }, + }, + inline = { + adapter = { + name = "copilot", + model = "gemini-3.1-pro-preview", + } + }, + keymaps = { + accept_change = { + modes = { n = "ga" }, + description = "Accept the suggested change", + }, + reject_change = { + modes = { n = "gr" }, + opts = { nowait = true }, + description = "Reject the suggested change", + }, + }, + }, + display = { + chat = { + show_settings = false, + window = { + layout = "horizontal", + position = "bottom", + height = 0.33, + }, + }, + }, + prompt_library = { + ["expert"] = { + interaction = "chat", + description = "Get expert advice from an LLM", + --opts = create_common_opts("ae", "expert"), + prompts = { + { + role = "system", + content = create_system_prompt( + "I will ask you specific questions and I want you to return concise explanations and codeblock examples." + ), + }, + { + role = "user", + content = function(context) + return "I have the following code:\n\n" .. get_code_block(context) .. "\n\n" + end, + opts = { contains_code = true }, + }, + }, + }, + ["fixer"] = { + interaction = "chat", + description = "Fix code errors with expert guidance", + --opts = create_common_opts("af", "afixer"), + prompts = { + { + role = "system", + content = create_system_prompt( + "I have a block of code that is not working and will give you a hint about the error. I want you to return the corrected code and a concise explanation of the corrections." + ), + }, + { + role = "user", + content = function(context) + return "The following code has an error:\n\n" .. get_code_block(context) .. "\n\nThe error is:" + end, + opts = { contains_code = true }, + }, + }, + }, + ["suggest"] = { + interaction = "chat", + description = "Suggest improvements to the buffer", + --opts = create_common_opts("as", "suggest"), + prompts = { + { + role = "system", + content = create_system_prompt( + "When asked to improve code, follow these steps:\n" .. + "1. Identify the programming language.\n" .. + "2. Think separately for each function or significant block of code and think about possible improvements (e.g., for better readability or speed) in the context of the language.\n" .. + "3. Think about the whole document and think about possible improvements.\n" .. + "4. Provide the improved code.\n" .. + "5. Provide a concise explanation of the improvements." + ), + }, + { + role = "user", + content = function(context) + return "Please improve the following code:\n\n" .. get_code_block(context) + end, + opts = { contains_code = true }, + }, + }, + }, + } + } +end + -- Batch add simple plugins later(function() for _, plugin in ipairs(PLUGIN_ADDS) do @@ -102,33 +243,19 @@ later(function() }) end) + later(function() - vim.g.opencode_opts = { - events = { - reload = true, - permissions = { - enabled = true, - edits = { enabled = true }, - }, - }, - server = { - start = function() - vim.cmd("vsplit term://opencode --port") - vim.cmd("vertical resize " .. math.floor(vim.o.columns * 0.4)) - vim.cmd("wincmd p") - end - }, - } - add("opencode.nvim") + add("codecompanion.nvim") + + -- now use function + require("codecompanion").setup(get_codecompanion_config()) + vim.cmd([[cab cc CodeCompanion]]) end) now_if_args(function() add("blink.cmp") require("blink.cmp").setup({ - -- Direct blink keymaps (C-space/C-l). - -- Tab/Enter/Up/Down are handled via multistep chains in 23_editor.lua, - -- which chain blink_next/blink_prev/blink_accept with other editor actions. keymap = { preset = "default", [""] = { "show", "select_next" }, @@ -165,7 +292,6 @@ now_if_args(function() }, completion = { menu = { - border = "rounded", draw = { treesitter = { "lsp" }, components = { @@ -185,13 +311,12 @@ now_if_args(function() list = { selection = { preselect = false, auto_insert = true } }, - ghost_text = { enabled = true, show_with_menu = true }, - documentation = { auto_show = true, window = { border = "rounded" } }, + documentation = { auto_show = true }, trigger = { show_in_snippet = false }, }, snippets = { preset = "mini_snippets" }, sources = { - default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot" }, + default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot", "codecompanion" }, providers = { path = { score_offset = 50, @@ -210,12 +335,22 @@ 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", score_offset = 45, async = true, }, + codecompanion = { + name = "CodeCompanion", + module = "codecompanion.providers.completion.blink", + score_offset = 45, + async = true, + }, references = { name = "pandoc_references", module = "cmp-pandoc-references.blink", diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index 6a719ca..8c67295 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -1,4 +1,3 @@ -local Config = require('config') local now_if_args = Config.now_if_args if not Config.isNixCats then @@ -9,81 +8,24 @@ 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" }, - }, - harper_ls = { - cmd = { "harper-ls", "--stdio" }, - filetypes = { "markdown", "quarto", "text", "tex", "typst" }, - root_markers = { ".git", ".harper" }, - settings = { - ["harper-ls"] = { - linters = { - SpellCheck = true, - SpelledNumbers = false, - AnA = true, - SentenceCapitalization = true, - UnclosedQuotes = true, - WrongApostrophe = false, - LongSentences = true, - RepeatedWords = true, - Spaces = true, - CorrectNumberSuffix = true, - }, - codeActions = { - ForceStable = false, - }, - markdown = { - IgnoreLinkTitle = false, - }, - diagnosticSeverity = "hint", - isolateEnglish = false, - dialect = "American", - maxFileLength = 120000, - }, - }, - }, - nil_ls = { - settings = { - ["nil"] = { - formatting = { - command = { "alejandra" }, - }, - }, - }, - }, - nixd = { - settings = { - nixd = { - formatting = { - command = { "alejandra" }, - }, - options = { - -- Downstream flakes can override these via lib.mkMerge on the lsp config. - nixos = { expr = "(builtins.getFlake \"/etc/nixos\").nixosConfigurations.\"default\".options" }, - home_manager = { expr = "(builtins.getFlake \"/etc/nixos\").homeConfigurations.\"default\".options" }, - }, - }, - }, - }, - yamlls = { - settings = { - yaml = { - schemas = { - ["https://raw.githubusercontent.com/quarto-dev/quarto-cli/main/src/resources/schema/project.json"] = "**/_quarto.yml", - ["https://raw.githubusercontent.com/quarto-dev/quarto-cli/main/src/resources/schema/document-quarto.json"] = "**/*.qmd", - }, - }, - }, - }, - texlab = { - single_file_support = true, + filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, + -- r_language_server = { + -- filetypes = { 'r', 'rmd', 'rmarkdown' }, + -- settings = { + -- ['r_language_server'] = { + -- lsp = { + -- rich_documentation = true, + -- enable = true, + -- }, + -- }, + -- } + -- }, julials = { settings = { julia = { diff --git a/plugin/26_dap.lua b/plugin/26_dap.lua deleted file mode 100644 index 73deeb8..0000000 --- a/plugin/26_dap.lua +++ /dev/null @@ -1,79 +0,0 @@ -local Config = require('config') - -local later = MiniDeps.later -local nix = require('config.nix') - -later(function() - -- Only pull in the DAP packages when R (the only cat that has a real - -- adapter for now) is enabled; otherwise the lazy load + autoload chain - -- still works but those three would sit unused on the runtime path. - if not nix.get_cat("r", false) then - return - end - - Config.add("nvim-dap") - Config.add("nvim-dap-ui") - Config.add("nvim-dap-virtual-text") -end) - -later(function() - if not nix.get_cat("r", false) then - return - end - - local dap_ok, dap = pcall(require, "dap") - if not dap_ok then - vim.notify("nvim-dap not available", vim.log.levels.WARN) - return - end - - local dapui_ok, dapui = pcall(require, "dapui") - if dapui_ok then - dapui.setup() - dap.listeners.after.event_initialized["dapui_config"] = function() - dapui.open() - end - dap.listeners.before.event_terminated["dapui_config"] = function() - dapui.close() - end - dap.listeners.before.event_exited["dapui_config"] = function() - dapui.close() - end - end - - local vt_ok, _ = pcall(require, "nvim-dap-virtual-text") - if vt_ok then - -- Default setup is enough; virtual text is enabled automatically. - end - - -- R adapter via vscDebugger (https://github.com/cwida/vscDebugger) - if nix.get_cat("r", false) then - dap.adapters.r = { - type = "executable", - command = "R", - args = { - "--quiet", - "--no-save", - "-e", - "vscDebugger::main()", - }, - } - - dap.configurations.r = { - { - type = "r", - name = "Debug current R script", - request = "launch", - program = "${file}", - debugMode = "function", - }, - { - type = "r", - name = "Attach to R process", - request = "attach", - hostName = "localhost", - port = 18721, - }, - } - end -end) diff --git a/plugin/27_image.lua b/plugin/27_image.lua deleted file mode 100644 index 89e0d2f..0000000 --- a/plugin/27_image.lua +++ /dev/null @@ -1,50 +0,0 @@ -local Config = require('config') - -local later = MiniDeps.later -local nix = require('config.nix') - --- Only load image-nvim when a cat that benefits from in-buffer plots is on. -later(function() - if not nix.get_cat({ "r", "markdown" }, false) then - return - end - - Config.add("image-nvim") -end) - -later(function() - if not nix.get_cat({ "r", "markdown" }, false) then - return - end - - local ok, image = pcall(require, "image") - if not ok then - vim.notify("image.nvim not available", vim.log.levels.DEBUG) - return - end - - image.setup({ - -- Backend is intentionally NOT set so image.nvim auto-detects the - -- graphics protocol (kitty / wezterm / iterm / sixel) at runtime and - -- falls back to no rendering on unsupported terminals. The literal - -- strings "auto" / "none" are not accepted by image.nvim.setup(), so - -- setting either would silently disable rendering everywhere. - integrations = { - markdown = { - enabled = true, - clear_in_insert_mode = false, - download_remote_images = true, - only_render_image_at_cursor = false, - filetypes = { "markdown", "quarto" }, - }, - }, - max_width = nil, - max_height = nil, - max_width_window_percentage = nil, - max_height_window_percentage = 50, - window_overlap_clear_enabled = false, - window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs" }, - editor_only_render_when_focused = false, - hijack_file_patterns = { "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp" }, - }) -end) diff --git a/plugin/28_latex.lua b/plugin/28_latex.lua deleted file mode 100644 index 3b24f9e..0000000 --- a/plugin/28_latex.lua +++ /dev/null @@ -1,38 +0,0 @@ --- vimtex integration for raw .tex workflows (paper drafts, AEA submissions, --- beamer slides). Rides in the `markdown` cat because Quarto users routinely --- also write standalone .tex. - -local Config = require('config') -local later = MiniDeps.later -local nix = require('config.nix') - -later(function() - if not nix.get_cat("markdown", false) then - return - end - - -- vimtex is a VimL plugin: it does not expose a Lua module. Configure it - -- via globals before loading so the plugin picks them up on startup. - vim.g.vimtex_compiler_method = "latexmk" - vim.g.vimtex_compiler_latexmk = { - -- Keep conservative defaults: no continuous background compilation and no - -- callback chatter. Manual :VimtexCompile still works on demand. - continuous = 0, - callback = 0, - } - -- Let vimtex choose the first available viewer (zathura, Skim, Evince, ...). - - Config.add("vimtex") - - -- Filetype detection is normally on, but force it explicitly so .tex files - -- opened outside Quarto still pick up the LSP + viewer hooks. - vim.api.nvim_create_autocmd("BufReadPost", { - pattern = { "*.tex", "*.sty", "*.cls" }, - callback = function(args) - local buf = args.buf - if vim.api.nvim_buf_is_loaded(buf) then - vim.bo[buf].filetype = "tex" - end - end, - }) -end) diff --git a/plugin/29_bloocky.lua b/plugin/29_bloocky.lua deleted file mode 100644 index 5e6a3ea..0000000 --- a/plugin/29_bloocky.lua +++ /dev/null @@ -1,43 +0,0 @@ --- Bloocky: timeblocking calendar (day/week/month views), persisted to JSON. --- Loaded via the `general` cat spec (nix mode) or MiniDeps (non-nix). --- Default global toggle is tb, which collides with the terminal map --- (toggle bracketed paste), so it is moved to the calendar/tasks group c. -local Config = require('config') - -if not Config.isNixCats then - local later = MiniDeps.later - later(function() - MiniDeps.add({ source = 'atiladefreitas/bloocky' }) - end) -end - -local nix = require('config.nix') -local later = MiniDeps.later - -later(function() - if not nix.get_cat('general', false) then - return - end - require('bloocky').setup({ - week_start = "monday", - window = { - -- Width per view: fraction of the editor width (or absolute columns if > 1). - -- A single number applies to every view. - width = { - month = 0.99, - week = 0.99, - day = 0.8, - }, - border = "rounded", - }, - integrations = { - dooing = { - enabled = true, -- show Dooing todos on their due date - show_done = false, -- also show completed todos - }, - }, - keymaps = { - toggle = 'cb', - }, - }) -end) diff --git a/plugin/30_dooing.lua b/plugin/30_dooing.lua deleted file mode 100644 index f47ecc2..0000000 --- a/plugin/30_dooing.lua +++ /dev/null @@ -1,44 +0,0 @@ --- Dooing: minimalist todo list manager with a floating window, persisted to JSON. --- Loaded via the `general` cat spec (nix mode) or MiniDeps (non-nix). --- Default globals td / tN occupy the terminal group, so the --- todo toggles are moved to the calendar/tasks group c. -local Config = require('config') - -if not Config.isNixCats then - local later = MiniDeps.later - later(function() - MiniDeps.add({ source = 'atiladefreitas/dooing' }) - end) -end - -local nix = require('config.nix') -local later = MiniDeps.later -local now = MiniDeps.now -later(function() - if not nix.get_cat('general', false) then - return - end - local dooing = require('dooing') - require("dooing").setup({ - keymaps = { - toggle_window = "cd", - open_project_todo = "cD", - show_due_notification = "cN", - create_nested_task = "cn", -- Create nested subtask under current todo - toggle_priority = "a", - }, - calendar = { - week_start_day = "monday", - }, - }) - -- Remove Dooing's old defaults. - vim.keymap.del("n", "td") - vim.keymap.del("n", "tD") - vim.keymap.del("n", "tN") - - -- Restore mappings overwritten by Dooing. - local helpers = require('keymap.helpers') - local nmap_leader = helpers.nmap_leader - -- t is for 'terminal' - nmap_leader("td", 'lua Config.terminal.open_duckdb();Config.terminal.toggle_bracket()', 'Open DuckDB') -end) diff --git a/plugin/31_nvim_omp.lua b/plugin/31_nvim_omp.lua deleted file mode 100644 index 44076cf..0000000 --- a/plugin/31_nvim_omp.lua +++ /dev/null @@ -1,8 +0,0 @@ --- omp bridge entrypoint: start the RPC socket as early as possible so the omp --- harness can read this instance's buffers on demand (see lua/nvim_omp/init.lua --- and .omp/tools/nvim_buffers.mjs). Safe to fail silently — the socket is a --- convenience, not a dependency of the editor. -local ok, nvim_omp = pcall(require, "nvim_omp") -if ok then - nvim_omp.start() -end \ No newline at end of file diff --git a/scripts/updater.nix b/scripts/updater.nix new file mode 100644 index 0000000..063ec8d --- /dev/null +++ b/scripts/updater.nix @@ -0,0 +1,14 @@ +{pkgs}: +pkgs.writeShellApplication { + name = "updateR"; + + # Tools your script needs at runtime + runtimeInputs = [ + pkgs.wget + pkgs.gnused + pkgs.coreutils + ]; + + # Keep script in separate file, but embed contents + text = builtins.readFile ./updater.sh; +} diff --git a/scripts/updater.sh b/scripts/updater.sh new file mode 100644 index 0000000..ccf5e2e --- /dev/null +++ b/scripts/updater.sh @@ -0,0 +1,22 @@ +echo "📡 Fetching latest R version from rstats-on-nix..." +RVER=$( wget -qO- 'https://raw.githubusercontent.com/ropensci/rix/refs/heads/main/inst/extdata/available_df.csv' | tail -n 1 | head -n 1 | cut -d',' -f4 | tr -d '"' ) + +# Validate RVER matches YYYY-MM-DD format +if [[ ! "$RVER" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + echo "❌ Error: Failed to fetch valid R version date. Got: '$RVER'" + exit 1 +fi + +echo "✅ R date is $RVER" + +# Create backup of flake.nix before modifying +cp flake.nix flake.nix.backup + +# Update rixpkgs date in flake.nix +if sed -i "s|rixpkgs.url = \"github:rstats-on-nix/nixpkgs/[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\";|rixpkgs.url = \"github:rstats-on-nix/nixpkgs/$RVER\";|" flake.nix; then + echo "✅ Updated rixpkgs date in flake.nix" + rm flake.nix.backup +else + echo "⚠️ Warning: Failed to update flake.nix, restoring backup" + mv flake.nix.backup flake.nix +fi diff --git a/tests/smoke.lua b/tests/smoke.lua deleted file mode 100644 index 2e4112e..0000000 --- a/tests/smoke.lua +++ /dev/null @@ -1,268 +0,0 @@ --- Comprehensive CI smoke tests for the Neovim configuration. --- Run inside the wrapped Neovim binary, e.g.: --- vv --headless -c "luafile tests/smoke.lua" -c "qa!" --- --- The script loads the full config (plugin/*.lua files are sourced by Neovim on --- startup), waits for mini.deps deferred work, then exercises key functionality --- without user interaction. - -local M = {} - --- --------------------------------------------------------------------------- --- Tiny test harness --- --------------------------------------------------------------------------- -local failures = {} -local passed = 0 - -local function fail(msg) - table.insert(failures, msg) - print(" FAIL: " .. msg) -end - -local function pass(msg) - passed = passed + 1 - print(" PASS: " .. msg) -end - -local function assert_eq(a, b, msg) - if a == b then - pass(msg) - else - fail(string.format("%s (expected %s, got %s)", msg, vim.inspect(b), vim.inspect(a))) - end -end - -local function assert_true(cond, msg) - if cond then - pass(msg) - else - fail(msg) - end -end - -local function assert_loaded(mod, msg) - assert_true(package.loaded[mod] ~= nil, msg or ("module '" .. mod .. "' loaded")) -end - --- --------------------------------------------------------------------------- --- Wait for deferred mini.deps/later work. --- We patch MiniDeps.later to count pending callbacks and then drain the event --- loop until the counter returns to zero (or we time out). --- --------------------------------------------------------------------------- -local function wait_for_deferred(timeout_ms) - timeout_ms = timeout_ms or 10000 - - -- Fire VimEnter so startup autocmds run, then drain the event loop so - -- mini.deps' deferred setup closures have a chance to execute before we - -- assert anything. - -- The headless smoke run has no start screen to display. Disabling - -- mini.starter also prevents it from replacing the current buffer and - -- trying to create a swap file in the read-only Nix sandbox. - vim.g.ministarter_disable = true - vim.cmd('doautocmd VimEnter') - vim.wait(timeout_ms, function() return false end, 50) -end - --- --------------------------------------------------------------------------- --- Helper: check whether a keymap is registered for a given lhs in normal mode. --- --------------------------------------------------------------------------- -local function has_normal_map(lhs) - -- is stored as the literal leader key, so test both the raw - -- symbolic form and the expanded form (e.g. "ff" and " ff"). - local expanded = lhs:gsub('^', vim.g.mapleader or '\\') - - if vim.keymap and vim.keymap.get then - local maps = vim.keymap.get('n') - for _, map in ipairs(maps) do - if map.lhs == lhs or map.lhs == expanded then - return true - end - end - end - - return vim.fn.maparg(lhs, 'n') ~= '' or vim.fn.maparg(expanded, 'n') ~= '' -end - --- --------------------------------------------------------------------------- --- Helper: check whether a Treesitter parser is available. --- --------------------------------------------------------------------------- -local function has_parser(lang) - return #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) > 0 -end - --- --------------------------------------------------------------------------- --- Helper: check whether an LSP config was registered (Neovim 0.11+ native API). --- --------------------------------------------------------------------------- -local function has_lsp_config(name) - if vim.lsp and vim.lsp.config then - -- Neovim 0.12 exposes configs through the callable `vim.lsp.config` - -- table; older versions expose a function-like API. - local ok, cfg = pcall(function() return vim.lsp.config[name] end) - if not ok or type(cfg) ~= 'table' or next(cfg) == nil then - ok, cfg = pcall(vim.lsp.config, name) - end - if ok and cfg and next(cfg) ~= nil then - return true - end - end - -- Fallback: inspect lspconfig internal table. - local ok, configs = pcall(require, 'lspconfig.configs') - if ok and configs and configs[name] then - return true - end - return false -end - --- --------------------------------------------------------------------------- --- Test suites --- --------------------------------------------------------------------------- -function M.test_core_config() - print("\n=== Core configuration ===") - assert_eq(vim.g.mapleader, ' ', "leader is space") - assert_eq(vim.g.maplocalleader, ',', "localleader is comma") - assert_true(vim.o.backup, "backup option enabled") - assert_true(vim.o.undofile, "undofile enabled") - assert_eq(vim.o.mouse, 'a', "mouse enabled") -end - -function M.test_config_module() - print("\n=== Config module ===") - assert_true(_G.Config ~= nil, "global Config table exists") - assert_true(type(Config.edit) == 'function', "Config.edit helper exists") - assert_true(type(Config.terminal) == 'table', "Config.terminal namespace exists") - assert_true(type(Config.treesitter_helpers) == 'table', "Config.treesitter_helpers exists") -end - -function M.test_mini_modules() - print("\n=== mini.nvim modules ===") - assert_loaded('mini.basics', 'mini.basics loaded') - assert_loaded('mini.statusline', 'mini.statusline loaded') - assert_loaded('mini.tabline', 'mini.tabline loaded') - assert_loaded('mini.clue', 'mini.clue loaded') - assert_loaded('mini.pick', 'mini.pick loaded') - assert_loaded('mini.notify', 'mini.notify loaded') -end - -function M.test_keymaps() - print("\n=== Keymaps ===") - assert_true(has_normal_map('ff'), "leader ff -> pick files") - assert_true(has_normal_map('fg'), "leader fg -> live grep") - assert_true(has_normal_map('bb'), "leader bb -> alternate buffer") - assert_true(has_normal_map('ed'), "leader ed -> mini.files open") - assert_true(has_normal_map(''), "Esc clears search highlight") -end - -function M.test_treesitter() - print("\n=== Treesitter parsers ===") - local expected = { 'lua', 'python', 'nix', 'markdown', 'latex', 'r', 'julia' } - for _, lang in ipairs(expected) do - assert_true(has_parser(lang), "parser available: " .. lang) - end -end - -function M.test_filetype_detection() - print("\n=== Filetype detection ===") - local test_buf = vim.api.nvim_create_buf(false, true) - local orig_buf = vim.api.nvim_get_current_buf() - vim.api.nvim_set_current_buf(test_buf) - - -- Test by manually triggering filetype detection for a couple of languages. - vim.api.nvim_buf_set_name(test_buf, 'test.py') - vim.api.nvim_set_option_value('filetype', 'python', { buf = test_buf }) - assert_eq(vim.bo.filetype, 'python', "python filetype set") - - vim.api.nvim_buf_set_name(test_buf, 'test.lua') - vim.api.nvim_set_option_value('filetype', 'lua', { buf = test_buf }) - assert_eq(vim.bo.filetype, 'lua', "lua filetype set") - - vim.api.nvim_set_current_buf(orig_buf) - vim.api.nvim_buf_delete(test_buf, { force = true }) -end - -function M.test_lsp_config() - print("\n=== LSP server registration ===") - local servers = { 'lua_ls', 'basedpyright', 'ruff', 'nil_ls', 'texlab', 'marksman', 'harper_ls' } - for _, name in ipairs(servers) do - assert_true(has_lsp_config(name), "LSP config registered: " .. name) - end -end - -function M.test_plugin_configs() - print("\n=== Plugin-specific configuration ===") - -- vimtex globals should be set when the markdown cat is on. - if vim.g.vimtex_compiler_method then - assert_eq(vim.g.vimtex_compiler_method, 'latexmk', "vimtex compiler is latexmk") - else - pass("vimtex not configured (markdown cat disabled)") - end - - -- conform formatters should be registered. - local ok, conform = pcall(require, 'conform') - if ok and conform then - local formatters = require('conform').formatters - assert_true(formatters ~= nil, "conform formatters table exists") - else - pass("conform not loaded (expected if utils cat disabled)") - end - - -- blink.cmp keymap preset should be available. - local blink_ok, blink = pcall(require, 'blink.cmp') - if blink_ok and blink then - pass("blink.cmp loaded") - else - pass("blink.cmp not loaded (expected if utils cat disabled)") - end -end - -function M.test_nix_cats_helper() - print("\n=== nixCats helper ===") - local ok, nix = pcall(require, 'config.nix') - assert_true(ok and nix ~= nil, "config.nix can be required") - if ok and nix then - local cat = nix.get_cat('general', true) - assert_true(type(cat) == 'boolean', "get_cat returns boolean default") - end -end - --- --------------------------------------------------------------------------- --- Entry point --- --------------------------------------------------------------------------- -function M.run() - print("Neovim config CI smoke tests") - local v = vim.version() - print(string.format("Neovim version: %d.%d.%d", v.major, v.minor, v.patch)) - - -- Drain deferred plugin setup before asserting. - wait_for_deferred() - - M.test_core_config() - M.test_config_module() - M.test_mini_modules() - M.test_keymaps() - M.test_treesitter() - M.test_filetype_detection() - M.test_lsp_config() - M.test_plugin_configs() - M.test_nix_cats_helper() - - print("\n=== Summary ===") - print(string.format("Passed: %d", passed)) - print(string.format("Failed: %d", #failures)) - - if #failures > 0 then - print("\nFailed tests:") - for _, f in ipairs(failures) do - print(" - " .. f) - end - vim.cmd('cquit 1') - else - print("\nAll smoke tests passed!") - vim.cmd('cquit 0') - end -end - -local ok, err = pcall(M.run) -if not ok then - print("CRASH: " .. tostring(err)) - vim.cmd('cquit 1') -end