From e9c99811d4b8684814608c7bff89dd5bb4d20cda Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sun, 3 May 2026 16:35:56 +1000 Subject: [PATCH 01/55] feat(devshell): add language tools conditionally based on cats - Extract mkWrapperConfig to share cats/settings between package and devshell - Add python, R, julia, and markdown packages to devShells when their cats are enabled - Reuse same package construction logic as specs/deps.nix for consistency - Keep lang_packages overridable via lib.mkDefault for downstream flakes --- flake.nix | 120 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/flake.nix b/flake.nix index cce0107..6dc8545 100644 --- a/flake.nix +++ b/flake.nix @@ -36,45 +36,58 @@ wrappers, ... } @ inputs: let + mkWrapperConfig = pkgs: { + cats = { + clickhouse = false; + gitPlugins = true; + julia = false; + lua = true; + markdown = false; + nix = true; + optional = false; + python = false; + r = false; + }; + settings = { + lang_packages = { + python = with pkgs.python3Packages; [ + duckdb + polars + ]; + r = with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + ]; + julia = ["DataFramesMeta" "QuackIO"]; + }; + colorscheme = "cyberdream"; + background = "dark"; + wrapRc = true; + }; + binName = "vv"; + }; + wrapperSettings = pkgs: let + cfg = mkWrapperConfig pkgs; 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; - }; - + cats = pkgs.lib.mapAttrs (_: v: def v) cfg.cats; settings = { lang_packages = { - python = with pkgs.python3Packages; [ - duckdb - polars - ]; - - r = with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - styler - ]; - - julia = ["DataFramesMeta" "QuackIO"]; + python = def cfg.settings.lang_packages.python; + r = def cfg.settings.lang_packages.r; + julia = def cfg.settings.lang_packages.julia; }; - colorscheme = def "cyberdream"; - background = def "dark"; - wrapRc = def true; + colorscheme = def cfg.settings.colorscheme; + background = def cfg.settings.background; + wrapRc = def cfg.settings.wrapRc; }; - binName = def "vv"; + binName = def cfg.binName; }; systems = [ @@ -146,12 +159,55 @@ devShells = forAllSystems ( system: let pkgs = mkPkgs system; + cfg = mkWrapperConfig pkgs; nvimPkg = wrapperSettings pkgs; + + pythonPackages = let + python_packages_fn = + if pkgs ? basePythonPackages + then ps: pkgs.basePythonPackages ps ++ cfg.settings.lang_packages.python + else _: cfg.settings.lang_packages.python; + in + with pkgs; [ + (python3.withPackages python_packages_fn) + nodejs + ruff + basedpyright + uv + ]; + + rPackages = let + r_packages = (pkgs.baseRPackages or []) ++ cfg.settings.lang_packages.r; + in + with pkgs; [ + (rWrapper.override {packages = r_packages;}) + radianWrapper + (quarto.override {extraRPackages = r_packages;}) + air-formatter + yaml-language-server + updateR + ]; + + juliaPackages = let + julia_with_packages = pkgs.julia-bin.withPackages cfg.settings.lang_packages.julia; + in [julia_with_packages]; + + markdownPackages = with pkgs; [ + python313Packages.pylatexenc + quarto + zk + ]; + + shellPackages = [nvimPkg] + ++ pkgs.lib.optionals cfg.cats.python pythonPackages + ++ pkgs.lib.optionals cfg.cats.r rPackages + ++ pkgs.lib.optionals cfg.cats.julia juliaPackages + ++ pkgs.lib.optionals cfg.cats.markdown markdownPackages; in { default = pkgs.mkShell { name = "vShell"; - packages = [nvimPkg]; - nativeBuildInputs = with pkgs; [] ++ (pkgs.lib.optionals self.wrappers.default.cats.optional [devenv]); + packages = shellPackages; + nativeBuildInputs = with pkgs; [] ++ (pkgs.lib.optionals cfg.cats.optional [devenv]); inputsFrom = []; shellHook = ""; }; From ed24f176bc7bdb3a042d8fbca9a5e9a1fa165209 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sun, 3 May 2026 16:58:11 +1000 Subject: [PATCH 02/55] feat(r): add R languageserver for Neovim LSP - Add languageserver to settings.lang_packages.r - Enable r_language_server in LSP config (plugin/25_lsp.lua) --- flake.nix | 1 + plugin/25_lsp.lua | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/flake.nix b/flake.nix index 6dc8545..f7db07c 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,7 @@ broom data_table janitor + languageserver styler ]; julia = ["DataFramesMeta" "QuackIO"]; diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index 8c67295..9591da3 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -15,17 +15,17 @@ now_if_args(function() marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, - -- r_language_server = { - -- filetypes = { 'r', 'rmd', 'rmarkdown' }, - -- settings = { - -- ['r_language_server'] = { - -- lsp = { - -- rich_documentation = true, - -- enable = true, - -- }, - -- }, - -- } - -- }, + r_language_server = { + filetypes = { 'r', 'rmd', 'rmarkdown' }, + settings = { + ['r_language_server'] = { + lsp = { + rich_documentation = true, + enable = true, + }, + }, + } + }, julials = { settings = { julia = { From 4884bf685f157da4f4d60f8e40febeac0b9bf0e9 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 7 May 2026 10:48:37 +1000 Subject: [PATCH 03/55] up --- flake.lock | 19 ++++++++++--------- flake.nix | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index 78a5bd8..32a6765 100644 --- a/flake.lock +++ b/flake.lock @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776169885, - "narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=", + "lastModified": 1777954456, + "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", + "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", "type": "github" }, "original": { @@ -55,15 +55,16 @@ "plugins-r": { "flake": false, "locked": { - "lastModified": 1776340770, - "narHash": "sha256-o/8UZIc/Bq9dWTjA+MpSR5uMUpE7KHTErk+TwWID8Ww=", + "lastModified": 1776905071, + "narHash": "sha256-dXox6qEs1VDE7vPNDoN8bY4g06uj1IEs6uki72w8lpA=", "owner": "R-nvim", "repo": "R.nvim", - "rev": "b9cfffeb9b4e484aa9e13f01c0eb80230aada455", + "rev": "582f2af11290ac067e49018db38e12a511325556", "type": "github" }, "original": { "owner": "R-nvim", + "ref": "v0.99.4", "repo": "R.nvim", "type": "github" } @@ -101,11 +102,11 @@ ] }, "locked": { - "lastModified": 1776375800, - "narHash": "sha256-/SSAR77Brr9fbapsh1cb2K47JXCbvwS1GjM4yyDxle8=", + "lastModified": 1777991014, + "narHash": "sha256-0DS24OW9d9iz+w0LCz6KpS2IpE2z2gHxeBdMZg9xpDY=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "f11469ca69068bac13d9e163b2bd268cc06dff57", + "rev": "dc5184095ad488e937ec308b52c9c0b218959d8b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f7db07c..8482605 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,7 @@ }; "plugins-r" = { - url = "github:R-nvim/R.nvim"; + url = "github:R-nvim/R.nvim/v0.99.4"; flake = false; }; From 07d2156dfb4620e8e18b4d1244a17b7fde4f91da Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Tue, 12 May 2026 15:58:31 +1000 Subject: [PATCH 04/55] fixed R.nvim --- .commandcode/taste/taste.md | 8 ++++++++ flake.nix | 13 ++++++++++--- modules/module/settings/cats.nix | 2 +- modules/module/settings/env.nix | 7 ++++++- modules/module/specs/plugins.nix | 8 +++++++- overlays/r.nix | 28 +++++++++++++++++++++++++++- plugin/21_datascience.lua | 6 ++++++ 7 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 .commandcode/taste/taste.md diff --git a/.commandcode/taste/taste.md b/.commandcode/taste/taste.md new file mode 100644 index 0000000..4b588b2 --- /dev/null +++ b/.commandcode/taste/taste.md @@ -0,0 +1,8 @@ +# nix +- For R.nvim in the Nix wrapper, both RNVIM_COMPLDIR (C server compilation) and a writable R_LIBS_USER directory (nvimcom R package installation) must be configured — fixing only one leaves permission errors in the other. Confidence: 0.65 +- For R.nvim writable directories (RNVIM_COMPLDIR, R_LIBS_USER, TMPDIR), prefer project-local paths (e.g., $PWD/.Rlibs) over global cache paths — the cache approach may let the build succeed but still fail at runtime. Confidence: 0.70 + +# Taste (Continuously Learned by [CommandCode][cmd]) + +[cmd]: https://commandcode.ai/ + diff --git a/flake.nix b/flake.nix index 8482605..be28255 100644 --- a/flake.nix +++ b/flake.nix @@ -46,7 +46,7 @@ nix = true; optional = false; python = false; - r = false; + r = true; }; settings = { lang_packages = { @@ -199,7 +199,8 @@ zk ]; - shellPackages = [nvimPkg] + shellPackages = + [nvimPkg] ++ pkgs.lib.optionals cfg.cats.python pythonPackages ++ pkgs.lib.optionals cfg.cats.r rPackages ++ pkgs.lib.optionals cfg.cats.julia juliaPackages @@ -210,7 +211,13 @@ packages = shellPackages; nativeBuildInputs = with pkgs; [] ++ (pkgs.lib.optionals cfg.cats.optional [devenv]); inputsFrom = []; - shellHook = ""; + shellHook = '' + echo 'I am a NixShell' + export R_HOME=$(R RHOME) + export R_LIBS_SITE=$(strings "$(command -v R)" | grep -oP '/nix/store/[^:]+/library' | sort -u | paste -sd: -) + export R_LIBS_USER="$PWD/.r-libs" + mkdir -p "$R_LIBS_USER" + ''; }; } ); diff --git a/modules/module/settings/cats.nix b/modules/module/settings/cats.nix index 22b3638..3da043e 100644 --- a/modules/module/settings/cats.nix +++ b/modules/module/settings/cats.nix @@ -43,7 +43,7 @@ nix = lib.mkDefault true; optional = lib.mkDefault false; python = lib.mkDefault false; - r = lib.mkDefault false; + r = lib.mkDefault true; test = lib.mkDefault false; treesitterParsers = lib.mkDefault true; utils = lib.mkDefault true; diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index 2ae4b9c..0749ede 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -11,12 +11,17 @@ UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON = pkgs.python.interpreter; }) + (lib.mkIf (config.cats.r or false) { + RNVIM_COMPLDIR = "$PWD/.r-compl"; + R_LIBS_USER = "${pkgs.nvimcom}:$PWD/.Rlibs"; + TMPDIR = "$PWD/.r-tmp"; + }) ]; # Environment variables with defaults (can be overridden by user) config.envDefault = lib.mkMerge [ (lib.mkIf (config.cats.r or false) { - R_LIBS_USER = "./.Rlibs"; + R_LIBS_USER = "${pkgs.nvimcom}:$PWD/.Rlibs"; }) ]; } diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ded8195..eb9d499 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -11,7 +11,13 @@ config.specs.r = { data = with pkgs.vimPlugins; [ - config.nvim-lib.neovimPlugins.r + (config.nvim-lib.neovimPlugins.r.overrideAttrs (old: { + postInstall = (old.postInstall or "") + '' + mkdir -p $out/rnvimserver + cp ${pkgs.nvimcom}/bin/rnvimserver $out/rnvimserver/rnvimserver + chmod +x $out/rnvimserver/rnvimserver + ''; + })) quarto-nvim { data = otter-nvim; diff --git a/overlays/r.nix b/overlays/r.nix index 2fb71c8..22c42fa 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -33,12 +33,38 @@ overlays = [inputs.fran.overlays.default]; }; # rixpkgs.legacyPackages.${prev.stdenv.hostPlatform.system}; + # Pre-build nvimcom from R.nvim plugin source so R.nvim never tries to + # compile it at runtime into the read-only nix store. + nvimcom = final.stdenv.mkDerivation { + pname = "nvimcom"; + version = "0.9.92"; + src = inputs.plugins-r; + nativeBuildInputs = [ + (rpkgs.rWrapper.override { packages = []; }) + ]; + buildPhase = '' + mkdir -p $out/bin + R CMD INSTALL -l $out nvimcom + cd rnvimserver + $CC -pthread -O2 -Wall \ + complete.c resolve.c hover.c definition.c signature.c \ + rhelp.c chunk.c roxygen.c data_structures.c logging.c \ + rnvimserver.c obbr.c tcp.c utilities.c ../nvimcom/src/common.c \ + -o $out/bin/rnvimserver + cd .. + mkdir -p $out/nvimcom/bin + cp $out/bin/rnvimserver $out/nvimcom/bin/rnvimserver + chmod +x $out/bin/rnvimserver $out/nvimcom/bin/rnvimserver + ''; + installPhase = "true"; + }; + # Standard R packages used by default in rWrapper and quarto reqPkgs = with rpkgs.rPackages; [ # languageserver ]; in { - inherit rpkgs; + inherit rpkgs nvimcom; baseRPackages = reqPkgs; # R wrapper with standard packages diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index 118ee20..62d8f49 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -44,6 +44,12 @@ end) -- r now(function() if nix.get_cat("r", false) then + local cwd = vim.fn.getcwd(-1) + vim.env.RNVIM_COMPLDIR = cwd .. "/.r-compl" + vim.env.R_LIBS_USER = (vim.env.R_LIBS_USER or ""):gsub("%$PWD", cwd) + vim.env.TMPDIR = cwd .. "/.r-tmp" + vim.fn.mkdir(vim.env.RNVIM_COMPLDIR, "p") + vim.fn.mkdir(vim.env.TMPDIR, "p") vim.g.rout_follow_colorscheme = true require("r").setup({ -- Create a table with the options to be passed to setup() From 1055522af932f5c781ff475cb390143534d49141 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Wed, 13 May 2026 12:30:17 +1000 Subject: [PATCH 05/55] moved r-nvim to flake build --- .Rprofile | 5 ++++ .r-tmp/R.nvim-daniel/globenv_1202824815 | 0 .r-tmp/R.nvim-daniel/liblist_1202824815 | 0 flake.lock | 38 ++++++++++++++++++++----- flake.nix | 9 +++++- modules/module/settings/env.nix | 4 +-- modules/module/specs/plugins.nix | 2 +- overlays/default.nix | 4 +++ overlays/r.nix | 29 ++----------------- 9 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 .Rprofile create mode 100644 .r-tmp/R.nvim-daniel/globenv_1202824815 create mode 100644 .r-tmp/R.nvim-daniel/liblist_1202824815 diff --git a/.Rprofile b/.Rprofile new file mode 100644 index 0000000..7617f61 --- /dev/null +++ b/.Rprofile @@ -0,0 +1,5 @@ +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/.r-tmp/R.nvim-daniel/globenv_1202824815 b/.r-tmp/R.nvim-daniel/globenv_1202824815 new file mode 100644 index 0000000..e69de29 diff --git a/.r-tmp/R.nvim-daniel/liblist_1202824815 b/.r-tmp/R.nvim-daniel/liblist_1202824815 new file mode 100644 index 0000000..e69de29 diff --git a/flake.lock b/flake.lock index 32a6765..bf489c1 100644 --- a/flake.lock +++ b/flake.lock @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1777954456, - "narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=", + "lastModified": 1778443072, + "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", "type": "github" }, "original": { @@ -69,6 +69,29 @@ "type": "github" } }, + "r-nvim-nix": { + "inputs": { + "nixpkgs": [ + "rixpkgs" + ], + "rnvimsrc": [ + "plugins-r" + ] + }, + "locked": { + "lastModified": 1778638679, + "narHash": "sha256-+mdjgYfyNtI/5E6X8uUx3u9qpGC9POS4LagOHSM3Iy0=", + "owner": "dwinkler1", + "repo": "r_nvim_nix", + "rev": "eb784558e18b8f1dead5bd211b96fd287ac1bed7", + "type": "github" + }, + "original": { + "owner": "dwinkler1", + "repo": "r_nvim_nix", + "type": "github" + } + }, "rixpkgs": { "locked": { "lastModified": 1771303851, @@ -80,8 +103,8 @@ }, "original": { "owner": "dwinkler1", - "ref": "nixpkgs", "repo": "rixpkgs", + "rev": "af2dd3f7b4b172077747c0869d4e30702fb71b0e", "type": "github" } }, @@ -91,6 +114,7 @@ "nixpkgs": "nixpkgs", "plugins-cmp-pandoc-references": "plugins-cmp-pandoc-references", "plugins-r": "plugins-r", + "r-nvim-nix": "r-nvim-nix", "rixpkgs": "rixpkgs", "wrappers": "wrappers" } @@ -102,11 +126,11 @@ ] }, "locked": { - "lastModified": 1777991014, - "narHash": "sha256-0DS24OW9d9iz+w0LCz6KpS2IpE2z2gHxeBdMZg9xpDY=", + "lastModified": 1778560014, + "narHash": "sha256-Hu9RMo7vJt/4dx/vAvyG+cE9RBwpaH1ouyunvruYaDI=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "dc5184095ad488e937ec308b52c9c0b218959d8b", + "rev": "e30aa99c9c7038e16efae3cad7916a47307a9e36", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index be28255..1ffa69a 100644 --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,11 @@ url = "github:BirdeeHub/nix-wrapper-modules"; inputs.nixpkgs.follows = "nixpkgs"; }; - rixpkgs.url = "github:dwinkler1/rixpkgs/nixpkgs"; + rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; + + r-nvim-nix.url = "github:dwinkler1/r_nvim_nix"; + r-nvim-nix.inputs.rnvimsrc.follows = "plugins-r"; + r-nvim-nix.inputs.nixpkgs.follows = "rixpkgs"; fran = { url = "github:dwinkler1/fran"; @@ -61,6 +65,7 @@ janitor languageserver styler + pkgs.nvimcom ]; julia = ["DataFramesMeta" "QuackIO"]; }; @@ -187,6 +192,8 @@ air-formatter yaml-language-server updateR + nvimcom + rnvimserver ]; juliaPackages = let diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index 0749ede..14e0a32 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -13,7 +13,7 @@ }) (lib.mkIf (config.cats.r or false) { RNVIM_COMPLDIR = "$PWD/.r-compl"; - R_LIBS_USER = "${pkgs.nvimcom}:$PWD/.Rlibs"; + R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.Rlibs"; TMPDIR = "$PWD/.r-tmp"; }) ]; @@ -21,7 +21,7 @@ # Environment variables with defaults (can be overridden by user) config.envDefault = lib.mkMerge [ (lib.mkIf (config.cats.r or false) { - R_LIBS_USER = "${pkgs.nvimcom}:$PWD/.Rlibs"; + R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.Rlibs"; }) ]; } diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index eb9d499..a47ba65 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -14,7 +14,7 @@ (config.nvim-lib.neovimPlugins.r.overrideAttrs (old: { postInstall = (old.postInstall or "") + '' mkdir -p $out/rnvimserver - cp ${pkgs.nvimcom}/bin/rnvimserver $out/rnvimserver/rnvimserver + cp ${pkgs.rnvimserver}/bin/rnvimserver $out/rnvimserver/rnvimserver chmod +x $out/rnvimserver/rnvimserver ''; })) diff --git a/overlays/default.nix b/overlays/default.nix index 821ff88..ebb6518 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -3,12 +3,14 @@ let lib = nixpkgs.lib; rOverlay = import ./r.nix {inherit inputs;}; + rNvimNixOverlay = inputs.r-nvim-nix.overlays.default; franOverlay = inputs.fran.overlays.default; pythonOverlay = import ./python.nix inputs; pluginsOverlay = import ./plugins.nix inputs; dependencyOverlays = [ rOverlay + rNvimNixOverlay pythonOverlay pluginsOverlay ]; @@ -17,6 +19,7 @@ in { inherit rOverlay + rNvimNixOverlay franOverlay pythonOverlay pluginsOverlay @@ -30,6 +33,7 @@ in overlays = { inherit rOverlay + rNvimNixOverlay franOverlay pythonOverlay pluginsOverlay diff --git a/overlays/r.nix b/overlays/r.nix index 22c42fa..a9961fb 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -33,38 +33,15 @@ overlays = [inputs.fran.overlays.default]; }; # rixpkgs.legacyPackages.${prev.stdenv.hostPlatform.system}; - # Pre-build nvimcom from R.nvim plugin source so R.nvim never tries to - # compile it at runtime into the read-only nix store. - nvimcom = final.stdenv.mkDerivation { - pname = "nvimcom"; - version = "0.9.92"; - src = inputs.plugins-r; - nativeBuildInputs = [ - (rpkgs.rWrapper.override { packages = []; }) - ]; - buildPhase = '' - mkdir -p $out/bin - R CMD INSTALL -l $out nvimcom - cd rnvimserver - $CC -pthread -O2 -Wall \ - complete.c resolve.c hover.c definition.c signature.c \ - rhelp.c chunk.c roxygen.c data_structures.c logging.c \ - rnvimserver.c obbr.c tcp.c utilities.c ../nvimcom/src/common.c \ - -o $out/bin/rnvimserver - cd .. - mkdir -p $out/nvimcom/bin - cp $out/bin/rnvimserver $out/nvimcom/bin/rnvimserver - chmod +x $out/bin/rnvimserver $out/nvimcom/bin/rnvimserver - ''; - installPhase = "true"; - }; + # nvimcom and rnvimserver are provided by the r-nvim-nix flake overlay + # (inputs.r-nvim-nix.overlays.default) # Standard R packages used by default in rWrapper and quarto reqPkgs = with rpkgs.rPackages; [ # languageserver ]; in { - inherit rpkgs nvimcom; + inherit rpkgs; baseRPackages = reqPkgs; # R wrapper with standard packages From 55866137ba5379c266defbb50f3fd9b40fe1a8b2 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Wed, 13 May 2026 12:30:36 +1000 Subject: [PATCH 06/55] cleanup --- .r-tmp/R.nvim-daniel/globenv_1202824815 | 0 .r-tmp/R.nvim-daniel/liblist_1202824815 | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .r-tmp/R.nvim-daniel/globenv_1202824815 delete mode 100644 .r-tmp/R.nvim-daniel/liblist_1202824815 diff --git a/.r-tmp/R.nvim-daniel/globenv_1202824815 b/.r-tmp/R.nvim-daniel/globenv_1202824815 deleted file mode 100644 index e69de29..0000000 diff --git a/.r-tmp/R.nvim-daniel/liblist_1202824815 b/.r-tmp/R.nvim-daniel/liblist_1202824815 deleted file mode 100644 index e69de29..0000000 From 22809d94cd5f03cde3150d93b6e3b385cca738f6 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Wed, 13 May 2026 13:28:55 +1000 Subject: [PATCH 07/55] update --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index bf489c1..7f552ab 100644 --- a/flake.lock +++ b/flake.lock @@ -79,11 +79,11 @@ ] }, "locked": { - "lastModified": 1778638679, - "narHash": "sha256-+mdjgYfyNtI/5E6X8uUx3u9qpGC9POS4LagOHSM3Iy0=", + "lastModified": 1778641093, + "narHash": "sha256-Cq0spPQCYJkyHFTBTXqjmbq663kIVZA63/TQkTzE4ps=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "eb784558e18b8f1dead5bd211b96fd287ac1bed7", + "rev": "435103d25d56dbe43197f7969cf535fda17ff597", "type": "github" }, "original": { From 8811591fe0eabed342283e897bc25536a67fdca4 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 14 May 2026 00:52:03 +1000 Subject: [PATCH 08/55] Move to bundled R.nvim + rnvimserver without override --- flake.lock | 12 ++++++------ modules/module/specs/plugins.nix | 8 +------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/flake.lock b/flake.lock index 7f552ab..73686b6 100644 --- a/flake.lock +++ b/flake.lock @@ -79,11 +79,11 @@ ] }, "locked": { - "lastModified": 1778641093, - "narHash": "sha256-Cq0spPQCYJkyHFTBTXqjmbq663kIVZA63/TQkTzE4ps=", + "lastModified": 1778683425, + "narHash": "sha256-bY44RR+7q+8R8PDNWbavFEcpSKiBvWyoWHI7iSLvsQc=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "435103d25d56dbe43197f7969cf535fda17ff597", + "rev": "e54fb57802fa9eca81024762f397ec7caa446842", "type": "github" }, "original": { @@ -126,11 +126,11 @@ ] }, "locked": { - "lastModified": 1778560014, - "narHash": "sha256-Hu9RMo7vJt/4dx/vAvyG+cE9RBwpaH1ouyunvruYaDI=", + "lastModified": 1778662548, + "narHash": "sha256-e6XKnrzKr48r1UdCr+5bekibSqe2L1/Sgi46IODHtGQ=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "e30aa99c9c7038e16efae3cad7916a47307a9e36", + "rev": "46e7c1e3f0e149b28f539eb8601152db8a67ad0c", "type": "github" }, "original": { diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index a47ba65..ca8d970 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -11,13 +11,7 @@ config.specs.r = { data = with pkgs.vimPlugins; [ - (config.nvim-lib.neovimPlugins.r.overrideAttrs (old: { - postInstall = (old.postInstall or "") + '' - mkdir -p $out/rnvimserver - cp ${pkgs.rnvimserver}/bin/rnvimserver $out/rnvimserver/rnvimserver - chmod +x $out/rnvimserver/rnvimserver - ''; - })) + pkgs.r-nvim quarto-nvim { data = otter-nvim; From 4d28d0d4907c2d3d20bdf0c5b2482ccc860114dc Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Tue, 19 May 2026 13:15:29 +1000 Subject: [PATCH 09/55] fix for codecompanion --- flake.lock | 18 +++++++++--------- flake.nix | 2 +- modules/module/specs/plugins.nix | 11 ++++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/flake.lock b/flake.lock index 73686b6..9fbb80f 100644 --- a/flake.lock +++ b/flake.lock @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778443072, - "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { @@ -79,11 +79,11 @@ ] }, "locked": { - "lastModified": 1778683425, - "narHash": "sha256-bY44RR+7q+8R8PDNWbavFEcpSKiBvWyoWHI7iSLvsQc=", + "lastModified": 1778684156, + "narHash": "sha256-Z4y1tQfkIsPK4NRxGn668HMDfWxnxNxSJ0CAOOXiIfY=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "e54fb57802fa9eca81024762f397ec7caa446842", + "rev": "2f49dfee27886068e2f49cbd54558ce4cc424c82", "type": "github" }, "original": { @@ -126,11 +126,11 @@ ] }, "locked": { - "lastModified": 1778662548, - "narHash": "sha256-e6XKnrzKr48r1UdCr+5bekibSqe2L1/Sgi46IODHtGQ=", + "lastModified": 1779145538, + "narHash": "sha256-j2RQqBLYhPuddU6C8n5hGKboXq1tDLCZ7bWe5/LgTHM=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "46e7c1e3f0e149b28f539eb8601152db8a67ad0c", + "rev": "597b35c93dd0ab0ae38758e3193582b2fd259aa1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1ffa69a..fa1140a 100644 --- a/flake.nix +++ b/flake.nix @@ -105,7 +105,7 @@ forAllSystems = nixpkgs.lib.genAttrs systems; extra_pkg_config = { - # allowUnfree = true; + allowUnfree = true; }; overlayDefs = import ./overlays inputs; diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ca8d970..431005d 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -3,10 +3,9 @@ pkgs, lib, ... -}: -{ +}: { config.specs.gitPlugins = { - data = [ ]; + data = []; }; config.specs.r = { @@ -112,7 +111,9 @@ nvim-treesitter-context nvim-treesitter-textobjects { - data = pkgs.codecompanion-nvim; + data = pkgs.codecompanion-nvim.overrideAttrs (old: { + doCheck = false; + }); pname = "codecompanion"; } ]; @@ -176,6 +177,6 @@ config.specs.gitPlugins-lazy = { lazy = true; - data = [ ]; + data = []; }; } From bfd4ba417d3f385436974b5388981994116a13d0 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Tue, 19 May 2026 13:18:29 +1000 Subject: [PATCH 10/55] remove languageserver package from config --- plugin/25_lsp.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index 9591da3..8c67295 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -15,17 +15,17 @@ now_if_args(function() marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, - r_language_server = { - filetypes = { 'r', 'rmd', 'rmarkdown' }, - settings = { - ['r_language_server'] = { - lsp = { - rich_documentation = true, - enable = true, - }, - }, - } - }, + -- r_language_server = { + -- filetypes = { 'r', 'rmd', 'rmarkdown' }, + -- settings = { + -- ['r_language_server'] = { + -- lsp = { + -- rich_documentation = true, + -- enable = true, + -- }, + -- }, + -- } + -- }, julials = { settings = { julia = { From ad9473a9166fc7e220449c9fd7625f8c52e05423 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Tue, 19 May 2026 13:28:40 +1000 Subject: [PATCH 11/55] different ls --- plugin/25_lsp.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index 8c67295..f3c4c50 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -15,17 +15,17 @@ now_if_args(function() marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, - -- r_language_server = { - -- filetypes = { 'r', 'rmd', 'rmarkdown' }, - -- settings = { - -- ['r_language_server'] = { - -- lsp = { - -- rich_documentation = true, - -- enable = true, - -- }, - -- }, - -- } - -- }, + r_ls = { + filetypes = { 'r', 'rmd', 'rmarkdown' }, + settings = { + ['r_ls'] = { + lsp = { + rich_documentation = true, + enable = true, + }, + }, + } + }, julials = { settings = { julia = { From 74600519a5f4a73a96b44156b2f4c5b273d2268b Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 21 May 2026 18:45:36 +1000 Subject: [PATCH 12/55] Simplified codebase --- .commandcode/taste/taste.md | 1 + flake.lock | 6 +- flake.nix | 105 ++++++++-------------- modules/module/settings/cats.nix | 6 +- modules/module/settings/core.nix | 6 +- modules/module/settings/env.nix | 6 +- modules/module/settings/hosts.nix | 2 +- modules/module/settings/lang-packages.nix | 2 +- modules/module/settings/runtime-path.nix | 4 +- modules/module/specs/cats-enable.nix | 51 ----------- modules/module/specs/deps.nix | 66 +++----------- modules/module/specs/plugins.nix | 20 ++--- modules/neovim.nix | 16 ---- overlays/default.nix | 24 ++--- overlays/r.nix | 48 +--------- 15 files changed, 83 insertions(+), 280 deletions(-) delete mode 100644 modules/module/specs/cats-enable.nix diff --git a/.commandcode/taste/taste.md b/.commandcode/taste/taste.md index 4b588b2..54e4488 100644 --- a/.commandcode/taste/taste.md +++ b/.commandcode/taste/taste.md @@ -1,6 +1,7 @@ # nix - For R.nvim in the Nix wrapper, both RNVIM_COMPLDIR (C server compilation) and a writable R_LIBS_USER directory (nvimcom R package installation) must be configured — fixing only one leaves permission errors in the other. Confidence: 0.65 - For R.nvim writable directories (RNVIM_COMPLDIR, R_LIBS_USER, TMPDIR), prefer project-local paths (e.g., $PWD/.Rlibs) over global cache paths — the cache approach may let the build succeed but still fail at runtime. Confidence: 0.70 +- Do not use lib.mkDefault on values consumed by lib.optionals or other boolean-checking functions — mkDefault wraps values in a priority set that fails "expected a Boolean" at evaluation time. Use plain booleans for inline conditionals, reserving mkDefault for module options resolved by the merge system. Confidence: 0.70 # Taste (Continuously Learned by [CommandCode][cmd]) diff --git a/flake.lock b/flake.lock index 9fbb80f..698ccf9 100644 --- a/flake.lock +++ b/flake.lock @@ -126,11 +126,11 @@ ] }, "locked": { - "lastModified": 1779145538, - "narHash": "sha256-j2RQqBLYhPuddU6C8n5hGKboXq1tDLCZ7bWe5/LgTHM=", + "lastModified": 1779297405, + "narHash": "sha256-VFoBwH7ZjVxCnvZTb5ODRXt70sLtWMxstive0N+RS50=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "597b35c93dd0ab0ae38758e3193582b2fd259aa1", + "rev": "e7ed7a1205945befdf2e0d73ba7df91d935e5af1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index fa1140a..f84715f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,10 @@ -# Copyright (c) 2026 BirdeeHub +# Copyright (c) 2026 Daniel # 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"; @@ -40,13 +39,15 @@ wrappers, ... } @ inputs: let - mkWrapperConfig = pkgs: { - cats = { + mkWrapperConfig = pkgs: let + def = pkgs.lib.mkDefault; + in { + cats = pkgs.lib.mapAttrs (_: v: def v) { clickhouse = false; gitPlugins = true; julia = false; lua = true; - markdown = false; + markdown = true; nix = true; optional = false; python = false; @@ -54,46 +55,30 @@ }; settings = { lang_packages = { - python = with pkgs.python3Packages; [ + python = def (with pkgs.python3Packages; [ duckdb polars - ]; - r = with pkgs.rpkgs.rPackages; [ + ]); + r = def ((with pkgs.rpkgs.rPackages; [ arrow broom data_table janitor languageserver styler - pkgs.nvimcom - ]; - julia = ["DataFramesMeta" "QuackIO"]; + ]) ++ [ pkgs.nvimcom ]); + julia = def ["DataFramesMeta" "QuackIO"]; }; - colorscheme = "cyberdream"; - background = "dark"; - wrapRc = true; }; - binName = "vv"; + binName = def "vv"; }; wrapperSettings = pkgs: let cfg = mkWrapperConfig pkgs; - def = pkgs.lib.mkDefault; in wrapper.config.wrap { inherit pkgs; - cats = pkgs.lib.mapAttrs (_: v: def v) cfg.cats; - settings = { - lang_packages = { - python = def cfg.settings.lang_packages.python; - r = def cfg.settings.lang_packages.r; - julia = def cfg.settings.lang_packages.julia; - }; - colorscheme = def cfg.settings.colorscheme; - background = def cfg.settings.background; - wrapRc = def cfg.settings.wrapRc; - }; - binName = def cfg.binName; + inherit (cfg) cats settings binName; }; systems = [ @@ -104,54 +89,39 @@ 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 = extra_pkg_config; - overlays = [dependencyOverlay]; + config = { allowUnfree = true; }; + overlays = [ overlayDefs.dependencyOverlay ]; }; - module = nixpkgs.lib.modules.importApply ./modules/neovim.nix inputs; + module = (import ./modules/neovim.nix) inputs; wrapper = wrappers.lib.evalModule module; in { overlays = { + # overlay `vv` wraps the module with default settings only. + # For the fully-configured binary (including mkWrapperConfig overrides), + # use `packages..default` instead. default = nixpkgs.lib.composeManyExtensions [ - dependencyOverlay + overlayDefs.dependencyOverlay (final: prev: { vv = wrapper.config.wrap {pkgs = final;}; }) ]; - dependencies = dependencyOverlay; - vv = self.overlays.default; + dependencies = overlayDefs.dependencyOverlay; }; - wrapperModules = { - default = module; - neovim = self.wrapperModules.default; - }; - - wrappers = { - default = wrapper.config; - neovim = self.wrappers.default; - }; + wrapperModules.default = module; + wrapperConfigs.default = wrapper.config; packages = forAllSystems ( system: let pkgs = mkPkgs system; - nvimPkg = wrapperSettings pkgs; in { - default = nvimPkg; - vv = nvimPkg; + default = wrapperSettings pkgs; } ); @@ -208,16 +178,15 @@ shellPackages = [nvimPkg] - ++ pkgs.lib.optionals cfg.cats.python pythonPackages - ++ pkgs.lib.optionals cfg.cats.r rPackages - ++ pkgs.lib.optionals cfg.cats.julia juliaPackages - ++ pkgs.lib.optionals cfg.cats.markdown markdownPackages; + ++ pkgs.lib.optionals wrapper.config.cats.python pythonPackages + ++ pkgs.lib.optionals wrapper.config.cats.r rPackages + ++ pkgs.lib.optionals wrapper.config.cats.julia juliaPackages + ++ pkgs.lib.optionals wrapper.config.cats.markdown markdownPackages; in { default = pkgs.mkShell { name = "vShell"; packages = shellPackages; - nativeBuildInputs = with pkgs; [] ++ (pkgs.lib.optionals cfg.cats.optional [devenv]); - inputsFrom = []; + nativeBuildInputs = pkgs.lib.optionals wrapper.config.cats.optional [ pkgs.devenv ]; shellHook = '' echo 'I am a NixShell' export R_HOME=$(R RHOME) @@ -234,18 +203,11 @@ pkgs = mkPkgs system; nvimPkg = wrapperSettings pkgs; in { - default = nvimPkg; - module-eval = let - _ = wrapper.config; - in - pkgs.runCommand "check-module-eval" {} '' - echo "Module evaluation successful" > $out - ''; - package-build = pkgs.runCommand "check-vv" {} '' + default = pkgs.runCommand "check-vv" {} '' BINARY_PATH="${nvimPkg}/bin/vv" if [ ! -x "$BINARY_PATH" ]; then - echo "Error: Binary n not found or not executable" + echo "Error: Binary not found or not executable" exit 1 fi @@ -258,6 +220,11 @@ cat version_output.txt >> $out fi ''; + module-eval = + let _ = wrapper.config; + in pkgs.runCommand "check-module-eval" {} '' + echo "Module evaluation successful" > $out + ''; } ); diff --git a/modules/module/settings/cats.nix b/modules/module/settings/cats.nix index 3da043e..13758d4 100644 --- a/modules/module/settings/cats.nix +++ b/modules/module/settings/cats.nix @@ -14,7 +14,6 @@ Available categories: - clickhouse: Clickhouse client and tools - - customPlugins: local plugin specs - external: external tools and integrations - general: core Neovim plugins/features - gitPlugins: git-related plugins @@ -25,7 +24,6 @@ - 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 ''; @@ -33,18 +31,16 @@ config.cats = { 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 false; + markdown = lib.mkDefault true; nix = lib.mkDefault true; optional = lib.mkDefault false; python = lib.mkDefault false; r = lib.mkDefault true; - 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 42e9e11..ec9bb5c 100644 --- a/modules/module/settings/core.nix +++ b/modules/module/settings/core.nix @@ -16,7 +16,7 @@ # Lua packages available to neovim (for :lua require()) config.settings.nvim_lua_env = lp: - lib.optionals (config.cats.general or false) [ lp.tiktoken_core ]; + lib.optionals (config.cats.general or true) [ lp.tiktoken_core ]; # Binary name for the wrapper config.binName = lib.mkDefault "vv"; @@ -25,10 +25,10 @@ config.settings.block_normal_config = true; # Don't symlink the config (we wrap it instead) - config.settings.dont_link = false; + config.settings.dont_link = lib.mkDefault false; # Create additional aliases for the binary - config.settings.aliases = [ "vvim" ]; + config.settings.aliases = lib.mkDefault [ "vvim" ]; # Enable wrapper handling of spec runtimeDeps (template pattern). config.settings.autowrapRuntimeDeps = true; diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index 14e0a32..2fcab8a 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -7,11 +7,11 @@ # Environment variables set for the wrapper. # These are available when running neovim. config.env = lib.mkMerge [ - (lib.mkIf (config.cats.python or false) { + (lib.mkIf (config.cats.python or true) { UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON = pkgs.python.interpreter; }) - (lib.mkIf (config.cats.r or false) { + (lib.mkIf (config.cats.r or true) { RNVIM_COMPLDIR = "$PWD/.r-compl"; R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.Rlibs"; TMPDIR = "$PWD/.r-tmp"; @@ -20,7 +20,7 @@ # Environment variables with defaults (can be overridden by user) config.envDefault = lib.mkMerge [ - (lib.mkIf (config.cats.r or false) { + (lib.mkIf (config.cats.r or true) { R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.Rlibs"; }) ]; diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index 0743979..f248a00 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -15,7 +15,7 @@ nvim-host.enable = true; nvim-host.package = "${pkgs.neovide}/bin/neovide"; nvim-host.argv0 = "neovide"; - nvim-host.flags."--neovim-bin" = "${placeholder "out"}/bin/${config.binName}"; + nvim-host.flags."--neovim-bin" = "${builtins.placeholder "out"}/bin/${config.binName}"; }; m = { diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index 3315363..531b811 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -26,7 +26,7 @@ }; default = { }; description = '' - Language-specific package overrides appended to each language spec's extraPackages. + Language-specific package overrides appended to each language spec's runtimePackages. Intended for flake.nix overrides via wrapper.config.wrap. ''; }; diff --git a/modules/module/settings/runtime-path.nix b/modules/module/settings/runtime-path.nix index 96c5d0f..b321019 100644 --- a/modules/module/settings/runtime-path.nix +++ b/modules/module/settings/runtime-path.nix @@ -10,14 +10,13 @@ let 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 [ ]; + packages = spec.runtimePackages 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: [ { @@ -31,5 +30,4 @@ let 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 deleted file mode 100644 index f5a9826..0000000 --- a/modules/module/specs/cats-enable.nix +++ /dev/null @@ -1,51 +0,0 @@ -{ 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 a8bfaf6..c3a40ea 100644 --- a/modules/module/specs/deps.nix +++ b/modules/module/specs/deps.nix @@ -5,24 +5,14 @@ wlib, ... }: { - # ============================================================================ - # SPEC MODULE DEFAULTS - # ============================================================================ - # Define default options available to all specs - config.specMods = {parentSpec ? null, ...}: { - options.extraPackages = lib.mkOption { + options.runtimePkgs = lib.mkOption { type = lib.types.listOf wlib.types.stringable; default = []; - description = "a extraPackages spec field to put packages to suffix to the PATH"; + description = "a runtimePkgs spec field to put packages to suffix to the PATH"; }; }; - # ============================================================================ - # EXTERNAL TOOLS SPEC - # ============================================================================ - # Core system tools and utilities - config.specs.external = { data = lib.mkDefault null; before = ["INIT_MAIN"]; @@ -30,7 +20,7 @@ vim.o.shell = "${pkgs.zsh}/bin/zsh" ''; runtimeDeps = "prefix"; - extraPackages = with pkgs; [ + runtimePkgs = with pkgs; [ perl ruby shfmt @@ -39,15 +29,11 @@ ]; }; - # ============================================================================ - # OPTIONAL TOOLS SPEC - # ============================================================================ - config.specs.optional = lib.mkIf (config.cats.optional or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; before = ["INIT_MAIN"]; - extraPackages = with pkgs; [ + runtimePkgs = with pkgs; [ bat broot devenv @@ -76,54 +62,38 @@ ]; }; - # ============================================================================ - # MARKDOWN SPEC - # ============================================================================ - config.specs.markdown = lib.mkIf (config.cats.markdown or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = with pkgs; [ + runtimePkgs = with pkgs; [ python313Packages.pylatexenc quarto zk ]; }; - # ============================================================================ - # NIX SPEC - # ============================================================================ - config.specs.nix = lib.mkIf (config.cats.nix or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = with pkgs; [ + runtimePkgs = with pkgs; [ alejandra nix-doc nixd ]; }; - # ============================================================================ - # LUA SPEC - # ============================================================================ - config.specs.lua = lib.mkIf (config.cats.lua or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = with pkgs; [ + runtimePkgs = with pkgs; [ lua-language-server ]; }; - # ============================================================================ - # PYTHON SPEC - # ============================================================================ - config.specs.python = lib.mkIf (config.cats.python or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = let + runtimePkgs = let python_packages_fn = if pkgs ? basePythonPackages then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python @@ -139,14 +109,10 @@ ]; }; - # ============================================================================ - # R SPEC - # ============================================================================ - config.specs.r = lib.mkIf (config.cats.r or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = let + runtimePkgs = let r_packages = (pkgs.baseRPackages or []) ++ config.settings.lang_packages.r; in with pkgs; [ @@ -159,30 +125,22 @@ ]; }; - # ============================================================================ - # JULIA SPEC - # ============================================================================ - config.specs.julia = lib.mkIf (config.cats.julia or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = let + runtimePkgs = let julia_with_packages = pkgs.julia-bin.withPackages config.settings.lang_packages.julia; in [julia_with_packages]; }; - # ============================================================================ - # CLICKHOUSE SPEC - # ============================================================================ - config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - extraPackages = with pkgs; [ + runtimePkgs = with pkgs; [ clickhouse-lts ]; }; - config.extraPackages = config.specCollect (acc: v: acc ++ (v.extraPackages or [])) []; + config.runtimePkgs = config.specCollect (acc: v: acc ++ (v.runtimePkgs or [])) []; } diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 431005d..ff00a72 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -4,11 +4,11 @@ lib, ... }: { - config.specs.gitPlugins = { + config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or true) { data = []; }; - config.specs.r = { + config.specs.r = lib.mkIf (config.cats.r or true) { data = with pkgs.vimPlugins; [ pkgs.r-nvim quarto-nvim @@ -19,14 +19,14 @@ ]; }; - config.specs.markdown-lazy = { + config.specs.markdown-lazy = lib.mkIf (config.cats.markdown or true) { lazy = true; data = [ config.nvim-lib.neovimPlugins.cmp-pandoc-references ]; }; - config.specs.general = { + config.specs.general = lib.mkIf (config.cats.general or true) { data = with pkgs.vimPlugins; [ lze lzextras @@ -79,7 +79,7 @@ ]; }; - config.specs.lua = { + config.specs.lua = lib.mkIf (config.cats.lua or true) { data = with pkgs.vimPlugins; [ luvit-meta { @@ -89,7 +89,7 @@ ]; }; - config.specs.markdown = { + config.specs.markdown = lib.mkIf (config.cats.markdown or true) { data = with pkgs.vimPlugins; [ quarto-nvim render-markdown-nvim @@ -104,7 +104,7 @@ ]; }; - config.specs.utils = { + config.specs.utils = lib.mkIf (config.cats.utils or true) { data = with pkgs.vimPlugins; [ blink-cmp nvim-lspconfig @@ -119,7 +119,7 @@ ]; }; - config.specs.treesitterParsers = { + config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or true) { data = with pkgs.vimPlugins.nvim-treesitter-parsers; [ bash c @@ -158,7 +158,7 @@ ]; }; - config.specs.utils-lazy = { + config.specs.utils-lazy = lib.mkIf (config.cats.utils or true) { lazy = true; data = with pkgs.vimPlugins; [ blink-compat @@ -175,7 +175,7 @@ ]; }; - config.specs.gitPlugins-lazy = { + config.specs.gitPlugins-lazy = lib.mkIf (config.cats.gitPlugins or true) { lazy = true; data = []; }; diff --git a/modules/neovim.nix b/modules/neovim.nix index ba0361c..64d2cda 100644 --- a/modules/neovim.nix +++ b/modules/neovim.nix @@ -7,16 +7,10 @@ inputs: ... }: { - # ============================================================================ - # IMPORTS - # ============================================================================ - # Import the base neovim wrapper module and all configuration modules - imports = [ wlib.wrapperModules.neovim ./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 @@ -25,11 +19,6 @@ inputs: ./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; @@ -58,11 +47,6 @@ 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/overlays/default.nix b/overlays/default.nix index ebb6518..5a4cc6c 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -4,9 +4,8 @@ let rOverlay = import ./r.nix {inherit inputs;}; rNvimNixOverlay = inputs.r-nvim-nix.overlays.default; - franOverlay = inputs.fran.overlays.default; - pythonOverlay = import ./python.nix inputs; - pluginsOverlay = import ./plugins.nix inputs; + pythonOverlay = import ./python.nix {inherit inputs;}; + pluginsOverlay = import ./plugins.nix {inherit inputs;}; dependencyOverlays = [ rOverlay @@ -15,6 +14,11 @@ let 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 @@ -26,20 +30,6 @@ in dependencyOverlays dependencyOverlay; - # Named exports for downstream composition. default = dependencyOverlay; dependencies = dependencyOverlays; - - overlays = { - inherit - rOverlay - rNvimNixOverlay - franOverlay - pythonOverlay - pluginsOverlay - dependencyOverlays - dependencyOverlay; - default = dependencyOverlay; - dependencies = dependencyOverlays; - }; } diff --git a/overlays/r.nix b/overlays/r.nix index a9961fb..14c02eb 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -1,55 +1,15 @@ -# 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]; - }; # rixpkgs.legacyPackages.${prev.stdenv.hostPlatform.system}; - - # nvimcom and rnvimserver are provided by the r-nvim-nix flake overlay - # (inputs.r-nvim-nix.overlays.default) - - # Standard R packages used by default in rWrapper and quarto - reqPkgs = with rpkgs.rPackages; [ - # languageserver - ]; + }; in { inherit rpkgs; - 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 + baseRPackages = [ ]; + rWrapper = rpkgs.rWrapper.override {packages = [ ];}; + quarto = rpkgs.quarto.override {extraRPackages = [ ];}; updateR = import ../scripts/updater.nix {pkgs = final;}; } From d23c7f6f51b5730f94628dab6ce43f76fb6048f2 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 21 May 2026 20:04:45 +1000 Subject: [PATCH 13/55] load packages only if installed --- flake.nix | 51 +++++++++++++++++++----------------- ftplugin/markdown.lua | 55 +++++++++++++++++++++------------------ plugin/20_startup.lua | 36 +++++++++++++------------ plugin/21_datascience.lua | 32 ++++++++++++----------- 4 files changed, 92 insertions(+), 82 deletions(-) diff --git a/flake.nix b/flake.nix index f84715f..2780328 100644 --- a/flake.nix +++ b/flake.nix @@ -39,10 +39,8 @@ wrappers, ... } @ inputs: let - mkWrapperConfig = pkgs: let - def = pkgs.lib.mkDefault; - in { - cats = pkgs.lib.mapAttrs (_: v: def v) { + mkWrapperConfig = pkgs: { + cats = { clickhouse = false; gitPlugins = true; julia = false; @@ -55,22 +53,12 @@ }; settings = { lang_packages = { - python = def (with pkgs.python3Packages; [ - duckdb - polars - ]); - r = def ((with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - languageserver - styler - ]) ++ [ pkgs.nvimcom ]); - julia = def ["DataFramesMeta" "QuackIO"]; + python = []; + r = []; + julia = []; }; }; - binName = def "vv"; + binName = "vv"; }; wrapperSettings = pkgs: let @@ -78,7 +66,7 @@ in wrapper.config.wrap { inherit pkgs; - inherit (cfg) cats settings binName; + inherit (cfg) settings binName; }; systems = [ @@ -135,14 +123,29 @@ devShells = forAllSystems ( system: let pkgs = mkPkgs system; - cfg = mkWrapperConfig pkgs; nvimPkg = wrapperSettings pkgs; + pythonPkgs = with pkgs.python3Packages; [ + duckdb + polars + ]; + + rPkgs = (with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + languageserver + styler + ]) ++ [ pkgs.nvimcom ]; + + juliaPkgs = ["DataFramesMeta" "QuackIO"]; + pythonPackages = let python_packages_fn = if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ cfg.settings.lang_packages.python - else _: cfg.settings.lang_packages.python; + then ps: pkgs.basePythonPackages ps ++ pythonPkgs + else _: pythonPkgs; in with pkgs; [ (python3.withPackages python_packages_fn) @@ -153,7 +156,7 @@ ]; rPackages = let - r_packages = (pkgs.baseRPackages or []) ++ cfg.settings.lang_packages.r; + r_packages = (pkgs.baseRPackages or []) ++ rPkgs; in with pkgs; [ (rWrapper.override {packages = r_packages;}) @@ -167,7 +170,7 @@ ]; juliaPackages = let - julia_with_packages = pkgs.julia-bin.withPackages cfg.settings.lang_packages.julia; + julia_with_packages = pkgs.julia-bin.withPackages juliaPkgs; in [julia_with_packages]; markdownPackages = with pkgs; [ diff --git a/ftplugin/markdown.lua b/ftplugin/markdown.lua index b673b60..db316fc 100644 --- a/ftplugin/markdown.lua +++ b/ftplugin/markdown.lua @@ -1,32 +1,35 @@ -- Add the key mappings only for Markdown files in a zk notebook. -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 }) +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 }) - -- 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/plugin/20_startup.lua b/plugin/20_startup.lua index ca18bd8..0404b2c 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -390,23 +390,25 @@ end) -- zk now_if_args(function() - 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()` - }, + 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" }, - }, + -- automatically attach buffers in a zk notebook that match the given filetypes + auto_attach = { + enabled = true, + filetypes = { "markdown" }, + }, - }, - }) + }, + }) + end end) diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index 62d8f49..70902ca 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -71,22 +71,24 @@ end) now(function() vim.treesitter.language.register("markdown", { "quarto", "rmd" }) - vim.api.nvim_create_autocmd("FileType", { - pattern = { "quarto" }, - callback = function() - require("otter").activate() - end, - }) + if nix.get_cat({"r", "markdown"}, false) then + 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, - }, - }) + require("otter").setup({ + lsp = { + diagnostic_update_events = { "BufWritePost", "InsertLeave" }, + }, + buffers = { + set_filetype = true, + write_to_disk = true, + }, + }) + end end) later(function() From 6226a1c9b1a9525aa0cf2ed0a4993a89d062911e Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 21 May 2026 21:10:30 +1000 Subject: [PATCH 14/55] make lua package loading conditional on cat --- plugin/22_languages.lua | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index 1983b25..ed4682a 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -1,6 +1,7 @@ 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 m_add = MiniDeps.add @@ -12,19 +13,21 @@ end -- lua later(function() - 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" } }, - }, - }) + 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) -- Markdown From af6d975129768cce36ac4c46198ab2b3ebaafad3 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 21 May 2026 21:15:50 +1000 Subject: [PATCH 15/55] make quarto load optional on r or markdown cat --- plugin/21_datascience.lua | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index 70902ca..2e9f86c 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -71,7 +71,7 @@ end) now(function() vim.treesitter.language.register("markdown", { "quarto", "rmd" }) - if nix.get_cat({"r", "markdown"}, false) then + if nix.get_cat({ "r", "markdown" }, false) then vim.api.nvim_create_autocmd("FileType", { pattern = { "quarto" }, callback = function() @@ -92,22 +92,24 @@ now(function() end) later(function() - require("quarto").setup({ - lspFeatures = { - enabled = true, - chunks = "curly", - languages = { "r", "python", "julia" }, - diagnostics = { + if nix.get_cat({ "r", "markdown" }, false) then + require("quarto").setup({ + lspFeatures = { enabled = true, - triggers = { "BufWritePost" }, + chunks = "curly", + languages = { "r", "python", "julia" }, + diagnostics = { + enabled = true, + triggers = { "BufWritePost" }, + }, + completion = { + enabled = true, + }, }, - completion = { + codeRunner = { enabled = true, + default_method = "slime", }, - }, - codeRunner = { - enabled = true, - default_method = "slime", - }, - }) + }) + end end) From 10254b11c727c139794d4d3b555c24562d9847ba Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 18:41:22 +1000 Subject: [PATCH 16/55] Corrected langpackage installation --- .commandcode/taste/taste.md | 9 ------- .gitignore | 2 ++ flake.nix | 52 ++++++++++++++++++------------------- plugin/10_keymap.lua | 4 +-- 4 files changed, 30 insertions(+), 37 deletions(-) delete mode 100644 .commandcode/taste/taste.md diff --git a/.commandcode/taste/taste.md b/.commandcode/taste/taste.md deleted file mode 100644 index 54e4488..0000000 --- a/.commandcode/taste/taste.md +++ /dev/null @@ -1,9 +0,0 @@ -# nix -- For R.nvim in the Nix wrapper, both RNVIM_COMPLDIR (C server compilation) and a writable R_LIBS_USER directory (nvimcom R package installation) must be configured — fixing only one leaves permission errors in the other. Confidence: 0.65 -- For R.nvim writable directories (RNVIM_COMPLDIR, R_LIBS_USER, TMPDIR), prefer project-local paths (e.g., $PWD/.Rlibs) over global cache paths — the cache approach may let the build succeed but still fail at runtime. Confidence: 0.70 -- Do not use lib.mkDefault on values consumed by lib.optionals or other boolean-checking functions — mkDefault wraps values in a priority set that fails "expected a Boolean" at evaluation time. Use plain booleans for inline conditionals, reserving mkDefault for module options resolved by the merge system. Confidence: 0.70 - -# Taste (Continuously Learned by [CommandCode][cmd]) - -[cmd]: https://commandcode.ai/ - diff --git a/.gitignore b/.gitignore index 8474af7..e402ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ *.R .Rlibs .nvimcom +.commandcode +.commandcode/ diff --git a/flake.nix b/flake.nix index 2780328..a19f7dc 100644 --- a/flake.nix +++ b/flake.nix @@ -1,4 +1,4 @@ -# Copyright (c) 2026 Daniel +# Copyright (c) 2026 BirdeeHub & Daniel # Licensed under the MIT license { description = "Daniel's NixCats"; @@ -39,6 +39,25 @@ wrappers, ... } @ inputs: let + langPackages = pkgs: { + python = with pkgs.python3Packages; [ + duckdb + polars + ]; + r = (with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + pkgs.nvimcom + ]) ++ [ pkgs.nvimcom ]; + julia = [ + "DataFramesMeta" + "QuackIO" + ]; + }; + mkWrapperConfig = pkgs: { cats = { clickhouse = false; @@ -52,11 +71,7 @@ r = true; }; settings = { - lang_packages = { - python = []; - r = []; - julia = []; - }; + lang_packages = langPackages pkgs; }; binName = "vv"; }; @@ -125,27 +140,13 @@ pkgs = mkPkgs system; nvimPkg = wrapperSettings pkgs; - pythonPkgs = with pkgs.python3Packages; [ - duckdb - polars - ]; - - rPkgs = (with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - languageserver - styler - ]) ++ [ pkgs.nvimcom ]; - - juliaPkgs = ["DataFramesMeta" "QuackIO"]; + langPkgs = langPackages pkgs; pythonPackages = let python_packages_fn = if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ pythonPkgs - else _: pythonPkgs; + then ps: pkgs.basePythonPackages ps ++ langPkgs.python + else _: langPkgs.python; in with pkgs; [ (python3.withPackages python_packages_fn) @@ -156,7 +157,7 @@ ]; rPackages = let - r_packages = (pkgs.baseRPackages or []) ++ rPkgs; + r_packages = (pkgs.baseRPackages or []) ++ langPkgs.r; in with pkgs; [ (rWrapper.override {packages = r_packages;}) @@ -164,13 +165,12 @@ (quarto.override {extraRPackages = r_packages;}) air-formatter yaml-language-server - updateR nvimcom rnvimserver ]; juliaPackages = let - julia_with_packages = pkgs.julia-bin.withPackages juliaPkgs; + julia_with_packages = pkgs.julia-bin.withPackages langPkgs.julia; in [julia_with_packages]; markdownPackages = with pkgs; [ diff --git a/plugin/10_keymap.lua b/plugin/10_keymap.lua index ebbebf0..5682b93 100644 --- a/plugin/10_keymap.lua +++ b/plugin/10_keymap.lua @@ -37,7 +37,7 @@ _G.Config.leader_group_clues = { { 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 = 'n', keys = 'zr', desc = '+Reviews' }, { mode = 'x', keys = 'a', desc = '+AI' }, } @@ -144,7 +144,7 @@ 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("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') From a0ba90c4d2ff20c4deb961b839b8c8e7518f4268 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 20:15:23 +1000 Subject: [PATCH 17/55] refactor package installation --- README.md | 247 +++++++++++++++++++++++ flake.lock | 28 ++- flake.nix | 109 ++++------ modules/module/settings/cat-packages.nix | 109 ++++++++++ modules/module/settings/cats.nix | 2 + modules/module/settings/runtime-path.nix | 33 --- modules/module/specs/deps.nix | 96 ++------- modules/neovim.nix | 2 +- 8 files changed, 433 insertions(+), 193 deletions(-) create mode 100644 README.md create mode 100644 modules/module/settings/cat-packages.nix delete mode 100644 modules/module/settings/runtime-path.nix diff --git a/README.md b/README.md new file mode 100644 index 0000000..c50d03e --- /dev/null +++ b/README.md @@ -0,0 +1,247 @@ +# 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.inputs.wrappers.lib.evalModules { + modules = [ + nvimConfig.wrapperModules.default + 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.inputs.wrappers.lib.evalModules { + modules = [ + nvimConfig.wrapperModules.default + projectSettings + ]; + }; + nv = evalResult.config.wrap { inherit pkgs; }; + in { + default = pkgs.mkShell { + packages = + [ nv ] + ++ (evalResult.config.catPkgs.markdown or []) + ++ (evalResult.config.catPkgs.nix or []); + }; + }); + }; +} +``` + +## 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 as the second module in `evalModules`. Every option in `nvimConfig.wrapperConfigs.default` can be overridden here. + +### 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 (overlay extension) + +Add Python/R/Julia libraries that get appended to the overlay base packages. + +```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 add tools that appear in both the wrapper PATH and the devShell. To add always-available packages not gated by any cat, use `runtimePkgs` directly (see next section). + +```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. + +### 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 + +``` +langPackages (flake.nix) ──► settings.lang_packages (module option) + │ +cats.nix ──► config.cats │ + │ │ + ▼ ▼ + cat-packages.nix ──► config.catPkgs. + │ │ + ┌───────┘ │ + ▼ ▼ + deps.nix (runtimePkgs) flake.nix (devShells) + │ │ + ▼ ▼ + wrapped vv PATH nix develop PATH +``` + +- **Overlays** (`overlays/`) inject base packages into nixpkgs (rWrapper, quarto, baseRPackages, basePythonPackages). +- **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. +- **flake.nix** reads `catPkgs` for the devShell. `langPackages` feeds into `settings.lang_packages`. diff --git a/flake.lock b/flake.lock index 698ccf9..02745a6 100644 --- a/flake.lock +++ b/flake.lock @@ -74,20 +74,19 @@ "nixpkgs": [ "rixpkgs" ], - "rnvimsrc": [ - "plugins-r" - ] + "rnvimsrc": "rnvimsrc" }, "locked": { - "lastModified": 1778684156, - "narHash": "sha256-Z4y1tQfkIsPK4NRxGn668HMDfWxnxNxSJ0CAOOXiIfY=", + "lastModified": 1779438909, + "narHash": "sha256-1lvv0bdvSVyeCIgeZ7Ws7ffbDFurA5LJscS9dRLHzC8=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "2f49dfee27886068e2f49cbd54558ce4cc424c82", + "rev": "ec17e22ab362a0ddfd6c2e9c5e95d43897a143be", "type": "github" }, "original": { "owner": "dwinkler1", + "ref": "v0.99.4", "repo": "r_nvim_nix", "type": "github" } @@ -108,6 +107,23 @@ "type": "github" } }, + "rnvimsrc": { + "flake": false, + "locked": { + "lastModified": 1776905071, + "narHash": "sha256-dXox6qEs1VDE7vPNDoN8bY4g06uj1IEs6uki72w8lpA=", + "owner": "R-nvim", + "repo": "R.nvim", + "rev": "582f2af11290ac067e49018db38e12a511325556", + "type": "github" + }, + "original": { + "owner": "R-nvim", + "ref": "v0.99.4", + "repo": "R.nvim", + "type": "github" + } + }, "root": { "inputs": { "fran": "fran", diff --git a/flake.nix b/flake.nix index a19f7dc..cde55df 100644 --- a/flake.nix +++ b/flake.nix @@ -11,9 +11,12 @@ }; rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; - r-nvim-nix.url = "github:dwinkler1/r_nvim_nix"; - r-nvim-nix.inputs.rnvimsrc.follows = "plugins-r"; - r-nvim-nix.inputs.nixpkgs.follows = "rixpkgs"; + r-nvim-nix = { + url = "github:dwinkler1/r_nvim_nix/v0.99.4"; + inputs = { + nixpkgs.follows = "rixpkgs"; + }; + }; fran = { url = "github:dwinkler1/fran"; @@ -44,14 +47,15 @@ duckdb polars ]; - r = (with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - styler - pkgs.nvimcom - ]) ++ [ pkgs.nvimcom ]; + r = + (with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + ]) + ++ [pkgs.nvimcom]; julia = [ "DataFramesMeta" "QuackIO" @@ -59,17 +63,6 @@ }; mkWrapperConfig = pkgs: { - cats = { - clickhouse = false; - gitPlugins = true; - julia = false; - lua = true; - markdown = true; - nix = true; - optional = false; - python = false; - r = true; - }; settings = { lang_packages = langPackages pkgs; }; @@ -97,8 +90,12 @@ mkPkgs = system: import nixpkgs { inherit system; - config = { allowUnfree = true; }; - overlays = [ overlayDefs.dependencyOverlay ]; + config = {allowUnfree = true;}; + overlays = [ + overlayDefs.dependencyOverlay + inputs.r-nvim-nix.overlays.default + inputs.fran.overlays.default + ]; }; module = (import ./modules/neovim.nix) inputs; @@ -140,56 +137,21 @@ pkgs = mkPkgs system; nvimPkg = wrapperSettings pkgs; - langPkgs = langPackages pkgs; - - pythonPackages = let - python_packages_fn = - if pkgs ? basePythonPackages - then ps: pkgs.basePythonPackages ps ++ langPkgs.python - else _: langPkgs.python; - in - with pkgs; [ - (python3.withPackages python_packages_fn) - nodejs - ruff - basedpyright - uv - ]; - - rPackages = let - r_packages = (pkgs.baseRPackages or []) ++ langPkgs.r; - in - with pkgs; [ - (rWrapper.override {packages = r_packages;}) - radianWrapper - (quarto.override {extraRPackages = r_packages;}) - air-formatter - yaml-language-server - nvimcom - rnvimserver - ]; - - juliaPackages = let - julia_with_packages = pkgs.julia-bin.withPackages langPkgs.julia; - in [julia_with_packages]; - - markdownPackages = with pkgs; [ - python313Packages.pylatexenc - quarto - zk - ]; - - shellPackages = - [nvimPkg] - ++ pkgs.lib.optionals wrapper.config.cats.python pythonPackages - ++ pkgs.lib.optionals wrapper.config.cats.r rPackages - ++ pkgs.lib.optionals wrapper.config.cats.julia juliaPackages - ++ pkgs.lib.optionals wrapper.config.cats.markdown markdownPackages; + shellPackages = [nvimPkg] + ++ wrapper.config.catPkgs.always or [] + ++ wrapper.config.catPkgs.python or [] + ++ wrapper.config.catPkgs.r or [] + ++ wrapper.config.catPkgs.julia or [] + ++ wrapper.config.catPkgs.markdown or [] + ++ wrapper.config.catPkgs.optional or [] + ++ wrapper.config.catPkgs.external or [] + ++ wrapper.config.catPkgs.nix or [] + ++ wrapper.config.catPkgs.lua or [] + ++ wrapper.config.catPkgs.clickhouse or []; in { default = pkgs.mkShell { name = "vShell"; packages = shellPackages; - nativeBuildInputs = pkgs.lib.optionals wrapper.config.cats.optional [ pkgs.devenv ]; shellHook = '' echo 'I am a NixShell' export R_HOME=$(R RHOME) @@ -223,9 +185,10 @@ cat version_output.txt >> $out fi ''; - module-eval = - let _ = wrapper.config; - in pkgs.runCommand "check-module-eval" {} '' + module-eval = let + _ = wrapper.config; + in + pkgs.runCommand "check-module-eval" {} '' echo "Module evaluation successful" > $out ''; } diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix new file mode 100644 index 0000000..3570ff6 --- /dev/null +++ b/modules/module/settings/cat-packages.nix @@ -0,0 +1,109 @@ +{ + config, + pkgs, + lib, + ... +}: +let + maybe = cat: pkgsList: + lib.optionals (config.cats.${cat} or false) pkgsList; +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; [ ]); + + clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]); + + external = maybe "external" (with pkgs; [ + 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; [ + python313Packages.pylatexenc + quarto + zk + ]); + + 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 + zathura + ]); + + 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 + nodejs + ruff + basedpyright + uv + ]); + + r = maybe "r" (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 + rnvimserver + ]); + + # cats without packages get empty lists + general = [ ]; + gitPlugins = [ ]; + treesitterParsers = [ pkgs.tree-sitter ]; + utils = [ ]; + }; +} diff --git a/modules/module/settings/cats.nix b/modules/module/settings/cats.nix index 13758d4..e1895f7 100644 --- a/modules/module/settings/cats.nix +++ b/modules/module/settings/cats.nix @@ -13,6 +13,7 @@ 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 - external: external tools and integrations - general: core Neovim plugins/features @@ -30,6 +31,7 @@ }; config.cats = { + always = lib.mkDefault true; clickhouse = lib.mkDefault false; external = lib.mkDefault true; general = lib.mkDefault true; diff --git a/modules/module/settings/runtime-path.nix b/modules/module/settings/runtime-path.nix deleted file mode 100644 index b321019..0000000 --- a/modules/module/settings/runtime-path.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ - 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.runtimePackages or [ ]; - in - acc ++ lib.optionals (is_enabled && has_runtime_deps) packages - ) - [ ]; - - prefix_packages = collect_runtime_packages "prefix"; - - to_path_specs = packages: [ - { - data = [ - "PATH" - ":" - "${lib.makeBinPath packages}" - ]; - } - ]; -in -{ - config.prefixVar = lib.optionals (prefix_packages != [ ]) (to_path_specs prefix_packages); -} diff --git a/modules/module/specs/deps.nix b/modules/module/specs/deps.nix index c3a40ea..b0c437f 100644 --- a/modules/module/specs/deps.nix +++ b/modules/module/specs/deps.nix @@ -13,133 +13,69 @@ }; }; - config.specs.external = { + config.specs.always = lib.mkIf (config.cats.always or true) { + data = lib.mkDefault null; + runtimeDeps = "prefix"; + runtimePkgs = config.catPkgs.always; + }; + + config.specs.external = lib.mkIf (config.cats.external or true) { data = lib.mkDefault null; before = ["INIT_MAIN"]; config = '' vim.o.shell = "${pkgs.zsh}/bin/zsh" ''; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - perl - ruby - shfmt - sqlfluff - tree-sitter - ]; + runtimePkgs = config.catPkgs.external; }; config.specs.optional = lib.mkIf (config.cats.optional or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; before = ["INIT_MAIN"]; - runtimePkgs = 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 - ]; + runtimePkgs = config.catPkgs.optional; }; config.specs.markdown = lib.mkIf (config.cats.markdown or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - python313Packages.pylatexenc - quarto - zk - ]; + runtimePkgs = config.catPkgs.markdown; }; config.specs.nix = lib.mkIf (config.cats.nix or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - alejandra - nix-doc - nixd - ]; + runtimePkgs = config.catPkgs.nix; }; config.specs.lua = lib.mkIf (config.cats.lua or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - lua-language-server - ]; + runtimePkgs = config.catPkgs.lua; }; config.specs.python = lib.mkIf (config.cats.python or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = 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 - ]; + runtimePkgs = config.catPkgs.python; }; config.specs.r = lib.mkIf (config.cats.r or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = 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 - ]; + runtimePkgs = config.catPkgs.r; }; config.specs.julia = lib.mkIf (config.cats.julia or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = let - julia_with_packages = - pkgs.julia-bin.withPackages config.settings.lang_packages.julia; - in [julia_with_packages]; + runtimePkgs = config.catPkgs.julia; }; config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or true) { data = lib.mkDefault null; runtimeDeps = "prefix"; - runtimePkgs = with pkgs; [ - clickhouse-lts - ]; + runtimePkgs = config.catPkgs.clickhouse; }; config.runtimePkgs = config.specCollect (acc: v: acc ++ (v.runtimePkgs or [])) []; diff --git a/modules/neovim.nix b/modules/neovim.nix index 64d2cda..f4ca40f 100644 --- a/modules/neovim.nix +++ b/modules/neovim.nix @@ -9,6 +9,7 @@ inputs: { imports = [ wlib.wrapperModules.neovim + ./module/settings/cat-packages.nix ./module/specs/deps.nix ./module/specs/plugins.nix ./module/settings/core.nix @@ -16,7 +17,6 @@ inputs: ./module/settings/env.nix ./module/settings/hosts.nix ./module/settings/lang-packages.nix - ./module/settings/runtime-path.nix ]; options.nvim-lib.neovimPlugins = lib.mkOption { From db610620b3a3f4a1b4976f466486393cd76f35b2 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 21:59:54 +1000 Subject: [PATCH 18/55] large refactor --- README.md | 158 +++++++++++++++----- flake.nix | 167 +++++++++++++--------- modules/module/settings/cat-packages.nix | 18 ++- modules/module/settings/core.nix | 2 +- modules/module/settings/env.nix | 10 +- modules/module/settings/hosts.nix | 6 +- modules/module/settings/lang-packages.nix | 26 +++- modules/module/specs/deps.nix | 20 +-- modules/module/specs/plugins.nix | 20 +-- plugin/21_datascience.lua | 2 +- 10 files changed, 286 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index c50d03e..cec155c 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,9 @@ Modular Neovim wrapper config. Defines a wrapped `vv` binary with per-category p config.allowUnfree = true; overlays = [ nvimConfig.overlays.dependencies ]; }; - evalResult = nvimConfig.inputs.wrappers.lib.evalModules { - modules = [ - nvimConfig.wrapperModules.default - projectSettings # see below - ]; + evalResult = nvimConfig.lib.eval { + inherit pkgs; + modules = [ projectSettings ]; # see below }; in { default = evalResult.config.wrap { inherit pkgs; }; @@ -49,19 +47,14 @@ Modular Neovim wrapper config. Defines a wrapped `vv` binary with per-category p config.allowUnfree = true; overlays = [ nvimConfig.overlays.dependencies ]; }; - evalResult = nvimConfig.inputs.wrappers.lib.evalModules { - modules = [ - nvimConfig.wrapperModules.default - projectSettings - ]; + evalResult = nvimConfig.lib.eval { + inherit pkgs; + modules = [ projectSettings ]; }; nv = evalResult.config.wrap { inherit pkgs; }; in { default = pkgs.mkShell { - packages = - [ nv ] - ++ (evalResult.config.catPkgs.markdown or []) - ++ (evalResult.config.catPkgs.nix or []); + packages = [ nv ] ++ nvimConfig.lib.devShellPackages evalResult.config; }; }); }; @@ -92,7 +85,96 @@ Modular Neovim wrapper config. Defines a wrapped `vv` binary with per-category p ## Overriding settings -Define a `projectSettings` attrset and pass it as the second module in `evalModules`. Every option in `nvimConfig.wrapperConfigs.default` can be overridden here. +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; + }; + }; +} +``` ### Categories @@ -112,9 +194,13 @@ projectSettings = { }; ``` -### Language packages (overlay extension) +### Language packages (module defaults) -Add Python/R/Julia libraries that get appended to the overlay base packages. +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`, `nvimcom` +- Julia: `DataFramesMeta`, `QuackIO` ```nix projectSettings = { @@ -138,7 +224,7 @@ projectSettings = { ### Runtime dependencies (per-cat packages) -Override `catPkgs` to add tools that appear in both the wrapper PATH and the devShell. To add always-available packages not gated by any cat, use `runtimePkgs` directly (see next section). +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 = { @@ -179,6 +265,14 @@ projectSettings = { `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 @@ -226,22 +320,20 @@ projectSettings = { ## Architecture ``` -langPackages (flake.nix) ──► settings.lang_packages (module option) - │ -cats.nix ──► config.cats │ - │ │ - ▼ ▼ - cat-packages.nix ──► config.catPkgs. - │ │ - ┌───────┘ │ - ▼ ▼ - deps.nix (runtimePkgs) flake.nix (devShells) - │ │ - ▼ ▼ - wrapped vv PATH nix develop PATH +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 base packages into nixpkgs (rWrapper, quarto, baseRPackages, basePythonPackages). +- **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. -- **flake.nix** reads `catPkgs` for the devShell. `langPackages` feeds into `settings.lang_packages`. +- **settings.lang_packages** holds the shared defaults used by local outputs and downstream module consumers. +- **flake.nix** exports `lib.eval`, `lib.mkWrapper`, and `lib.devShellPackages` as the canonical downstream helpers, and builds `packages.default` and `devShells.default` from the same module config. diff --git a/flake.nix b/flake.nix index cde55df..b9e77e3 100644 --- a/flake.nix +++ b/flake.nix @@ -42,40 +42,42 @@ wrappers, ... } @ inputs: let - langPackages = pkgs: { - python = with pkgs.python3Packages; [ - duckdb - polars - ]; - r = - (with pkgs.rpkgs.rPackages; [ - arrow - broom - data_table - janitor - styler - ]) - ++ [pkgs.nvimcom]; - julia = [ - "DataFramesMeta" - "QuackIO" - ]; - }; - - mkWrapperConfig = pkgs: { - settings = { - lang_packages = langPackages pkgs; - }; - binName = "vv"; - }; - - wrapperSettings = pkgs: let - cfg = mkWrapperConfig pkgs; - in - wrapper.config.wrap { - inherit pkgs; - inherit (cfg) settings binName; + devShellCatOrder = [ + "always" + "clickhouse" + "external" + "julia" + "lua" + "markdown" + "nix" + "optional" + "python" + "r" + "treesitterParsers" + ]; + evalWithPkgs = pkgs: extraModules: + wrappers.lib.evalModules { + specialArgs = { + inherit pkgs; + }; + modules = + [ + module + ] + ++ extraModules; }; + 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)" | grep -oP '/nix/store/[^:]+/library' | sort -u | paste -sd: -) + export R_LIBS_USER="$PWD/.r-libs" + mkdir -p "$R_LIBS_USER" + ''; systems = [ "aarch64-darwin" @@ -93,35 +95,40 @@ config = {allowUnfree = true;}; overlays = [ overlayDefs.dependencyOverlay - inputs.r-nvim-nix.overlays.default - inputs.fran.overlays.default ]; }; module = (import ./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; + }; + overlays = { # overlay `vv` wraps the module with default settings only. - # For the fully-configured binary (including mkWrapperConfig overrides), - # use `packages..default` instead. + # 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 (final: prev: { - vv = wrapper.config.wrap {pkgs = final;}; + vv = (evalWithPkgs final []).config.wrap {pkgs = final;}; }) ]; dependencies = overlayDefs.dependencyOverlay; }; wrapperModules.default = module; - wrapperConfigs.default = wrapper.config; + wrapperConfigs.default = {pkgs, modules ? []}: (self.lib.eval {inherit pkgs modules;}).config; packages = forAllSystems ( system: let pkgs = mkPkgs system; in { - default = wrapperSettings pkgs; + default = self.lib.mkWrapper { + inherit pkgs; + }; } ); @@ -135,30 +142,13 @@ devShells = forAllSystems ( system: let pkgs = mkPkgs system; - nvimPkg = wrapperSettings pkgs; - - shellPackages = [nvimPkg] - ++ wrapper.config.catPkgs.always or [] - ++ wrapper.config.catPkgs.python or [] - ++ wrapper.config.catPkgs.r or [] - ++ wrapper.config.catPkgs.julia or [] - ++ wrapper.config.catPkgs.markdown or [] - ++ wrapper.config.catPkgs.optional or [] - ++ wrapper.config.catPkgs.external or [] - ++ wrapper.config.catPkgs.nix or [] - ++ wrapper.config.catPkgs.lua or [] - ++ wrapper.config.catPkgs.clickhouse or []; + config = (self.lib.eval {inherit pkgs;}).config; + nvimPkg = config.wrap {inherit pkgs;}; in { default = pkgs.mkShell { name = "vShell"; - packages = shellPackages; - shellHook = '' - echo 'I am a NixShell' - export R_HOME=$(R RHOME) - export R_LIBS_SITE=$(strings "$(command -v R)" | grep -oP '/nix/store/[^:]+/library' | sort -u | paste -sd: -) - export R_LIBS_USER="$PWD/.r-libs" - mkdir -p "$R_LIBS_USER" - ''; + packages = [nvimPkg] ++ self.lib.devShellPackages config; + shellHook = mkShellHook config; }; } ); @@ -166,10 +156,31 @@ checks = forAllSystems ( system: let pkgs = mkPkgs system; - nvimPkg = wrapperSettings pkgs; + 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; in { default = pkgs.runCommand "check-vv" {} '' - BINARY_PATH="${nvimPkg}/bin/vv" + BINARY_PATH="${defaultNvimPkg}/bin/vv" if [ ! -x "$BINARY_PATH" ]; then echo "Error: Binary not found or not executable" @@ -186,11 +197,37 @@ fi ''; module-eval = let - _ = wrapper.config; + _ = (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 + ''; } ); diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 3570ff6..7dd3049 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -70,7 +70,6 @@ in tokei wget yq - zathura ]); python = maybe "python" (let @@ -90,15 +89,14 @@ in r = maybe "r" (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 - rnvimserver - ]); + in [ + (pkgs.rpkgs.rWrapper.override { packages = r_packages; }) + pkgs.rpkgs.radianWrapper + (pkgs.rpkgs.quarto.override { extraRPackages = r_packages; }) + pkgs.air-formatter + pkgs.yaml-language-server + pkgs.rnvimserver + ]); # cats without packages get empty lists general = [ ]; diff --git a/modules/module/settings/core.nix b/modules/module/settings/core.nix index ec9bb5c..f8b3188 100644 --- a/modules/module/settings/core.nix +++ b/modules/module/settings/core.nix @@ -16,7 +16,7 @@ # Lua packages available to neovim (for :lua require()) config.settings.nvim_lua_env = lp: - lib.optionals (config.cats.general or true) [ lp.tiktoken_core ]; + lib.optionals (config.cats.general or false) [ lp.tiktoken_core ]; # Binary name for the wrapper config.binName = lib.mkDefault "vv"; diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index 2fcab8a..db2986d 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -7,21 +7,21 @@ # Environment variables set for the wrapper. # These are available when running neovim. config.env = lib.mkMerge [ - (lib.mkIf (config.cats.python or true) { + (lib.mkIf (config.cats.python or false) { UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON = pkgs.python.interpreter; }) - (lib.mkIf (config.cats.r or true) { + (lib.mkIf (config.cats.r or false) { RNVIM_COMPLDIR = "$PWD/.r-compl"; - R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.Rlibs"; + R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.r-libs"; TMPDIR = "$PWD/.r-tmp"; }) ]; # Environment variables with defaults (can be overridden by user) config.envDefault = lib.mkMerge [ - (lib.mkIf (config.cats.r or true) { - R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.Rlibs"; + (lib.mkIf (config.cats.r or false) { + R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.r-libs"; }) ]; } diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index f248a00..7637633 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -29,7 +29,7 @@ ]; }; } - (lib.mkIf (config.cats.julia or true) { + (lib.mkIf (config.cats.julia or false) { jl = { nvim-host.enable = true; nvim-host.package = "${pkgs.julia-bin}/bin/julia"; @@ -39,10 +39,10 @@ ]; }; }) - (lib.mkIf (config.cats.python or true) { + (lib.mkIf (config.cats.python or false) { python3.nvim-host.enable = true; }) - (lib.mkIf (config.cats.r or true) { + (lib.mkIf (config.cats.r or false) { r = { nvim-host.enable = true; nvim-host.package = "${pkgs.rWrapper}/bin/R"; diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index 531b811..b6fff27 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -1,5 +1,6 @@ { config, + pkgs, lib, ... }: @@ -26,14 +27,29 @@ }; default = { }; description = '' - Language-specific package overrides appended to each language spec's runtimePackages. - Intended for flake.nix overrides via wrapper.config.wrap. + Language-specific package defaults and downstream overrides appended to each + language spec's runtime packages. ''; }; config.settings.lang_packages = { - python = lib.mkDefault [ ]; - r = lib.mkDefault [ ]; - julia = lib.mkDefault [ ]; + python = lib.mkDefault (with pkgs.python3Packages; [ + duckdb + polars + ]); + r = lib.mkDefault ( + (with pkgs.rpkgs.rPackages; [ + arrow + broom + data_table + janitor + styler + ]) + ++ [pkgs.nvimcom] + ); + julia = lib.mkDefault [ + "DataFramesMeta" + "QuackIO" + ]; }; } diff --git a/modules/module/specs/deps.nix b/modules/module/specs/deps.nix index b0c437f..5cbfe63 100644 --- a/modules/module/specs/deps.nix +++ b/modules/module/specs/deps.nix @@ -13,13 +13,13 @@ }; }; - config.specs.always = lib.mkIf (config.cats.always or true) { + config.specs.always = lib.mkIf (config.cats.always or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.always; }; - config.specs.external = lib.mkIf (config.cats.external or true) { + config.specs.external = lib.mkIf (config.cats.external or false) { data = lib.mkDefault null; before = ["INIT_MAIN"]; config = '' @@ -29,50 +29,50 @@ runtimePkgs = config.catPkgs.external; }; - config.specs.optional = lib.mkIf (config.cats.optional or true) { + config.specs.optional = lib.mkIf (config.cats.optional or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; before = ["INIT_MAIN"]; runtimePkgs = config.catPkgs.optional; }; - config.specs.markdown = lib.mkIf (config.cats.markdown or true) { + config.specs.markdown = lib.mkIf (config.cats.markdown or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.markdown; }; - config.specs.nix = lib.mkIf (config.cats.nix or true) { + config.specs.nix = lib.mkIf (config.cats.nix or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.nix; }; - config.specs.lua = lib.mkIf (config.cats.lua or true) { + config.specs.lua = lib.mkIf (config.cats.lua or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.lua; }; - config.specs.python = lib.mkIf (config.cats.python or true) { + config.specs.python = lib.mkIf (config.cats.python or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.python; }; - config.specs.r = lib.mkIf (config.cats.r or true) { + config.specs.r = lib.mkIf (config.cats.r or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.r; }; - config.specs.julia = lib.mkIf (config.cats.julia or true) { + config.specs.julia = lib.mkIf (config.cats.julia or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.julia; }; - config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or true) { + config.specs.clickhouse = lib.mkIf (config.cats.clickhouse or false) { data = lib.mkDefault null; runtimeDeps = "prefix"; runtimePkgs = config.catPkgs.clickhouse; diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ff00a72..ad464b8 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -4,11 +4,11 @@ lib, ... }: { - config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or true) { + config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or false) { data = []; }; - config.specs.r = lib.mkIf (config.cats.r or true) { + config.specs.r = lib.mkIf (config.cats.r or false) { data = with pkgs.vimPlugins; [ pkgs.r-nvim quarto-nvim @@ -19,14 +19,14 @@ ]; }; - config.specs.markdown-lazy = lib.mkIf (config.cats.markdown or true) { + config.specs.markdown-lazy = lib.mkIf (config.cats.markdown or false) { lazy = true; data = [ config.nvim-lib.neovimPlugins.cmp-pandoc-references ]; }; - config.specs.general = lib.mkIf (config.cats.general or true) { + config.specs.general = lib.mkIf (config.cats.general or false) { data = with pkgs.vimPlugins; [ lze lzextras @@ -79,7 +79,7 @@ ]; }; - config.specs.lua = lib.mkIf (config.cats.lua or true) { + config.specs.lua = lib.mkIf (config.cats.lua or false) { data = with pkgs.vimPlugins; [ luvit-meta { @@ -89,7 +89,7 @@ ]; }; - config.specs.markdown = lib.mkIf (config.cats.markdown or true) { + config.specs.markdown = lib.mkIf (config.cats.markdown or false) { data = with pkgs.vimPlugins; [ quarto-nvim render-markdown-nvim @@ -104,7 +104,7 @@ ]; }; - config.specs.utils = lib.mkIf (config.cats.utils or true) { + config.specs.utils = lib.mkIf (config.cats.utils or false) { data = with pkgs.vimPlugins; [ blink-cmp nvim-lspconfig @@ -119,7 +119,7 @@ ]; }; - config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or true) { + config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or false) { data = with pkgs.vimPlugins.nvim-treesitter-parsers; [ bash c @@ -158,7 +158,7 @@ ]; }; - config.specs.utils-lazy = lib.mkIf (config.cats.utils or true) { + config.specs.utils-lazy = lib.mkIf (config.cats.utils or false) { lazy = true; data = with pkgs.vimPlugins; [ blink-compat @@ -175,7 +175,7 @@ ]; }; - config.specs.gitPlugins-lazy = lib.mkIf (config.cats.gitPlugins or true) { + config.specs.gitPlugins-lazy = lib.mkIf (config.cats.gitPlugins or false) { lazy = true; data = []; }; diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index 2e9f86c..aeab360 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -61,7 +61,7 @@ now(function() min_editor_width = 80, rconsole_height = 20, nvimpager = "split_h", - pdfviewer = "zathura", + pdfviewer = "", }) end end) From ed7abad63cb63025d5d87a9a767ec5c00d7b3126 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 22:53:52 +1000 Subject: [PATCH 19/55] Expose shellhooks via API --- README.md | 4 +++- flake.nix | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cec155c..715d4c6 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Modular Neovim wrapper config. Defines a wrapped `vv` binary with per-category p in { default = pkgs.mkShell { packages = [ nv ] ++ nvimConfig.lib.devShellPackages evalResult.config; + shellHook = nvimConfig.lib.shellHook evalResult.config; }; }); }; @@ -171,6 +172,7 @@ This example enables a few cats, adds runtime tools to the shared wrapper/devShe devShells.${system}.default = pkgs.mkShell { packages = [ nv ] ++ nvimConfig.lib.devShellPackages evalResult.config; + shellHook = nvimConfig.lib.shellHook evalResult.config; }; }; } @@ -336,4 +338,4 @@ cats.nix ───────────────► config.cats │ - **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`, and `lib.devShellPackages` as the canonical downstream helpers, and builds `packages.default` and `devShells.default` from the same module config. +- **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.nix b/flake.nix index b9e77e3..4c8df73 100644 --- a/flake.nix +++ b/flake.nix @@ -104,6 +104,7 @@ 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 = { From 1c5619312db77493cc07fd5ac991347e47638e5f Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 23:07:54 +1000 Subject: [PATCH 20/55] simplified --- flake.lock | 18 ------------------ flake.nix | 5 ----- overlays/r.nix | 1 - scripts/updater.nix | 14 -------------- scripts/updater.sh | 22 ---------------------- 5 files changed, 60 deletions(-) delete mode 100644 scripts/updater.nix delete mode 100644 scripts/updater.sh diff --git a/flake.lock b/flake.lock index 02745a6..0b89d7a 100644 --- a/flake.lock +++ b/flake.lock @@ -52,23 +52,6 @@ "type": "github" } }, - "plugins-r": { - "flake": false, - "locked": { - "lastModified": 1776905071, - "narHash": "sha256-dXox6qEs1VDE7vPNDoN8bY4g06uj1IEs6uki72w8lpA=", - "owner": "R-nvim", - "repo": "R.nvim", - "rev": "582f2af11290ac067e49018db38e12a511325556", - "type": "github" - }, - "original": { - "owner": "R-nvim", - "ref": "v0.99.4", - "repo": "R.nvim", - "type": "github" - } - }, "r-nvim-nix": { "inputs": { "nixpkgs": [ @@ -129,7 +112,6 @@ "fran": "fran", "nixpkgs": "nixpkgs", "plugins-cmp-pandoc-references": "plugins-cmp-pandoc-references", - "plugins-r": "plugins-r", "r-nvim-nix": "r-nvim-nix", "rixpkgs": "rixpkgs", "wrappers": "wrappers" diff --git a/flake.nix b/flake.nix index 4c8df73..ee9c05c 100644 --- a/flake.nix +++ b/flake.nix @@ -25,11 +25,6 @@ }; }; - "plugins-r" = { - url = "github:R-nvim/R.nvim/v0.99.4"; - flake = false; - }; - "plugins-cmp-pandoc-references" = { url = "github:jmbuhr/cmp-pandoc-references"; flake = false; diff --git a/overlays/r.nix b/overlays/r.nix index 14c02eb..18f01dd 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -11,5 +11,4 @@ in { baseRPackages = [ ]; rWrapper = rpkgs.rWrapper.override {packages = [ ];}; quarto = rpkgs.quarto.override {extraRPackages = [ ];}; - updateR = import ../scripts/updater.nix {pkgs = final;}; } diff --git a/scripts/updater.nix b/scripts/updater.nix deleted file mode 100644 index 063ec8d..0000000 --- a/scripts/updater.nix +++ /dev/null @@ -1,14 +0,0 @@ -{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 deleted file mode 100644 index ccf5e2e..0000000 --- a/scripts/updater.sh +++ /dev/null @@ -1,22 +0,0 @@ -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 From d218b05d485d8d2db387c1d7a6016fdd6f3f1310 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 23:38:18 +1000 Subject: [PATCH 21/55] hopefully R fix --- modules/module/settings/cat-packages.nix | 17 ++++++++++------- modules/module/settings/hosts.nix | 6 +++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 7dd3049..8f027ba 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -7,6 +7,11 @@ let maybe = cat: pkgsList: lib.optionals (config.cats.${cat} or false) pkgsList; + rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; + quartoPkg = + if config.cats.r or false + then pkgs.rpkgs.quarto.override { extraRPackages = rPackages; } + else pkgs.quarto; in { options.catPkgs = lib.mkOption { @@ -35,7 +40,7 @@ in markdown = maybe "markdown" (with pkgs; [ python313Packages.pylatexenc - quarto + quartoPkg zk ]); @@ -87,16 +92,14 @@ in uv ]); - r = maybe "r" (let - r_packages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; - in [ - (pkgs.rpkgs.rWrapper.override { packages = r_packages; }) + r = maybe "r" [ + (pkgs.rpkgs.rWrapper.override { packages = rPackages; }) pkgs.rpkgs.radianWrapper - (pkgs.rpkgs.quarto.override { extraRPackages = r_packages; }) + quartoPkg pkgs.air-formatter pkgs.yaml-language-server pkgs.rnvimserver - ]); + ]; # cats without packages get empty lists general = [ ]; diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index 7637633..a47467a 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -4,6 +4,10 @@ lib, ... }: +let + rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; + rWrapperPkg = pkgs.rpkgs.rWrapper.override { packages = rPackages; }; +in { config.hosts = lib.mkMerge [ { @@ -45,7 +49,7 @@ (lib.mkIf (config.cats.r or false) { r = { nvim-host.enable = true; - nvim-host.package = "${pkgs.rWrapper}/bin/R"; + nvim-host.package = "${rWrapperPkg}/bin/R"; nvim-host.argv0 = "R"; nvim-host.addFlag = [ "--no-save" From 740f86ad7b3494f51e1e051bf709aedcaa0ec11e Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 23:41:48 +1000 Subject: [PATCH 22/55] hopefully R fix --- modules/module/settings/env.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index db2986d..d7ecc36 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -13,7 +13,7 @@ }) (lib.mkIf (config.cats.r or false) { RNVIM_COMPLDIR = "$PWD/.r-compl"; - R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.r-libs"; + R_LIBS_USER = "$PWD/.r-libs"; TMPDIR = "$PWD/.r-tmp"; }) ]; @@ -21,7 +21,7 @@ # Environment variables with defaults (can be overridden by user) config.envDefault = lib.mkMerge [ (lib.mkIf (config.cats.r or false) { - R_LIBS_USER = "${pkgs.nvimcom}/library:$PWD/.r-libs"; + R_LIBS_USER = "$PWD/.r-libs"; }) ]; } From 458332a141840cdad6dc431a8fe9c3f3be5fea94 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 23:44:56 +1000 Subject: [PATCH 23/55] hopefully R fix --- README.md | 2 +- modules/module/settings/cat-packages.nix | 3 ++- modules/module/settings/hosts.nix | 2 +- modules/module/settings/lang-packages.nix | 1 - 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 715d4c6..068d704 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ projectSettings = { 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`, `nvimcom` +- R: `arrow`, `broom`, `data_table`, `janitor`, `styler` - Julia: `DataFramesMeta`, `QuackIO` ```nix diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 8f027ba..6406327 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -8,6 +8,7 @@ let maybe = cat: pkgsList: lib.optionals (config.cats.${cat} or false) pkgsList; rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; + rWrapperPackages = rPackages ++ [pkgs.nvimcom]; quartoPkg = if config.cats.r or false then pkgs.rpkgs.quarto.override { extraRPackages = rPackages; } @@ -93,7 +94,7 @@ in ]); r = maybe "r" [ - (pkgs.rpkgs.rWrapper.override { packages = rPackages; }) + (pkgs.rpkgs.rWrapper.override { packages = rWrapperPackages; }) pkgs.rpkgs.radianWrapper quartoPkg pkgs.air-formatter diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index a47467a..aadd70f 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -6,7 +6,7 @@ }: let rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; - rWrapperPkg = pkgs.rpkgs.rWrapper.override { packages = rPackages; }; + rWrapperPkg = pkgs.rpkgs.rWrapper.override { packages = rPackages ++ [pkgs.nvimcom]; }; in { config.hosts = lib.mkMerge [ diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index b6fff27..a91d9a5 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -45,7 +45,6 @@ janitor styler ]) - ++ [pkgs.nvimcom] ); julia = lib.mkDefault [ "DataFramesMeta" From 62f456ed46a4c283877bad845af6e404d637284a Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sat, 23 May 2026 23:54:07 +1000 Subject: [PATCH 24/55] hopefully R fix --- modules/module/settings/cat-packages.nix | 2 +- modules/module/settings/env.nix | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 6406327..b92c331 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -8,7 +8,7 @@ let maybe = cat: pkgsList: lib.optionals (config.cats.${cat} or false) pkgsList; rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r; - rWrapperPackages = rPackages ++ [pkgs.nvimcom]; + rWrapperPackages = rPackages; quartoPkg = if config.cats.r or false then pkgs.rpkgs.quarto.override { extraRPackages = rPackages; } diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index d7ecc36..c7a1a8b 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -13,15 +13,10 @@ }) (lib.mkIf (config.cats.r or false) { RNVIM_COMPLDIR = "$PWD/.r-compl"; - R_LIBS_USER = "$PWD/.r-libs"; TMPDIR = "$PWD/.r-tmp"; }) ]; # Environment variables with defaults (can be overridden by user) - config.envDefault = lib.mkMerge [ - (lib.mkIf (config.cats.r or false) { - R_LIBS_USER = "$PWD/.r-libs"; - }) - ]; + config.envDefault = lib.mkMerge [ ]; } From aec9110e709e1aa50d78f06293c4bde14974b23f Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Sun, 24 May 2026 00:01:12 +1000 Subject: [PATCH 25/55] hopefully R fix --- modules/module/settings/core.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/module/settings/core.nix b/modules/module/settings/core.nix index f8b3188..822fc53 100644 --- a/modules/module/settings/core.nix +++ b/modules/module/settings/core.nix @@ -32,4 +32,10 @@ # 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; } From 83b70216a980559ecd6823cf3e878f274ea743b7 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Mon, 8 Jun 2026 11:02:45 +0100 Subject: [PATCH 26/55] fixed treesitter --- lua/nix_smart_send.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lua/nix_smart_send.lua b/lua/nix_smart_send.lua index c03af4c..1f82383 100644 --- a/lua/nix_smart_send.lua +++ b/lua/nix_smart_send.lua @@ -22,9 +22,11 @@ local COMMENT_TYPES = { } function M.get_current_node() - 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) + return vim.treesitter.get_node({ + winid = cur_win, + ignore_injections = true, + }) end function M.detect_global_node() @@ -50,7 +52,6 @@ function M.detect_global_node() end 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") @@ -86,25 +87,27 @@ function M.move_to_next_non_empty_line() 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() + node = vim.treesitter.get_node() 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") + local start_row, _, end_row, _ = node:range() + + 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 }) + return true end function M.select_until_global(global_nodes) - 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] @@ -113,7 +116,7 @@ function M.select_until_global(global_nodes) -- Use empty table if no global nodes provided global_nodes = global_nodes or {} - local node = ts_utils.get_node_at_cursor() + local node = vim.treesitter.get_node() if not node then -- print("No syntax node found at cursor position") return nil @@ -167,7 +170,6 @@ function M.slime_send_region() end function M.send_repl(global_nodes) - local ts_utils = require('nvim-treesitter.ts_utils') local cur_node = M.get_current_node() if not cur_node then @@ -189,7 +191,9 @@ function M.send_repl(global_nodes) M.slime_send_region() -- Move cursor and continue - ts_utils.goto_node(sel_node, true) + local _, _, er, ec = sel_node:range() + vim.api.nvim_win_set_cursor(0, { er + 1, ec }) + M.move_to_next_non_empty_line() end From 3cf282e4f8d342021addd743400f11db8d88f811 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Mon, 8 Jun 2026 11:03:05 +0100 Subject: [PATCH 27/55] new font for gui --- plugin/00_options.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/00_options.lua b/plugin/00_options.lua index 2b4485b..e824333 100644 --- a/plugin/00_options.lua +++ b/plugin/00_options.lua @@ -176,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 = 'Iosevka Nerd Font,Symbols Nerd Font:h14:#e-subpixelantialias:#h-none' + vim.o.guifont = 'JetBrainsMono 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 From 3ba96f380caf6e994515a76a31c43c2d1265400a Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Mon, 29 Jun 2026 09:59:40 +0200 Subject: [PATCH 28/55] Corrected Rnvim setup --- .gitignore | 1 + flake.lock | 24 ++++++++++++------------ flake.nix | 4 ++-- overlays/r.nix | 13 +++++-------- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index e402ff0..6e33f00 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .nvimcom .commandcode .commandcode/ +.r-* diff --git a/flake.lock b/flake.lock index 0b89d7a..21dc5ef 100644 --- a/flake.lock +++ b/flake.lock @@ -60,49 +60,49 @@ "rnvimsrc": "rnvimsrc" }, "locked": { - "lastModified": 1779438909, - "narHash": "sha256-1lvv0bdvSVyeCIgeZ7Ws7ffbDFurA5LJscS9dRLHzC8=", + "lastModified": 1781392459, + "narHash": "sha256-9CMG+trBd9AX7cWiGPrMr0eJwuZaql+pj2WVGyT417I=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "ec17e22ab362a0ddfd6c2e9c5e95d43897a143be", + "rev": "03d7872d7702db37ede41f3c6e4d4ce4c129e862", "type": "github" }, "original": { "owner": "dwinkler1", - "ref": "v0.99.4", + "ref": "v0.99.5", "repo": "r_nvim_nix", "type": "github" } }, "rixpkgs": { "locked": { - "lastModified": 1771303851, - "narHash": "sha256-tgveHozOJ2D/mi3LxVy/FcmLFDlM5XKZxsNB2XpvzaM=", + "lastModified": 1782576256, + "narHash": "sha256-KOvpL9DJJmShb64mX9QTjhxHaxE8MizQIqNUAniuJ2E=", "owner": "dwinkler1", "repo": "rixpkgs", - "rev": "af2dd3f7b4b172077747c0869d4e30702fb71b0e", + "rev": "815afc01bc0cc9a2eba80906645bd08f976a4401", "type": "github" }, "original": { "owner": "dwinkler1", + "ref": "nixpkgs", "repo": "rixpkgs", - "rev": "af2dd3f7b4b172077747c0869d4e30702fb71b0e", "type": "github" } }, "rnvimsrc": { "flake": false, "locked": { - "lastModified": 1776905071, - "narHash": "sha256-dXox6qEs1VDE7vPNDoN8bY4g06uj1IEs6uki72w8lpA=", + "lastModified": 1780759435, + "narHash": "sha256-VxgKMOP1hseQre3cas2dmMXZu4PVyl05INla2OdHTU4=", "owner": "R-nvim", "repo": "R.nvim", - "rev": "582f2af11290ac067e49018db38e12a511325556", + "rev": "6ca306191531c3e3d501ee1609c84a5d29059386", "type": "github" }, "original": { "owner": "R-nvim", - "ref": "v0.99.4", + "ref": "v0.99.5", "repo": "R.nvim", "type": "github" } diff --git a/flake.nix b/flake.nix index ee9c05c..d8c0018 100644 --- a/flake.nix +++ b/flake.nix @@ -9,10 +9,10 @@ url = "github:BirdeeHub/nix-wrapper-modules"; inputs.nixpkgs.follows = "nixpkgs"; }; - rixpkgs.url = "github:dwinkler1/rixpkgs/af2dd3f7b4b172077747c0869d4e30702fb71b0e"; + rixpkgs.url = "github:dwinkler1/rixpkgs/nixpkgs"; r-nvim-nix = { - url = "github:dwinkler1/r_nvim_nix/v0.99.4"; + url = "github:dwinkler1/r_nvim_nix/v0.99.5"; inputs = { nixpkgs.follows = "rixpkgs"; }; diff --git a/overlays/r.nix b/overlays/r.nix index 18f01dd..e736b77 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -1,14 +1,11 @@ -{ - inputs, - ... -}: final: prev: let +{inputs, ...}: final: prev: let rpkgs = import inputs.rixpkgs { system = prev.stdenv.hostPlatform.system; - overlays = [inputs.fran.overlays.default]; + overlays = [inputs.fran.overlays.default inputs.r-nvim-nix.overlays.default]; }; in { inherit rpkgs; - baseRPackages = [ ]; - rWrapper = rpkgs.rWrapper.override {packages = [ ];}; - quarto = rpkgs.quarto.override {extraRPackages = [ ];}; + baseRPackages = [rpkgs.nvimcom]; + rWrapper = rpkgs.rWrapper.override {packages = [];}; + quarto = rpkgs.quarto.override {extraRPackages = [];}; } From 5269e76608d9b42f29764dd8aa900391fade8ee5 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:04:12 +1000 Subject: [PATCH 29/55] updates --- .github/{dependapot.yml => dependabot.yml} | 0 .github/workflows/check.yml | 14 +- .../1784851829193-codecompanion-revamp.md | 85 +++++++ .../plans/1784858731550-nix-neovim-review.md | 191 ++++++++++++++++ flake.lock | 34 +-- flake.nix | 10 +- ftplugin/quarto.lua | 35 +-- modules/module/settings/cat-packages.nix | 4 +- modules/module/settings/core.nix | 4 - modules/module/settings/env.nix | 7 +- modules/module/settings/hosts.nix | 10 - plugin/00_options.lua | 46 ++-- plugin/01_lib.lua | 4 +- plugin/10_keymap.lua | 23 +- plugin/20_startup.lua | 209 ++++-------------- plugin/21_datascience.lua | 30 ++- plugin/22_languages.lua | 6 +- plugin/23_editor.lua | 18 +- plugin/24_completion.lua | 199 +++++++++++++---- plugin/25_lsp.lua | 14 +- 20 files changed, 599 insertions(+), 344 deletions(-) rename .github/{dependapot.yml => dependabot.yml} (100%) create mode 100644 .kilo/plans/1784851829193-codecompanion-revamp.md create mode 100644 .kilo/plans/1784858731550-nix-neovim-review.md diff --git a/.github/dependapot.yml b/.github/dependabot.yml similarity index 100% rename from .github/dependapot.yml rename to .github/dependabot.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 637fb10..fcf1db9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -5,12 +5,20 @@ on: paths: - 'flake.lock' - 'flake.nix' - - 'modules' + - 'modules/**' + - 'plugin/**' + - 'lua/**' + - 'overlays/**' + - 'ftplugin/**' pull_request: paths: - 'flake.lock' - 'flake.nix' - - 'modules' + - 'modules/**' + - 'plugin/**' + - 'lua/**' + - 'overlays/**' + - 'ftplugin/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -35,5 +43,5 @@ jobs: authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' extraPullNames: rstats-on-nix, nix-community - run: nix build - - run: nix develop + - run: nix develop -c echo ok - run: nix flake check diff --git a/.kilo/plans/1784851829193-codecompanion-revamp.md b/.kilo/plans/1784851829193-codecompanion-revamp.md new file mode 100644 index 0000000..b5898e4 --- /dev/null +++ b/.kilo/plans/1784851829193-codecompanion-revamp.md @@ -0,0 +1,85 @@ +# CodeCompanion Revamp Plan + +## Context + +- Current: CodeCompanion **19.18.0** via nixpkgs `vimPlugins.codecompanion-nvim` (nixpkgs input lastModified ~2026-05-15). +- Latest: **19.20.0**. Key changes since 19.18.0: + - v19.19.0: `claude-sonnet-5` support, async/dynamic model fetching, copilot `top_p` fixes, inline orphaned-keymap fix, background command deregistration. + - v19.20.0: `gemini_interactions` adapter, PDF support for http adapters (`/file` on Anthropic/Copilot/OpenAI/OpenRouter), env vars from files, prompt-library items auto-receive default rule groups, copilot schema options removed. +- **Bug in current setup:** config sets `model = "claude-sonnet-5"` but 19.18.0 predates its support; the model name may not resolve. The nixpkgs bump fixes this. +- Files involved: + - `plugin/24_completion.lua` — `get_codecompanion_config()`, setup, blink integration. + - `plugin/10_keymap.lua` — `a*` keymaps (lines 69–84, contains duplicates/commented cruft). + - `overlays/plugins.nix` — codecompanion overlay (nvimSkipModules); likely unchanged. + - `modules/module/specs/plugins.nix` — plugin spec; unchanged. + +## Decisions (confirmed with user) + +1. **Version bump:** `nix flake update nixpkgs` (accepts wider plugin bump; do NOT override src). +2. **Adopt:** agent-mode keymaps, one workflow prompt, minor slash-command config (`/share` token). **No MCP.** +3. **Models:** `claude-sonnet-5` (copilot) for **chat only**; cheaper copilot model for **inline** and **background** (title generation) — use `gpt-5-mini` as placeholder; verify exact model id via `ga` model picker in a chat buffer after the bump and adjust. +4. **Codex (ChatGPT Edu) = heavy agent lane.** Copilot stays the default chat adapter; Codex ACP is launched explicitly for heavy autonomous tasks. Auth via ChatGPT login only (no API key) — user's existing `auth_method = "chatgpt"` is correct per the v19.20.0 adapter source (`"openai-api-key"|"codex-api-key"|"chatgpt"`; the docs page comment `"chat-gpt"` is stale). + +## Tasks + +1. **Bump nixpkgs** + - Run `nix flake update nixpkgs`. + - Verify: `nix eval --raw nixpkgs#vimPlugins.codecompanion-nvim.version` reports ≥ 19.20.0. + - Rebuild the wrapped Neovim per this repo's normal build (`nix build` / the repo's usual package target) and smoke-test that the editor starts. + +2. **`plugin/24_completion.lua` — `get_codecompanion_config()`** + - Keep `interactions.chat.adapter = { name = "copilot", model = "claude-sonnet-5" }`. + - Change `interactions.inline.adapter` to `{ name = "copilot", model = "gpt-5-mini" }` (cheap/fast). + - Add a background adapter so title generation doesn't use Sonnet: set the background chat adapter to copilot/`gpt-5-mini` (config lives under `interactions.background.chat`; confirm exact key against `:h codecompanion` / `:checkhealth codecompanion` after the bump — the docs page "Generating Titles" says an adapter must be configured for background interactions). + - Keep existing context_management, rules, display, shared keymaps unchanged. + - Add `interactions.chat.slash_commands["share"].opts.token = os.getenv("GITHUB_GIST_TOKEN")`. + - **Prompt library additions** (keep `expert`, `fixer`, `suggest`): + - `["agent"]`: interaction "chat", alias `agent`, first user prompt starts with `@{agent}` plus selected code block, so Copilot/Claude gets the file-editing tool group (read_file, insert_edit_into_file, grep_search, run_command, etc., with approvals). + - `["tdd"]` (name flexible): interaction "chat", `opts = { is_workflow = true, alias = "tdd" }` — 3-stage workflow: (1) plan/understand `#buffer`, (2) `@{agent}` implement, (3) `@{run_command}` run the test suite (leverages run_command's test-flag for agentic workflows). + +3. **`plugin/10_keymap.lua` — clean up + add** + - Remove duplicated/commented lines (70–71, 77, 81). + - Add: + - `nmap_leader("aa", "CodeCompanionChat", "Agent chat (use @{agent})")` or directly `CodeCompanion /agent`. + - `xmap_leader("aa", "CodeCompanion /agent", "Agent on selection")`. + - `nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan→implement→test")`. + - `nmap_leader("aC", "CodeCompanion /compact", "Compact chat")` (verify `aC` doesn't clash). + - Keep existing mappings unchanged. + +4. **No changes** to `overlays/plugins.nix` or `modules/module/specs/plugins.nix` unless the new version introduces new lazy-module load failures (re-run the build's check phase; `doCheck = false` is already set on the spec entry). + +5. **Codex ACP lane (ChatGPT Edu)** + - **Resolved prerequisite:** user has installed `codex-acp` at `~/.nix-profile/bin/codex-acp` (verified on PATH 2026-07-24). The preset adapter's default command (`codex-acp`) now works as-is — **no `commands` override in the Lua config**. Fallback only if the binary misbehaves: override with `commands = { default = { "codex", "acp" } }` (requires the codex CLI's built-in `acp` subcommand; verify with `codex acp --help`). + - **Config change (small):** none strictly required — the existing `extend("codex", { defaults = { auth_method = "chatgpt" } })` block in `plugin/24_completion.lua` is correct. Keep it. + - Auth: prereq is an active `codex login` session with the ChatGPT Edu account (`~/.codex/auth.json` exists — user confirms validity; if expired, re-run `codex login`, browser/ChatGPT-app flow, no API key). + - Keep codex on-demand (do NOT make it the default chat adapter): + - Keep `ak` (`:CodeCompanionChat adapter=codex`). + - Add comment documenting ACP-only slash commands for this lane: `/resume` (restore past codex session, fresh chat only), `/mode` (switch agent mode), `/command`, `/acp_session_options`, plus `\`-triggered ACP command completion in the chat buffer. + - Do not set a default codex model in config; pick per-session via `ga` / `/acp_session_options` (avoids hardcoding model ids that change with the Edu plan). + - PATH note: Neovim must inherit a PATH containing `~/.nix-profile/bin` so the spawned `codex-acp` resolves — true when nvim is launched from the user's normal shell; call it out if validation fails with "command not found". + +6. **Docs/habit notes** (add as comment block above the codecompanion setup, no separate docs file): `/compact`, `/fork`, `/symbols`, `/share`, `/resume`+`/mode` (codex ACP), `gm` (btw), `gty` (YOLO), `gba`/`gbd` (buffer sync), `gd` (debug window). + +## Validation + +- `nix flake update nixpkgs` then repo build succeeds. +- In Neovim: `:checkhealth codecompanion` clean. +- `:CodeCompanionChat`, press `ga` → copilot adapter lists `claude-sonnet-5`; send a trivial message and confirm response + auto title generation. +- In chat: type `@` → completion shows `agent`, `files`, `memory`, etc.; run `@{agent}` task on a scratch repo and confirm the approval prompt flow works. +- `:CodeCompanion /tdd` starts the workflow stages in order. +- Inline: visual-select code, `:CodeCompanion` prompt → confirm diff shows and `ga`/`gr` accept/reject still work on the cheap model. +- `:CodeCompanion /share` prompts/errors sensibly if `GITHUB_GIST_TOKEN` unset. +- **Codex lane:** `ak` opens a chat with the codex ACP adapter; the spawned `codex-acp` process initializes without auth errors (ChatGPT method), a trivial prompt gets a response, and `/mode` lists codex session modes. If it fails with command-not-found, check that Neovim inherited `~/.nix-profile/bin` in PATH (`:echo $PATH` inside nvim). + +## Risks + +- `nix flake update nixpkgs` bumps **all** vimPlugins and Neovim itself; other plugins may break. If the blast radius is too large, fall back to overriding codecompanion `src` to tag `v19.20.0` in `overlays/plugins.nix` (fetchFromGitHub, `doCheck = false`). +- Model ids (`claude-sonnet-5`, `gpt-5-mini`) must match the copilot adapter's choices post-bump; verify via `ga` picker and adjust literals. +- `gpt-5-mini` may not support tool use on copilot — fine, since inline/background don't use tools. +- `codex-acp` lives in the user's nix profile (outside this flake). If the profile is rebuilt/removed, the codex lane breaks — long-term consider adding `codex-acp` to this flake's runtime deps so it's pinned with the rest of the setup (optional follow-up, not required now). +- ChatGPT Edu accounts authenticate codex via browser/ChatGPT-app login; token expiry will surface as ACP auth errors → re-run `codex login`. + +## Out of scope + +- MCP server integration. +- Custom rules parsers, custom tools, extensions (mcphub/history/vectorcode). diff --git a/.kilo/plans/1784858731550-nix-neovim-review.md b/.kilo/plans/1784858731550-nix-neovim-review.md new file mode 100644 index 0000000..d0f0c63 --- /dev/null +++ b/.kilo/plans/1784858731550-nix-neovim-review.md @@ -0,0 +1,191 @@ +# Neovim + Nix setup review + +Scope: `flake.nix`, `overlays/`, `modules/`, `plugin/*.lua`, `lua/*`, `ftplugin/*`, CI/workflows, and cross-check against the pinned versions in `flake.lock` (nixpkgs weekly `241313f4`). + +## Pinned package versions (from current lock) +- neovim `0.12.2` +- blink-cmp `1.10.2` +- codecompanion-nvim `19.13.0` +- nvim-treesitter `0.10.0-unstable-2026-04-03` (main-branch rewrite) +- nvim-treesitter-textobjects `0-unstable-2026-04-07` (main-branch rewrite) +- copilot-lua `2.0.3` +- render-markdown-nvim `8.12.0-unstable-2026-05-07` +- zk-nvim `0.4.7-unstable-2026-03-13` +- mini.nvim `0.17.0-unstable-2026-05-12` +- quarto-nvim `2.1.0` +- otter-nvim `2.14.5` +- lspconfig `2.9.0` + +--- + +## CRITICAL: Treesitter is currently non-functional + +### Finding +The nixpkgs `nvim-treesitter` package is the **main-branch rewrite** — there is no `lua/nvim-treesitter/configs.lua`. The whole module in `plugin/20_startup.lua` lines 212-294 is dead code: + +```lua +local ok_configs, configs = pcall(require, "nvim-treesitter.configs") -- fails +``` + +The working tree already removed the fallback `vim.treesitter.start()` FileType autocmd (the "fixed treesitter" commit deleted it). Net result: **no treesitter highlighting, indentexpr, foldexpr, or textobjects are configured**. + +### Impact +- Syntax highlighting falls back to Neovim's regex-only engine. +- All textobject keymaps (`]a`, `[a`, `]f`, `[f`, `]e`/`[e`, `x`/`X`, lsp_interop `lm`) are inert. +- `foldexpr`/`indentexpr` based folding does nothing. + +### Fix direction +Rewrite `plugin/20_startup.lua` for main-branch API: +1. FileType autocmd → `vim.treesitter.start()` (and optionally `indentexpr`/`foldexpr`). +2. `require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true } })` + explicit keymaps using `move.goto_next_start(query, "textobjects")` and `swap.swap_next(...)`. +3. The non-Nix `ensure_installed` block should set `opts.ensure_installed` BEFORE the filter (line 278 reads it before line 286 defines it). + +Also remove duplicate `vim.treesitter.language.register("markdown", ...)` between `plugin/20_startup.lua` and `plugin/21_datascience.lua`. + +--- + +## CodeCompanion version mismatch + +### Finding +Config targets features absent in `19.13.0` (present only in `≥19.19.0`/main): + +| Config usage | Present in 19.13? | Present in main (`v19.20.0`) | +|---|---|---| +| `interactions.chat.opts.context_management.editing` | No (flat `trigger`/`enabled` only) | Yes | +| `interactions.chat.opts.context_management.compaction` | No | Yes | +| `aC` requiring `codecompanion.interactions.chat.context_management.compaction` | **RUNTIME ERROR** | Works | +| `slash_commands.share` (`opts.token = ...`) | No (absent from defaults) | Yes | +| `adapters.acp.codex.defaults.auth_method = "chatgpt"` | Yes | Yes | +| `interactions.chat.adapter = { name = ..., model = ... }` table form | Yes | Yes | + +### Risk +On today's lock (`19.13.0`): `/share` is inert, compaction-only keymap throws module-not-found, and `editing`/`compaction` tuning is silently ignored. On `≥19.19.0`/main: everything works. + +### Decision needed (BLOCKING) +Choose **exactly one**: + +- **A — Upgrade the plugin pin** (`overlays/plugins.nix` fetch to `v19.20.0` or `main`, bump the lock, and keep the current config). Riskiest change but matches what the config is written for. +- **B — Downgrade config call sites** to match `19.13.0`: remove `slash_commands.share`, remove `editing`/`compaction` keys (or collapse to flat `trigger: 0.75`), remove or gate `aC` on "newer" version. +- **C — Version-gate the config**: keep current code, read the installed version at startup and skip the new features when < 19.19. + +I recommend A (the config clearly intends to track upstream main-ish behavior; `nix flake update` already bumped it to `19.18.0` in a prior session and you only reverted because the nightly was still too old — `v19.20.0` is now available). + +--- + +## DEAD / DUPLICATE code + +- `plugin/20_startup.lua` — the entire `configs.setup(opts)` block is dead (see Treesitter finding above). +- `plugin/10_keymap.lua`: + - `nmap_leader('od', 'Neogen', ...)` — `neogen` plugin is **not shipped** in `specs/plugins.nix`. Mapping errors on press. + - `nmap_leader('fp', 'Pick projects', ...)` — `MiniExtra.pickers.projects` doesn't exist in mini.extra; would error. + - `nmap_leader('oS', 'lua Config.insert_section()', ...)` — `Config.insert_section` is never defined. + - `vim.lsp.buf.definition()` is bound to `grd`; Neovim 0.11+ defaults include `gr` aliases. Cosmetic, but `]d`/`[d` exist on modern LSP config and would be more idiomatic. +- `plugin/23_editor.lua`: + - `my_styler` formatter (calls `R -s -e styler::...`) is defined but never referenced — dead. +- `plugin/24_completion.lua`: + - `providers.cmp_r` is defined in the blink source list but never enabled in `default` or `per_filetype`; inert. + - `BLINK_VERSION = "v1.4.1"` — only consulted in the non-Nix install path; nixpkgs is `1.10.2`. Pin it to current or drop. + - `get_blink_fuzzy_setting().prebuilt_binary = { force_version = BLINK_VERSION }` — singular key is wrong; blink option is `fuzzy.prebuilt_binaries` (plural). Being skipped in-Nix anyway, but still wrong key. +- `modules/module/specs/plugins.nix` — `specs.utils-lazy` ships `nvim-dap*` and `nvim-lint` but there is zero config/tooling that references them in this repo. +- `ftplugin/quarto.lua`: + - Second top-level `require('quarto').setup()` with **no args** runs _after_ `21_datascience.lua`'s setup and **resets** it to defaults (no `lspFeatures`, no `codeRunner`). + - Top-level `require('quarto')` at FileType load also crashes if the quarto plugin isn't installed (e.g. cats off and a `.qmd`/`.quarto` file is opened): Neovim detects `quarto` ft natively in 0.10+. +- `plugin/00_options.lua`: + - Lines 146-163: two back-to-back FileType autocmds that both remove `r`/`o` from `formatoptions`. The second references an undefined `augroup` variable (nil → autocmd is global, happens to still work). + +--- + +## STALE / WRONG options + +- `plugin/10_keymap.lua` line 174: + ```lua + require("conform").format({ lsp_fallback = true }) + ``` + `lsp_fallback` is the **deprecated** boolean form of `conform.nvim`; should be `lsp_format = "fallback"` (the form already used in `plugin/23_editor.lua` and `ftplugin/python.lua`). In newer conform this may warn or error. +- `ftplugin/quarto.lua`: + - Sets `RDSendLine` and R-style keymaps on _all_ quarto buffers, including Python/Julia chunks. This collides with quarto-runner mappings set in `21_datascience.lua`. Should gate on `vim.bo.filetype == "r"` (the ftplugin already imports quarto.runner for python, but the R keys leak). +- `.github/dependapot.yml` — filename typo. GitHub expects `dependabot.yml`; Dependabot won't run. +- `.github/workflows/check.yml`: + - `nix develop` without a `-c` command just launches an interactive shell. In CI it does nothing useful (or hangs). Replace with `nix develop -c echo ok` or drop. + - Path filter `'modules'` only matches the root directory itself; should be `modules/**` (or `'modules/**'`). + - Changing `plugin/`, `lua/`, `overlays/`, `ftplugin/`, etc. does **not** trigger CI. +- `flake.nix` / `.envrc` shellHook: + ```sh + export R_LIBS_SITE=$(strings "$(command -v R)" | grep -oP '/nix/store/[^:]+/library' ...) + ``` + `grep -oP` (PCRE) is **not available in macOS BSD grep**. On `aarch64-darwin` this silently fails → `R_LIBS_SITE` is empty. Either depend on `ripgrep` regex (`strings ... | rg -o '...'`) or use `gsed` (GNU sed). +- `overlays/plugins.nix` — `zk-nvim` skip list contains `zk.pickers.fzf_lua`; current zk-nvim package likely doesn't load that regardless, but harmless. +- `modules/module/settings/core.nix` — `config.settings.nvim_lua_env` references `lp.tiktoken_core` but `tiktoken_core` is **not in `catPkgs.general` or anywhere else** in these files. If there's an extra Lua/tiktoken module, fine; otherwise this option is placeholder dead code. +- `modules/module/settings/hosts.nix` — host `m` (marimo) is defined with `enable = false` but also configured with `package`, `argv0`, `addFlag`. Dead block. + +--- + +## PORTABILITY / DARWIN issues + +- Shell hook `grep -oP` (above) fails on macOS. +- `mkdir -p "$R_LIBS_USER"` fine; but `command -v R` on macOS returns the wrapper; the wrapper path injection still works. + +--- + +## DEAD WEIGHT (shipped but unused) + +- `catPkgs.r` includes `pkgs.rnvimserver` — `rnvimserver` is needed by R.nvim only when using the _socket_ transport; `21_datascience.lua` only uses vim-slime. Acceptable, but `rnvimserver` adds to build time. Include if you actually use it? Currently not used. +- `specs.utils-lazy` ships `nvim-dap`, `nvim-dap-ui`, `nvim-dap-virtual-text`, `nvim-lint` — none of these are referenced anywhere in `plugin/` or `lua/`. Consider moving them to a devShell-only cat, or drop them. +- `.gitignore` `*.R` — prevents tracking any new `.R` files. `tests/test.R` is already committed so it isn't actively harmful, but it's surprising for a repo whose default cats include R. +- `.commandcode/` dir is listed in `.gitignore` but is in the worktree; fine, but worth cleaning up if it's an artifact. + +--- + +## IMPROVEMENTS given updated packages + +- `catPkgs.markdown` — add `marksman` (it's the LSP used by `render-markdown.nvim` wiki links and configured in LSP). Same binary is needed by `render-markdown` for wiki link ISP. +- `vim.lsp.enable` servers configured but binaries missing in PATH: + - `marksman` (LSP + render-markdown wiki integration). + - `r_ls` (new R language server — package name in nixpkgs is likely `r-languageserver` or `r_ls`; current `catPkgs.r` doesn't ship either). + - `clangd` — not required for this data-science setup; either drop or add to `external`. + - `julials` — require `LanguageServer.jl` in `settings.lang_packages.julia` for it to be useful. +- `blink.cmp` is `1.10.2` but config pins `BLINK_VERSION = "v1.4.1"` for the non-Nix path. Either drop the `BLINK_VERSION` constant (let MiniDeps track HEAD or the tag pinned in flake) or update it to `v1.10.2`. +- `conform.nvim` already uses `lsp_format = "fallback"` in `23_editor.lua` and `ftplugin/python.lua`, but `10_keymap.lua` still calls the deprecated `lsp_fallback = true`. Align to `lsp_format = "fallback"`. +- `.github/workflows/check.yml` — add path filters for `plugin/**`, `lua/**`, `overlays/**`, `ftplugin/**`, `modules/**`. +- `tests/init.lua` smoke-test and `tests/test.R` are present but **not wired into `nix flake check`**. Add a trivial check that runs `lua tests/init.lua` via `nix-shell -A ...`. + +--- + +## CONFLICTS + +- `up` (terminal bracketed paste toggle in `10_keymap.lua`) conflicts with `mini.basics.mappings.option_toggle_prefix = "u"` (paste toggle), which is set up later in `20_startup.lua` via `now()`. Result: the user's `up` mapping gets overwritten. Portable workaround: remap to `tp` (terminal namespace) and keep `tb` as alternative — both already mapped in 10_keymap. +- `ftplugin/quarto.lua` resets quarto-nvim config (kills `lspFeatures`/`codeRunner` set in `21_datascience.lua`) and injects R plug mappings into non-R quarto chunks. + +--- + +## CLEAN summary for implementer + +### Immediate (not version-dependent) +1. Restore treesitter highlighting + textobjects in `plugin/20_startup.lua` using main-branch API. +2. Remove duplicate `vim.treesitter.language.register` in `21_datascience.lua`. +3. Fix `plugin/10_keymap.lua`: remove/resolve `Neogen`, `Pick projects`, `Config.insert_section` dangling mappings. +4. Replace deprecated `lsp_fallback = true` with `lsp_format = "fallback"` in `plugin/10_keymap.lua`. +5. Gate `ftplugin/quarto.lua` to not reset config and to not leak R keys into non-R chunks; guard against missing `quarto` plugin. +6. Remove duplicate `formatoptions` autocmds (or at least dedupe). +7. Dedupe `RNVIM_COMPLDIR` / `TMPDIR` setup (do it in one place). +8. Rename `.github/dependapot.yml` to `.github/dependabot.yml`. +9. Remove or gate dead host `m` in `hosts.nix`. +10. Remove unused `rsplit`? no, irrelevant. +11. Add `marksman` to `catPkgs.markdown` and remove orphan LSP entries or ship the binaries. +12. Remove dead `conform` formatter `my_styler` and dead blink `cmp_r` provider, or wire them up. + +### Version-dependent (BLOCKED) +- **Reset the CodeCompanion version decision**: either pin to `v19.20.0` (keep config, fix `slash_commands.share`, drop the `aC` compatibility shim, keep `editing`/`compaction`) OR downgrade config to match `19.13.0` (remove `share`, collapse `context_management`). + +### CI / packaging +- Fix `nix develop` usage and path filters in `.github/workflows/check.yml`. +- Replace `grep -oP` in `flake.nix` shellHook with portable `rg -o` or add `gnused` to `catPkgs.always` and use `gsed`. +- Wire `tests/init.lua` into `flake check`. + +### Verification +- `nix flake check` on both `aarch64-darwin` and `x86_64-linux`. +- `nix build .#packages..default` succeeds (already does). +- Start nvim, verify :TSContext works, treesitter highlighting is on, and `]f`/`[f` textobjects move. +- Open a `.qmd` file with cats off → no ftplugin crash. +- Confirm `up` still toggles bracketed paste. +- Confirm `:Pick projects` and `:Neogen` and `Config.insert_section` no longer error (or are mapped to valid handlers). diff --git a/flake.lock b/flake.lock index 21dc5ef..17fe244 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1776413584, - "narHash": "sha256-xqqv46MTveuT4yJH2YihmbHGy5mdLnnLFDebVmUws/E=", + "lastModified": 1781337902, + "narHash": "sha256-QjXxUvpOBbnlpSskBOZJ9nUbpYyFRD4cBo4s1aHniGs=", "owner": "dwinkler1", "repo": "fran", - "rev": "da09626e4dd8f0f57078b3a04e0443a8c20defa1", + "rev": "30ab141871ede23b245a90afea58e076ef83b515", "type": "github" }, "original": { @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778869304, - "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "lastModified": 1784796856, + "narHash": "sha256-wWFrV5/Qbm+lyt5x20E/bSbfJiGKMo4RCxZV8cl/WZI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "rev": "e2587caef70cea85dd97d7daab492899902dbf5d", "type": "github" }, "original": { @@ -60,16 +60,16 @@ "rnvimsrc": "rnvimsrc" }, "locked": { - "lastModified": 1781392459, - "narHash": "sha256-9CMG+trBd9AX7cWiGPrMr0eJwuZaql+pj2WVGyT417I=", + "lastModified": 1784867506, + "narHash": "sha256-5zXVJLG7+Gi9sgb4Dn7P6O/ywQQxpvcjjoHSJXZEuVo=", "owner": "dwinkler1", "repo": "r_nvim_nix", - "rev": "03d7872d7702db37ede41f3c6e4d4ce4c129e862", + "rev": "1d26452e3c940153271fc57fbad0b295fcb0e04f", "type": "github" }, "original": { "owner": "dwinkler1", - "ref": "v0.99.5", + "ref": "v1.0.0", "repo": "r_nvim_nix", "type": "github" } @@ -93,16 +93,16 @@ "rnvimsrc": { "flake": false, "locked": { - "lastModified": 1780759435, - "narHash": "sha256-VxgKMOP1hseQre3cas2dmMXZu4PVyl05INla2OdHTU4=", + "lastModified": 1783264911, + "narHash": "sha256-FywUL3mV2+kfu+rO6uUFyUv80EflbdgSkYuSnW965UE=", "owner": "R-nvim", "repo": "R.nvim", - "rev": "6ca306191531c3e3d501ee1609c84a5d29059386", + "rev": "c56ebe0f8445e251673981c40ac2d74659ecd6ed", "type": "github" }, "original": { "owner": "R-nvim", - "ref": "v0.99.5", + "ref": "v1.0.0", "repo": "R.nvim", "type": "github" } @@ -124,11 +124,11 @@ ] }, "locked": { - "lastModified": 1779297405, - "narHash": "sha256-VFoBwH7ZjVxCnvZTb5ODRXt70sLtWMxstive0N+RS50=", + "lastModified": 1782135443, + "narHash": "sha256-vAmbArdCyjqpVW+37aCy/PMBOLIqukUXLQuEKLwUhA4=", "owner": "BirdeeHub", "repo": "nix-wrapper-modules", - "rev": "e7ed7a1205945befdf2e0d73ba7df91d935e5af1", + "rev": "6e7f66fa2cdf4d63162580b438f7fcf87c28a46f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index d8c0018..a8584e0 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ rixpkgs.url = "github:dwinkler1/rixpkgs/nixpkgs"; r-nvim-nix = { - url = "github:dwinkler1/r_nvim_nix/v0.99.5"; + url = "github:dwinkler1/r_nvim_nix/v1.0.0"; inputs = { nixpkgs.follows = "rixpkgs"; }; @@ -69,7 +69,7 @@ '' + nixpkgs.lib.optionalString (config.cats.r or false) '' export R_HOME=$(R RHOME) - export R_LIBS_SITE=$(strings "$(command -v R)" | grep -oP '/nix/store/[^:]+/library' | sort -u | paste -sd: -) + export R_LIBS_SITE=$(strings "$(command -v R)" | rg -o '/nix/store/[^:]+/library' | sort -u | paste -sd: -) export R_LIBS_USER="$PWD/.r-libs" mkdir -p "$R_LIBS_USER" ''; @@ -224,6 +224,12 @@ fi echo "Downstream override assertions passed" > $out ''; + lua-test = pkgs.runCommand "lua-test" { + buildInputs = [ pkgs.neovim-unwrapped ]; + } '' + nvim --headless -u NONE -c "set runtimepath+=${./.}" -l ${./tests/init.lua} + touch $out + ''; } ); diff --git a/ftplugin/quarto.lua b/ftplugin/quarto.lua index 7ee10a0..b737644 100644 --- a/ftplugin/quarto.lua +++ b/ftplugin/quarto.lua @@ -1,23 +1,28 @@ -local quarto = require('quarto') -quarto.setup() -vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true }) +local quarto_ok, quarto = pcall(require, 'quarto') +if quarto_ok then + vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true, buffer = true }) +end -vim.keymap.set("n", "", "RDSendLine", { buffer = true }) -vim.keymap.set("v", "", "RSendSelection", { buffer = true }) +if vim.bo.filetype == "r" then + vim.keymap.set("n", "", "RDSendLine", { buffer = true }) + vim.keymap.set("v", "", "RSendSelection", { buffer = true }) --- Assignment operator (--) -vim.keymap.set("i", "--", "lua MiniTrailspace.trim()RInsertAssign", { buffer = true, noremap = true }) + -- Assignment operator (--) + vim.keymap.set("i", "--", "lua MiniTrailspace.trim()RInsertAssign", { buffer = true, noremap = true }) --- Pipe operator (;;) -vim.keymap.set("i", ";;", "lua MiniTrailspace.trim()RInsertPipe", { buffer = true, noremap = true }) + -- Pipe operator (;;) + vim.keymap.set("i", ";;", "lua MiniTrailspace.trim()RInsertPipe", { buffer = true, noremap = true }) +end -local runner = require("quarto.runner") -vim.keymap.set("n", "a", runner.run_cell, { desc = "run cell", silent = true }) -vim.keymap.set("n", "A", runner.run_all, { desc = "run all cells", silent = true }) -vim.keymap.set("n", "RA", function() - runner.run_all(true) -end, { desc = "run all cells of all languages", silent = true }) +local runner_ok, runner = pcall(require, "quarto.runner") +if runner_ok then + vim.keymap.set("n", "a", runner.run_cell, { desc = "run cell", silent = true, buffer = true }) + vim.keymap.set("n", "A", runner.run_all, { desc = "run all cells", silent = true, buffer = true }) + vim.keymap.set("n", "RA", function() + runner.run_all(true) + end, { desc = "run all cells of all languages", silent = true, buffer = true }) +end diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index b92c331..2709ca0 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -43,6 +43,7 @@ in python313Packages.pylatexenc quartoPkg zk + marksman ]); nix = maybe "nix" (with pkgs; [ @@ -99,13 +100,12 @@ in quartoPkg pkgs.air-formatter pkgs.yaml-language-server - pkgs.rnvimserver ]; # cats without packages get empty lists general = [ ]; gitPlugins = [ ]; - treesitterParsers = [ pkgs.tree-sitter ]; + treesitterParsers = [ ]; utils = [ ]; }; } diff --git a/modules/module/settings/core.nix b/modules/module/settings/core.nix index 822fc53..bebc487 100644 --- a/modules/module/settings/core.nix +++ b/modules/module/settings/core.nix @@ -14,10 +14,6 @@ # Enable RC wrapping (allows neovim to find the config) config.settings.wrapRc = lib.mkDefault true; - # Lua packages available to neovim (for :lua require()) - config.settings.nvim_lua_env = lp: - lib.optionals (config.cats.general or false) [ lp.tiktoken_core ]; - # Binary name for the wrapper config.binName = lib.mkDefault "vv"; diff --git a/modules/module/settings/env.nix b/modules/module/settings/env.nix index c7a1a8b..75ac273 100644 --- a/modules/module/settings/env.nix +++ b/modules/module/settings/env.nix @@ -11,10 +11,9 @@ UV_PYTHON_DOWNLOADS = "never"; UV_PYTHON = pkgs.python.interpreter; }) - (lib.mkIf (config.cats.r or false) { - RNVIM_COMPLDIR = "$PWD/.r-compl"; - TMPDIR = "$PWD/.r-tmp"; - }) + # R.nvim v1.x owns its cache and temporary directories and exports + # RNVIM_COMPLDIR/RNVIM_TMPDIR during setup. Do not inject literal `$PWD` + # values into the wrapper environment. ]; # Environment variables with defaults (can be overridden by user) diff --git a/modules/module/settings/hosts.nix b/modules/module/settings/hosts.nix index aadd70f..2db1515 100644 --- a/modules/module/settings/hosts.nix +++ b/modules/module/settings/hosts.nix @@ -22,16 +22,6 @@ in nvim-host.flags."--neovim-bin" = "${builtins.placeholder "out"}/bin/${config.binName}"; }; - m = { - nvim-host.enable = false; - nvim-host.package = "${pkgs.uv}/bin/uv"; - nvim-host.argv0 = "uv"; - nvim-host.addFlag = [ - "run" - "marimo" - "edit" - ]; - }; } (lib.mkIf (config.cats.julia or false) { jl = { diff --git a/plugin/00_options.lua b/plugin/00_options.lua index e824333..16f3832 100644 --- a/plugin/00_options.lua +++ b/plugin/00_options.lua @@ -128,27 +128,20 @@ local diagnostic_opts = { -- Don't update diagnostics when typing update_in_insert = false, } -later(function() vim.diagnostic.config(diagnostic_opts) end) +later(function() + vim.diagnostic.config(diagnostic_opts) - --- Custom autocommands ======================================================== -local augroup = vim.api.nvim_create_augroup('CustomSettings', {}) - -vim.api.nvim_create_autocmd('FileType', { - pattern = { 'markdown' }, - group = augroup, - callback = function() - vim.diagnostic.config({ - signs = { - severity = { min = 'WARN', max = 'ERROR' } - }, - virtual_text = { - current_line = false, - severity = { min = 'HINT', max = 'ERROR' } - } - }) - end -}) + -- Relax diagnostics for markdown (many false positives) + vim.api.nvim_create_autocmd('FileType', { + pattern = { 'markdown' }, + callback = function() + vim.diagnostic.config({ + signs = { severity = { min = 'WARN', max = 'ERROR' } }, + virtual_text = { severity = { min = 'HINT', max = 'ERROR' }, current_line = false }, + }) + end, + }) +end) vim.api.nvim_create_autocmd("FileType", { desc = "remove formatoptions", @@ -157,17 +150,6 @@ vim.api.nvim_create_autocmd("FileType", { vim.b.minitrailspace_disable = true -- Don't highlight trailing space by default end, }) - -vim.api.nvim_create_autocmd('FileType', { - group = augroup, - callback = function() - -- Don't auto-wrap comments and don't insert comment leader after hitting 'o' - -- If don't do this on `FileType`, this keeps reappearing due to being set in - -- filetype plugins. - vim.cmd('setlocal formatoptions-=r formatoptions-=o') - end, - desc = [[Ensure proper 'formatoptions']], -}) -- Neovide ============================================== if vim.g.neovide then vim.g.neovide_cursor_vfx_mode = "pixiedust" @@ -176,7 +158,7 @@ if vim.g.neovide then vim.g.neovide_cursor_short_animation_length = 0 vim.g.neovide_font_hinting = 'none' vim.g.neovide_font_edging = 'subpixelantialias' - vim.o.guifont = 'JetBrainsMono Nerd Font,Symbols Nerd Font:h14:#e-subpixelantialias:#h-none' + vim.o.guifont = 'JetBrainsMono Nerd Font:h14:#e-subpixelantialias:#h-none' vim.g.neovide_floating_corner_radius = 0.35 vim.keymap.set("n", "nf", "NeovideFullscreen", { desc = "Toggle Neovide Fullscreen" }) end diff --git a/plugin/01_lib.lua b/plugin/01_lib.lua index 1197ee8..64bd37e 100644 --- a/plugin/01_lib.lua +++ b/plugin/01_lib.lua @@ -12,7 +12,7 @@ end Config.log = {} Config.log_buf_id = Config.log_buf_id or nil -Config.start_hrtime = Config.start_hrtime or vim.loop.hrtime() +Config.start_hrtime = Config.start_hrtime or vim.uv.hrtime() Config.log_print = function() if Config.log_buf_id == nil or not vim.api.nvim_buf_is_valid(Config.log_buf_id) then @@ -24,7 +24,7 @@ end Config.log_clear = function() Config.log = {} - Config.start_hrtime = vim.loop.hrtime() + Config.start_hrtime = vim.uv.hrtime() vim.cmd('echo "Cleared log"') end diff --git a/plugin/10_keymap.lua b/plugin/10_keymap.lua index 5682b93..8ae0a1c 100644 --- a/plugin/10_keymap.lua +++ b/plugin/10_keymap.lua @@ -66,21 +66,28 @@ nmap_leader('', 'bnext', 'Next buffer') nmap_leader('', 'bprev', 'Prev buffer') -- a is for 'AI' +nmap_leader("aa", "CodeCompanion /agent", "Agent chat (@{agent} tools)") nmap_leader("ac", "CodeCompanionChat Toggle", "Chat Toggle") --- nmap_leader("ae", "CodeCompanion /explain", "Explain Code") --- nmap_leader("af", "CodeCompanion /fix", "Fix Code") +nmap_leader("aC", function() + local chat = require("codecompanion").last_chat() + if not chat then + return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN) + end + require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 }) +end, "Compact chat") nmap_leader("ag", "CodeCompanion /commit", "Generate commit message") nmap_leader("ai", "CodeCompanionActions", "Chat Action") nmap_leader("al", "CodeCompanion /lsp", "Explain LSP Diagnostics") nmap_leader("an", "CodeCompanionChat Add", "Chat New") nmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") ---nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") +nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan, implement, test") nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") +xmap_leader("aa", "CodeCompanion /agent", "Agent on selection") xmap_leader("ae", "CodeCompanion /explain", "Explain Code") xmap_leader("af", "CodeCompanion /fix", "Fix Code") ---xmap_leader("ap", "CodeCompanion /expert", "Code Fixer") -xmap_leader("ap", "CodeCompanion /expert", "Code Fixer") +xmap_leader("ap", "CodeCompanion /expert", "Code Expert") xmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") +nmap_leader("ak", "CodeCompanionChat adapter=codex", "Chat with Codex") -- b is for 'buffer' nmap_leader('bb', 'b#', 'Alternate') @@ -123,7 +130,7 @@ nmap_leader('fm', 'Pick marks', 'Marks') nmap_leader('fn', 'ZkNotes', "Notes") nmap_leader('fk', 'Pick keymaps', 'Keymaps') nmap_leader('fR', 'Pick resume', 'Resume') -nmap_leader('fp', 'Pick projects', 'Projects') +nmap_leader('fp', 'Pick files', 'Files') nmap_leader('fq', 'Pick list scope="quickfix"', 'Quickfix') nmap_leader('fr', 'Pick lsp scope="references"', 'References (LSP)') nmap_leader('flr', 'Pick lsp scope="references"', 'References (LSP)') @@ -164,7 +171,7 @@ vim.keymap.set({ 'n' }, 'grk', 'lua vim.lsp.buf.hover()', { desc = 'Doc vim.keymap.set({ 'n' }, 'gre', 'lua vim.diagnostic.open_float()', { desc = 'Diagnostics' }) nmap_lsp("K", 'lua vim.lsp.buf.hover()', "Documentation") -local formatting_cmd = 'lua require("conform").format({ lsp_fallback = true })' +local formatting_cmd = 'lua require("conform").format({ lsp_format = "fallback" })' nmap_leader('la', 'lua vim.lsp.buf.code_action()', 'Actions') nmap_leader('le', 'lua vim.diagnostic.open_float()', 'Diagnostics popup') nmap_leader('lf', formatting_cmd, 'Format') @@ -189,11 +196,9 @@ nmap_leader('Lx', 'lua Config.execute_lua_line()', 'Execute `lua` line' -- o is for 'other' local trailspace_toggle_command = 'lua vim.b.minitrailspace_disable = not vim.b.minitrailspace_disable' -nmap_leader('od', 'Neogen', 'Document') nmap_leader('oh', 'normal gxiagxila', 'Move arg left') nmap_leader('ol', 'normal gxiagxina', 'Move arg right') nmap_leader('or', 'lua MiniMisc.resize_window()', 'Resize to default width') -nmap_leader('oS', 'lua Config.insert_section()', 'Section insert') nmap_leader('ot', 'lua MiniTrailspace.trim()', 'Trim trailspace') nmap_leader('oT', trailspace_toggle_command, 'Trailspace hl toggle') nmap_leader('oz', 'lua MiniMisc.zoom()', 'Zoom toggle') diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index 0404b2c..d17e2f9 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -208,112 +208,31 @@ now_if_args(function() on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching } - - local ok_configs, configs = pcall(require, "nvim-treesitter.configs") - - if ok_configs and configs.setup then - local opts = { - highlight = { enable = true }, - indent = { enable = false }, - textobjects = { - move = { - enable = true, - set_jumps = true, - goto_next_start = { - ["]a"] = "@parameter.inner", -- fixed typo - ["]f"] = "@function.outer", - ["]o"] = "@loop.*", - ["]s"] = { query = "@local.scope", desc = "Next scope" }, - ["]z"] = { query = "@fold", desc = "Next fold" }, - }, - goto_next_end = { - ["]M"] = "@function.outer", - ["]["] = "@class.outer", - }, - goto_previous_start = { - ["[a"] = "@parameter.inner", - ["[f"] = "@function.outer", - ["[o"] = "@loop.*", - ["[s"] = { query = "@local.scope", query_group = "locals", desc = "Prev. scope" }, - ["[z"] = { query = "@fold", query_group = "folds", desc = "Prev. fold" }, - }, - goto_previous_end = { - ["[M"] = "@function.outer", - ["[]"] = "@class.outer", - }, - goto_next = { - ["]e"] = "@conditional.outer", - }, - goto_previous = { - ["[e"] = "@conditional.outer", - }, - }, - swap = { - enable = true, - swap_next = { - ["x"] = "@parameter.inner", - }, - swap_previous = { - ["X"] = "@parameter.inner", - }, - }, - lsp_interop = { - enable = true, - border = "none", - floating_preview_opts = {}, - peek_definition_code = { - ["lm"] = "@function.outer", - ["lM"] = "@class.outer", - }, - }, - }, - } - - - -- Manual parser check for non-Nix users - if not Config.isNixCats then - local installed_check = function(lang) - return #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 - end - local to_install = vim.tbl_filter(installed_check, opts.ensure_installed) - if #to_install > 0 then - require("nvim-treesitter").install(to_install) - end - end - -- Environment-specific Overrides - if not Config.isNixCats then - opts.auto_install = true - opts.ensure_installed = Config.treesitter_helpers.default_parsers - else - opts.auto_install = false - -- Nix handles installation, so ensure_installed is skipped/empty - end - - - configs.setup(opts) - return - end - + local ts_filetypes = { + "c", "cpp", "lua", "nix", "python", "r", "markdown", "query", + "vim", "vimdoc", "yaml", "json", "toml", "rust", "go", + "javascript", "typescript", "tsx", "html", "css", "sql", + "julia", "rnoweb", "latex", "gitcommit", "gitignore", + "git_config", "git_rebase", "diff", "dockerfile", + "make", "xml", "zig", "regex", "csv", "bash", + "markdown_inline", "quarto", "rmd", "codecompanion", + } vim.api.nvim_create_autocmd("FileType", { - pattern = "*", - callback = function(args) - -- Use explicit buffer + filetype to avoid any ambiguity - local ok = pcall(vim.treesitter.start, args.buf, args.match) - vim.bo.syntax = 'on' - vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" - vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()' - vim.wo[0][0].foldmethod = 'expr' + pattern = ts_filetypes, + callback = function(ev) + local lang = ev.match + if vim.treesitter.language.get_lang then + lang = vim.treesitter.language.get_lang(lang) or lang + end + if #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 then + return + end + vim.treesitter.start(ev.buf, lang) + vim.bo[ev.buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" end, }) - -- Textobjects: require plugin and bail out quietly if missing - local ok_nto, nto = pcall(require, "nvim-treesitter-textobjects") - if not ok_nto then - return - end - - vim.g.no_plugin_maps = true - nto.setup({ + require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true, }, @@ -322,70 +241,36 @@ now_if_args(function() local move = require("nvim-treesitter-textobjects.move") local swap = require("nvim-treesitter-textobjects.swap") - -- Map motion function names to actual functions - local move_fns = { - goto_next_start = move.goto_next_start, - goto_next_end = move.goto_next_end, - goto_previous_start = move.goto_previous_start, - goto_previous_end = move.goto_previous_end, - goto_next = move.goto_next, - goto_previous = move.goto_previous, - } + vim.keymap.set("n", "]a", function() move.goto_next_start("@parameter.inner", "textobjects") end, { desc = "Next parameter" }) + vim.keymap.set("n", "[a", function() move.goto_previous_start("@parameter.inner", "textobjects") end, { desc = "Prev parameter" }) + vim.keymap.set("n", "]f", function() move.goto_next_start("@function.outer", "textobjects") end, { desc = "Next function" }) + vim.keymap.set("n", "[f", function() move.goto_previous_start("@function.outer", "textobjects") end, { desc = "Prev function" }) + vim.keymap.set("n", "]o", function() move.goto_next_start("@loop.*", "textobjects") end, { desc = "Next loop" }) + vim.keymap.set("n", "[o", function() move.goto_previous_start("@loop.*", "textobjects") end, { desc = "Prev loop" }) + vim.keymap.set("n", "]s", function() move.goto_next_start("@local.scope", "textobjects") end, { desc = "Next scope" }) + vim.keymap.set("n", "[s", function() move.goto_previous_start("@local.scope", "locals") end, { desc = "Prev scope" }) + vim.keymap.set("n", "]z", function() move.goto_next_start("@fold", "folds") end, { desc = "Next fold" }) + vim.keymap.set("n", "[z", function() move.goto_previous_start("@fold", "folds") end, { desc = "Prev fold" }) + vim.keymap.set("n", "]M", function() move.goto_next_end("@function.outer", "textobjects") end, { desc = "Next function end" }) + vim.keymap.set("n", "][", function() move.goto_next_end("@class.outer", "textobjects") end, { desc = "Next class end" }) + vim.keymap.set("n", "[M", function() move.goto_previous_end("@function.outer", "textobjects") end, { desc = "Prev function end" }) + vim.keymap.set("n", "[]", function() move.goto_previous_end("@class.outer", "textobjects") end, { desc = "Prev class end" }) + vim.keymap.set("n", "]e", function() move.goto_next("@conditional.outer", "textobjects") end, { desc = "Next conditional" }) + vim.keymap.set("n", "[e", function() move.goto_previous("@conditional.outer", "textobjects") end, { desc = "Prev conditional" }) - -- All motions defined in one place - -- spec = { query_or_list, query_group, desc } - local move_maps = { - goto_next_start = { - ["]a"] = { "@parameter.inner", "textobjects", "Next parameter" }, - ["]f"] = { "@function.outer", "textobjects", "Next function start" }, - ["]o"] = { { "@loop.inner", "@loop.outer" }, "textobjects", "Next loop" }, - ["]s"] = { "@local.scope", "locals", "Next scope" }, - ["]z"] = { "@fold", "folds", "Next fold" }, - }, - goto_next_end = { - ["]M"] = { "@function.outer", "textobjects", "Next function end" }, - ["]["] = { "@class.outer", "textobjects", "Next class end" }, - }, - goto_previous_start = { - ["[a"] = { "@parameter.inner", "textobjects", "Previous parameter" }, - ["[f"] = { "@function.outer", "textobjects", "Previous function start" }, - ["[o"] = { { "@loop.inner", "@loop.outer" }, "textobjects", "Previous loop" }, - ["[s"] = { "@local.scope", "locals", "Previous scope" }, - ["[z"] = { "@fold", "folds", "Previous fold" }, - }, - goto_previous_end = { - ["[M"] = { "@function.outer", "textobjects", "Previous function end" }, - ["[]"] = { "@class.outer", "textobjects", "Previous class end" }, - }, - goto_next = { - ["]e"] = { "@conditional.outer", "textobjects", "Next conditional" }, - }, - goto_previous = { - ["[e"] = { "@conditional.outer", "textobjects", "Previous conditional" }, - }, - } + vim.keymap.set("n", "x", function() swap.swap_next("@parameter.inner", "textobjects") end, { desc = "Swap parameter next" }) + vim.keymap.set("n", "X", function() swap.swap_previous("@parameter.inner", "textobjects") end, { desc = "Swap parameter prev" }) - -- Generate motion keymaps - for fn_name, maps in pairs(move_maps) do - local fn = move_fns[fn_name] - if fn then - for lhs, spec in pairs(maps) do - local query_or_list, group, desc = spec[1], spec[2], spec[3] - vim.keymap.set({ "n", "x", "o" }, lhs, function() - fn(query_or_list, group) - end, { desc = desc }) - end + if not Config.isNixCats then + local installed_check = function(lang) + return #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 + end + local default_parsers = Config.treesitter_helpers.default_parsers + local to_install = vim.tbl_filter(installed_check, default_parsers) + if #to_install > 0 then + require("nvim-treesitter").install(to_install) end end - - -- Swap keymaps (unchanged, but minimal) - vim.keymap.set("n", "x", function() - swap.swap_next("@parameter.inner") - end, { desc = "Swap with next parameter" }) - - vim.keymap.set("n", "X", function() - swap.swap_previous("@parameter.inner") - end, { desc = "Swap with previous parameter" }) end) -- zk diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index aeab360..cb496b9 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -5,18 +5,18 @@ local add = Config.add local nix = require('config.nix') if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add now(function() - m_add({ source = "R-nvim/R.nvim" }) + add({ source = "R-nvim/R.nvim" }) end) now_if_args(function() - m_add({ source = "jmbuhr/otter.nvim" }) + add({ source = "jmbuhr/otter.nvim" }) end) later(function() - m_add({ source = "jpalardy/vim-slime" }) + add({ source = "jpalardy/vim-slime" }) end) end @@ -44,12 +44,6 @@ end) -- r now(function() if nix.get_cat("r", false) then - local cwd = vim.fn.getcwd(-1) - vim.env.RNVIM_COMPLDIR = cwd .. "/.r-compl" - vim.env.R_LIBS_USER = (vim.env.R_LIBS_USER or ""):gsub("%$PWD", cwd) - vim.env.TMPDIR = cwd .. "/.r-tmp" - vim.fn.mkdir(vim.env.RNVIM_COMPLDIR, "p") - vim.fn.mkdir(vim.env.TMPDIR, "p") vim.g.rout_follow_colorscheme = true require("r").setup({ -- Create a table with the options to be passed to setup() @@ -62,6 +56,20 @@ now(function() rconsole_height = 20, nvimpager = "split_h", pdfviewer = "", + -- Use R.nvim's built-in rnvimserver-backed language server. Do not + -- configure the external R languageserver through plugin/25_lsp.lua. + r_ls = { + completion = true, + hover = true, + signature = true, + definition = true, + references = true, + implementation = true, + document_symbol = true, + workspace_symbol = true, + document_highlight = true, + rename = true, + }, }) end end) @@ -69,8 +77,6 @@ end) -- Quarto now(function() - vim.treesitter.language.register("markdown", { "quarto", "rmd" }) - if nix.get_cat({ "r", "markdown" }, false) then vim.api.nvim_create_autocmd("FileType", { pattern = { "quarto" }, diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index ed4682a..70bec23 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -4,10 +4,10 @@ local later = MiniDeps.later local nix = require('config.nix') if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add later(function() - m_add({ source = "Bilal2453/luvit-meta" }) - m_add({ source = "folke/lazydev.nvim" }) + add({ source = "Bilal2453/luvit-meta" }) + add({ source = "folke/lazydev.nvim" }) end) end diff --git a/plugin/23_editor.lua b/plugin/23_editor.lua index d4669d8..4345e2d 100644 --- a/plugin/23_editor.lua +++ b/plugin/23_editor.lua @@ -2,10 +2,10 @@ local later = MiniDeps.later local add = Config.add if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add later(function() - m_add("stevearc/conform.nvim") + add("stevearc/conform.nvim") end) end @@ -23,20 +23,6 @@ later(function() rmd = { "injected" }, quarto = { "injected" }, }, - - default_format_opts = { - lsp_format = "fallback", - }, - - formatters = { - my_styler = { - command = "R", - -- A list of strings, or a function that returns a list of strings - -- Return a single string instead of a list to run the command in a shell - args = { "-s", "-e", "styler::style_file(commandArgs(TRUE)[1])", "--args", "$FILENAME" }, - stdin = false, - }, - }, }) end) diff --git a/plugin/24_completion.lua b/plugin/24_completion.lua index 3e0f00c..f6516a1 100644 --- a/plugin/24_completion.lua +++ b/plugin/24_completion.lua @@ -4,7 +4,7 @@ local now = MiniDeps.now local now_if_args = Config.now_if_args -- Constants -local BLINK_VERSION = "v1.4.1" +local BLINK_VERSION = "v1.10.2" -- Plugin sources configuration local PLUGIN_SOURCES = { @@ -26,26 +26,16 @@ local PLUGIN_ADDS = { -- Helper functions local function create_system_prompt(role_description) return function(context) - return "I want you to act as a senior " .. context.filetype .. " developer. " .. role_description + local lang = context.filetype or "programmer" + return "I want you to act as a senior " .. lang .. " developer. " .. role_description end end local function get_code_block(context) - local text = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line) + local text = require("codecompanion.helpers.code").get_code(context.start_line, context.end_line) return "```" .. context.filetype .. "\n" .. text .. "\n```" end -local function create_common_opts(mapping, short_name) - return { - mapping = mapping, - modes = { "v" }, - short_name = short_name, - auto_submit = true, - stop_context_insertion = true, - user_prompt = true, - } -end - local function get_mini_icons_highlight(ctx) local _, hl, _ = require("mini.icons").get("lsp", ctx.kind) return hl @@ -57,7 +47,7 @@ local function get_blink_fuzzy_setting() } if not Config.isNixCats then - setting.prebuilt_binary = { force_version = BLINK_VERSION } + setting.prebuilt_binaries = { force_version = BLINK_VERSION } end return setting @@ -65,10 +55,10 @@ end -- Plugin loading if not Config.isNixCats then - local m_add = MiniDeps.add + local add = MiniDeps.add now_if_args(function() - m_add({ + add({ source = "saghen/blink.cmp", depends = { "rafamadriz/friendly-snippets" }, checkout = BLINK_VERSION, @@ -77,38 +67,91 @@ if not Config.isNixCats then later(function() for _, source in ipairs(PLUGIN_SOURCES) do - m_add({ source = source }) + add({ source = source }) end end) end local function get_codecompanion_config() return { + adapters = { + acp = { + -- Codex = heavy agent lane (ChatGPT Edu login via `codex login`; ~/.codex/auth.json). + -- Requires `codex-acp` on PATH (~/.nix-profile/bin). ACP-only slash commands in the + -- chat buffer: /resume (restore a past session, fresh chat only), /mode (switch agent + -- mode), /command, /acp_session_options (e.g. model per session); `\` triggers ACP + -- command completion (1-5s delay after chat open). + codex = function() + return require("codecompanion.adapters").extend("codex", { + defaults = { + auth_method = "chatgpt", + }, + }) + end, + }, + }, interactions = { chat = { adapter = { name = "copilot", - model = "gemini-3.1-pro-preview", + model = "claude-sonnet-5", + }, + slash_commands = { + ["share"] = { + opts = { + token = os.getenv("GITHUB_GIST_TOKEN"), + }, + }, }, opts = { completion_provider = "blink", + context_management = { + editing = { + trigger = 0.65, + keep_cycles = 3, + exclude_tools = { "memory" }, + }, + compaction = { + trigger = 0.85, + min_token_savings = 10000, + }, + }, }, }, inline = { adapter = { name = "copilot", - model = "gemini-3.1-pro-preview", - } - }, - keymaps = { - accept_change = { - modes = { n = "ga" }, - description = "Accept the suggested change", + model = "gpt-5-mini", }, - reject_change = { - modes = { n = "gr" }, - opts = { nowait = true }, - description = "Reject the suggested change", + }, + shared = { + keymaps = { + accept_change = { + modes = { n = "ga" }, + description = "Accept the suggested change", + }, + reject_change = { + modes = { n = "gr" }, + opts = { nowait = true }, + description = "Reject the suggested change", + }, + }, + }, + background = { + adapter = { + name = "copilot", + model = "gpt-5-mini", + }, + chat = { + callbacks = { + ["on_ready"] = { + actions = { "interactions.background.builtin.chat_make_title" }, + enabled = true, + }, + }, + opts = { + enabled = true, + }, }, }, }, @@ -121,12 +164,38 @@ local function get_codecompanion_config() height = 0.33, }, }, + diff = { + enabled = true, + threshold_for_chat = 6, + }, + }, + rules = { + default = { + description = "Collection of common files for all projects", + files = { + ".clinerules", + ".cursorrules", + ".rules", + ".github/copilot-instructions.md", + "AGENT.md", + "AGENTS.md", + { path = "CLAUDE.md", parser = "claude" }, + { path = "CLAUDE.local.md", parser = "claude" }, + { path = "~/.claude/CLAUDE.md", parser = "claude" }, + }, + }, + opts = { + chat = { + autoload = "default", + enabled = true, + }, + }, }, prompt_library = { ["expert"] = { interaction = "chat", description = "Get expert advice from an LLM", - --opts = create_common_opts("ae", "expert"), + opts = { alias = "expert" }, prompts = { { role = "system", @@ -146,7 +215,7 @@ local function get_codecompanion_config() ["fixer"] = { interaction = "chat", description = "Fix code errors with expert guidance", - --opts = create_common_opts("af", "afixer"), + opts = { alias = "fixer" }, prompts = { { role = "system", @@ -166,7 +235,7 @@ local function get_codecompanion_config() ["suggest"] = { interaction = "chat", description = "Suggest improvements to the buffer", - --opts = create_common_opts("as", "suggest"), + opts = { alias = "suggest" }, prompts = { { role = "system", @@ -188,7 +257,53 @@ local function get_codecompanion_config() }, }, }, - } + ["agent"] = { + interaction = "chat", + description = "Agentic coding with the @{agent} tool group (read/edit/grep/run)", + opts = { alias = "agent" }, + prompts = { + { + role = "user", + content = function(context) + return "@{agent} Work on the following code:\n\n" .. get_code_block(context) .. "\n\n" + end, + opts = { contains_code = true }, + }, + }, + }, + ["tdd"] = { + interaction = "chat", + description = "Workflow: plan the buffer change, implement it, run the tests", + opts = { alias = "tdd", is_workflow = true }, + prompts = { + { + { + role = "user", + content = function(context) + return "Let's work test-driven. First, study #buffer and the relevant parts of the codebase, then propose a concise implementation plan (no code yet).\n\nThe code under discussion:\n\n" + .. get_code_block(context) + .. "\n\nThe task: " + end, + opts = { contains_code = true }, + }, + }, + { + { + role = "user", + content = "Implement the plan now, writing or updating tests alongside the code. @{agent}", + opts = { auto_submit = true }, + }, + }, + { + { + role = "user", + content = "Run the project's test suite with @{run_command} and fix any failures until it passes.", + opts = { auto_submit = true }, + }, + }, + }, + }, + }, } end @@ -244,6 +359,13 @@ later(function() end) +-- CodeCompanion habit notes (chat buffer unless stated): +-- /compact compact history, keep summary /fork fork the conversation +-- /symbols insert symbols for a file /share export chat to a GitHub gist +-- gm toggle "btw" ephemeral message gty YOLO: approve all tool calls +-- gba/gbd buffer sync add/drop gd debug window (adapter/tools info) +-- Codex (ACP) lane: /resume (fresh chat only), /mode, /command, /acp_session_options, +-- `\` ACP command completion. Prompt library: /expert /fixer /suggest /agent /tdd (workflow). later(function() add("codecompanion.nvim") @@ -316,7 +438,10 @@ now_if_args(function() }, snippets = { preset = "mini_snippets" }, sources = { - default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot", "codecompanion" }, + per_filetype = { + codecompanion = { "codecompanion" }, + }, + default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot" }, providers = { path = { score_offset = 50, @@ -335,10 +460,6 @@ now_if_args(function() score_offset = 10, opts = { cmp_name = "cmdline" } }, - cmp_r = { - name = "cmp_r", - module = "blink.compat.source", - }, copilot = { name = "copilot", module = "blink-copilot", diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index f3c4c50..8c40915 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -8,24 +8,14 @@ if not Config.isNixCats then end now_if_args(function() + -- R.nvim owns the R `r_ls` client and starts its bundled rnvimserver from + -- its own plugin directory. Keep R out of this generic server registry. local servers = { - clangd = {}, basedpyright = {}, ruff = {}, marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, - r_ls = { - filetypes = { 'r', 'rmd', 'rmarkdown' }, - settings = { - ['r_ls'] = { - lsp = { - rich_documentation = true, - enable = true, - }, - }, - } - }, julials = { settings = { julia = { From 6db6d131be4fa61c1ac5b01ce17e069912781396 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 05:04:54 +0000 Subject: [PATCH 30/55] Bump actions/checkout from 4 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 2 +- .github/workflows/flakehub-publish-rolling.yml | 2 +- .github/workflows/update.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index fcf1db9..8c670c4 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -29,7 +29,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: wimpysworld/nothing-but-nix@main if: runner.os == 'Linux' with: diff --git a/.github/workflows/flakehub-publish-rolling.yml b/.github/workflows/flakehub-publish-rolling.yml index 57250d0..50ccb47 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@v5" + - uses: "actions/checkout@v7" with: persist-credentials: false - uses: "DeterminateSystems/determinate-nix-action@v3" diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index a65fbdc..bed5d7c 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - uses: wimpysworld/nothing-but-nix@main with: hatchet-protocol: 'carve' From 60b23e42d0a637b442a0d0cc36acc797acdbf05a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 05:04:58 +0000 Subject: [PATCH 31/55] Bump cachix/cachix-action from 14 to 17 Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action) from 14 to 17. - [Release notes](https://github.com/cachix/cachix-action/releases) - [Changelog](https://github.com/cachix/cachix-action/blob/master/RELEASE.md) - [Commits](https://github.com/cachix/cachix-action/compare/v14...v17) --- updated-dependencies: - dependency-name: cachix/cachix-action dependency-version: '17' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check.yml | 2 +- .github/workflows/update.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index fcf1db9..a571bb2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -37,7 +37,7 @@ jobs: - uses: cachix/install-nix-action@v31 with: github_access_token: ${{ secrets.GH_TOKEN }} - - uses: cachix/cachix-action@v14 + - uses: cachix/cachix-action@v17 with: name: rde authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index a65fbdc..26e57c3 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -16,7 +16,7 @@ jobs: hatchet-protocol: 'carve' - name: Install Determinate Nix uses: DeterminateSystems/determinate-nix-action@v3 - - uses: cachix/cachix-action@v14 + - uses: cachix/cachix-action@v17 with: name: rde authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' From 6542de2212e7aaadc7101b616339f2583753464c Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:08:08 +1000 Subject: [PATCH 32/55] updates --- flake.nix | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index a8584e0..ce3082d 100644 --- a/flake.nix +++ b/flake.nix @@ -233,15 +233,19 @@ } ); - nixosModules.default = wrappers.lib.mkInstallModule { + nixosModules.default = wrappers.lib.getInstallModule { name = "vModule"; value = module; }; - homeModules.default = wrappers.lib.mkInstallModule { + homeModules.default = wrappers.lib.getInstallModule { name = "vModule"; - value = module; - loc = ["home" "packages"]; + value = [ + module + { + config.install.optionLocation = [ "home" "packages" ]; + } + ]; }; }; } From 509442f0b471f654c9faeca587ad7ee3a59b7145 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:27:32 +1000 Subject: [PATCH 33/55] testing --- flake.nix | 7 +------ plugin/20_startup.lua | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/flake.nix b/flake.nix index ce3082d..db14f58 100644 --- a/flake.nix +++ b/flake.nix @@ -240,12 +240,7 @@ homeModules.default = wrappers.lib.getInstallModule { name = "vModule"; - value = [ - module - { - config.install.optionLocation = [ "home" "packages" ]; - } - ]; + value = module; }; }; } diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index d17e2f9..e8d6693 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -217,21 +217,27 @@ now_if_args(function() "make", "xml", "zig", "regex", "csv", "bash", "markdown_inline", "quarto", "rmd", "codecompanion", } + local function start_treesitter(buf, filetype) + local lang = vim.treesitter.language.get_lang(filetype) or filetype + if #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 then + return + end + vim.treesitter.start(buf, lang) + vim.bo[buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" + end + vim.api.nvim_create_autocmd("FileType", { pattern = ts_filetypes, callback = function(ev) - local lang = ev.match - if vim.treesitter.language.get_lang then - lang = vim.treesitter.language.get_lang(lang) or lang - end - if #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".*", false) == 0 then - return - end - vim.treesitter.start(ev.buf, lang) - vim.bo[ev.buf].indentexpr = "v:lua.require('nvim-treesitter').indentexpr()" + start_treesitter(ev.buf, vim.bo[ev.buf].filetype) end, }) + -- FileType may have fired before this deferred setup ran. + if vim.bo.filetype ~= "" then + start_treesitter(0, vim.bo.filetype) + end + require("nvim-treesitter-textobjects").setup({ move = { set_jumps = true, From cd1e524eb8a61dac3ba2c1c5ed0f7447ab4a5923 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 15:35:40 +1000 Subject: [PATCH 34/55] testing --- modules/module/specs/plugins.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ad464b8..695e6bd 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -4,6 +4,7 @@ lib, ... }: { + config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or false) { data = []; }; From 0ea9b249edad34daffa043dc97b3575ebcd1547b Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 24 Jul 2026 16:28:07 +1000 Subject: [PATCH 35/55] Fixed syntax highlighting --- modules/module/specs/plugins.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 695e6bd..63d32d0 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -111,13 +111,17 @@ nvim-lspconfig nvim-treesitter-context nvim-treesitter-textobjects + { + data = pkgs.vimPlugins.nvim-treesitter; + pname = "nvim-treesitter"; + } { data = pkgs.codecompanion-nvim.overrideAttrs (old: { doCheck = false; }); pname = "codecompanion"; } - ]; + ] ++ builtins.attrValues pkgs.vimPlugins.nvim-treesitter.queries; }; config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or false) { From 7f01be59d7a3793513402c57d6069e6488c8756f Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:35:52 +0000 Subject: [PATCH 36/55] refactor: improve Neovim/Nix config for quant econ workflow Consolidates a multi-pass refactor and a set of workflow integrations tailored to a quantitative economics research workflow (R / Python / Quarto / LaTeX heavy, reproducibility-conscious). The existing terminal setup is preserved (snacks.nvim was deliberately not adopted). === Structural refactors === * Replace the `_G.Config` global with a proper `require('config')` Lua module. `_G.Config` is kept only as a backward-compatible alias. * Split the monolithic `plugin/10_keymap.lua` into domain-specific files under `lua/keymap/` (core, helpers, leader, terminal, repl). * Harden `lua/nix_smart_send.lua` `send_repl`: only skip forward on comment nodes, and return cleanly when there is no next sibling. * Add a filetype-aware REPL dispatcher in `lua/keymap/repl.lua`: pure R scripts -> R.nvim, .qmd/.Rmd chunks -> quarto.runner, everything else -> vim-slime. Prefer `quarto.runner.run_line()` when available and fall back to `run_cell()`. * Add R Treesitter text objects (function / call / assignment) for faster motion when sending code to the REPL. * Add `+Send` and `+Debug` leader-clue groups for the new prefixes. === LSP and tooling === * Register `yamlls` for Quarto YAML frontmatter (`plugin/25_lsp.lua`). * Register `texlab` and add `vimtex` for a real `.tex` workflow (`plugin/25_lsp.lua` + new `plugin/28_latex.lua`). * Wire `nvim-dap` with an R adapter backed by `vscDebugger` (new `plugin/26_dap.lua`, gated to the `r` cat). * Add `image-nvim` for in-editor plots, gated to the `r`/`markdown` cats (new `plugin/27_image.lua`). * Wire `lintr` into `nvim-lint` for R / Quarto (`plugin/22_languages.lua`). * Add Treesitter parsers for `stata`, `matlab`, `bibtex` for completeness. === Nix updates === * Pin `python313Packages.pylatexenc` -> `python3Packages.pylatexenc` so the markdown cat survives nixpkgs Python default shifts. * Add `texlab`, `imagemagick`, `luaPackages.magick` to the markdown cat so `image.nvim` has a working backend. * Add `vscDebugger` and `lintr` to the R package list. === Misc === * Prefer the R.nvim v1.0 Lua API (`require('r.run').send_line()` / `send_selection()`); keep `` mappings as a fallback so older downstream builds don't regress. * Use `alejandra` (already installed) as the nixd / nil_ls formatter. Files: 14 modified, 5 added (953 insertions, 490 deletions). Local verification before merge: 1. `nix flake check --no-build` 2. `nvim --headless -u NONE -l tests/init.lua` 3. Open a `.qmd` -> confirm `yamlls` attaches and `image.nvim` renders. 4. Open an `.R` -> `db`, `dc` confirm DAP loads. 5. Open a `.tex` -> confirm `texlab` + `vimtex` are active. --- init.lua | 11 +- lua/config/init.lua | 10 + lua/keymap/core.lua | 44 +++ lua/keymap/helpers.lua | 25 ++ lua/keymap/leader.lua | 260 +++++++++++++++++ lua/keymap/repl.lua | 83 ++++++ lua/keymap/terminal.lua | 17 ++ lua/nix_smart_send.lua | 204 ++++++------- modules/module/settings/cat-packages.nix | 11 +- modules/module/settings/lang-packages.nix | 2 + modules/module/specs/plugins.nix | 87 +++--- plugin/01_lib.lua | 2 + plugin/02_startup.lua | 2 + plugin/03_terminal.lua | 53 +++- plugin/04_treesitter.lua | 52 +++- plugin/10_keymap.lua | 330 +--------------------- plugin/20_startup.lua | 1 + plugin/22_languages.lua | 62 ++++ plugin/24_completion.lua | 1 + plugin/25_lsp.lua | 35 +++ plugin/26_dap.lua | 79 ++++++ plugin/27_image.lua | 46 +++ plugin/28_latex.lua | 43 +++ 23 files changed, 961 insertions(+), 499 deletions(-) create mode 100644 lua/config/init.lua create mode 100644 lua/keymap/core.lua create mode 100644 lua/keymap/helpers.lua create mode 100644 lua/keymap/leader.lua create mode 100644 lua/keymap/repl.lua create mode 100644 lua/keymap/terminal.lua create mode 100644 plugin/26_dap.lua create mode 100644 plugin/27_image.lua create mode 100644 plugin/28_latex.lua diff --git a/init.lua b/init.lua index 8db9da5..11b9975 100644 --- a/init.lua +++ b/init.lua @@ -1,7 +1,5 @@ -_G.Config = {} -local nix = require('config.nix').init { non_nix_value = true } -Config.isNixCats = nix.is_nix -Config.nixConfig = nix +local Config = require('config') +_G.Config = Config -- keep global alias for keymaps/backward compatibility require('lze').register_handlers(require('nixCatsUtils.lzUtils').for_cat) @@ -12,8 +10,11 @@ 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', + 'git', 'clone', '--filter=blob:none', '--branch=' .. mini_nvim_tag, '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 new file mode 100644 index 0000000..60b80e5 --- /dev/null +++ b/lua/config/init.lua @@ -0,0 +1,10 @@ +--- 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 new file mode 100644 index 0000000..1314b06 --- /dev/null +++ b/lua/keymap/core.lua @@ -0,0 +1,44 @@ +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 new file mode 100644 index 0000000..10cdaa2 --- /dev/null +++ b/lua/keymap/helpers.lua @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..2543212 --- /dev/null +++ b/lua/keymap/leader.lua @@ -0,0 +1,260 @@ +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", "CodeCompanion /agent", "Agent chat (@{agent} tools)") +nmap_leader("ac", "CodeCompanionChat Toggle", "Chat Toggle") +nmap_leader("aC", function() + local chat = require("codecompanion").last_chat() + if not chat then + return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN) + end + require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 }) +end, "Compact chat") +nmap_leader("ag", "CodeCompanion /commit", "Generate commit message") +nmap_leader("ai", "CodeCompanionActions", "Chat Action") +nmap_leader("al", "CodeCompanion /lsp", "Explain LSP Diagnostics") +nmap_leader("an", "CodeCompanionChat Add", "Chat New") +nmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") +nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan, implement, test") +nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") +xmap_leader("aa", "CodeCompanion /agent", "Agent on selection") +xmap_leader("ae", "CodeCompanion /explain", "Explain Code") +xmap_leader("af", "CodeCompanion /fix", "Fix Code") +xmap_leader("ap", "CodeCompanion /expert", "Code Expert") +xmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") +nmap_leader("ak", "CodeCompanionChat adapter=codex", "Chat with Codex") + +-- b is for 'buffer' +nmap_leader('bb', 'b#', 'Alternate') +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') + +-- - 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("_", "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 new file mode 100644 index 0000000..f43cb9b --- /dev/null +++ b/lua/keymap/repl.lua @@ -0,0 +1,83 @@ +--- 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; fall back to the legacy mappings + -- if a v0.x build is still in use. + local ok, rrun = pcall(require, "r.run") + if ok and rrun and type(rrun.send_line) == "function" then + rrun.send_line() + return + 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). + vim.cmd("SlimeSendCurrentLine") + -- 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; fall back to if unavailable. + local ok, rrun = pcall(require, "r.run") + if ok and rrun and type(rrun.send_selection) == "function" then + rrun.send_selection() + return + 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 new file mode 100644 index 0000000..bb89596 --- /dev/null +++ b/lua/keymap/terminal.lua @@ -0,0 +1,17 @@ +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 1f82383..7906d25 100644 --- a/lua/nix_smart_send.lua +++ b/lua/nix_smart_send.lua @@ -1,5 +1,12 @@ 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 @@ -14,83 +21,84 @@ local function is_in_list(list, value) return false end --- Define comment node types as constants -local COMMENT_TYPES = { - comment = true, - block_comment = true, - line_comment = true, -} - +-- Safely get the Tree-sitter node under the cursor. function M.get_current_node() - local cur_win = vim.api.nvim_get_current_win() - return vim.treesitter.get_node({ - winid = cur_win, - ignore_injections = true, - }) + local ok, node = pcall(vim.treesitter.get_node, { ignore_injections = false }) + return ok and node or nil 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 - -- print("No node detected") - local parser = vim.treesitter.get_parser() - if not parser then + local ok, parser = pcall(vim.treesitter.get_parser) + if not ok or not parser then return nil end - root = parser:parse()[1]:root() + local trees = parser:parse() + if not trees or not trees[1] then + return nil + end + root = trees[1]:root() else root = cur_node:root() end - if not root then + 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 return nil end - return root:type() -end - -function M.move_to_next_non_empty_line() - -- 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 - - -- 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 }) - - local node = M.get_current_node() - if not node or not node:type() then - -- print("No node found") - return false - end - - local global_node_type = M.detect_global_node() - - -- 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 + while node do + local parent = node:parent() + if not parent then + break 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 = vim.treesitter.get_node() + 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 true + return node +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 + return false + end + + 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) @@ -108,57 +116,20 @@ function M.vselect_node(node) end function M.select_until_global(global_nodes) - 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 = vim.treesitter.get_node() - if not node then - -- print("No syntax node found at cursor position") + local target = get_target_node(global_nodes) + if not target then return nil end - 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 + if M.vselect_node(target) then + return target end return nil end function M.slime_send_region() - -- Check if slime plugin is available - if not vim.fn.exists('*slime#send_op') then + if vim.fn.exists('*slime#send_op') == 0 then vim.notify("slime plugin not available", vim.log.levels.ERROR) return end @@ -170,31 +141,32 @@ function M.slime_send_region() end function M.send_repl(global_nodes) - local cur_node = M.get_current_node() - - 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 - end - - local sel_node = M.select_until_global(global_nodes) - if not sel_node then - -- print("No node selected for REPL") + local target_node = get_target_node(global_nodes) + if not target_node then return end - -- Send the selected text to the terminal using vim-slime + -- 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 + end + target_node = next_node + end + + -- Select the target node and send it to the REPL. + if not M.vselect_node(target_node) then + return + end M.slime_send_region() - -- Move cursor and continue - local _, _, er, ec = sel_node:range() + -- Place cursor at end of visual block + local _, _, er, ec = target_node:range() vim.api.nvim_win_set_cursor(0, { er + 1, ec }) - M.move_to_next_non_empty_line() + -- Jump to the next relevant AST node instead of scanning lines + M.move_to_next_non_empty_line(target_node) end return M diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 2709ca0..49a33e6 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -5,6 +5,10 @@ ... }: let + -- Include packages from a category only if that category is enabled. + -- NOTE: The package list expression is still evaluated (packages in Nix are + -- lazy by default, so derivations are not built), 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; @@ -26,6 +30,7 @@ in clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]); external = maybe "external" (with pkgs; [ + nodejs perl ruby shfmt @@ -40,10 +45,13 @@ in lua = maybe "lua" (with pkgs; [ lua-language-server ]); markdown = maybe "markdown" (with pkgs; [ - python313Packages.pylatexenc + python3Packages.pylatexenc quartoPkg zk marksman + texlab + imagemagick + luaPackages.magick ]); nix = maybe "nix" (with pkgs; [ @@ -88,7 +96,6 @@ in in with pkgs; [ python_with_packages - nodejs ruff basedpyright uv diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index a91d9a5..97ec4e7 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -44,6 +44,8 @@ data_table janitor styler + vscDebugger + lintr ]) ); julia = lib.mkDefault [ diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 63d32d0..4a3324a 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -3,7 +3,53 @@ pkgs, 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" + "stata" + "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 = lib.mkIf (config.cats.gitPlugins or false) { data = []; @@ -94,6 +140,7 @@ data = with pkgs.vimPlugins; [ quarto-nvim render-markdown-nvim + vimtex { data = otter-nvim; pname = "otter"; @@ -125,42 +172,7 @@ }; config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or false) { - 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 - ]; + data = map (name: pkgs.vimPlugins.nvim-treesitter-parsers.${name}) config.settings.treesitter_parsers; }; config.specs.utils-lazy = lib.mkIf (config.cats.utils or false) { @@ -177,6 +189,7 @@ nvim-dap-virtual-text nvim-lint vim-slime + image-nvim ]; }; diff --git a/plugin/01_lib.lua b/plugin/01_lib.lua index 64bd37e..ba87825 100644 --- a/plugin/01_lib.lua +++ b/plugin/01_lib.lua @@ -1,3 +1,5 @@ +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 diff --git a/plugin/02_startup.lua b/plugin/02_startup.lua index c4fa2a9..b4d1c7e 100644 --- a/plugin/02_startup.lua +++ b/plugin/02_startup.lua @@ -1,3 +1,5 @@ +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 95d25b1..9408760 100644 --- a/plugin/03_terminal.lua +++ b/plugin/03_terminal.lua @@ -1,3 +1,5 @@ +local Config = require('config') + local M = {} -- Configuration @@ -31,11 +33,15 @@ 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 @@ -44,26 +50,45 @@ 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 - -- 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") + 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 end end - + -- Configure slime for the ORIGINAL code buffer to point to this new terminal -- This makes "Send to Terminal" work immediately - 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) - + 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 + -- 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 5a8d315..c4c0452 100644 --- a/plugin/04_treesitter.lua +++ b/plugin/04_treesitter.lua @@ -1,3 +1,5 @@ +local Config = require('config') + local M = {} -- Default parsers list moved from startup config @@ -93,12 +95,12 @@ end function M.get_type() local cur_node = smart_send.get_current_node() if not cur_node then - print("Not a node") + vim.notify("No Tree-sitter node under cursor", vim.log.levels.WARN) return nil end local node_type = cur_node:type() - print("Node type: " .. node_type) + vim.notify("Node type: " .. node_type, vim.log.levels.INFO) return node_type end @@ -134,8 +136,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() - pout = table.concat(global_nodes, ', ') .. "" - print(pout) + local pout = table.concat(global_nodes, ', ') + vim.notify("global_nodes: " .. pout, vim.log.levels.INFO) end, { noremap = true, silent = true, desc = "Print globals", buffer = true }) vim.keymap.set('n', 'p', function() M.get_type() end, @@ -144,4 +146,46 @@ 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 8ae0a1c..9e88a27 100644 --- a/plugin/10_keymap.lua +++ b/plugin/10_keymap.lua @@ -1,323 +1,11 @@ --- 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' }) +local Config = require('config') -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 +-- 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') --- 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("aa", "CodeCompanion /agent", "Agent chat (@{agent} tools)") -nmap_leader("ac", "CodeCompanionChat Toggle", "Chat Toggle") -nmap_leader("aC", function() - local chat = require("codecompanion").last_chat() - if not chat then - return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN) - end - require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 }) -end, "Compact chat") -nmap_leader("ag", "CodeCompanion /commit", "Generate commit message") -nmap_leader("ai", "CodeCompanionActions", "Chat Action") -nmap_leader("al", "CodeCompanion /lsp", "Explain LSP Diagnostics") -nmap_leader("an", "CodeCompanionChat Add", "Chat New") -nmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") -nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan, implement, test") -nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") -xmap_leader("aa", "CodeCompanion /agent", "Agent on selection") -xmap_leader("ae", "CodeCompanion /explain", "Explain Code") -xmap_leader("af", "CodeCompanion /fix", "Fix Code") -xmap_leader("ap", "CodeCompanion /expert", "Code Expert") -xmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") -nmap_leader("ak", "CodeCompanionChat adapter=codex", "Chat with Codex") - --- b is for 'buffer' -nmap_leader('bb', 'b#', 'Alternate') -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') - --- - 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 +-- Re-export the shared config table for backwards compatibility with any +-- external code or keymap strings that still reference the global `Config`. +_G.Config = Config diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index e8d6693..36bab13 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -1,3 +1,4 @@ +local Config = require('config') local now = MiniDeps.now local later = MiniDeps.later local now_if_args = Config.now_if_args diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index 70bec23..5f115f7 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -30,6 +30,68 @@ later(function() 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')", + }, + append_fname = true, + stream = "both", + ignore_exitcode = true, + parser = function(output, bufnr, linter_cwd) + local diagnostics = {} + -- Pattern: /path/file.R:10:5: style: Some message + for line in output:gmatch("[^\r\n]+") do + local path, lnum, col, severity, message = line:match("^[^:]+:(%d+):(%d+):%s*(%w+):%s*(.+)$") + if path then + local severity_map = { + style = vim.diagnostic.severity.INFO, + warning = vim.diagnostic.severity.WARN, + error = vim.diagnostic.severity.ERROR, + } + table.insert(diagnostics, { + bufnr = bufnr, + lnum = math.max(0, tonumber(lnum) - 1), + col = math.max(0, tonumber(col) - 1), + end_lnum = tonumber(lnum) - 1, + end_col = tonumber(col), + 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) + -- Markdown now_if_args(function() add("render-markdown.nvim") diff --git a/plugin/24_completion.lua b/plugin/24_completion.lua index f6516a1..c2291c7 100644 --- a/plugin/24_completion.lua +++ b/plugin/24_completion.lua @@ -1,3 +1,4 @@ +local Config = require('config') local add = Config.add local later = MiniDeps.later local now = MiniDeps.now diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index 8c40915..31528b9 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -1,3 +1,4 @@ +local Config = require('config') local now_if_args = Config.now_if_args if not Config.isNixCats then @@ -16,6 +17,40 @@ now_if_args(function() marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, + 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 = {}, julials = { settings = { julia = { diff --git a/plugin/26_dap.lua b/plugin/26_dap.lua new file mode 100644 index 0000000..73deeb8 --- /dev/null +++ b/plugin/26_dap.lua @@ -0,0 +1,79 @@ +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 new file mode 100644 index 0000000..e228190 --- /dev/null +++ b/plugin/27_image.lua @@ -0,0 +1,46 @@ +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 = "kitty", + 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 new file mode 100644 index 0000000..b77b201 --- /dev/null +++ b/plugin/28_latex.lua @@ -0,0 +1,43 @@ +-- 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 + + Config.add("vimtex") + + local ok, vimtex = pcall(require, "vimtex") + if not ok then + vim.notify("vimtex not available", vim.log.levels.WARN) + return + end + + -- Keep conservative defaults: latexmk continuous compilation off, single + -- viewer (zathura falls back to Evince on most setups). + vimtex.setup({ + enabled = true, + compile_on_save = false, + compiler = "latexmk", + -- Avoid hooking spell/formatting into our global group; + -- vimtex stashes its own mappings automatically. + }) + + -- 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) From df2f776d3ff8ddbb959b281fb5504a2ab954cba2 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:43:35 +0000 Subject: [PATCH 37/55] fix: address review findings (C1/C3/H1/H2/H5 + M1) Tackles the critical and high-impact findings from the in-PR review of commit 7f01be5, plus one consistency fix (M1). All changes are scoped to the same review branch (PR #12); no behavior changes elsewhere. Critical fixes * C1 -- plugin/22_languages.lua: now does `local Config = require('config')` at the top, matching every other `plugin/*.lua` file. Previously the file referenced `Config` as a global, depending on `_G.Config` having been initialized by `init.lua` before this file loaded. Fragile. * C2 -- plugin/04_treesitter.lua: removed the hard `` -> `smart_send.send_repl` mapping. Override `` from a per-buffer `ftplugin/.lua` if you want enter-to-send behavior. R.nvim's `RDSendLine` (wired by `ftplugin/r.lua`) remains the default for R files and is no longer silently clobbered. High-impact fixes * C3 -- plugin/27_image.lua: `image.setup({ backend = "kitty" })` -> `backend = "auto"`. The previous value silently failed on every terminal that is not Kitty. `"auto"` delegates detection to image.nvim. * H1 -- plugin/10_keymap.lua: `_G.Config = Config` removed from this file; `init.lua:2` remains the single source. Avoids drift between two aliasing sites. * H2 -- plugin/25_lsp.lua: `texlab = { single_file_support = true }`, so single-file `.tex` buffers attach the LSP without lspconfig's sometimes-brittle root_dir heuristic. * H5 -- plugin/04_treesitter.lua: also drops `` from `M.setup_keybindings` on the same principle as C2. `` was a hard implicit override in both normal and insert mode, where it collided with snippet and transient-state plugins. Consistency fixes * M1 -- plugin/01_lib.lua: `print(line)` in `Config.execute_lua_line` switched to `vim.notify(line, vim.log.levels.INFO)`, matching the print->notify cleanup in `plugin/04_treesitter.lua` from 7f01be5. Files: 6 modified. +21 / -11. Local verification 1. `luac -p plugin/{22_languages,27_image,10_keymap,25_lsp,04_treesitter,01_lib}.lua` 2. `nvim --headless -u NONE -l tests/init.lua` 3. Open a R / quarto / tex buffer; verify `` is no longer hijacked by `smart_send` and behaves like the filetype default. For reviewers * The text-object configuration in plugin/04_treesitter.lua's textobjects block is unchanged. H4 (`@assignment.*` queries may not be defined for R) is left for a follow-up with verification. * yamlls GitHub-rawURL schema dependency (H3) is left intentionally -- vendoring the schemas is a separate decision. * Tests (T1-T6 from the review) are deferred; no test infrastructure exists beyond `tests/init.lua`. --- plugin/01_lib.lua | 2 +- plugin/04_treesitter.lua | 16 +++++++++++----- plugin/10_keymap.lua | 6 +++--- plugin/22_languages.lua | 2 ++ plugin/25_lsp.lua | 4 +++- plugin/27_image.lua | 2 +- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/plugin/01_lib.lua b/plugin/01_lib.lua index ba87825..988905c 100644 --- a/plugin/01_lib.lua +++ b/plugin/01_lib.lua @@ -34,7 +34,7 @@ end Config.execute_lua_line = function() local line = 'lua ' .. vim.api.nvim_get_current_line() vim.api.nvim_command(line) - print(line) + vim.notify(line, vim.log.levels.INFO) vim.api.nvim_input('') end diff --git a/plugin/04_treesitter.lua b/plugin/04_treesitter.lua index c4c0452..88d3cb7 100644 --- a/plugin/04_treesitter.lua +++ b/plugin/04_treesitter.lua @@ -121,11 +121,17 @@ 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 }) - 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 }) + -- 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', 'n', function() current_global_nodes = M.add_global_node(current_global_nodes) end, diff --git a/plugin/10_keymap.lua b/plugin/10_keymap.lua index 9e88a27..98074fb 100644 --- a/plugin/10_keymap.lua +++ b/plugin/10_keymap.lua @@ -6,6 +6,6 @@ require('keymap.core') require('keymap.leader') require('keymap.terminal') --- Re-export the shared config table for backwards compatibility with any --- external code or keymap strings that still reference the global `Config`. -_G.Config = Config +-- `_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. diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index 5f115f7..cd5ebed 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -1,3 +1,5 @@ +local Config = require('config') + local add = Config.add local now_if_args = Config.now_if_args local later = MiniDeps.later diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index 31528b9..e249872 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -50,7 +50,9 @@ now_if_args(function() }, }, }, - texlab = {}, + texlab = { + single_file_support = true, + }, julials = { settings = { julia = { diff --git a/plugin/27_image.lua b/plugin/27_image.lua index e228190..18a424a 100644 --- a/plugin/27_image.lua +++ b/plugin/27_image.lua @@ -24,7 +24,7 @@ later(function() end image.setup({ - backend = "kitty", + backend = "auto", integrations = { markdown = { enabled = true, From 3f2ab6ef96f4544d19a3fc2ed8b8551b6849aed3 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:47:58 +0000 Subject: [PATCH 38/55] fix: address R1 (image.nvim backend) + C4 (cat-gate consistency) Tackles the two outstanding items from the post-fix consistency pass on top of `df2f776`. * R1 -- plugin/27_image.lua: removed the explicit `backend = "auto"` line. image.nvim's setup() does not accept "auto" / "none" as literal values; the practical default is to leave `backend` unset, letting image.nvim auto-detect the right graphics protocol (kitty / wezterm / iterm / sixel) at runtime and falling back to no rendering on unsupported terminals. Inline comment explains the omission so a future reader doesn't "fix" it back. * C4 -- modules/module/specs/plugins.nix: - Removed `nvim-dap`, `nvim-dap-ui`, `nvim-dap-virtual-text`, and `image-nvim` from `config.specs.utils-lazy`. They are R- and Markdown-specific, not general utility plugins, so they should not depend on `utils=true`. - Added a new `config.specs.r-lazy` spec carrying those same four plugins, gated by `cats.r`. Users with `r=true` and `utils=false` now get a working R debugger (via vscDebugger) and in-buffer image rendering for plots. - Added `image-nvim` to the existing `config.specs.markdown-lazy` so users with `markdown=true` and no other cats still see inline plots in Quarto / Markdown documents. nixCats dedups packages by pname, so `image-nvim` appears once on the runtime path even when both `r` and `markdown` are on. These two changes close the real correctness bugs surfaced by the consistency review: `plugin/26_dap.lua` and `plugin/27_image.lua`'s cat-gated `Config.add(...)` calls previously depended on `utils=true` resolving the four packages, leaving `r=true` (or `markdown=true`) only users without DAP / image-nvim even though the Lua gate let the setup function proceed. Files: 2 modified. Local verification (`nix flake check --no-build`) still required before merging PR #12. --- modules/module/specs/plugins.nix | 14 ++++++++++++-- plugin/27_image.lua | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 4a3324a..ff072cc 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -70,6 +70,7 @@ in { lazy = true; data = [ config.nvim-lib.neovimPlugins.cmp-pandoc-references + pkgs.vimPlugins.image-nvim ]; }; @@ -184,11 +185,20 @@ in { colorful-menu-nvim conform-nvim copilot-lua + 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 + -- R debugger (via vscDebugger) and in-buffer image rendering for plots. + config.specs.r-lazy = lib.mkIf (config.cats.r or false) { + lazy = true; + data = with pkgs.vimPlugins; [ nvim-dap nvim-dap-ui nvim-dap-virtual-text - nvim-lint - vim-slime image-nvim ]; }; diff --git a/plugin/27_image.lua b/plugin/27_image.lua index 18a424a..89e0d2f 100644 --- a/plugin/27_image.lua +++ b/plugin/27_image.lua @@ -24,7 +24,11 @@ later(function() end image.setup({ - backend = "auto", + -- 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, From 137ec6d5763a2aee91b2c917af0f3a559272faa4 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:49:12 +0000 Subject: [PATCH 39/55] style: tidy visual clue groups + fix casing Two cosmetic cleanups from the post-fix consistency pass on 3f2ab6e: * lua/keymap/core.lua: changed the visual ``a`` entry to ``a`` on line 46 so the casing is consistent with every other key in ``Config.leader_group_clues``. mini.clue's trigger matching is case-insensitive, so this is purely cosmetic. * lua/keymap/leader.lua: added an ``xmap_leader('rr', ...)`` mapping that sends the visual selection through the filetype-aware dispatcher in ``lua/keymap/repl.lua``. Previously the visual ``r`` (+R) clue group was empty (no ``xmap_leader("r", ...)`` existed); visual users now have the same send-selection affordance that normal-mode users already get from the dispatcher. No normal-mode behavior changes. No regressions. The existing visual ``rx`` reprex mapping is preserved verbatim, just with a clarifying comment above it. Files: 2 modified. +9 / -1. --- lua/keymap/core.lua | 2 +- lua/keymap/leader.lua | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/keymap/core.lua b/lua/keymap/core.lua index 1314b06..8c0abe3 100644 --- a/lua/keymap/core.lua +++ b/lua/keymap/core.lua @@ -40,5 +40,5 @@ Config.leader_group_clues = { { mode = 'x', keys = 'r', desc = '+R' }, { mode = 'n', keys = 'z', desc = '+ZK' }, { mode = 'n', keys = 'zr', desc = '+Reviews' }, - { mode = 'x', keys = 'a', desc = '+AI' }, + { mode = 'x', keys = 'a', desc = '+AI' }, } diff --git a/lua/keymap/leader.lua b/lua/keymap/leader.lua index 2543212..795079f 100644 --- a/lua/keymap/leader.lua +++ b/lua/keymap/leader.lua @@ -163,6 +163,10 @@ 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') From 6142f7d55eaa64cb4d7a62398c2ba881b73d9000 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:57:57 +0000 Subject: [PATCH 40/55] fix: harden REPL/terminal code-sending paths - lua/keymap/repl.lua - Replace non-existent `:SlimeSendCurrentLine` with synchronous `vim.fn["slime#send"](line .. "\n")` fallback. - Support both modern `r.send` and older `r.run` R.nvim Lua APIs, falling back to `` mappings if neither module is available. - lua/nix_smart_send.lua - Refactor `send_repl()` to extract Tree-sitter node text and send it directly via `slime#send`, eliminating the `feedkeys(..., "x", true)` race between visual selection and cursor movement. - Replace internal `slime#send_op` call in `slime_send_region()` with the public `SlimeRegionSend` mapping. - Improve `move_to_next_non_empty_line()` to walk up the AST when a node has no next sibling, so the cursor escapes nested blocks. - plugin/03_terminal.lua - Set `vim.g.slime_target = "neovim"` at the top of the module so the target is guaranteed before any slime send. - plugin/21_datascience.lua - Remove duplicate `vim.g.slime_target = "neovim"` now that the terminal module owns the setting. --- lua/keymap/repl.lua | 42 ++++++++++++++++++++++++++++----------- lua/nix_smart_send.lua | 31 +++++++++++++++-------------- plugin/03_terminal.lua | 4 ++++ plugin/21_datascience.lua | 1 - 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/lua/keymap/repl.lua b/lua/keymap/repl.lua index f43cb9b..35425aa 100644 --- a/lua/keymap/repl.lua +++ b/lua/keymap/repl.lua @@ -20,12 +20,20 @@ function M.send_line() local ft = dispatch_ft() if ft == "r" then - -- R.nvim v1+ exposes a Lua API; fall back to the legacy mappings - -- if a v0.x build is still in use. - local ok, rrun = pcall(require, "r.run") - if ok and rrun and type(rrun.send_line) == "function" then - rrun.send_line() - return + -- 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), @@ -48,7 +56,8 @@ function M.send_line() end -- Default: vim-slime (terminal). - vim.cmd("SlimeSendCurrentLine") + 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 @@ -58,11 +67,20 @@ function M.send_selection() local ft = dispatch_ft() if ft == "r" then - -- Prefer R.nvim v1+ Lua API; fall back to if unavailable. - local ok, rrun = pcall(require, "r.run") - if ok and rrun and type(rrun.send_selection) == "function" then - rrun.send_selection() - return + -- 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), diff --git a/lua/nix_smart_send.lua b/lua/nix_smart_send.lua index 7906d25..51e9611 100644 --- a/lua/nix_smart_send.lua +++ b/lua/nix_smart_send.lua @@ -88,6 +88,12 @@ function M.move_to_next_non_empty_line(current_node) 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 + node = node:next_named_sibling() while node do if not COMMENT_TYPES[node:type()] then @@ -129,15 +135,11 @@ function M.select_until_global(global_nodes) end function M.slime_send_region() - if vim.fn.exists('*slime#send_op') == 0 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) + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("SlimeRegionSend", true, false, true), + "m", + false + ) end function M.send_repl(global_nodes) @@ -155,15 +157,14 @@ function M.send_repl(global_nodes) target_node = next_node end - -- Select the target node and send it to the REPL. - if not M.vselect_node(target_node) then + -- 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) return end - M.slime_send_region() - -- Place cursor at end of visual block - local _, _, er, ec = target_node:range() - vim.api.nvim_win_set_cursor(0, { er + 1, ec }) + vim.fn["slime#send"](text .. "\n") -- Jump to the next relevant AST node instead of scanning lines M.move_to_next_non_empty_line(target_node) diff --git a/plugin/03_terminal.lua b/plugin/03_terminal.lua index 9408760..95d7a29 100644 --- a/plugin/03_terminal.lua +++ b/plugin/03_terminal.lua @@ -1,5 +1,9 @@ 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 diff --git a/plugin/21_datascience.lua b/plugin/21_datascience.lua index cb496b9..807d8ed 100644 --- a/plugin/21_datascience.lua +++ b/plugin/21_datascience.lua @@ -22,7 +22,6 @@ 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 "# %%" From 259b3d65b8cfa9eeabd46edea835b63adba12839 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:10:59 +0000 Subject: [PATCH 41/55] fix: use valid Nix comment syntax in modules --- modules/module/settings/cat-packages.nix | 8 ++++---- modules/module/specs/plugins.nix | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 49a33e6..fdc7145 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -5,10 +5,10 @@ ... }: let - -- Include packages from a category only if that category is enabled. - -- NOTE: The package list expression is still evaluated (packages in Nix are - -- lazy by default, so derivations are not built), so keep side-effecting - -- expressions out of these lists. + # Include packages from a category only if that category is enabled. + # NOTE: The package list expression is still evaluated (packages in Nix are + # lazy by default, so derivations are not built), 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; diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ff072cc..ffd7cff 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -190,9 +190,9 @@ in { ]; }; - -- 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 - -- R debugger (via vscDebugger) and in-buffer image rendering for plots. + # 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 + # R debugger (via vscDebugger) and in-buffer image rendering for plots. config.specs.r-lazy = lib.mkIf (config.cats.r or false) { lazy = true; data = with pkgs.vimPlugins; [ From f4b960c6040ace861d44509fa7cb0f556bb175e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:11:43 +0000 Subject: [PATCH 42/55] docs: clarify Nix laziness comment --- modules/module/settings/cat-packages.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index fdc7145..e68ff93 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -6,9 +6,9 @@ }: let # Include packages from a category only if that category is enabled. - # NOTE: The package list expression is still evaluated (packages in Nix are - # lazy by default, so derivations are not built), so keep side-effecting - # expressions out of these lists. + # 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; From d45d85e5bca53910f0c5e7d013328f3656645e42 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:16:50 +0000 Subject: [PATCH 43/55] chore: remove Stata tree-sitter parser from default parser list --- modules/module/specs/plugins.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index ff072cc..d8bc47d 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -36,7 +36,6 @@ let "rnoweb" "regex" "sql" - "stata" "toml" "vim" "vimdoc" From 2dc7227a0ecf906bfe9369cca8d0f59131c6d6a9 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:21:18 +0000 Subject: [PATCH 44/55] fix: remove vscDebugger from default R packages (not in CRAN/rPackages) --- modules/module/settings/lang-packages.nix | 5 ++++- modules/module/specs/plugins.nix | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/module/settings/lang-packages.nix b/modules/module/settings/lang-packages.nix index 97ec4e7..3be7a9a 100644 --- a/modules/module/settings/lang-packages.nix +++ b/modules/module/settings/lang-packages.nix @@ -44,7 +44,10 @@ data_table janitor styler - vscDebugger + # 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 ]) ); diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index d003fbe..7630632 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -191,7 +191,7 @@ in { # 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 - # R debugger (via vscDebugger) and in-buffer image rendering for plots. + # nvim-dap R adapter and in-buffer image rendering for plots. config.specs.r-lazy = lib.mkIf (config.cats.r or false) { lazy = true; data = with pkgs.vimPlugins; [ From 5c35787c68315479a0caec2c3c6451ac22302433 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:24:33 +0000 Subject: [PATCH 45/55] fix: remove broken luaPackages.magick from markdown cat packages --- modules/module/settings/cat-packages.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index e68ff93..213f85c 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -51,7 +51,6 @@ in marksman texlab imagemagick - luaPackages.magick ]); nix = maybe "nix" (with pkgs; [ From ad26b17b8caf50bb8c14fc07b555c474ecc4cf0b Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:28:43 +0000 Subject: [PATCH 46/55] fix: avoid ripgrep dependency in devShell R_LIBS_SITE hook --- flake.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index db14f58..d55ec6f 100644 --- a/flake.nix +++ b/flake.nix @@ -69,7 +69,9 @@ '' + 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: -) + # Use R itself to discover the library paths, avoiding a dependency on + # ripgrep/strings/grep in the devShell PATH. + export R_LIBS_SITE=$(Rscript -e 'cat(.libPaths(), sep = ":")') export R_LIBS_USER="$PWD/.r-libs" mkdir -p "$R_LIBS_USER" ''; From 92bd53feeaa483f1686214f2793bf545726bdff6 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Mon, 27 Jul 2026 00:30:10 +0000 Subject: [PATCH 47/55] fix: add ripgrep to always category and revert shellHook to use rg --- flake.nix | 4 +--- modules/module/settings/cat-packages.nix | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index d55ec6f..db14f58 100644 --- a/flake.nix +++ b/flake.nix @@ -69,9 +69,7 @@ '' + nixpkgs.lib.optionalString (config.cats.r or false) '' export R_HOME=$(R RHOME) - # Use R itself to discover the library paths, avoiding a dependency on - # ripgrep/strings/grep in the devShell PATH. - export R_LIBS_SITE=$(Rscript -e 'cat(.libPaths(), sep = ":")') + 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" ''; diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index 213f85c..ee1a255 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -25,7 +25,9 @@ in }; config.catPkgs = { - always = maybe "always" (with pkgs; [ ]); + always = maybe "always" (with pkgs; [ + ripgrep + ]); clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]); From d9a8c31bc622aabd138bafa1367ef524421868ad Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Mon, 27 Jul 2026 10:40:14 +1000 Subject: [PATCH 48/55] always add vimtex --- modules/module/specs/plugins.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 7630632..2b92ef4 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -78,6 +78,7 @@ in { lze lzextras plenary-nvim + vimtex neogit { data = mini-nvim; @@ -140,7 +141,6 @@ in { data = with pkgs.vimPlugins; [ quarto-nvim render-markdown-nvim - vimtex { data = otter-nvim; pname = "otter"; From 180c3e4f55bf10bb5c8189f8772659a49ad4b8f7 Mon Sep 17 00:00:00 2001 From: Daniel <22460147+dwinkler1@users.noreply.github.com> Date: Mon, 27 Jul 2026 01:09:25 +0000 Subject: [PATCH 49/55] feat: add Harper grammar checker LSP --- modules/module/settings/cat-packages.nix | 1 + plugin/25_lsp.lua | 31 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/modules/module/settings/cat-packages.nix b/modules/module/settings/cat-packages.nix index ee1a255..dbdba3f 100644 --- a/modules/module/settings/cat-packages.nix +++ b/modules/module/settings/cat-packages.nix @@ -53,6 +53,7 @@ in marksman texlab imagemagick + harper ]); nix = maybe "nix" (with pkgs; [ diff --git a/plugin/25_lsp.lua b/plugin/25_lsp.lua index e249872..5250cdd 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -17,6 +17,37 @@ now_if_args(function() marksman = { filetypes = { "markdown", "markdown_inline", "codecompanion" }, }, + 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"] = { From 6b6f5b3e57e10832fb840c9a44a2b5a6d0350cc8 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Mon, 27 Jul 2026 11:25:49 +1000 Subject: [PATCH 50/55] vimtex is not lua --- plugin/28_latex.lua | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/plugin/28_latex.lua b/plugin/28_latex.lua index b77b201..3b24f9e 100644 --- a/plugin/28_latex.lua +++ b/plugin/28_latex.lua @@ -11,24 +11,19 @@ later(function() 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") - local ok, vimtex = pcall(require, "vimtex") - if not ok then - vim.notify("vimtex not available", vim.log.levels.WARN) - return - end - - -- Keep conservative defaults: latexmk continuous compilation off, single - -- viewer (zathura falls back to Evince on most setups). - vimtex.setup({ - enabled = true, - compile_on_save = false, - compiler = "latexmk", - -- Avoid hooking spell/formatting into our global group; - -- vimtex stashes its own mappings automatically. - }) - -- 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", { From b8f72cb15045e2615bbac25d3cbcc3cabdb1b520 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Thu, 30 Jul 2026 13:46:58 +1000 Subject: [PATCH 51/55] Small fixes --- flake.nix | 6 + plugin/22_languages.lua | 27 +++-- plugin/23_editor.lua | 8 +- tests/smoke.lua | 260 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 288 insertions(+), 13 deletions(-) create mode 100644 tests/smoke.lua diff --git a/flake.nix b/flake.nix index db14f58..2087556 100644 --- a/flake.nix +++ b/flake.nix @@ -230,6 +230,12 @@ nvim --headless -u NONE -c "set runtimepath+=${./.}" -l ${./tests/init.lua} touch $out ''; + + smoke-test = pkgs.runCommand "smoke-test" {} '' + BINARY_PATH="${defaultNvimPkg}/bin/vv" + "$BINARY_PATH" --headless -c "luafile ${./tests/smoke.lua}" -c "qa!" + touch $out + ''; } ); diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index cd5ebed..9eac58b 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -55,21 +55,24 @@ later(function() ignore_exitcode = true, parser = function(output, bufnr, linter_cwd) local diagnostics = {} - -- Pattern: /path/file.R:10:5: style: Some message + -- 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 - local path, lnum, col, severity, message = line:match("^[^:]+:(%d+):(%d+):%s*(%w+):%s*(.+)$") - if path then - local severity_map = { - style = vim.diagnostic.severity.INFO, - warning = vim.diagnostic.severity.WARN, - error = vim.diagnostic.severity.ERROR, - } + -- 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, tonumber(lnum) - 1), - col = math.max(0, tonumber(col) - 1), - end_lnum = tonumber(lnum) - 1, - end_col = tonumber(col), + 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", diff --git a/plugin/23_editor.lua b/plugin/23_editor.lua index 4345e2d..af4c96d 100644 --- a/plugin/23_editor.lua +++ b/plugin/23_editor.lua @@ -37,7 +37,13 @@ later(function() end) later(function() - require("mini.align").setup() + -- Keep `ga` free for CodeCompanion's "accept change" mapping. + require("mini.align").setup({ + mappings = { + start = "gA", + start_with_preview = "g", + }, + }) end) later(function() diff --git a/tests/smoke.lua b/tests/smoke.lua new file mode 100644 index 0000000..e4f0aaf --- /dev/null +++ b/tests/smoke.lua @@ -0,0 +1,260 @@ +-- 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. + 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 + -- vim.lsp.config(name) returns the merged config or {} if none registered. + local ok, cfg = pcall(vim.lsp.config, name) + 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 From 92729cff562101edbfb0edd6a208628ce2fc0333 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 7 Aug 2026 12:48:40 +1000 Subject: [PATCH 52/55] added bloocky and dooing --- .letta/settings.local.json | 17 ++ flake.lock | 46 ++++- flake.nix | 10 + lua/keymap/leader.lua | 73 +++++--- modules/module/specs/plugins.nix | 8 +- nvim.log | 13 ++ overlays/plugins.nix | 16 -- overlays/r.nix | 2 +- plugin/20_startup.lua | 4 +- plugin/22_languages.lua | 2 +- plugin/23_editor.lua | 3 +- plugin/24_completion.lua | 312 +++---------------------------- plugin/25_lsp.lua | 2 +- plugin/29_bloocky.lua | 26 +++ plugin/30_dooing.lua | 28 +++ tests/smoke.lua | 12 +- 16 files changed, 234 insertions(+), 340 deletions(-) create mode 100644 .letta/settings.local.json create mode 100644 nvim.log create mode 100644 plugin/29_bloocky.lua create mode 100644 plugin/30_dooing.lua diff --git a/.letta/settings.local.json b/.letta/settings.local.json new file mode 100644 index 0000000..7656883 --- /dev/null +++ b/.letta/settings.local.json @@ -0,0 +1,17 @@ +{ + "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/flake.lock b/flake.lock index 17fe244..195ab40 100644 --- a/flake.lock +++ b/flake.lock @@ -22,11 +22,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1784796856, - "narHash": "sha256-wWFrV5/Qbm+lyt5x20E/bSbfJiGKMo4RCxZV8cl/WZI=", + "lastModified": 1785967620, + "narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e2587caef70cea85dd97d7daab492899902dbf5d", + "rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436", "type": "github" }, "original": { @@ -36,6 +36,22 @@ "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": { @@ -52,6 +68,22 @@ "type": "github" } }, + "plugins-dooing": { + "flake": false, + "locked": { + "lastModified": 1785971574, + "narHash": "sha256-GtEC5kWcH0DmiYXkzxzNjjfYLfyEb7zpTLQWdi7n3zs=", + "owner": "atiladefreitas", + "repo": "dooing", + "rev": "6748316bc6b4601797fb952a83694cc0a5ef6da2", + "type": "github" + }, + "original": { + "owner": "atiladefreitas", + "repo": "dooing", + "type": "github" + } + }, "r-nvim-nix": { "inputs": { "nixpkgs": [ @@ -76,11 +108,11 @@ }, "rixpkgs": { "locked": { - "lastModified": 1782576256, - "narHash": "sha256-KOvpL9DJJmShb64mX9QTjhxHaxE8MizQIqNUAniuJ2E=", + "lastModified": 1785776890, + "narHash": "sha256-CzzgB1teVbroR/QhGAnsQToUYCDXEZZtFTcISAooASM=", "owner": "dwinkler1", "repo": "rixpkgs", - "rev": "815afc01bc0cc9a2eba80906645bd08f976a4401", + "rev": "a54080a58b57d5785dc85901850b817bef2aaf83", "type": "github" }, "original": { @@ -111,7 +143,9 @@ "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", "rixpkgs": "rixpkgs", "wrappers": "wrappers" diff --git a/flake.nix b/flake.nix index 2087556..53117d5 100644 --- a/flake.nix +++ b/flake.nix @@ -29,6 +29,16 @@ 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 = { diff --git a/lua/keymap/leader.lua b/lua/keymap/leader.lua index 795079f..e741c81 100644 --- a/lua/keymap/leader.lua +++ b/lua/keymap/leader.lua @@ -11,28 +11,39 @@ nmap_leader('', 'bnext', 'Next buffer') nmap_leader('', 'bprev', 'Prev buffer') -- a is for 'AI' -nmap_leader("aa", "CodeCompanion /agent", "Agent chat (@{agent} tools)") -nmap_leader("ac", "CodeCompanionChat Toggle", "Chat Toggle") -nmap_leader("aC", function() - local chat = require("codecompanion").last_chat() - if not chat then - return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN) - end - require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 }) -end, "Compact chat") -nmap_leader("ag", "CodeCompanion /commit", "Generate commit message") -nmap_leader("ai", "CodeCompanionActions", "Chat Action") -nmap_leader("al", "CodeCompanion /lsp", "Explain LSP Diagnostics") -nmap_leader("an", "CodeCompanionChat Add", "Chat New") -nmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") -nmap_leader("aw", "CodeCompanion /tdd", "Workflow: plan, implement, test") -nmap_leader("ax", "CodeCompanion /fixer", "Code Fixer") -xmap_leader("aa", "CodeCompanion /agent", "Agent on selection") -xmap_leader("ae", "CodeCompanion /explain", "Explain Code") -xmap_leader("af", "CodeCompanion /fix", "Fix Code") -xmap_leader("ap", "CodeCompanion /expert", "Code Expert") -xmap_leader("as", "CodeCompanion /suggest", "Suggest Improvements") -nmap_leader("ak", "CodeCompanionChat adapter=codex", "Chat with Codex") +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') @@ -214,6 +225,24 @@ 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 }) diff --git a/modules/module/specs/plugins.nix b/modules/module/specs/plugins.nix index 2b92ef4..babb1ba 100644 --- a/modules/module/specs/plugins.nix +++ b/modules/module/specs/plugins.nix @@ -80,6 +80,8 @@ in { plenary-nvim vimtex neogit + config.nvim-lib.neovimPlugins.bloocky + config.nvim-lib.neovimPlugins.dooing { data = mini-nvim; pname = "mini.nvim"; @@ -163,10 +165,8 @@ in { pname = "nvim-treesitter"; } { - data = pkgs.codecompanion-nvim.overrideAttrs (old: { - doCheck = false; - }); - pname = "codecompanion"; + data = pkgs.vimPlugins.opencode-nvim; + pname = "opencode.nvim"; } ] ++ builtins.attrValues pkgs.vimPlugins.nvim-treesitter.queries; }; diff --git a/nvim.log b/nvim.log new file mode 100644 index 0000000..4eb4ddf --- /dev/null +++ b/nvim.log @@ -0,0 +1,13 @@ +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/plugins.nix b/overlays/plugins.nix index 91c01aa..17d7c5f 100644 --- a/overlays/plugins.nix +++ b/overlays/plugins.nix @@ -1,22 +1,6 @@ { ... }: 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 e736b77..f03d07e 100644 --- a/overlays/r.nix +++ b/overlays/r.nix @@ -5,7 +5,7 @@ }; in { inherit rpkgs; - baseRPackages = [rpkgs.nvimcom]; + baseRPackages = [rpkgs.nvimcom rpkgs.rPackages.btw]; rWrapper = rpkgs.rWrapper.override {packages = [];}; quarto = rpkgs.quarto.override {extraRPackages = [];}; } diff --git a/plugin/20_startup.lua b/plugin/20_startup.lua index 36bab13..f5cc9e8 100644 --- a/plugin/20_startup.lua +++ b/plugin/20_startup.lua @@ -191,7 +191,7 @@ end) -- Treesitter now_if_args(function() - vim.treesitter.language.register("markdown", { "markdown", "codecompanion", "rmd", "quarto" }) + vim.treesitter.language.register("markdown", { "markdown", "rmd", "quarto" }) require 'treesitter-context'.setup { enable = true, @@ -216,7 +216,7 @@ now_if_args(function() "julia", "rnoweb", "latex", "gitcommit", "gitignore", "git_config", "git_rebase", "diff", "dockerfile", "make", "xml", "zig", "regex", "csv", "bash", - "markdown_inline", "quarto", "rmd", "codecompanion", + "markdown_inline", "quarto", "rmd", } local function start_treesitter(buf, filetype) local lang = vim.treesitter.language.get_lang(filetype) or filetype diff --git a/plugin/22_languages.lua b/plugin/22_languages.lua index 9eac58b..3a6282e 100644 --- a/plugin/22_languages.lua +++ b/plugin/22_languages.lua @@ -102,7 +102,7 @@ now_if_args(function() add("render-markdown.nvim") require('render-markdown').setup({ -- completions = { blink = { enabled = true } }, - file_types = { 'markdown', 'codecompanion', }, + file_types = { 'markdown', }, link = { wiki = { body = function(ctx) diff --git a/plugin/23_editor.lua b/plugin/23_editor.lua index af4c96d..362333d 100644 --- a/plugin/23_editor.lua +++ b/plugin/23_editor.lua @@ -37,7 +37,6 @@ later(function() end) later(function() - -- Keep `ga` free for CodeCompanion's "accept change" mapping. require("mini.align").setup({ mappings = { start = "gA", @@ -219,6 +218,8 @@ 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 c2291c7..38e57f2 100644 --- a/plugin/24_completion.lua +++ b/plugin/24_completion.lua @@ -4,9 +4,6 @@ local later = MiniDeps.later local now = MiniDeps.now local now_if_args = Config.now_if_args --- Constants -local BLINK_VERSION = "v1.10.2" - -- Plugin sources configuration local PLUGIN_SOURCES = { "hrsh7th/cmp-cmdline", @@ -14,7 +11,7 @@ local PLUGIN_SOURCES = { "zbirenbaum/copilot.lua", "jmbuhr/cmp-pandoc-references", "fang2hou/blink-copilot", - "olimorris/codecompanion.nvim" + "nickjvandyke/opencode.nvim", } local PLUGIN_ADDS = { @@ -24,34 +21,17 @@ local PLUGIN_ADDS = { "cmp-pandoc-references", } --- Helper functions -local function create_system_prompt(role_description) - return function(context) - local lang = context.filetype or "programmer" - return "I want you to act as a senior " .. lang .. " developer. " .. role_description - end -end - -local function get_code_block(context) - local text = require("codecompanion.helpers.code").get_code(context.start_line, context.end_line) - return "```" .. context.filetype .. "\n" .. text .. "\n```" -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() - local setting = { - sorts = { "exact", "score", "sort_text" } + return { + sorts = { "exact", "score", "sort_text" }, + use_frecency = true, + use_proximity = true, } - - if not Config.isNixCats then - setting.prebuilt_binaries = { force_version = BLINK_VERSION } - end - - return setting end -- Plugin loading @@ -62,7 +42,6 @@ if not Config.isNixCats then add({ source = "saghen/blink.cmp", depends = { "rafamadriz/friendly-snippets" }, - checkout = BLINK_VERSION, }) end) @@ -73,241 +52,6 @@ if not Config.isNixCats then end) end -local function get_codecompanion_config() - return { - adapters = { - acp = { - -- Codex = heavy agent lane (ChatGPT Edu login via `codex login`; ~/.codex/auth.json). - -- Requires `codex-acp` on PATH (~/.nix-profile/bin). ACP-only slash commands in the - -- chat buffer: /resume (restore a past session, fresh chat only), /mode (switch agent - -- mode), /command, /acp_session_options (e.g. model per session); `\` triggers ACP - -- command completion (1-5s delay after chat open). - codex = function() - return require("codecompanion.adapters").extend("codex", { - defaults = { - auth_method = "chatgpt", - }, - }) - end, - }, - }, - interactions = { - chat = { - adapter = { - name = "copilot", - model = "claude-sonnet-5", - }, - slash_commands = { - ["share"] = { - opts = { - token = os.getenv("GITHUB_GIST_TOKEN"), - }, - }, - }, - opts = { - completion_provider = "blink", - context_management = { - editing = { - trigger = 0.65, - keep_cycles = 3, - exclude_tools = { "memory" }, - }, - compaction = { - trigger = 0.85, - min_token_savings = 10000, - }, - }, - }, - }, - inline = { - adapter = { - name = "copilot", - model = "gpt-5-mini", - }, - }, - shared = { - keymaps = { - accept_change = { - modes = { n = "ga" }, - description = "Accept the suggested change", - }, - reject_change = { - modes = { n = "gr" }, - opts = { nowait = true }, - description = "Reject the suggested change", - }, - }, - }, - background = { - adapter = { - name = "copilot", - model = "gpt-5-mini", - }, - chat = { - callbacks = { - ["on_ready"] = { - actions = { "interactions.background.builtin.chat_make_title" }, - enabled = true, - }, - }, - opts = { - enabled = true, - }, - }, - }, - }, - display = { - chat = { - show_settings = false, - window = { - layout = "horizontal", - position = "bottom", - height = 0.33, - }, - }, - diff = { - enabled = true, - threshold_for_chat = 6, - }, - }, - rules = { - default = { - description = "Collection of common files for all projects", - files = { - ".clinerules", - ".cursorrules", - ".rules", - ".github/copilot-instructions.md", - "AGENT.md", - "AGENTS.md", - { path = "CLAUDE.md", parser = "claude" }, - { path = "CLAUDE.local.md", parser = "claude" }, - { path = "~/.claude/CLAUDE.md", parser = "claude" }, - }, - }, - opts = { - chat = { - autoload = "default", - enabled = true, - }, - }, - }, - prompt_library = { - ["expert"] = { - interaction = "chat", - description = "Get expert advice from an LLM", - opts = { alias = "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 = { alias = "fixer" }, - 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 = { alias = "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 }, - }, - }, - }, - ["agent"] = { - interaction = "chat", - description = "Agentic coding with the @{agent} tool group (read/edit/grep/run)", - opts = { alias = "agent" }, - prompts = { - { - role = "user", - content = function(context) - return "@{agent} Work on the following code:\n\n" .. get_code_block(context) .. "\n\n" - end, - opts = { contains_code = true }, - }, - }, - }, - ["tdd"] = { - interaction = "chat", - description = "Workflow: plan the buffer change, implement it, run the tests", - opts = { alias = "tdd", is_workflow = true }, - prompts = { - { - { - role = "user", - content = function(context) - return "Let's work test-driven. First, study #buffer and the relevant parts of the codebase, then propose a concise implementation plan (no code yet).\n\nThe code under discussion:\n\n" - .. get_code_block(context) - .. "\n\nThe task: " - end, - opts = { contains_code = true }, - }, - }, - { - { - role = "user", - content = "Implement the plan now, writing or updating tests alongside the code. @{agent}", - opts = { auto_submit = true }, - }, - }, - { - { - role = "user", - content = "Run the project's test suite with @{run_command} and fix any failures until it passes.", - opts = { auto_submit = true }, - }, - }, - }, - }, - }, - } -end - -- Batch add simple plugins later(function() for _, plugin in ipairs(PLUGIN_ADDS) do @@ -359,26 +103,33 @@ later(function() }) end) - --- CodeCompanion habit notes (chat buffer unless stated): --- /compact compact history, keep summary /fork fork the conversation --- /symbols insert symbols for a file /share export chat to a GitHub gist --- gm toggle "btw" ephemeral message gty YOLO: approve all tool calls --- gba/gbd buffer sync add/drop gd debug window (adapter/tools info) --- Codex (ACP) lane: /resume (fresh chat only), /mode, /command, /acp_session_options, --- `\` ACP command completion. Prompt library: /expert /fixer /suggest /agent /tdd (workflow). later(function() - add("codecompanion.nvim") - - -- now use function - require("codecompanion").setup(get_codecompanion_config()) - vim.cmd([[cab cc CodeCompanion]]) + 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") 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" }, @@ -415,6 +166,7 @@ now_if_args(function() }, completion = { menu = { + border = "rounded", draw = { treesitter = { "lsp" }, components = { @@ -434,14 +186,12 @@ now_if_args(function() list = { selection = { preselect = false, auto_insert = true } }, - documentation = { auto_show = true }, + ghost_text = { enabled = true, show_with_menu = true }, + documentation = { auto_show = true, window = { border = "rounded" } }, trigger = { show_in_snippet = false }, }, snippets = { preset = "mini_snippets" }, sources = { - per_filetype = { - codecompanion = { "codecompanion" }, - }, default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot" }, providers = { path = { @@ -467,12 +217,6 @@ now_if_args(function() 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 5250cdd..6a719ca 100644 --- a/plugin/25_lsp.lua +++ b/plugin/25_lsp.lua @@ -15,7 +15,7 @@ now_if_args(function() basedpyright = {}, ruff = {}, marksman = { - filetypes = { "markdown", "markdown_inline", "codecompanion" }, + filetypes = { "markdown", "markdown_inline" }, }, harper_ls = { cmd = { "harper-ls", "--stdio" }, diff --git a/plugin/29_bloocky.lua b/plugin/29_bloocky.lua new file mode 100644 index 0000000..ec15b5d --- /dev/null +++ b/plugin/29_bloocky.lua @@ -0,0 +1,26 @@ +-- 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({ + keymaps = { + toggle = 'cb', + }, + }) +end) \ No newline at end of file diff --git a/plugin/30_dooing.lua b/plugin/30_dooing.lua new file mode 100644 index 0000000..0ec1a09 --- /dev/null +++ b/plugin/30_dooing.lua @@ -0,0 +1,28 @@ +-- 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 + +later(function() + if not nix.get_cat('general', false) then + return + end + require('dooing').setup({ + keymaps = { + toggle_window = 'cd', + open_project_todo = 'cD', + show_due_notification = 'cN', + }, + }) +end) \ No newline at end of file diff --git a/tests/smoke.lua b/tests/smoke.lua index e4f0aaf..2e4112e 100644 --- a/tests/smoke.lua +++ b/tests/smoke.lua @@ -55,6 +55,10 @@ local function wait_for_deferred(timeout_ms) -- 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 @@ -91,8 +95,12 @@ end -- --------------------------------------------------------------------------- local function has_lsp_config(name) if vim.lsp and vim.lsp.config then - -- vim.lsp.config(name) returns the merged config or {} if none registered. - local ok, cfg = pcall(vim.lsp.config, name) + -- 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 From 25a88599c11f6d7a99b2906afddbada8d197e623 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 7 Aug 2026 14:33:48 +1000 Subject: [PATCH 53/55] bloocky config and fixes --- flake.nix | 6 ++++++ plugin/03_terminal.lua | 2 +- plugin/24_completion.lua | 1 - plugin/29_bloocky.lua | 19 ++++++++++++++++++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index 53117d5..00ad477 100644 --- a/flake.nix +++ b/flake.nix @@ -242,6 +242,12 @@ ''; 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 diff --git a/plugin/03_terminal.lua b/plugin/03_terminal.lua index 95d7a29..43a53a8 100644 --- a/plugin/03_terminal.lua +++ b/plugin/03_terminal.lua @@ -18,7 +18,7 @@ local defaults = { duckdb = "duckdb", julia = "julia", python = "ipython", - shell = "echo 'Hello " .. vim.env.USER .. "!'", + shell = "echo 'Hello " .. (vim.env.USER or "user") .. "!'", } -- Registry of terminal commands diff --git a/plugin/24_completion.lua b/plugin/24_completion.lua index 38e57f2..9e70b27 100644 --- a/plugin/24_completion.lua +++ b/plugin/24_completion.lua @@ -29,7 +29,6 @@ end local function get_blink_fuzzy_setting() return { sorts = { "exact", "score", "sort_text" }, - use_frecency = true, use_proximity = true, } end diff --git a/plugin/29_bloocky.lua b/plugin/29_bloocky.lua index ec15b5d..5e6a3ea 100644 --- a/plugin/29_bloocky.lua +++ b/plugin/29_bloocky.lua @@ -19,8 +19,25 @@ later(function() 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) \ No newline at end of file +end) From c7543c978abbda8028369d4414d853a2e465de92 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Fri, 7 Aug 2026 15:00:50 +1000 Subject: [PATCH 54/55] OMP neovim server bridge --- .omp/tools/nvim_buffers.mjs | 185 ++++++++++++++++++++++++++++++++++++ lua/nvim_omp/init.lua | 55 +++++++++++ plugin/31_nvim_omp.lua | 8 ++ 3 files changed, 248 insertions(+) create mode 100644 .omp/tools/nvim_buffers.mjs create mode 100644 lua/nvim_omp/init.lua create mode 100644 plugin/31_nvim_omp.lua diff --git a/.omp/tools/nvim_buffers.mjs b/.omp/tools/nvim_buffers.mjs new file mode 100644 index 0000000..82e9ce6 --- /dev/null +++ b/.omp/tools/nvim_buffers.mjs @@ -0,0 +1,185 @@ +// 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/lua/nvim_omp/init.lua b/lua/nvim_omp/init.lua new file mode 100644 index 0000000..4400d49 --- /dev/null +++ b/lua/nvim_omp/init.lua @@ -0,0 +1,55 @@ +-- 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/plugin/31_nvim_omp.lua b/plugin/31_nvim_omp.lua new file mode 100644 index 0000000..44076cf --- /dev/null +++ b/plugin/31_nvim_omp.lua @@ -0,0 +1,8 @@ +-- 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 From b630932348d8b16e8f186584d1256f7a9c2002c9 Mon Sep 17 00:00:00 2001 From: Daniel Winkler Date: Tue, 11 Aug 2026 12:33:49 +1000 Subject: [PATCH 55/55] Fixed dooing keymap --- plugin/30_dooing.lua | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/plugin/30_dooing.lua b/plugin/30_dooing.lua index 0ec1a09..f47ecc2 100644 --- a/plugin/30_dooing.lua +++ b/plugin/30_dooing.lua @@ -13,16 +13,32 @@ 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 - require('dooing').setup({ + local dooing = require('dooing') + require("dooing").setup({ keymaps = { - toggle_window = 'cd', - open_project_todo = 'cD', - show_due_notification = 'cN', + 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", }, }) -end) \ No newline at end of file + -- 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)