init wrapper-module config

This commit is contained in:
Daniel Winkler 2026-01-30 14:22:39 +11:00
commit 91755583fd
46 changed files with 4277 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.DS_Store
.direnv

133
flake.lock generated Normal file
View file

@ -0,0 +1,133 @@
{
"nodes": {
"fran": {
"inputs": {
"nixpkgs": [
"rixpkgs"
]
},
"locked": {
"lastModified": 1768802006,
"narHash": "sha256-czGb4RwTBahNNK9S7ySXeTWIrn/jKH+74hN/uIS2XrM=",
"owner": "dwinkler1",
"repo": "fran",
"rev": "a3879317519fd3685f3b7679a1de4c5ae65a7cb9",
"type": "github"
},
"original": {
"owner": "dwinkler1",
"repo": "fran",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1769598131,
"narHash": "sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "fa83fd837f3098e3e678e6cf017b2b36102c7211",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"plugins-cmp-pandoc-references": {
"flake": false,
"locked": {
"lastModified": 1743491695,
"narHash": "sha256-XsdneGNJzmRBggk8lz9JNDQYk7wbYfUAF2oZLXzFb9c=",
"owner": "jmbuhr",
"repo": "cmp-pandoc-references",
"rev": "130eae4f75029d6495808e0ea4b769fa1ce4c9ac",
"type": "github"
},
"original": {
"owner": "jmbuhr",
"repo": "cmp-pandoc-references",
"type": "github"
}
},
"plugins-cmp-r": {
"flake": false,
"locked": {
"lastModified": 1764700377,
"narHash": "sha256-xb7VFWM/BKAkN7fg62y8n618t2qkQjdYbPwhBhLJwtk=",
"owner": "R-nvim",
"repo": "cmp-r",
"rev": "70bfe8f4c062acc10266e24825439c009a0b1b89",
"type": "github"
},
"original": {
"owner": "R-nvim",
"repo": "cmp-r",
"type": "github"
}
},
"plugins-r": {
"flake": false,
"locked": {
"lastModified": 1769736135,
"narHash": "sha256-T4QgcBL+LCXvrEiRE2JW4jtUKl8DKzFHk8czGUO1jgY=",
"owner": "R-nvim",
"repo": "R.nvim",
"rev": "2701ec64f5485e17c0e057081a9ae2058d776464",
"type": "github"
},
"original": {
"owner": "R-nvim",
"repo": "R.nvim",
"type": "github"
}
},
"rixpkgs": {
"locked": {
"lastModified": 1768825970,
"narHash": "sha256-m/BI9IO7tMuOSdVNSqr0knQ4V9R6rgSXyGQOp3FovSA=",
"type": "tarball",
"url": "https://github.com/rstats-on-nix/nixpkgs/archive/2026-01-19.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/rstats-on-nix/nixpkgs/archive/2026-01-19.tar.gz"
}
},
"root": {
"inputs": {
"fran": "fran",
"nixpkgs": "nixpkgs",
"plugins-cmp-pandoc-references": "plugins-cmp-pandoc-references",
"plugins-cmp-r": "plugins-cmp-r",
"plugins-r": "plugins-r",
"rixpkgs": "rixpkgs",
"wrappers": "wrappers"
}
},
"wrappers": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1769731670,
"narHash": "sha256-iHq0bwOsDI5IkJuxVsljdztZdVKIBb8yO0RBRMrco68=",
"owner": "BirdeeHub",
"repo": "nix-wrapper-modules",
"rev": "4622dd5ef8e152475fce48307d3bc77211249c6a",
"type": "github"
},
"original": {
"owner": "BirdeeHub",
"repo": "nix-wrapper-modules",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

202
flake.nix Normal file
View file

@ -0,0 +1,202 @@
# Copyright (c) 2026 BirdeeHub
# Licensed under the MIT license
{
description = "Daniel's NixCats";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
wrappers = {
url = "github:BirdeeHub/nix-wrapper-modules";
inputs.nixpkgs.follows = "nixpkgs";
};
rixpkgs.url = "https://github.com/rstats-on-nix/nixpkgs/archive/2026-01-19.tar.gz";
fran = {
url = "github:dwinkler1/fran";
inputs = {
nixpkgs.follows = "rixpkgs";
};
};
"plugins-r" = {
url = "github:R-nvim/R.nvim";
flake = false;
};
"plugins-cmp-r" = {
url = "github:R-nvim/cmp-r";
flake = false;
};
"plugins-cmp-pandoc-references" = {
url = "github:jmbuhr/cmp-pandoc-references";
flake = false;
};
};
outputs = {
self,
nixpkgs,
wrappers,
...
} @ inputs: let
wrapperSettings = pkgs:
wrapper.config.wrap {
inherit pkgs;
cats = {
clickhouse = false;
gitPlugins = false;
julia = false;
lua = false;
markdown = false;
nix = true;
optional = false;
python = false;
r = true;
};
settings.lang_packages = {
python = with pkgs.python3Packages; [
duckdb
polars
];
r = with pkgs.rpkgs.rPackages; [
arrow
broom
data_table
duckdb
janitor
styler
tidyverse
];
julia = ["DataFramesMeta" "QuackIO"];
};
};
systems = [
"aarch64-darwin"
"x86_64-linux"
"aarch64-linux"
];
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];
};
module = nixpkgs.lib.modules.importApply ./modules/neovim.nix inputs;
wrapper = wrappers.lib.evalModule module;
in {
overlays = {
default = nixpkgs.lib.composeManyExtensions [
dependencyOverlay
(final: prev: {
n = wrapper.config.wrap {pkgs = final;};
})
];
dependencies = dependencyOverlay;
n = self.overlays.default;
};
wrapperModules = {
default = module;
neovim = self.wrapperModules.default;
};
wrappers = {
default = wrapper.config;
neovim = self.wrappers.default;
};
packages = forAllSystems (
system: let
pkgs = mkPkgs system;
nvimPkg = wrapperSettings pkgs;
in {
default = nvimPkg;
n = nvimPkg;
vim = nvimPkg;
nvim = nvimPkg;
}
);
formatter = forAllSystems (
system: let
pkgs = mkPkgs system;
in
pkgs.nixfmt-tree
);
devShells = forAllSystems (
system: let
pkgs = mkPkgs system;
nvimPkg = wrapperSettings pkgs;
in {
default = pkgs.mkShell {
name = "n";
packages = [nvimPkg];
nativeBuildInputs = with pkgs; [] ++ (pkgs.lib.optionals self.wrappers.default.cats.optional [devenv]);
inputsFrom = [];
shellHook = "";
};
}
);
checks = forAllSystems (
system: let
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-n" {} ''
BINARY_PATH="${nvimPkg}/bin/n"
if [ ! -x "$BINARY_PATH" ]; then
echo "Error: Binary n not found or not executable"
exit 1
fi
"$BINARY_PATH" --version > version_output.txt 2>&1 || true
echo "Package validation successful" > $out
echo "Binary location: $BINARY_PATH" >> $out
if [ -s version_output.txt ]; then
echo "Version output:" >> $out
cat version_output.txt >> $out
fi
'';
}
);
nixosModules.default = wrappers.lib.mkInstallModule {
name = "n";
value = module;
};
homeModules.default = wrappers.lib.mkInstallModule {
name = "n";
value = module;
loc = ["home" "packages"];
};
};
}

6
ftplugin/julia.lua Normal file
View file

@ -0,0 +1,6 @@
vim.b.slime_cell_delimiter = vim.b.slime_cell_delimiter or "# %%"
local ts_lib = Config.treesitter_helpers
local global_nodes_julia = { 'source_file', 'module_definition' }
ts_lib.setup_keybindings(global_nodes_julia)

32
ftplugin/markdown.lua Normal file
View file

@ -0,0 +1,32 @@
-- 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", "<CR>", "<Cmd>lua vim.lsp.buf.definition()<CR>", { noremap = true, silent = false, buffer = true })
-- Create a new note after asking for its title.
-- This overrides the global `<leader>zn` mapping to create the note in the same directory as the current buffer.
map("n", "<leader>zhn", "<Cmd>ZkNew { dir = vim.fn.expand('%:p:h'), title = vim.fn.input('Title: ') }<CR>",
{ 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", "<leader>zhnt", ":'<,'>ZkNewFromTitleSelection { dir = vim.fn.expand('%:p:h') }<CR>",
{ 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", "<leader>zhnc",
":'<,'>ZkNewFromContentSelection { dir = vim.fn.expand('%:p:h'), title = vim.fn.input('Title: ') }<CR>",
{ noremap = true, silent = false, buffer = true, desc = "Note from selection (content)" })
-- Open notes linking to the current buffer.
map("n", "<leader>zb", "<Cmd>ZkBacklinks<CR>", { noremap = true, silent = false, buffer = true, desc = "Backlinks" })
-- Alternative for backlinks using pure LSP and showing the source context.
--map('n', '<leader>zb', '<Cmd>lua vim.lsp.buf.references()<CR>', opts)
-- Open notes linked by the current buffer.
map("n", "<leader>zL", "<Cmd>ZkLinks<CR>", { noremap = true, silent = false, buffer = true, desc = "Links" })
map("n", "<leader>zi", "<Cmd>ZkInsertLink<CR>", { noremap = true, silent = false, buffer = true, desc = "Insert link" })
-- Preview a linked note.
-- Open the code actions for a visual selection.
map("v", "<leader>za", ":'<,'>lua vim.lsp.buf.range_code_action()<CR>",
{ noremap = true, silent = false, buffer = true, desc = "Code actions" })
end

20
ftplugin/python.lua Normal file
View file

@ -0,0 +1,20 @@
vim.g.slime_python_ipython = 1
vim.b.slime_cell_delimiter = vim.b.slime_cell_delimiter or "# %%"
local ts_lib = Config.treesitter_helpers
local global_nodes_python = { 'module' }
ts_lib.setup_keybindings(global_nodes_python)
local conform_format_group =
vim.api.nvim_create_augroup("PythonConformFormat_" .. vim.api.nvim_get_current_buf(), { clear = true })
vim.api.nvim_create_autocmd("BufWritePre", {
group = conform_format_group,
buffer = 0,
callback = function()
require("conform").format({
timeout_ms = 1000,
lsp_format = "prefer",
})
end,
})

56
ftplugin/r.lua Normal file
View file

@ -0,0 +1,56 @@
vim.b.slime_cell_delimiter = vim.b.slime_cell_delimiter or "## ----"
local assign_action = function()
if vim.bo.filetype ~= "r" then
return
end
local ok, r_edit = pcall(require, "r.edit")
if not ok then
return
end
if MiniTrailspace and MiniTrailspace.trim then
MiniTrailspace.trim()
end
r_edit.assign()
end
vim.api.nvim_buf_create_user_command(0, "RAssign", assign_action, { desc = "Trim trailing space and insert <-" })
-- Settings
vim.bo.comments = [[:#',:####,:###,:##,:#]]
-- Keymaps
-- Note: These use <Plug> mappings provided by R.nvim
vim.keymap.set("n", "<Enter>", "<Plug>RDSendLine", { buffer = true })
vim.keymap.set("v", "<Enter>", "<Plug>RSendSelection", { buffer = true })
-- Assignment operator (--)
vim.keymap.set("i", "--", "<Cmd>lua MiniTrailspace.trim()<CR><Plug>RInsertAssign", { buffer = true, noremap = true })
-- Pipe operator (;;)
vim.keymap.set("i", ";;", "<Cmd>lua MiniTrailspace.trim()<CR><Plug>RInsertPipe<CR>", { buffer = true, noremap = true })
-- MiniClue / WhichKey hints
local r_clues = {
{ mode = "n", keys = "<localleader>a", desc = "+batch" },
{ mode = "n", keys = "<localleader>b", desc = "+between/debug" },
{ mode = "n", keys = "<localleader>c", desc = "+substitute" },
{ mode = "n", keys = "<localleader>f", desc = "+functions" },
{ mode = "n", keys = "<localleader>i", desc = "+install" },
{ mode = "n", keys = "<localleader>k", desc = "+knit" },
{ mode = "n", keys = "<localleader>p", desc = "+paragraph" },
{ mode = "n", keys = "<localleader>r", desc = "+regular" },
{ mode = "n", keys = "<localleader>s", desc = "+selection" },
{ mode = "n", keys = "<localleader>t", desc = "+dput" },
{ mode = "n", keys = "<localleader>u", desc = "+undebug" },
}
vim.b.miniclue_config = {
clues = {
r_clues,
},
triggers = {
{ mode = "n", keys = "<localleader>", desc = "+R" },
},
}

11
ftplugin/sql.lua Normal file
View file

@ -0,0 +1,11 @@
vim.g.omni_sql_default_compl_type = "syntax"
local ts_lib = Config.treesitter_helpers
local global_nodes_sql = { 'program', 'cte' }
ts_lib.setup_keybindings(global_nodes_sql)
-- SQL specific keybindings
vim.keymap.set({ 'n' }, '<localleader>;', function()
vim.api.nvim_call_function('slime#send', { ";\n" })
end,
{ noremap = true, silent = true, desc = "SQL statment return", buffer = true })

28
init.lua Normal file
View file

@ -0,0 +1,28 @@
_G.Config = {}
local nix = require('config.nix').init { non_nix_value = true }
Config.isNixCats = nix.is_nix
require('lze').register_handlers(require('nixCatsUtils.lzUtils').for_cat)
local mini_deps = require('mini.deps')
if not Config.isNixCats then
local path_package = vim.fn.stdpath('data') .. '/site/'
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')
local clone_cmd = {
'git', 'clone', '--filter=blob:none',
'https://github.com/echasnovski/mini.nvim', mini_path
}
vim.fn.system(clone_cmd)
vim.cmd('packadd mini.nvim | helptags ALL')
vim.cmd('echo "Installed `mini.nvim`" | redraw')
end
-- Set up 'mini.deps' (customize to your liking)
mini_deps.setup({ path = { package = path_package } })
else
mini_deps.setup()
end

166
lua/config/nix.lua Normal file
View file

@ -0,0 +1,166 @@
local M = {}
local function detect_nix()
if vim.g.nix_info_plugin_name ~= nil then
return true
end
if vim.g[ [[nixCats-special-rtp-entry-nixCats]] ] ~= nil then
return true
end
return false
end
M.is_nix = detect_nix()
M.non_nix_default = true
local function setup_nixcats(non_nix_value)
if M.is_nix then
return
end
local ok, utils = pcall(require, "nixCatsUtils")
if ok and utils and utils.setup then
utils.setup { non_nix_value = non_nix_value }
end
end
local function init_from_nix_info()
if vim.g.nix_info_plugin_name == nil then
return nil
end
local ok, nix_info = pcall(require, vim.g.nix_info_plugin_name)
if not ok or not nix_info then
return nil
end
local function cat_lookup(cat)
if type(cat) == "table" then
for _, name in ipairs(cat) do
if nix_info(false, "settings", "cats", name) or nix_info(false, "info", "cats", name) then
return true
end
end
return false
end
return nix_info(false, "settings", "cats", cat) or nix_info(false, "info", "cats", cat)
end
local nc = setmetatable({
cats = nix_info.settings and nix_info.settings.cats or {},
settings = nix_info.settings or {},
get = function(_, default, ...) return nix_info(default, ...) end,
}, {
__call = function(_, default, ...)
if select("#", ...) == 0 then
return cat_lookup(default)
end
return nix_info(default, ...)
end,
__index = nix_info,
})
_G.nixCats = nc
package.preload['nixCats.cats'] = function()
return setmetatable(_G.nixCats.cats or {}, getmetatable(_G.nixCats))
end
return nc
end
local function get_nixcats()
if _G.nixCats then
return _G.nixCats
end
local nc = init_from_nix_info()
if nc then
return nc
end
local ok, nc_module = pcall(require, "nixCats")
if ok and nc_module then
return nc_module
end
return nil
end
function M.init(opts)
local non_nix_value = true
if type(opts) == "table" and type(opts.non_nix_value) == "boolean" then
non_nix_value = opts.non_nix_value
end
M.non_nix_default = non_nix_value
setup_nixcats(non_nix_value)
get_nixcats()
M.is_nix = detect_nix()
return M
end
local function cat_enabled(nc, cat, default)
if not nc then
return default
end
if type(cat) == "table" then
for _, name in ipairs(cat) do
if nc(name) then
return true
end
end
return default
end
local val = nc(cat)
if val == nil then
return default
end
return val
end
function M.get_cat(cat, default)
if default == nil then
default = M.non_nix_default
end
if not M.is_nix then
return default
end
local nc = get_nixcats()
return cat_enabled(nc, cat, default)
end
function M.get_setting(default, ...)
if not M.is_nix then
return default
end
local nc = get_nixcats()
if not nc then
return default
end
local value = nc(default, "settings", ...)
if value == nil then
return default
end
return value
end
function M.get_info(default, ...)
if not M.is_nix then
return default
end
local nc = get_nixcats()
if not nc then
return default
end
local value = nc(default, "info", ...)
if value == nil then
return default
end
return value
end
return M

129
lua/nixCatsUtils/init.lua Normal file
View file

@ -0,0 +1,129 @@
--[[
This directory is the luaUtils template.
You can choose what things from it that you would like to use.
And then delete the rest.
Everything in this directory is optional.
--]]
local M = {}
--[[
This file is for making your config still work WITHOUT nixCats.
When you don't use nixCats to load your config,
you wont have the nixCats plugin.
The setup function defined here defines a mock nixCats plugin when nixCats wasnt used to load the config.
This will help avoid indexing errors when the nixCats plugin doesnt exist.
NOTE: If you only ever use nixCats to load your config, you don't need this file.
--]]
---@type boolean
M.isNixCats = vim.g.nix_info_plugin_name ~= nil or vim.g[ [[nixCats-special-rtp-entry-nixCats]] ] ~= nil
---@class nixCatsSetupOpts
---@field non_nix_value boolean|nil
---This function will setup a mock nixCats plugin when not using nix
---It will help prevent you from running into indexing errors without a nixCats plugin from nix.
---If you loaded the config via nix, it does nothing
---non_nix_value defaults to true if not provided or is not a boolean.
---@param v nixCatsSetupOpts
function M.setup(v)
if not M.isNixCats then
local nixCats_default_value
if type(v) == "table" and type(v.non_nix_value) == "boolean" then
nixCats_default_value = v.non_nix_value
else
nixCats_default_value = true
end
local mk_with_meta = function (tbl)
return setmetatable(tbl, {
__call = function(_, attrpath)
local strtable = {}
if type(attrpath) == "table" then
strtable = attrpath
elseif type(attrpath) == "string" then
for key in attrpath:gmatch("([^%.]+)") do
table.insert(strtable, key)
end
else
print("function requires a table of strings or a dot separated string")
return
end
return vim.tbl_get(tbl, unpack(strtable));
end
})
end
package.preload['nixCats'] = function ()
local ncsub = {
get = function(_) return nixCats_default_value end,
cats = mk_with_meta({
nixCats_config_location = vim.fn.stdpath('config'),
wrapRc = false,
}),
settings = mk_with_meta({
nixCats_config_location = vim.fn.stdpath('config'),
configDirName = os.getenv("NVIM_APPNAME") or "nvim",
wrapRc = false,
}),
petShop = mk_with_meta({}),
extra = mk_with_meta({}),
pawsible = mk_with_meta({
allPlugins = {
start = {},
opt = {},
},
}),
configDir = vim.fn.stdpath('config'),
packageBinPath = os.getenv('NVIM_WRAPPER_PATH_NIX') or vim.v.progpath
}
return setmetatable(ncsub, {__call = function(_, cat) return ncsub.get(cat) end})
end
_G.nixCats = require('nixCats')
end
end
---allows you to guarantee a boolean is returned, and also declare a different
---default value than specified in setup when not using nix to load the config
---@overload fun(v: string|string[]): boolean
---@overload fun(v: string|string[], default: boolean): boolean
function M.enableForCategory(v, default)
if M.isNixCats or default == nil then
if nixCats(v) then
return true
else
return false
end
else
return default
end
end
---if nix, return value of nixCats(v) else return default
---Exists to specify a different non_nix_value than the one in setup()
---@param v string|string[]
---@param default any
---@return any
function M.getCatOrDefault(v, default)
if M.isNixCats then
return nixCats(v)
else
return default
end
end
---for conditionally disabling build steps on nix, as they are done via nix
---I should probably have named it dontAddIfCats or something.
---@overload fun(v: any): any|nil
---Will return the second value if nix, otherwise the first
---@overload fun(v: any, o: any): any
function M.lazyAdd(v, o)
if M.isNixCats then
return o
else
return v
end
end
return M

View file

@ -0,0 +1,37 @@
--[[
This directory is the luaUtils template.
You can choose what things from it that you would like to use.
And then delete the rest.
Everything in this directory is optional.
--]]
local M = {}
local nix = require('config.nix')
-- A nixCats specific lze handler that you can use to conditionally enable by category easier.
-- at the start of your config, register with
-- require('lze').register_handlers(require('nixCatsUtils.lzUtils').for_cat)
-- before any calls to require('lze').load using the handler have been made.
-- accepts:
-- for_cat = { "your" "cat" };
-- for_cat = { cat = { "your" "cat" }, default = bool }
-- for_cat = "your.cat";
-- for_cat = { cat = "your.cat", default = bool }
-- where default is an alternate value for when nixCats was NOT used to install the config
M.for_cat = {
spec_field = "for_cat",
set_lazy = false,
modify = function(plugin)
if type(plugin.for_cat) == "table" and plugin.for_cat.cat ~= nil then
local default = plugin.for_cat.default
if default == nil then
default = false
end
plugin.enabled = nix.get_cat(plugin.for_cat.cat, default)
else
plugin.enabled = nix.get_cat(plugin.for_cat, false)
end
return plugin
end,
}
return M

196
lua/nix_smart_send.lua Normal file
View file

@ -0,0 +1,196 @@
local M = {}
-- 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
return false
end
for _, v in ipairs(list) do
if v == value then
return true
end
end
return false
end
-- Define comment node types as constants
local COMMENT_TYPES = {
comment = true,
block_comment = true,
line_comment = true,
}
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)
end
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
return nil
end
root = parser:parse()[1]:root()
else
root = cur_node:root()
end
if not root then
return nil
end
return root:type()
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")
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
end
-- Get the line content and find first non-whitespace character
line_content = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1]
first_non_ws = line_content:find("%S") or 1
vim.api.nvim_win_set_cursor(0, { line_num, first_non_ws - 1 })
node = ts_utils.get_node_at_cursor()
end
return true
end
function M.vselect_node(node)
local ts_utils = require('nvim-treesitter.ts_utils')
if not node then
return false
end
local cur_buf = vim.api.nvim_get_current_buf()
ts_utils.update_selection(cur_buf, node, "V")
return true
end
function M.select_until_global(global_nodes)
local ts_utils = require('nvim-treesitter.ts_utils')
local root_node = M.detect_global_node()
if not root_node and global_nodes then
root_node = global_nodes[1]
end
-- Use empty table if no global nodes provided
global_nodes = global_nodes or {}
local node = ts_utils.get_node_at_cursor()
if not node then
-- print("No syntax node found at cursor position")
return nil
end
local node_type = node:type()
if node_type == root_node then
-- print("Cursor is on the root " .. root_node .. " node or in an empty area.")
return nil
end
-- Check if current node is a global
if is_in_list(global_nodes, node_type) then
if M.vselect_node(node) then
return node
end
end
-- Traverse up the tree until we find a global node or reach the root
local parent = node:parent()
local parent_type = parent:type() or ""
if parent and is_in_list(global_nodes, parent_type) then
if M.vselect_node(node) then
return node
end
end
while parent and not is_in_list(global_nodes, parent:type()) do
node = parent
parent = node:parent()
end
if M.vselect_node(node) then
return node
end
return nil
end
function M.slime_send_region()
-- Check if slime plugin is available
if not vim.fn.exists('*slime#send_op') then
vim.notify("slime plugin not available", vim.log.levels.ERROR)
return
end
local slime_command = ":<C-u>call slime#send_op(visualmode(), 1)<CR>"
local termcodes = vim.api.nvim_replace_termcodes(slime_command, true, true, true)
vim.api.nvim_feedkeys(termcodes, "x", true)
end
function M.send_repl(global_nodes)
local ts_utils = require('nvim-treesitter.ts_utils')
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")
return
end
-- Send the selected text to the terminal using vim-slime
M.slime_send_region()
-- Move cursor and continue
ts_utils.goto_node(sel_node, true)
M.move_to_next_non_empty_line()
end
return M

View file

@ -0,0 +1,51 @@
{
config,
lib,
...
}:
{
options.cats = lib.mkOption {
type = lib.types.attrsOf lib.types.bool;
description = ''
Category toggles used to enable/disable specs by name.
Keys map directly to specs (e.g., `python` controls `specs.python`).
Set a category to `false` to skip its dependency/plugin specs.
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
- julia: Julia tooling and packages
- lua: Lua tooling and LSPs
- markdown: markdown tooling and plugins
- nix: Nix tooling and plugins
- optional: optional tools and utilities
- python: Python tooling and plugins
- r: R tooling and plugins
- test: test-only tooling (disabled by default)
- treesitterParsers: Treesitter parsers
- utils: general utilities
'';
};
config.cats = {
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 false;
markdown = lib.mkDefault false;
nix = lib.mkDefault false;
optional = lib.mkDefault false;
python = lib.mkDefault false;
r = lib.mkDefault false;
test = lib.mkDefault false;
treesitterParsers = lib.mkDefault true;
utils = lib.mkDefault true;
};
}

View file

@ -0,0 +1,35 @@
{
config,
lib,
...
}:
{
# Point to the directory containing init.lua, plugin/, lua/, etc.
config.settings.config_directory = ../../..;
# Default colorscheme and background
config.settings.colorscheme = "kanagawa";
config.settings.background = "dark";
# Enable RC wrapping (allows neovim to find the config)
config.settings.wrapRc = 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 = "n";
# Prevent neovim from loading system-wide config
config.settings.block_normal_config = true;
# Don't symlink the config (we wrap it instead)
config.settings.dont_link = false;
# Create additional aliases for the binary
config.settings.aliases = [ "vim" ];
# Enable wrapper handling of spec runtimeDeps (template pattern).
config.settings.autowrapRuntimeDeps = true;
}

View file

@ -0,0 +1,27 @@
{
config,
pkgs,
lib,
...
}:
{
# Environment variables set for the wrapper.
# These are available when running neovim.
config.env = lib.mkMerge [
(lib.mkIf (config.cats.r or false) {
R_LIBS_USER = "./.Rlibs";
})
(lib.mkIf (config.cats.python or false) {
UV_PYTHON_DOWNLOADS = "never";
UV_PYTHON = pkgs.python.interpreter;
})
(lib.mkIf (config.cats.test or false) {
TESTVAR = "It worked!";
})
];
# Environment variables with defaults (can be overridden by user)
config.envDefault = lib.mkIf (config.cats.test or false) {
TESTVAR2 = "It worked again!";
};
}

View file

@ -0,0 +1,57 @@
{
config,
pkgs,
lib,
...
}:
{
config.hosts = lib.mkMerge [
{
node.nvim-host.enable = true;
perl.nvim-host.enable = true;
ruby.nvim-host.enable = true;
g = {
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}";
};
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 true) {
jl = {
nvim-host.enable = true;
nvim-host.package = "${pkgs.julia-bin}/bin/julia";
nvim-host.argv0 = "julia";
nvim-host.addFlag = [
"--project=@."
];
};
})
(lib.mkIf (config.cats.python or true) {
python3.nvim-host.enable = true;
})
(lib.mkIf (config.cats.r or true) {
r = {
nvim-host.enable = true;
nvim-host.package = "${pkgs.rWrapper}/bin/R";
nvim-host.argv0 = "R";
nvim-host.addFlag = [
"--no-save"
"--no-restore"
];
};
})
];
}

View file

@ -0,0 +1,39 @@
{
config,
lib,
...
}:
{
options.settings.lang_packages = lib.mkOption {
type = lib.types.submodule {
options = {
python = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Additional Python-related packages appended to the python spec (overlay defaults remain).";
};
r = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Additional R-related packages appended to the r spec (overlay defaults remain).";
};
julia = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Additional Julia packages (names) passed to julia-bin.withPackages.";
};
};
};
default = { };
description = ''
Language-specific package overrides appended to each language spec's extraPackages.
Intended for flake.nix overrides via wrapper.config.wrap.
'';
};
config.settings.lang_packages = {
python = lib.mkDefault [ ];
r = lib.mkDefault [ ];
julia = lib.mkDefault [ ];
};
}

View file

@ -0,0 +1,35 @@
{
config,
lib,
...
}:
let
collect_runtime_packages = runtime_deps_type:
config.specCollect
(acc: spec:
let
is_enabled = if spec ? enable then spec.enable else true;
has_runtime_deps = (spec.runtimeDeps or false) == runtime_deps_type;
packages = spec.extraPackages or [ ];
in
acc ++ lib.optionals (is_enabled && has_runtime_deps) packages
)
[ ];
prefix_packages = collect_runtime_packages "prefix";
suffix_packages = collect_runtime_packages "suffix";
to_path_specs = packages: [
{
data = [
"PATH"
":"
"${lib.makeBinPath packages}"
];
}
];
in
{
config.prefixVar = lib.optionals (prefix_packages != [ ]) (to_path_specs prefix_packages);
config.suffixVar = lib.optionals (suffix_packages != [ ]) (to_path_specs suffix_packages);
}

View file

@ -0,0 +1,51 @@
{ config, lib, ... }:
{
# This module implements category-based enabling of specs.
# It runs early (order 200) so other specMaps can see the enable flags.
#
# How it works:
# 1. For each spec, extract its name (removing -lazy suffix if present)
# 2. Check if there's a corresponding cats.<name> 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;
}
];
}

View file

@ -0,0 +1,188 @@
{
config,
pkgs,
lib,
wlib,
...
}: {
# ============================================================================
# SPEC MODULE DEFAULTS
# ============================================================================
# Define default options available to all specs
config.specMods = {parentSpec ? null, ...}: {
options.extraPackages = lib.mkOption {
type = lib.types.listOf wlib.types.stringable;
default = [];
description = "a extraPackages 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"];
config = ''
vim.o.shell = "${pkgs.zsh}/bin/zsh"
'';
runtimeDeps = "prefix";
extraPackages = with pkgs; [
perl
ruby
shfmt
sqlfluff
tree-sitter
];
};
# ============================================================================
# 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; [
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
];
};
# ============================================================================
# MARKDOWN SPEC
# ============================================================================
config.specs.markdown = lib.mkIf (config.cats.markdown or true) {
data = lib.mkDefault null;
runtimeDeps = "prefix";
extraPackages = 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; [
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; [
lua-language-server
];
};
# ============================================================================
# PYTHON SPEC
# ============================================================================
config.specs.python = lib.mkIf (config.cats.python or true) {
data = lib.mkDefault null;
runtimeDeps = "prefix";
extraPackages = let
python_packages_fn =
if pkgs ? basePythonPackages
then ps: pkgs.basePythonPackages ps ++ config.settings.lang_packages.python
else _: config.settings.lang_packages.python;
python_with_packages = pkgs.python3.withPackages python_packages_fn;
in
with pkgs; [
python_with_packages
nodejs
ruff
basedpyright
uv
];
};
# ============================================================================
# R SPEC
# ============================================================================
config.specs.r = lib.mkIf (config.cats.r or true) {
data = lib.mkDefault null;
runtimeDeps = "prefix";
extraPackages = let
r_packages = (pkgs.baseRPackages or []) ++ config.settings.lang_packages.r;
in
with pkgs; [
(rWrapper.override {packages = r_packages;})
radianWrapper
(quarto.override {extraRPackages = r_packages;})
air-formatter
yaml-language-server
updateR
];
};
# ============================================================================
# JULIA SPEC
# ============================================================================
config.specs.julia = lib.mkIf (config.cats.julia or true) {
data = lib.mkDefault null;
runtimeDeps = "prefix";
extraPackages = 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; [
clickhouse-lts
];
};
config.extraPackages = config.specCollect (acc: v: acc ++ (v.extraPackages or [])) [];
}

View file

@ -0,0 +1,183 @@
{
config,
pkgs,
lib,
...
}:
{
config.specs.gitPlugins = {
data = [ ];
};
config.specs.r = {
data = [
config.nvim-lib.neovimPlugins.r
];
};
config.specs.markdown-lazy = {
lazy = true;
data = [
config.nvim-lib.neovimPlugins.cmp-pandoc-references
];
};
config.specs.r-lazy = {
lazy = true;
data = [
config.nvim-lib.neovimPlugins.cmp-r
];
};
config.specs.general = {
data = with pkgs.vimPlugins; [
lze
lzextras
plenary-nvim
neogit
{
data = mini-nvim;
pname = "mini.nvim";
}
{
data = cyberdream-nvim;
pname = "cyberdream";
}
{
data = onedark-nvim;
pname = "onedark";
}
{
data = tokyonight-nvim;
pname = "tokyonight";
}
{
data = kanagawa-nvim;
pname = "kanagawa";
}
{
data = gruvbox-nvim;
pname = "gruvbox";
}
{
data = nord-nvim;
pname = "nord";
}
{
data = dracula-nvim;
pname = "dracula";
}
{
data = vscode-nvim;
pname = "vscode";
}
{
data = nightfox-nvim;
pname = "nightfox";
}
{
data = catppuccin-nvim;
pname = "catppuccin";
}
];
};
config.specs.lua = {
data = with pkgs.vimPlugins; [
luvit-meta
{
data = lazydev-nvim;
pname = "lazydev";
}
];
};
config.specs.markdown = {
data = with pkgs.vimPlugins; [
quarto-nvim
render-markdown-nvim
{
data = otter-nvim;
pname = "otter";
}
{
data = zk-nvim;
pname = "zk";
}
];
};
config.specs.utils = {
data = with pkgs.vimPlugins; [
blink-cmp
nvim-lspconfig
nvim-treesitter-context
nvim-treesitter-textobjects
{
data = pkgs.codecompanion-nvim;
pname = "codecompanion";
}
];
};
config.specs.treesitterParsers = {
data = with pkgs.vimPlugins.nvim-treesitter-parsers; [
bash
c
cpp
csv
diff
dockerfile
git_config
git_rebase
gitattributes
gitcommit
gitignore
html
javascript
json
julia
latex
lua
luadoc
make
markdown
markdown_inline
nix
python
query
r
rnoweb
regex
sql
toml
vim
vimdoc
xml
yaml
zig
];
};
config.specs.utils-lazy = {
lazy = true;
data = with pkgs.vimPlugins; [
blink-compat
blink-copilot
cmp-cmdline
colorful-menu-nvim
conform-nvim
copilot-lua
nvim-dap
nvim-dap-ui
nvim-dap-virtual-text
nvim-lint
vim-slime
];
};
config.specs.gitPlugins-lazy = {
lazy = true;
data = [ ];
};
}

71
modules/neovim.nix Normal file
View file

@ -0,0 +1,71 @@
inputs:
{
config,
wlib,
lib,
pkgs,
...
}:
{
# ============================================================================
# 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
./module/settings/hosts.nix
./module/settings/lang-packages.nix
./module/settings/runtime-path.nix
];
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
# Utilities for working with plugin inputs
options.nvim-lib.neovimPlugins = lib.mkOption {
readOnly = true;
type = lib.types.attrsOf wlib.types.stringable;
default = config.nvim-lib.pluginsFromPrefix "plugins-" inputs;
};
options.nvim-lib.pluginsFromPrefix = lib.mkOption {
type = lib.types.raw;
readOnly = true;
default =
prefix: inputs:
lib.pipe inputs [
builtins.attrNames
(builtins.filter (s: lib.hasPrefix prefix s))
(map (
input:
let
name = lib.removePrefix prefix input;
in
{
inherit name;
value = config.nvim-lib.mkPlugin name inputs.${input};
}
))
builtins.listToAttrs
];
};
# ============================================================================
# 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;
config.info.nixCats_wrapRc = config.settings.wrapRc or false;
config.info.nixCats_configDirName = "nvim";
}

98
overlays/README.md Normal file
View file

@ -0,0 +1,98 @@
# Overlays
This directory contains composable Nix overlays used by the Neovim wrapper configuration. Each overlay is small and focused, so you can reuse or override them downstream.
## Files
- `r.nix`
R-related overrides (rix overlay). Exposes `pkgs.rpkgs` from rstats-on-nix and creates pre-configured `rWrapper` and `quarto` with standard R packages.
- `python.nix`
Python-related overrides and package additions (e.g., extra Python packages).
- `plugins.nix`
Neovim plugin overrides (e.g., patching or pinning plugin derivations).
- `default.nix`
Aggregates and exports the overlays in a composable way. Includes the fran overlay for custom R packages.
## Exports from `default.nix`
`overlays/default.nix` exposes:
- `rOverlay` - rix overlay for R packages from rstats-on-nix
- `franOverlay` - fran overlay for custom R packages and tools
- `pythonOverlay`
- `pluginsOverlay`
- `dependencyOverlays` (list of overlays in order)
- `dependencyOverlay` (composed overlay via `lib.composeManyExtensions`)
- `default` (alias of `dependencyOverlay`)
- `dependencies` (alias of `dependencyOverlays`)
## Downstream usage examples
### Use the composed default overlay
```/dev/null/example.nix#L1-18
{
inputs,
...
}:
let
overlayDefs = import ./overlays/default.nix inputs;
in {
nixpkgs.overlays = [
overlayDefs.default
];
}
```
### Use specific overlays only
```/dev/null/example.nix#L1-22
{
inputs,
...
}:
let
overlayDefs = import ./overlays/default.nix inputs;
in {
nixpkgs.overlays = [
overlayDefs.rOverlay
overlayDefs.pluginsOverlay
];
}
```
### Extend with your own overlay (composition)
```/dev/null/example.nix#L1-29
{
inputs,
...
}:
let
overlayDefs = import ./overlays/default.nix inputs;
myOverlay = final: prev: {
# Example: override a package
myTool = prev.myTool.override { /* ... */ };
};
in {
nixpkgs.overlays = [
overlayDefs.default
myOverlay
];
}
```
## Adding a new overlay
1. Create a new overlay file in this directory (e.g., `foo.nix`).
2. Import it in `overlays/default.nix` and add it to `dependencyOverlays`.
3. Optionally expose it as a named export (e.g., `fooOverlay`) for downstream reuse.
## Notes
- Keep overlays composable and focused.
- Avoid monolithic overlays; prefer small, purpose-specific overlays.
- When overriding plugins, keep patches minimal and document the intent.

42
overlays/default.nix Normal file
View file

@ -0,0 +1,42 @@
{ nixpkgs, ... }@inputs:
let
lib = nixpkgs.lib;
rOverlay = import ./r.nix inputs;
franOverlay = inputs.fran.overlays.default;
pythonOverlay = import ./python.nix inputs;
pluginsOverlay = import ./plugins.nix inputs;
dependencyOverlays = [
rOverlay
franOverlay
pythonOverlay
pluginsOverlay
];
dependencyOverlay = lib.composeManyExtensions dependencyOverlays;
in
{
inherit
rOverlay
franOverlay
pythonOverlay
pluginsOverlay
dependencyOverlays
dependencyOverlay;
# Named exports for downstream composition.
default = dependencyOverlay;
dependencies = dependencyOverlays;
overlays = {
inherit
rOverlay
franOverlay
pythonOverlay
pluginsOverlay
dependencyOverlays
dependencyOverlay;
default = dependencyOverlay;
dependencies = dependencyOverlays;
};
}

25
overlays/plugins.nix Normal file
View file

@ -0,0 +1,25 @@
{ ... }:
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"
];
};
}

12
overlays/python.nix Normal file
View file

@ -0,0 +1,12 @@
{ ... }:
final: prev:
let
reqPkgs = pyPackages:
with pyPackages; [
numpy
];
in
{
basePythonPackages = reqPkgs;
python = prev.python3.withPackages reqPkgs;
}

46
overlays/r.nix Normal file
View file

@ -0,0 +1,46 @@
# 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"
{rixpkgs, ...}: final: prev: let
# R packages from rstats-on-nix for the current system
rpkgs = rixpkgs.legacyPackages.${prev.stdenv.hostPlatform.system};
# Standard R packages used by default in rWrapper and quarto
reqPkgs = with rpkgs.rPackages; [
languageserver
];
in {
inherit rpkgs;
baseRPackages = reqPkgs;
# R wrapper with standard packages
rWrapper = rpkgs.rWrapper.override {packages = reqPkgs;};
# Quarto with R integration
quarto = rpkgs.quarto.override {extraRPackages = reqPkgs;};
# Update helper for rix
updateR = import ../scripts/updater.nix { pkgs = final; };
}

183
plugin/00_options.lua Normal file
View file

@ -0,0 +1,183 @@
--stylua: ignore start
local later = MiniDeps.later
local now = MiniDeps.now
local nix = require('config.nix')
if not Config.isNixCats then
now(function()
MiniDeps.add({ name = "mini.nvim" })
end)
end
-- Leader key =================================================================
vim.g.mapleader = ' '
vim.g.maplocalleader = ','
-- General ====================================================================
vim.o.backup = true -- Don't store backup
vim.opt.backupdir = vim.fn.stdpath('data') .. '/backups'
vim.o.mouse = 'a' -- Enable mouse
vim.o.mousescroll = 'ver:25,hor:6' -- Customize mouse scroll
vim.o.scrolloff = 8 -- Lines above and below cursor
vim.o.switchbuf = 'usetab' -- Use already opened buffers when switching
vim.o.writebackup = true -- Don't store backup (better performance)
vim.o.undofile = true -- Enable persistent undo
vim.o.wildmenu = true -- Enable wildmenu
vim.o.wildmode = 'full' -- Show all matches in command line completion
vim.o.shada = "'100,<50,s10,:1000,/100,@100,h" -- Limit what is stored in ShaDa file
vim.cmd('filetype plugin indent on') -- Enable all filetype plugins
-- UI =========================================================================
vim.o.breakindent = true -- Indent wrapped lines to match line start
vim.o.colorcolumn = '+1' -- Draw colored column one step to the right of desired maximum width
vim.o.cursorline = false -- Enable highlighting of the current line
vim.o.foldenable = false -- Enable folding
vim.o.linebreak = true -- Wrap long lines at 'breakat' (if 'wrap' is set)
vim.o.list = true -- Show helpful character indicators
vim.o.number = true -- Show line numbers
vim.o.pumheight = 10 -- Make popup menu smaller
vim.o.relativenumber = true -- Show relative line numbers
vim.o.ruler = false -- Don't show cursor position
vim.o.shortmess = 'FOSWaco' -- Disable certain messages from |ins-completion-menu|
vim.o.showmode = false -- Don't show mode in command line
vim.o.signcolumn = 'yes' -- Always show signcolumn or it would frequently shift
vim.o.splitbelow = true -- Horizontal splits will be below
vim.o.splitright = true -- Vertical splits will be to the right
vim.o.wrap = true -- Display long lines as just one line
vim.o.background = nix.get_setting('dark', "background") --'light' -- Set background
vim.o.listchars = table.concat({ 'extends:…', 'nbsp:␣', 'precedes:…', 'tab:> ', 'trail:·' }, ',') -- Special text symbols
vim.o.cursorlineopt = 'screenline,number' -- Show cursor line only screen line when wrapped
vim.o.breakindentopt = 'list:-1' -- Add padding for lists when 'wrap' is on
if vim.fn.has('nvim-0.9') == 1 then
vim.o.shortmess = 'CFOSWaco' -- Don't show "Scanning..." messages
vim.o.splitkeep = 'screen' -- Reduce scroll during window split
end
if vim.fn.has('nvim-0.10') == 0 then
vim.o.termguicolors = true -- Enable gui colors (Neovim>=0.10 does this automatically)
end
if vim.fn.has('nvim-0.11') == 1 then
-- vim.o.completeopt = 'menuone,fuzzy' -- Use fuzzy matching for built-in completion noselect,
vim.o.winborder = 'rounded' -- Use double-line as default border
end
if vim.fn.has('nvim-0.12') == 1 then
vim.o.pummaxwidth = 100 -- Limit maximum width of popup menu
vim.o.completefuzzycollect = 'keyword,files,whole_line' -- Use fuzzy matching when collecting candidates
end
vim.o.complete = '.,w,b,kspell' -- Use spell check and don't use tags for completion
-- Colors =====================================================================
-- Enable syntax highlighing if it wasn't already (as it is time consuming)
-- Don't use defer it because it affects start screen appearance
if vim.fn.exists('syntax_on') ~= 1 then vim.cmd('syntax enable') end
-- Editing ====================================================================
vim.o.autoindent = true -- Use auto indent
vim.o.expandtab = true -- Convert tabs to spaces
vim.o.formatoptions = 'qnl1j' -- Improve comment editing
vim.o.ignorecase = true -- Ignore case when searching (use `\C` to force not doing that)
vim.o.incsearch = true -- Show search results while typing
vim.o.infercase = true -- Infer letter cases for a richer built-in keyword completion
vim.o.shiftwidth = 2 -- Use this number of spaces for indentation
vim.o.smartcase = true -- Don't ignore case when searching if pattern has upper case
vim.o.smartindent = true -- Make indenting smart
vim.o.tabstop = 2 -- Insert 2 spaces for a tab
vim.o.virtualedit = 'block' -- Allow going past the end of line in visual block mode
vim.o.iskeyword = '@,48-57,_,192-255,-' -- Treat dash separated words as a word text object
-- Define pattern for a start of 'numbered' list. This is responsible for
-- correct formatting of lists when using `gw`. This basically reads as 'at
-- least one special character (digit, -, +, *) possibly followed some
-- punctuation (. or `)`) followed by at least one space is a start of list
-- item'
vim.o.formatlistpat = [[^\s*[0-9\-\+\*]\+[\.\)]*\s\+]]
-- Spelling ===================================================================
vim.o.spelllang = 'en_us,de' -- Define spelling dictionaries
vim.o.spelloptions = 'camel' -- Treat parts of camelCase words as seprate words
-- vim.o.dictionary = vim.fn.stdpath('config') .. '/misc/dict/english.txt' -- Use specific dictionaries
-- Folds ======================================================================
vim.o.foldmethod = 'indent' -- Set 'indent' folding method
vim.o.foldlevel = 1 -- Display all folds except top ones
vim.o.foldnestmax = 10 -- Create folds only for some number of nested levels
vim.g.markdown_folding = 1 -- Use folding by heading in markdown files
if vim.fn.has('nvim-0.10') == 1 then
vim.o.foldtext = '' -- Use underlying text with its highlighting
end
-- Diagnostics ================================================================
local diagnostic_opts = {
-- Define how diagnostic entries should be shown
signs = { priority = 9999, severity = { min = 'HINT', max = 'ERROR' } },
virtual_lines = false,
virtual_text = { current_line = false, severity = { min = 'WARN', max = 'ERROR' } },
jump = { float = false },
underline = false,
-- Don't update diagnostics when typing
update_in_insert = false,
}
later(function() vim.diagnostic.config(diagnostic_opts) end)
-- Custom autocommands ========================================================
local augroup = vim.api.nvim_create_augroup('CustomSettings', {})
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'markdown' },
group = augroup,
callback = function()
vim.diagnostic.config({
signs = {
severity = { min = 'WARN', max = 'ERROR' }
},
virtual_text = {
current_line = false,
severity = { min = 'HINT', max = 'ERROR' }
}
})
end
})
vim.api.nvim_create_autocmd("FileType", {
desc = "remove formatoptions",
callback = function()
vim.opt.formatoptions:remove({ "r", "o" }) -- Don't continue comments on enter or o
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"
vim.g.neovide_cursor_smooth_blink = true
vim.g.neovide_cursor_animation_length = 0.02
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.g.neovide_floating_corner_radius = 0.35
vim.keymap.set("n", "<leader>nf", "<cmd>NeovideFullscreen<CR>", { desc = "Toggle Neovide Fullscreen" })
end
--stylua: ignore end

119
plugin/01_lib.lua Normal file
View file

@ -0,0 +1,119 @@
-- Global Functions
Config.new_scratch_buffer = function() vim.api.nvim_win_set_buf(0, vim.api.nvim_create_buf(true, true)) end
-- Toggle quickfix window
Config.toggle_quickfix = function()
local cur_tabnr = vim.fn.tabpagenr()
for _, wininfo in ipairs(vim.fn.getwininfo()) do
if wininfo.quickfix == 1 and wininfo.tabnr == cur_tabnr then return vim.cmd('cclose') end
end
vim.cmd('copen')
end
Config.log = {}
Config.log_buf_id = Config.log_buf_id or nil
Config.start_hrtime = Config.start_hrtime or vim.loop.hrtime()
Config.log_print = function()
if Config.log_buf_id == nil or not vim.api.nvim_buf_is_valid(Config.log_buf_id) then
Config.log_buf_id = vim.api.nvim_create_buf(true, true)
end
vim.api.nvim_win_set_buf(0, Config.log_buf_id)
vim.api.nvim_buf_set_lines(Config.log_buf_id, 0, -1, false, vim.split(vim.inspect(Config.log), '\n'))
end
Config.log_clear = function()
Config.log = {}
Config.start_hrtime = vim.loop.hrtime()
vim.cmd('echo "Cleared log"')
end
-- Execute current line with `lua`
Config.execute_lua_line = function()
local line = 'lua ' .. vim.api.nvim_get_current_line()
vim.api.nvim_command(line)
print(line)
vim.api.nvim_input('<Down>')
end
-- Try opening current file's dir with fallback to cwd
Config.try_opendir = function()
local buff = vim.api.nvim_buf_get_name(0)
local ok, err = pcall(MiniFiles.open, buff)
if ok then return end
vim.notify(err)
MiniFiles.open()
end
-- For mini.start
--- Edit a file in the specified window, with smart buffer reuse
--- @param path string: File path to edit
--- @param win_id number|nil: Window ID (defaults to current window)
--- @return number|nil: Buffer ID on success, nil on failure
Config.edit = function(path, win_id)
-- Validate inputs
if type(path) ~= 'string' or path == '' then
return nil
end
win_id = win_id or 0
if not vim.api.nvim_win_is_valid(win_id == 0 and vim.api.nvim_get_current_win() or win_id) then
return nil
end
local current_buf = vim.api.nvim_win_get_buf(win_id)
-- Check if current buffer can be reused (empty, unmodified, single window)
local is_empty_buffer = vim.fn.bufname(current_buf) == ''
local is_regular_buffer = vim.bo[current_buf].buftype ~= 'quickfix'
local is_unmodified = not vim.bo[current_buf].modified
local is_single_window = #vim.fn.win_findbuf(current_buf) == 1
local has_only_empty_line = vim.deep_equal(vim.fn.getbufline(current_buf, 1, '$'), { '' })
local can_reuse_buffer = is_empty_buffer and is_regular_buffer and is_unmodified
and is_single_window and has_only_empty_line
-- Create or get buffer for the file
local normalized_path = vim.fn.fnamemodify(path, ':.')
local target_buf = vim.fn.bufadd(normalized_path)
-- Set buffer in window (use pcall to handle swap file messages gracefully)
local success = pcall(vim.api.nvim_win_set_buf, win_id, target_buf)
if not success then
return nil
end
-- Ensure buffer is listed
vim.bo[target_buf].buflisted = true
-- Clean up old buffer if it was reused
if can_reuse_buffer then
pcall(vim.api.nvim_buf_delete, current_buf, { unload = false })
end
return target_buf
end
-- Load library
local packdir = nixCats.vimPackDir or MiniDeps.config.path.package
-- See https://github.com/echasnovski/mini.deps/blob/2953b2089591a49a70e0a88194dbb47fb0e4635c/lua/mini/deps.lua#L518C5-L518C39
Config.source_path = function(path)
pcall(function() vim.cmd('source ' .. vim.fn.fnameescape(path)) end)
end
Config.add = (function(pkg)
vim.cmd.packadd(pkg)
local should_load_after_dir = vim.v.vim_did_enter == 1 and vim.o.loadplugins
if not should_load_after_dir then return end
local after_paths = vim.fn.glob(
packdir .. '/pack/myNeovimPackages/opt/' .. pkg .. '/after/plugin/**/*.{vim,lua}',
false,
true
)
vim.iter(after_paths):map(function(p)
Config.source_path(p)
end)
end)
Config.now_if_args = vim.fn.argc(-1) > 0 and MiniDeps.now or MiniDeps.later

130
plugin/02_startup.lua Normal file
View file

@ -0,0 +1,130 @@
local M = {}
-- Helper function to normalize input to a list
local function normalize_filetypes_input(input)
if type(input) == "string" then
return { input }
elseif type(input) == "table" then
return input
else
vim.notify("get_recent_files_by_ft_or_ext: Invalid input type for filetypes", vim.log.levels.ERROR)
return nil
end
end
-- Helper function to check if a file matches any target filetype
local function matches_target_filetype(file_path, file_ext, detected_ft, target_ft_map)
for target_ft in pairs(target_ft_map) do
if file_ext:lower() == target_ft:lower() or
(detected_ft and detected_ft == target_ft) then
return target_ft
end
end
return nil
end
-- Helper function to safely detect filetype
local function detect_filetype(file_path)
local success, ft_match_fn = pcall(function() return vim.filetype.match end)
if not (success and type(ft_match_fn) == "function") then
return nil
end
local ok, result = pcall(ft_match_fn, { filename = file_path })
return ok and type(result) == "string" and result ~= "" and result or nil
end
-- Helper function to capitalize first letter
local function capitalize_first(str)
return str:sub(1, 1):upper() .. str:sub(2)
end
function M.get_recent_files_by_ft_or_ext(target_filetypes_input)
local target_filetypes_list = normalize_filetypes_input(target_filetypes_input)
if not target_filetypes_list or #target_filetypes_list == 0 then
return {}
end
-- Create lookup map for O(1) filetype checking
local target_ft_map = {}
for _, ft in ipairs(target_filetypes_list) do
target_ft_map[ft] = true
end
local oldfiles = vim.v.oldfiles
if not oldfiles or #oldfiles == 0 then
return {}
end
local cwd = vim.fn.getcwd()
local fnamemodify = vim.fn.fnamemodify
local filereadable = vim.fn.filereadable
local getftime = vim.fn.getftime
-- Track most recent file for each target filetype
local most_recent_files = {}
for _, ft in ipairs(target_filetypes_list) do
most_recent_files[ft] = { file = nil, time = 0 }
end
local processed_paths = {}
for _, file_path in ipairs(oldfiles) do
local full_path = fnamemodify(file_path, ':p')
-- Skip if already processed or invalid
if processed_paths[full_path] or
filereadable(full_path) ~= 1 or
not full_path:find(cwd, 1, true) then
goto continue
end
processed_paths[full_path] = true
local file_ext = fnamemodify(full_path, ':e')
local detected_ft = detect_filetype(full_path)
local matched_ft = matches_target_filetype(full_path, file_ext, detected_ft, target_ft_map)
if matched_ft then
local mod_time = getftime(full_path)
if mod_time > most_recent_files[matched_ft].time then
most_recent_files[matched_ft] = { file = full_path, time = mod_time }
end
end
::continue::
end
-- Build result items
local result_items = {}
for ft, data in pairs(most_recent_files) do
if data.file then
local filename = fnamemodify(data.file, ':t')
local relative_path = fnamemodify(data.file, ':~:.')
table.insert(result_items, {
action = function() Config.edit(data.file) end,
name = string.format('%s (%s)', filename, relative_path),
section = 'Recent ' .. capitalize_first(ft),
})
end
end
return result_items
end
M.footer_text = (function()
return [[
$$$$$$$\ $$\ $$\ $$\ $$\ $$\ $$$$$$$\ $$\ $$\ $$\ $$\
$$ __$$\ \__| $$ |$ | $$ |$$ |$$ __$$\ $$ | $$ | $$ |\__|
$$ | $$ | $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$ |\_/$$$$$$$\ $$ /$$ / $$ | $$ | $$$$$$\ $$$$$$\ $$$$$$\ $$ | $$ |$$\ $$$$$$\$$$$\
$$ | $$ | \____$$\ $$ __$$\ $$ |$$ __$$\ $$ | $$ _____| $$ /$$ / $$ | $$ | \____$$\\_$$ _| \____$$\\$$\ $$ |$$ |$$ _$$ _$$\
$$ | $$ | $$$$$$$ |$$ | $$ |$$ |$$$$$$$$ |$$ | \$$$$$$\ $$ /$$ / $$ | $$ | $$$$$$$ | $$ | $$$$$$$ |\$$\$$ / $$ |$$ / $$ / $$ |
$$ | $$ |$$ __$$ |$$ | $$ |$$ |$$ ____|$$ | \____$$\ $$ /$$ / $$ | $$ |$$ __$$ | $$ |$$\ $$ __$$ | \$$$ / $$ |$$ | $$ | $$ |
$$$$$$$ |\$$$$$$$ |$$ | $$ |$$ |\$$$$$$$\ $$ | $$$$$$$ |$$ /$$ / $$$$$$$ |\$$$$$$$ | \$$$$ |\$$$$$$$ | \$ / $$ |$$ | $$ | $$ |
\_______/ \_______|\__| \__|\__| \_______|\__| \_______/ \__/ \__/ \_______/ \_______| \____/ \_______| \_/ \__|\__| \__| \__|
]]
end
)
Config.startup = M

78
plugin/03_terminal.lua Normal file
View file

@ -0,0 +1,78 @@
local M = {}
-- Configuration
Config.opt_bracket = true
M.opt_term = nil
-- Default terminal commands
-- Users can override this via Config.terminal_commands in their setup
local defaults = {
clickhouse_client = "clickhouse client -m",
clickhouse_local = "clickhouse local -m",
duckdb = "duckdb",
julia = "julia",
python = "ipython",
shell = "echo 'Hello " .. vim.env.USER .. "!'",
}
-- Registry of terminal commands
M.commands = vim.tbl_deep_extend("force", defaults, Config.terminal_commands or {})
-- Bracket paste control
function M.toggle_bracket()
Config.opt_bracket = not Config.opt_bracket
vim.g.slime_bracketed_paste = Config.opt_bracket
return Config.opt_bracket
end
-- Terminal management
function M.split_and_open_terminal()
vim.cmd("below 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
vim.b[term_buf].slime_config = { jobid = job_id }
return M.opt_term
end
-- Public functions
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()
-- Open terminal and get buffer
local term_buf = M.split_and_open_terminal()
-- Send command if provided
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")
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)
-- Switch back to code buffer
vim.api.nvim_set_current_win(current_window)
end
-- Predefined terminal commands
for name, command in pairs(M.commands) do
M["open_" .. name] = function()
M.open_in_terminal(command)
end
end
Config.terminal = M

147
plugin/04_treesitter.lua Normal file
View file

@ -0,0 +1,147 @@
local M = {}
-- Default parsers list moved from startup config
M.default_parsers = {
"bash", "bibtex", "c", "caddy", "cmake", "comment", "commonlisp", "cpp", "css", "csv",
"cuda", "desktop", "diff", "dockerfile", "doxygen", "editorconfig", "fortran", "git_config", "git_rebase",
"gitattributes", "gitcommit", "gitignore", "gnuplot", "go", "gpg", "html", "javascript", "jq", "json", "json5",
"julia", "just", "latex", "ledger", "lua", "luadoc", "luap", "luau", "make", "markdown", "markdown_inline",
"matlab", "meson", "muttrc", "nix", "nu", "passwd", "powershell", "prql", "python", "r", "query", "readline", "regex",
"requirements", "rnoweb", "rust", "sql", "ssh_config", "swift", "tmux", "toml", "tsv", "tsx", "typescript", "typst",
"vala", "vim", "vimdoc", "yaml", "zig",
}
-- Cache treesitter utils to avoid repeated requires
local smart_send = require('nix_smart_send')
-- 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
return false
end
for _, v in ipairs(list) do
if v == value then
return true
end
end
return false
end
function M.add_global_node(nodes)
if not nodes then
return nil
end
local node_type = M.get_type()
if not node_type then
return nodes
end
-- Create a copy to avoid modifying the original
local global_nodes = vim.deepcopy(nodes)
-- Check if node type already exists to avoid duplicates
if not is_in_list(global_nodes, node_type) then
table.insert(global_nodes, node_type)
end
return global_nodes
end
function M.remove_global_node(nodes)
if not nodes then
return nil
end
local node_type = M.get_type()
if not node_type then
return nodes
end
local global_nodes = vim.deepcopy(nodes)
-- Remove all occurrences (iterate backwards to avoid index issues)
for i = #global_nodes, 1, -1 do
if global_nodes[i] == node_type then
table.remove(global_nodes, i)
end
end
return global_nodes
end
function M.set_global_nodes()
local input = vim.fn.input("Enter root nodes: ")
if input == "" then
return {}
end
local nodes_in = {}
-- Trim whitespace from each node name
for node in string.gmatch(input, '([^,]+)') do
local trimmed = vim.trim(node)
if trimmed ~= "" then
table.insert(nodes_in, trimmed)
end
end
return nodes_in
end
function M.get_type()
local cur_node = smart_send.get_current_node()
if not cur_node then
print("Not a node")
return nil
end
local node_type = cur_node:type()
print("Node type: " .. node_type)
return node_type
end
function M.setup_keybindings(global_nodes)
local current_global_nodes = global_nodes
vim.keymap.set({ 'n' }, '<localleader>r', function()
current_global_nodes = M.set_global_nodes()
end,
{ noremap = true, silent = true, desc = "set global_nodes", buffer = true })
vim.keymap.set({ 'n', 'v' }, '<localleader>v', function()
smart_send.move_to_next_non_empty_line(); smart_send.select_until_global(current_global_nodes)
end,
{ noremap = true, silent = true, desc = "Visual select next node after WS", buffer = true })
vim.keymap.set('n', '<localleader>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' }, '<S-CR>', function() smart_send.send_repl(current_global_nodes) end,
{ noremap = true, silent = true, desc = "Send node to REPL", buffer = true })
vim.keymap.set('n', '<CR>', function() smart_send.send_repl(current_global_nodes) end,
{ noremap = true, silent = true, desc = "Send node to REPL", buffer = true })
vim.keymap.set('n', '<localleader>n',
function() current_global_nodes = M.add_global_node(current_global_nodes) end,
{ noremap = true, silent = true, desc = "Add node under cursor to globals", buffer = true })
vim.keymap.set('n', '<localleader>x',
function() current_global_nodes = M.remove_global_node(current_global_nodes) end,
{ noremap = true, silent = true, desc = "Remove node under cursor from globals", buffer = true })
vim.keymap.set('n', '<localleader>o', function()
pout = table.concat(global_nodes, ', ') .. ""
print(pout)
end, { noremap = true, silent = true, desc = "Print globals", buffer = true })
vim.keymap.set('n', '<localleader>p', function() M.get_type() end,
{ noremap = true, silent = true, desc = "Print node type", buffer = true })
end
Config.treesitter_helpers = M
return M

318
plugin/10_keymap.lua Normal file
View file

@ -0,0 +1,318 @@
-- 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', '<C-h>', [[<C-\><C-N><C-w>h]])
-- Select all
-- vim.keymap.set({ "n", "v", "x" }, "<C-a>", "gg3vG$", { noremap = true, silent = true, desc = "Select all" })
-- Escape deletes highlights
vim.keymap.set("n", "<Esc>", "<cmd>nohlsearch<CR>")
-- Paste before/after linewise
local cmd = vim.fn.has('nvim-0.12') == 1 and 'iput' or 'put'
vim.keymap.set({ 'n', 'x' }, '[p', '<Cmd>exe "' .. cmd .. '! " . v:register<CR>', { desc = 'Paste Above' })
vim.keymap.set({ 'n', 'x' }, ']p', '<Cmd>exe "' .. cmd .. ' " . v:register<CR>', { desc = 'Paste Below' })
vim.keymap.set({ "n", "v", "x" }, "<leader>p", '"+p', { noremap = true, silent = true, desc = "Paste from clipboard" })
vim.keymap.set({ "n", "v", "x" }, "<leader>y", '"+y', { noremap = true, silent = true, desc = "Copy toclipboard" })
-- Leader mappings ============================================================
-- stylua: ignore start
-- 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 = '<Leader>a', desc = '+AI' },
{ mode = 'n', keys = '<Leader>b', desc = '+Buffer' },
{ mode = 'n', keys = '<Leader>e', desc = '+Explore' },
{ mode = 'n', keys = '<Leader>f', desc = '+Find' },
{ mode = 'n', keys = '<Leader>fl', desc = '+LSP' },
{ mode = 'n', keys = '<Leader>fa', desc = '+Git' },
{ mode = 'n', keys = '<Leader>g', desc = '+Git' },
{ mode = 'n', keys = '<Leader>l', desc = '+LSP' },
{ mode = 'n', keys = '<Leader>L', desc = '+Lua/Log' },
{ mode = 'n', keys = '<Leader>o', desc = '+Other' },
{ mode = 'n', keys = '<Leader>r', desc = '+R' },
{ mode = 'n', keys = '<Leader>t', desc = '+Terminal' },
{ mode = 'n', keys = '<Leader>u', desc = '+UI' },
{ mode = 'n', keys = '<Leader>v', desc = '+Visits' },
{ mode = 'n', keys = '<Leader>w', desc = '+Windows' },
{ mode = 'x', keys = '<Leader>l', desc = '+LSP' },
{ mode = 'x', keys = '<Leader>r', desc = '+R' },
{ mode = 'n', keys = '<Leader>z', desc = '+ZK' },
{ mode = 'n', keys = '<Leader>zr', desc = '+Reviews' },
{ mode = 'x', keys = '<leader>a', desc = '+AI' },
}
-- Create `<Leader>` mappings
local nmap_leader = function(suffix, rhs, desc, opts)
opts = opts or {}
opts.desc = desc
vim.keymap.set('n', '<Leader>' .. suffix, rhs, opts)
end
local xmap_leader = function(suffix, rhs, desc, opts)
opts = opts or {}
opts.desc = desc
vim.keymap.set('x', '<Leader>' .. 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('<Tab>', '<Cmd>bnext<CR>', 'Next buffer')
nmap_leader('<S-Tab>', '<Cmd>bprev<CR>', 'Prev buffer')
-- a is for 'AI'
nmap_leader("ac", "<cmd>CodeCompanionChat Toggle<CR>", "Chat Toggle")
nmap_leader("ae", "<cmd>CodeCompanion /explain<CR>", "Explain Code")
nmap_leader("af", "<cmd>CodeCompanion /fix<CR>", "Fix Code")
nmap_leader("ag", "<cmd>CodeCompanion /commit<CR>", "Generate commit message")
nmap_leader("ai", "<cmd>CodeCompanionActions<CR>", "Chat Action")
nmap_leader("al", "<cmd>CodeCompanion /lsp<CR>", "Explain LSP Diagnostics")
nmap_leader("an", "<cmd>CodeCompanionChat Add<CR>", "Chat New")
nmap_leader("as", "<cmd>CodeCompanion /suggest<CR>", "Suggest Improvements")
nmap_leader("ax", "<cmd>CodeCompanion /fixer<CR>", "Code Fixer")
nmap_leader("ax", "<cmd>CodeCompanion /fixer<CR>", "Code Fixer")
xmap_leader("ae", "<cmd>CodeCompanion /explain<CR>", "Explain Code")
xmap_leader("af", "<cmd>CodeCompanion /fix<CR>", "Fix Code")
xmap_leader("ap", "<cmd>CodeCompanion /expert<CR>", "Code Fixer")
xmap_leader("ap", "<cmd>CodeCompanion /expert<CR>", "Code Fixer")
xmap_leader("as", "<cmd>CodeCompanion /suggest<CR>", "Suggest Improvements")
-- b is for 'buffer'
nmap_leader('bb', '<Cmd>b#<CR>', 'Alternate')
nmap_leader('bd', '<Cmd>lua MiniBufremove.delete()<CR>', 'Delete')
nmap_leader('bD', '<Cmd>lua MiniBufremove.delete(0, true)<CR>', 'Delete!')
nmap_leader('bs', '<Cmd>lua Config.new_scratch_buffer()<CR>', 'Scratch')
nmap_leader('bw', '<Cmd>lua MiniBufremove.wipeout()<CR>', 'Wipeout')
nmap_leader('bW', '<Cmd>lua MiniBufremove.wipeout(0, true)<CR>', 'Wipeout!')
nmap_leader('bq', '<Cmd>qall<CR>', 'Quit all')
-- e is for 'explore' and 'edit'
nmap_leader('ed', '<Cmd>lua MiniFiles.open()<CR>', 'Directory')
nmap_leader('ef', '<Cmd>lua Config.try_opendir()<CR>', 'File directory')
nmap_leader('es', '<Cmd>lua MiniSessions.select()<CR>', 'Sessions')
nmap_leader('eq', '<Cmd>lua Config.toggle_quickfix()<CR>', 'Quickfix')
nmap_leader('ez', '<Cmd>lua MiniFiles.open(os.getenv("ZK_NOTEBOOK_DIR"))<CR>', 'Notes directory')
-- f is for 'fuzzy find'
nmap_leader('f/', '<Cmd>Pick history scope="/"<CR>', '"/" history')
nmap_leader('f:', '<Cmd>Pick history scope=":"<CR>', '":" history')
nmap_leader('f,', '<Cmd>Pick visit_labels<CR>', 'Visit labels')
nmap_leader('faa', '<Cmd>Pick git_hunks scope="staged"<CR>', 'Added hunks (all)')
nmap_leader('faA', '<Cmd>Pick git_hunks path="%" scope="staged"<CR>', 'Added hunks (current)')
nmap_leader('fb', '<Cmd>Pick buffers<CR>', 'Buffers')
nmap_leader(',', '<Cmd>Pick buffers<CR>', 'Buffers')
nmap_leader('fac', '<Cmd>Pick git_commits<CR>', 'Commits (all)')
nmap_leader('faC', '<Cmd>Pick git_commits path="%"<CR>', 'Commits (current)')
nmap_leader('fd', '<Cmd>Pick diagnostic scope="all"<CR>', 'Diagnostic workspace')
nmap_leader('fD', '<Cmd>Pick diagnostic scope="current"<CR>', 'Diagnostic buffer')
nmap_leader('ff', '<Cmd>Pick files<CR>', 'Files')
nmap_leader('fg', '<Cmd>Pick grep_live<CR>', 'Grep live')
nmap_leader('fG', '<Cmd>Pick grep pattern="<cword>"<CR>', 'Grep current word')
nmap_leader('fh', '<Cmd>Pick help<CR>', 'Help tags')
nmap_leader('fH', '<Cmd>Pick hl_groups<CR>', 'Highlight groups')
nmap_leader('fj', '<Cmd>Pick buf_lines scope="all"<CR>', 'Lines (all)')
nmap_leader('fJ', '<Cmd>Pick buf_lines scope="current"<CR>', 'Lines (current)')
nmap_leader('fam', '<Cmd>Pick git_hunks<CR>', 'Modified hunks (all)')
nmap_leader('faM', '<Cmd>Pick git_hunks path="%"<CR>', 'Modified hunks (current)')
nmap_leader('fm', '<Cmd>Pick marks<CR>', 'Marks')
nmap_leader('fn', '<cmd>ZkNotes<CR>', "Notes")
nmap_leader('fk', '<Cmd>Pick keymaps<CR>', 'Keymaps')
nmap_leader('fR', '<Cmd>Pick resume<CR>', 'Resume')
nmap_leader('fp', '<Cmd>Pick projects<CR>', 'Projects')
nmap_leader('fq', '<Cmd>Pick list scope="quickfix"<CR>', 'Quickfix')
nmap_leader('fr', '<Cmd>Pick lsp scope="references"<CR>', 'References (LSP)')
nmap_leader('flr', '<Cmd>Pick lsp scope="references"<CR>', 'References (LSP)')
nmap_leader('fS', '<Cmd>Pick lsp scope="workspace_symbol"<CR>', 'Symbols workspace (LSP)')
nmap_leader('flS', '<Cmd>Pick lsp scope="workspace_symbol"<CR>', 'Symbols workspace (LSP)')
nmap_leader('fs', '<Cmd>Pick lsp scope="document_symbol"<CR>', 'Symbols buffer (LSP)')
nmap_leader('fls', '<Cmd>Pick lsp scope="document_symbol"<CR>', 'Symbols buffer (LSP)')
nmap_leader('fld', '<Cmd>Pick lsp scope="definition"<CR>', 'Definition (LSP)')
nmap_leader('flD', '<Cmd>Pick lsp scope="declaration"<CR>', 'Declaration (LSP)')
nmap_leader('flt', '<Cmd>Pick lsp scope="type_definition"<CR>', 'Type Definition (LSP)')
nmap_leader('fv', '<Cmd>Pick visit_paths cwd=""<CR>', 'Visit paths (all)')
nmap_leader('fV', '<Cmd>Pick visit_paths<CR>', 'Visit paths (cwd)')
-- g is for git
local git_log_cmd = [[Git log --pretty=format:\%h\ \%as\ │\ \%s --topo-order]]
nmap_leader('gc', '<Cmd>Git commit<CR>', 'Commit')
nmap_leader('gC', '<Cmd>Git commit --amend<CR>', 'Commit amend')
nmap_leader('gd', '<Cmd>Git diff<CR>', 'Diff')
nmap_leader('gD', '<Cmd>Git diff -- %<CR>', 'Diff buffer')
nmap_leader('gg', '<Cmd>lua require("neogit").open()<CR>', 'Git tab')
nmap_leader('gl', '<Cmd>' .. git_log_cmd .. '<CR>', 'Log')
nmap_leader('gL', '<Cmd>' .. git_log_cmd .. ' --follow -- %<CR>', 'Log buffer')
nmap_leader('go', '<Cmd>lua MiniDiff.toggle_overlay()<CR>', 'Toggle overlay')
nmap_leader('gp', '<Cmd>Git pull<CR>', 'Pull')
nmap_leader('gP', '<Cmd>Git push<CR>', 'Push')
nmap_leader('gs', '<Cmd>lua MiniGit.show_at_cursor()<CR>', 'Show at cursor')
xmap_leader('gs', '<Cmd>lua MiniGit.show_at_cursor()<CR>', 'Show at selection')
-- j/k navigate quickfix
nmap_leader("j", '<cmd>cnext<CR>zz', "Quickfix next")
nmap_leader("k", '<cmd>cprev<CR>zz', "Quickfix prev")
-- l is for 'LSP' (Language Server Protocol)
vim.keymap.set({ 'n' }, 'grd', '<Cmd>lua vim.lsp.buf.definition()<CR>', { desc = 'Definition' })
vim.keymap.set({ 'n' }, 'grk', '<Cmd>lua vim.lsp.buf.hover()<CR>', { desc = 'Documentation' })
vim.keymap.set({ 'n' }, 'gre', '<Cmd>lua vim.diagnostic.open_float()<CR>', { desc = 'Diagnostics' })
nmap_lsp("K", '<Cmd>lua vim.lsp.buf.hover()<CR>', "Documentation")
local formatting_cmd = '<Cmd>lua require("conform").format({ lsp_fallback = true })<CR>'
nmap_leader('la', '<Cmd>lua vim.lsp.buf.code_action()<CR>', 'Actions')
nmap_leader('le', '<Cmd>lua vim.diagnostic.open_float()<CR>', 'Diagnostics popup')
nmap_leader('lf', formatting_cmd, 'Format')
nmap_leader('lk', '<Cmd>lua vim.lsp.buf.hover()<CR>', 'Documentation')
nmap_leader('li', '<Cmd>lua vim.lsp.buf.implementation()<CR>', 'Information')
-- use ]d and [d
--nmap_leader('lj', '<Cmd>lua vim.diagnostic.goto_next()<CR>', 'Next diagnostic')
--nmap_leader('lk', '<Cmd>lua vim.diagnostic.goto_prev()<CR>', 'Prev diagnostic')
nmap_leader('lR', '<Cmd>lua vim.lsp.buf.references()<CR>', 'References')
nmap_leader('lr', '<Cmd>lua vim.lsp.buf.rename()<CR>', 'Rename')
nmap_leader('ls', '<Cmd>lua vim.lsp.buf.definition()<CR>', 'Source definition')
xmap_leader('lf', formatting_cmd, 'Format selection')
-- L is for 'Lua'
nmap_leader('Lc', '<Cmd>lua Config.log_clear()<CR>', 'Clear log')
nmap_leader('LL', '<Cmd>luafile %<CR><Cmd>echo "Sourced lua"<CR>', 'Source buffer')
nmap_leader('Ls', '<Cmd>lua Config.log_print()<CR>', 'Show log')
nmap_leader('Lx', '<Cmd>lua Config.execute_lua_line()<CR>', 'Execute `lua` line')
-- m is free
-- o is for 'other'
local trailspace_toggle_command = '<Cmd>lua vim.b.minitrailspace_disable = not vim.b.minitrailspace_disable<CR>'
nmap_leader('od', '<Cmd>Neogen<CR>', 'Document')
nmap_leader('oh', '<Cmd>normal gxiagxila<CR>', 'Move arg left')
nmap_leader('ol', '<Cmd>normal gxiagxina<CR>', 'Move arg right')
nmap_leader('or', '<Cmd>lua MiniMisc.resize_window()<CR>', 'Resize to default width')
nmap_leader('oS', '<Cmd>lua Config.insert_section()<CR>', 'Section insert')
nmap_leader('ot', '<Cmd>lua MiniTrailspace.trim()<CR>', 'Trim trailspace')
nmap_leader('oT', trailspace_toggle_command, 'Trailspace hl toggle')
nmap_leader('oz', '<Cmd>lua MiniMisc.zoom()<CR>', 'Zoom toggle')
nmap_leader('ow',
"<Cmd>lua MiniSessions.write(vim.fn.input('Session name: ', string.match(vim.fn.getcwd(), \"[^/]+$\") .. '-session.vim'))<CR>",
'Write session')
-- r is for 'R'
nmap_leader('rc', '<Cmd>RSend devtools::check()<CR>', 'Check')
nmap_leader('rC', '<Cmd>RSend devtools::test_coverage()<CR>', 'Coverage')
nmap_leader('rd', '<Cmd>RSend devtools::document()<CR>', 'Document')
nmap_leader('ri', '<Cmd>RSend devtools::install(keep_source=TRUE)<CR>', 'Install')
nmap_leader('rk', '<Cmd>RSend quarto::quarto_preview("%")<CR>', 'Knit file')
nmap_leader('rl', '<Cmd>RSend devtools::load_all()<CR>', 'Load all')
nmap_leader('rL', '<Cmd>RSend devtools::load_all(recompile=TRUE)<CR>', 'Load all recompile')
nmap_leader('rm', '<Cmd>RSend Rcpp::compileAttributes()<CR>', 'Run examples')
nmap_leader('rT', '<Cmd>RSend testthat::test_file("%")<CR>', 'Test file')
nmap_leader('rt', '<Cmd>RSend devtools::test()<CR>', 'Test')
-- - Copy to clipboard and make reprex (which itself is loaded to clipboard)
xmap_leader('rx', '"+y :RSend reprex::reprex()<CR>', 'Reprex selection')
-- s is for 'send' (Send text to neoterm buffer)
nmap_leader('s', '<Cmd>SlimeSendCurrentLine<CR>j', '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', '<Plug>SlimeRegionSend<CR>', 'Send to terminal')
-- t is for 'terminal'
vim.keymap.set("t", "<Esc>", [[<C-\><C-n>]], { desc = "Exit terminal mode" })
vim.keymap.set("n", "<leader>tc", '<Cmd>lua Config.terminal.open_clickhouse_client()<CR>',
{ desc = "Open Clickhouse client" })
vim.keymap.set("n", "<leader>tl", '<Cmd>lua Config.terminal.open_clickhouse_local()<CR>',
{ desc = "Open Clickhouse local" })
vim.keymap.set("n", "<leader>tp", '<Cmd>lua Config.terminal.open_python()<CR>', { desc = "Open Python" })
vim.keymap.set("n", "<leader>tj", '<Cmd>lua Config.terminal.open_julia()<CR>', { desc = "Open Julia" })
vim.keymap.set("n", "<leader>td", '<Cmd>lua Config.terminal.open_duckdb();Config.terminal.toggle_bracket()<CR>',
{ desc = "Open DuckDB" })
vim.keymap.set("n", "<leader>tx", '<Cmd>lua Config.terminal.open_in_terminal()<CR>', { desc = "Terminal Command" })
vim.keymap.set("n", "<leader>tt", '<Cmd>lua Config.terminal.open_shell()<CR>', { desc = "Terminal" })
nmap_leader("tb", '<Cmd>lua Config.terminal.toggle_bracket()<CR>', "Toggle bracketed paste")
nmap_leader("up", '<Cmd>lua Config.terminal.toggle_bracket()<CR>', "Toggle bracketed paste")
-- u is for UI
nmap_leader('ut', '<Cmd>TSContext toggle<CR>', 'Toggle TScontext')
nmap_leader('ua', '<Cmd>Copilot toggle<CR>', 'Toggle AI completion')
-- v is for 'visits'
nmap_leader('vv', '<Cmd>lua MiniVisits.add_label("core")<CR>', 'Add "core" label')
nmap_leader('vV', '<Cmd>lua MiniVisits.remove_label("core")<CR>', 'Remove "core" label')
nmap_leader('vl', '<Cmd>lua MiniVisits.add_label()<CR>', 'Add label')
nmap_leader('vL', '<Cmd>lua MiniVisits.remove_label()<CR>', '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", "<C-w>h", "Go to Left Window", { remap = true })
nmap_leader("wj", "<C-w>j", "Go to Lower Window", { remap = true })
nmap_leader("wk", "<C-w>k", "Go to Upper Window", { remap = true })
nmap_leader("wl", "<C-w>l", "Go to Right Window", { remap = true })
nmap_leader("_", "<C-W>s", "Split Window Below", { remap = true })
nmap_leader("|", "<C-W>v", "Split Window Right", { remap = true })
nmap_leader("wd", "<C-W>c", "Delete Window", { remap = true })
nmap_leader("wo", "<C-W>o", "Delete Other Windows", { remap = true })
-- z is for 'ZettelKasten'
nmap_leader("zo", '<Cmd>ZkNotes<CR>', "Notes")
nmap_leader("zt", '<Cmd>ZkTags<cr>', "Tags")
nmap_leader(
"zrd",
'<Cmd>ZkNew { group = "dreviews" }<CR>',
"Daily Review"
)
nmap_leader(
"zrw",
'<Cmd>ZkNew { group = "wreviews" }<CR>',
"Weekly Review"
)
nmap_leader(
"zn",
'<Cmd>ZkNew { group = "inbox", title = vim.fn.input("Title: ") }<CR>',
"New"
)
nmap_leader(
"zp",
"<Cmd>ZkNew { group = 'permanent', title = vim.fn.input('Title: ') }<CR>",
"Permanent"
)
nmap_leader(
"zl",
"<Cmd>ZkNew { group = 'literature', title = vim.fn.input('Title: '), extra.author = vim.fn.input('Author: '), extra.year = vim.fn.input('Year: ') }<CR>",
"Literature"
)
nmap_leader(
"zd",
"<Cmd>ZkNew { group = 'dashboard', title = vim.fn.input('Title: ') }<CR>",
"Dashboard"
)
nmap_leader(
"zP",
"<Cmd>ZkNew { group = 'project', title = vim.fn.input('Title: ')}<CR>",
"Project"
)
-- stylua: ignore end

314
plugin/20_startup.lua Normal file
View file

@ -0,0 +1,314 @@
local now = MiniDeps.now
local later = MiniDeps.later
local now_if_args = Config.now_if_args
local nix = require('config.nix')
if not Config.isNixCats then
local add = MiniDeps.add
now_if_args(function()
add({
source = "nvim-treesitter/nvim-treesitter",
checkout = "master",
monitor = "main",
hooks = {
post_checkout = function()
vim.cmd("TSUpdate")
end,
},
})
add({
source = "nvim-treesitter/nvim-treesitter-textobjects",
checkout = "main",
})
add({ source = "zk-org/zk-nvim" })
end)
end
-- Mini.nvim
now(function()
local colorschemeName = nix.get_setting("onedark_dark", "colorscheme")
if colorschemeName == 'light' then
local palette = require('mini.hues').make_palette({
background = '#fefcf5',
foreground = '#657b83',
accent = 'bg',
saturation = 'high',
n_hues = 8
})
palette.fg_mid2 = "#586e75"
palette.fg_mid = "#073642"
palette.bg_edge = "#fdf6e3"
palette.accent_bg = "#eee8d5"
require('mini.hues').apply_palette(palette)
else
if colorschemeName == "cyberdream" and vim.o.background == 'light' then
colorschemeName = colorschemeName .. '-light'
end
vim.cmd.colorscheme(colorschemeName)
end
end)
now(function()
require("mini.basics").setup({
options = {
basic = true,
extra_ui = true
},
mappings = {
-- jk linewise, gy/gp system clipboard, gV select last change/yank
basic = true,
-- <C-hjkl> move between windows, <C-arrow> resize
windows = true,
move_with_alt = true,
option_toggle_prefix = "<leader>u"
},
autocommands = {
basic = true,
relnum_in_visual_mode = true
},
})
end)
now(function()
require("mini.icons").setup({
use_file_extension = function(ext, _)
local suf3, suf4 = ext:sub(-3), ext:sub(-4)
return suf3 ~= "scm" and suf3 ~= "txt" and suf3 ~= "yml" and suf4 ~= "json" and suf4 ~= "yaml"
end,
})
later(MiniIcons.mock_nvim_web_devicons)
later(MiniIcons.tweak_lsp_kind)
end)
now(function()
local predicate = function(notif)
if not (notif.data.source == "lsp_progress" and notif.data.client_name == "lua_ls") then
return true
end
-- Filter out some LSP progress notifications from 'lua_ls'
return notif.msg:find("Diagnosing") == nil and notif.msg:find("semantic tokens") == nil
end
local custom_sort = function(notif_arr)
return MiniNotify.default_sort(vim.tbl_filter(predicate, notif_arr))
end
require("mini.notify").setup({ content = { sort = custom_sort } })
vim.notify = MiniNotify.make_notify()
end)
now(function()
require("mini.sessions").setup()
end)
now(function()
local starter = require("mini.starter")
starter.setup({
evaluate_single = true,
items = {
starter.sections.recent_files(5, true),
function()
local section = Config.startup.get_recent_files_by_ft_or_ext({
"r",
"sql",
"julia",
"python",
"lua",
})
return section
end,
starter.sections.pick(),
starter.sections.sessions(5, true),
starter.sections.builtin_actions(),
starter.sections.recent_files(3, false),
},
footer = Config.startup.footer_text,
content_hooks = {
starter.gen_hook.adding_bullet(),
starter.gen_hook.indexing(
"all",
{ "Builtin actions", "Recent files (current directory)", "Recent files", }
),
starter.gen_hook.aligning("center", "center"),
starter.gen_hook.padding(3, 2),
},
})
end)
now(function()
require("mini.statusline").setup()
end)
now(function()
require("mini.tabline").setup()
end)
now(function()
local miniclue = require("mini.clue")
--stylua: ignore
miniclue.setup({
window = {
config = {
width = 'auto'
},
delay = 100,
},
clues = {
Config.leader_group_clues,
miniclue.gen_clues.builtin_completion(),
miniclue.gen_clues.g(),
miniclue.gen_clues.marks(),
miniclue.gen_clues.registers(),
miniclue.gen_clues.windows({ submode_resize = true, submode_move = true }),
miniclue.gen_clues.z(),
},
triggers = {
{ mode = 'n', keys = '<Leader>' }, -- Leader triggers
{ mode = 'n', keys = '<LocalLeader>' }, -- LocalLeader triggers
{ mode = 'x', keys = '<Leader>' },
{ mode = 'x', keys = '<LocalLeader>' },
{ mode = 'n', keys = [[\]] }, -- mini.basics
{ mode = 'n', keys = '[' }, -- mini.bracketed
{ mode = 'n', keys = ']' },
{ mode = 'x', keys = '[' },
{ mode = 'x', keys = ']' },
{ mode = 'i', keys = '<C-x>' }, -- Built-in completion
{ mode = 'n', keys = 'g' }, -- `g` key
{ mode = 'x', keys = 'g' },
{ mode = 'n', keys = '`' },
{ mode = 'x', keys = '`' },
{ mode = 'n', keys = '"' }, -- Registers
{ mode = 'x', keys = '"' },
{ mode = 'i', keys = '<C-r>' },
{ mode = 'c', keys = '<C-r>' },
{ mode = 'n', keys = '<C-w>' }, -- Window commands
{ mode = 'n', keys = 'z' }, -- `z` key
{ mode = 'x', keys = 'z' },
},
})
end)
-- Treesitter
now_if_args(function()
vim.treesitter.language.register("markdown", { "markdown", "codecompanion" })
-- Base configuration
local opts = {
highlight = { enable = true },
indent = { enable = false },
textobjects = {
move = {
enable = true,
set_jumps = true, -- whether to set jumps in the jumplist
goto_next_start = {
["]a"] = "@paramter.inner",
["]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 = {
["<leader>x"] = "@parameter.inner",
},
swap_previous = {
["<leader>X"] = "@parameter.inner",
},
},
lsp_interop = {
enable = true,
border = 'none',
floating_preview_opts = {},
peek_definition_code = {
["<leader>lm"] = "@function.outer",
["<leader>lM"] = "@class.outer",
},
},
},
}
-- 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
-- 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
local configs = require("nvim-treesitter.configs")
configs.setup(opts)
require 'treesitter-context'.setup {
enable = true,
multiwindow = false, -- Enable multiwindow support.
max_lines = 30, -- How many lines the window should span. Values <= 0 mean no limit.
min_window_height = 70, -- Minimum editor window height to enable context. Values <= 0 mean no limit.
line_numbers = true,
multiline_threshold = 10, -- Maximum number of lines to show for a single context
trim_scope = 'outer', -- Which context lines to discard if `max_lines` is exceeded. Choices: 'inner', 'outer'
mode = 'cursor', -- Line used to calculate context. Choices: 'cursor', 'topline'
-- Separator between context and content. Should be a single character string, like '-'.
-- When separator is set, the context will only show up when there are at least 2 lines above cursorline.
separator = '-',
zindex = 20, -- The Z-index of the context window
on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching
}
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()`
},
-- automatically attach buffers in a zk notebook that match the given filetypes
auto_attach = {
enabled = true,
filetypes = { "markdown" },
},
},
})
end)

104
plugin/21_datascience.lua Normal file
View file

@ -0,0 +1,104 @@
local now = MiniDeps.now
local now_if_args = Config.now_if_args
local later = MiniDeps.later
local add = Config.add
local nix = require('config.nix')
if not Config.isNixCats then
local m_add = MiniDeps.add
now(function()
m_add({ source = "R-nvim/R.nvim" })
end)
now_if_args(function()
m_add({ source = "jmbuhr/otter.nvim" })
end)
later(function()
m_add({ source = "jpalardy/vim-slime" })
end)
end
-- terminal
later(function()
vim.g.slime_target = "neovim"
vim.g.slime_no_mappings = true
add("vim-slime")
vim.g.slime_cell_delimiter = vim.g.slime_cell_delimiter or "# %%"
vim.g.slime_bracketed_paste = Config.opt_bracket
vim.g.slime_input_pid = false
vim.g.slime_suggest_default = true
vim.g.slime_menu_config = false
vim.g.slime_neovim_ignore_unlisted = false
-- Define standard slime mappings
vim.keymap.set("v", "<CR>", "<Plug>SlimeRegionSend", { noremap = true })
vim.keymap.set("v", "<localleader><localleader>", "<Plug>SlimeRegionSend", { noremap = true })
vim.keymap.set("n", "<localleader><localleader>", "<Plug>SlimeLineSend", { noremap = true })
-- Standardize on C-c C-c as well (common convention)
vim.keymap.set("v", "<C-c><C-c>", "<Plug>SlimeRegionSend", { noremap = true })
vim.keymap.set("n", "<C-c><C-c>", "<Plug>SlimeParagraphSend", { noremap = true })
end)
-- r
now(function()
if nix.get_cat("r", false) then
vim.g.rout_follow_colorscheme = true
require("r").setup({
-- Create a table with the options to be passed to setup()
R_args = { "--quiet", "--no-save" },
auto_start = "no",
objbr_auto_start = false,
objbr_place = 'console,below',
rconsole_width = 120,
min_editor_width = 80,
rconsole_height = 20,
nvimpager = "split_h",
pdfviewer = "zathura",
})
end
end)
-- Quarto
now(function()
vim.treesitter.language.register("markdown", { "quarto", "rmd" })
vim.api.nvim_create_autocmd("FileType", {
pattern = { "quarto" },
callback = function()
require("otter").activate()
end,
})
require("otter").setup({
lsp = {
diagnostic_update_events = { "BufWritePost", "InsertLeave" },
},
buffers = {
set_filetype = true,
write_to_disk = true,
},
})
end)
later(function()
require("quarto").setup({
lspFeatures = {
enabled = true,
languages = { "r", "python", "julia" },
diagnostics = {
enabled = true,
triggers = { "BufWrite" },
},
completion = {
enabled = true,
},
},
codeRunner = {
enabled = true,
default_method = "slime",
},
})
end)

53
plugin/22_languages.lua Normal file
View file

@ -0,0 +1,53 @@
local add = Config.add
local now_if_args = Config.now_if_args
local later = MiniDeps.later
if not Config.isNixCats then
local m_add = MiniDeps.add
later(function()
m_add({ source = "Bilal2453/luvit-meta" })
m_add({ source = "folke/lazydev.nvim" })
end)
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" } },
},
})
end)
-- Markdown
now_if_args(function()
add("render-markdown.nvim")
require('render-markdown').setup({
-- completions = { blink = { enabled = true } },
file_types = { 'markdown', 'quarto', 'rmd', 'codecompanion', },
link = {
wiki = {
body = function(ctx)
local diagnostics = vim.diagnostic.get(ctx.buf, {
lnum = ctx.row,
severity = vim.diagnostic.severity.HINT,
})
for _, diagnostic in ipairs(diagnostics) do
if diagnostic.source == 'marksman' then
return diagnostic.message
end
end
return nil
end,
},
},
})
end)

348
plugin/23_editor.lua Normal file
View file

@ -0,0 +1,348 @@
local later = MiniDeps.later
local add = Config.add
if not Config.isNixCats then
local m_add = MiniDeps.add
later(function()
m_add("stevearc/conform.nvim")
end)
end
-- Formatting
later(function()
add("conform.nvim")
require("conform").setup({
-- Map of filetype to formatters
formatters_by_ft = {
javascript = { "prettier" },
json = { "prettier" },
python = { "black" },
nix = { "alejandra" },
-- r = { "my_styler" },
rmd = { "injected" },
quarto = { "injected" },
},
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)
-- Edit
later(function()
local ai = require("mini.ai")
local spec_treesitter = ai.gen_spec.treesitter
ai.setup({
search_method = "cover",
n_lines = 1000,
})
end)
later(function()
require("mini.align").setup()
end)
later(function()
require("mini.animate").setup({ scroll = { enable = false } })
end)
later(function()
require("mini.bracketed").setup({
diagnostic = {
options = {
float = false,
},
},
})
end)
later(function()
require("mini.bufremove").setup()
end)
later(function()
require("mini.comment").setup()
end)
later(function()
require("mini.cursorword").setup({ delay = 1000 })
end)
later(function()
require("mini.diff").setup({
view = {
style = "sign",
},
mappings = {
apply = "<leader>ga",
reset = "<leader>gr",
textobject = "o",
},
options = {
linematch = 1000,
algorithm = 'myers',
},
})
end)
later(function()
require("mini.files").setup({
windows = {
preview = true,
width_focus = 80,
width_preview = 90,
},
mappings = {
mark_goto = "'",
synchronize = ':',
}
})
local minifiles_augroup = vim.api.nvim_create_augroup("ec-mini-files", {})
vim.api.nvim_create_autocmd("User", {
group = minifiles_augroup,
pattern = "MiniFilesExplorerOpen",
callback = function()
MiniFiles.set_bookmark("h", os.getenv("HOME") or vim.env.HOME, { desc = "Home" })
MiniFiles.set_bookmark("c", vim.fn.stdpath("config"), { desc = "Config" })
MiniFiles.set_bookmark("w", vim.fn.getcwd, { desc = "Working directory" })
MiniFiles.set_bookmark("z", os.getenv("ZK_NOTEBOOK_DIR") or vim.env.HOME, { desc = "ZK" })
end,
})
-- Set focused directory as current working directory
local function remove_string(string1, string2)
return string2:gsub(string1, "", 1)
end
local set_cwd = function()
local path = (MiniFiles.get_fs_entry() or {}).path
if path == nil then
return vim.notify("Cursor is not on valid entry")
end
local pwd = vim.fs.dirname(path)
vim.notify("PWD: " .. '.' .. vim.fn.pathshorten(pwd, 6))
vim.fn.chdir(pwd)
end
-- Yank in register full path of entry under cursor
local yank_path = function()
local path = (MiniFiles.get_fs_entry() or {}).path
if path == nil then
return vim.notify("Cursor is not on valid entry")
end
vim.notify("Yanked: " .. path)
vim.fn.setreg(vim.v.register, path)
end
-- Yank in register relative path of entry under cursor
local yank_relpath = function()
local path = (MiniFiles.get_fs_entry() or {}).path
local cwd = vim.fn.getcwd() .. '/'
local relpath = remove_string(cwd, path)
if path == nil then
return vim.notify("Cursor is not on valid entry")
end
vim.notify("Yanked: " .. relpath)
vim.fn.setreg(vim.v.register, relpath)
end
local ui_open = function()
vim.ui.open(MiniFiles.get_fs_entry().path)
end
vim.api.nvim_create_autocmd("User", {
pattern = "MiniFilesBufferCreate",
callback = function(args)
local b = args.data.buf_id
vim.keymap.set("n", "g~", set_cwd, { buffer = b, desc = "Set cwd" })
vim.keymap.set("n", "gX", ui_open, { buffer = b, desc = "Open UI" })
vim.keymap.set("n", "gY", yank_path, { buffer = b, desc = "Yank path" })
vim.keymap.set("n", "gy", yank_relpath, { buffer = b, desc = "Yank relpath" })
end,
})
end)
later(function()
require("mini.git").setup()
end)
later(function()
require("mini.extra").setup()
end)
later(function()
local hipatterns = require("mini.hipatterns")
local hi_words = MiniExtra.gen_highlighter.words
hipatterns.setup({
highlighters = {
fixme = hi_words({ "FIXME", "Fixme", "fixme" }, "MiniHipatternsFixme"),
hack = hi_words({ "HACK", "Hack", "hack" }, "MiniHipatternsHack"),
todo = hi_words({ "TODO", "Todo", "todo" }, "MiniHipatternsTodo"),
note = hi_words({ "NOTE", "Note", "note" }, "MiniHipatternsNote"),
hex_color = hipatterns.gen_highlighter.hex_color(),
},
})
end)
later(function()
require("mini.indentscope").setup()
end)
later(function()
require("mini.jump").setup()
end)
later(function()
local jump2d = require("mini.jump2d")
jump2d.setup({
spotter = jump2d.gen_spotter.pattern("[^%s%p]+"),
allowed_lines = {
blank = false,
cursor_at = false
},
labels = "asdfghjklweruiopzxcnm,;",
view = { dim = true, n_steps_ahead = 2 },
mappings = {
start_jumping = "sj",
},
})
vim.keymap.set({ "n", "x", "o" }, "<leader><CR>", function()
MiniJump2d.start(MiniJump2d.builtin_opts.single_character)
end)
end)
later(function()
local minikeymap = require("mini.keymap")
minikeymap.setup()
local map_multistep = minikeymap.map_multistep
local tab_steps = {
"blink_next",
"pmenu_next",
"increase_indent",
"jump_after_close",
}
map_multistep("i", "<Tab>", tab_steps)
local shifttab_steps = {
"blink_prev",
"pmenu_prev",
"decrease_indent",
"jump_before_open",
}
map_multistep("i", "<S-Tab>", shifttab_steps)
map_multistep("i", "<CR>", {
"blink_accept",
"pmenu_accept",
"minipairs_cr",
})
map_multistep("i", "<BS>", { "hungry_bs", "minipairs_bs" })
local tab_steps_n = {
"minisnippets_next",
"jump_after_tsnode",
"jump_after_close",
}
map_multistep("n", "<Tab>", tab_steps_n)
local shifttab_steps_n = {
"minisnippets_prev",
"jump_before_tsnode",
"jump_before_open",
}
map_multistep("n", "<S-Tab>", shifttab_steps_n)
end)
later(function()
require("mini.move").setup({ options = { reindent_linewise = false } })
end)
later(function()
require("mini.operators").setup({
replace = {
prefix = "gl"
},
})
end)
later(function()
require("mini.pairs").setup({
mappings = {
['"'] = { neigh_pattern = '[^%a\\"].' },
["'"] = { neigh_pattern = "[^%a\\'#]." },
},
modes = {
insert = true,
command = true,
terminal = true
}
})
end)
later(function()
require("mini.misc").setup({ make_global = { "put", "put_text", "stat_summary", "bench_time" } })
-- MiniMisc.setup_auto_root()
MiniMisc.setup_termbg_sync()
MiniMisc.setup_restore_cursor()
end)
later(function()
local choose_all = function()
local mappings = MiniPick.get_picker_opts().mappings
vim.api.nvim_input(mappings.mark_all .. mappings.choose_marked)
end
require("mini.pick").setup({
mappings = {
choose_marked = '<C-CR>',
choose_all = { char = '<C-q>', func = choose_all },
}
})
vim.ui.select = MiniPick.ui_select
-- vim.api.nvim_set_hl(0, "MiniPickMatchCurrent", { bg = "#fe640b", bold = true })
end)
later(function()
local snippets = require("mini.snippets")
local gen_loader = snippets.gen_loader
local lang_patterns = {
markdown_inline = { "quarto.json" },
}
snippets.setup({
snippets = {
-- Load custom file with global snippets first (adjust for Windows)
gen_loader.from_file(vim.fn.stdpath('config') .. "/snippets/global.json"),
-- Load snippets based on current language by reading files from
-- "snippets/" subdirectories from 'runtimepath' directories.
gen_loader.from_lang({ lang_patterns = lang_patterns }),
},
-- expand = { match = match_strict },
})
end)
later(function()
require("mini.splitjoin").setup()
end)
later(function()
require("mini.surround").setup()
-- Disable `s` shortcut (use `cl` instead) for safer usage of 'mini.surround'
vim.keymap.set({ "n", "x" }, "s", "<Nop>")
end)
later(function()
require("mini.trailspace").setup()
end)
later(function()
require("mini.visits").setup()
end)

351
plugin/24_completion.lua Normal file
View file

@ -0,0 +1,351 @@
local add = Config.add
local later = MiniDeps.later
local now_if_args = Config.now_if_args
-- Constants
local BLINK_VERSION = "v1.4.1"
-- Plugin sources configuration
local PLUGIN_SOURCES = {
"hrsh7th/cmp-cmdline",
"xzbdmw/colorful-menu.nvim",
"zbirenbaum/copilot.lua",
"jmbuhr/cmp-pandoc-references",
"fang2hou/blink-copilot",
"olimorris/codecompanion.nvim"
}
local PLUGIN_ADDS = {
"cmp-cmdline",
"blink.compat",
"colorful-menu.nvim",
"cmp-pandoc-references",
}
-- Helper functions
local function create_system_prompt(role_description)
return function(context)
return "I want you to act as a senior " .. context.filetype .. " developer. " .. role_description
end
end
local function get_code_block(context)
local text = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line)
return "```" .. context.filetype .. "\n" .. text .. "\n```"
end
local function create_common_opts(mapping, short_name)
return {
mapping = mapping,
modes = { "v" },
short_name = short_name,
auto_submit = true,
stop_context_insertion = true,
user_prompt = true,
}
end
local function get_mini_icons_highlight(ctx)
local _, hl, _ = require("mini.icons").get("lsp", ctx.kind)
return hl
end
local function get_blink_fuzzy_setting()
local setting = {
sorts = { "exact", "score", "sort_text" }
}
if not Config.isNixCats then
setting.prebuilt_binary = { force_version = BLINK_VERSION }
end
return setting
end
-- Plugin loading
if not Config.isNixCats then
local m_add = MiniDeps.add
now_if_args(function()
m_add({
source = "saghen/blink.cmp",
depends = { "rafamadriz/friendly-snippets" },
checkout = BLINK_VERSION,
})
end)
later(function()
for _, source in ipairs(PLUGIN_SOURCES) do
m_add({ source = source })
end
end)
end
local function get_codecompanion_config()
return {
adapters = {
http = {
copilot = function()
return require("codecompanion.adapters").extend("copilot", {
schema = {
model = { default = "gemini-3-pro-preview" }
}
})
end,
}
},
display = {
chat = {
show_settings = false,
window = {
layout = "horizontal",
position = "bottom",
height = 0.33,
},
},
},
prompt_library = {
["Code Expert"] = {
strategy = "chat",
description = "Get expert advice from an LLM",
opts = create_common_opts("<localleader>ae", "expert"),
prompts = {
{
role = "system",
content = create_system_prompt(
"I will ask you specific questions and I want you to return concise explanations and codeblock examples."
),
},
{
role = "user",
content = function(context)
return "I have the following code:\n\n" .. get_code_block(context) .. "\n\n"
end,
opts = { contains_code = true },
},
},
},
["Code Fixer"] = {
strategy = "chat",
description = "Fix code errors with expert guidance",
opts = create_common_opts("<localleader>af", "afixer"),
prompts = {
{
role = "system",
content = create_system_prompt(
"I have a block of code that is not working and will give you a hint about the error. I want you to return the corrected code and a concise explanation of the corrections."
),
},
{
role = "user",
content = function(context)
return "The following code has an error:\n\n" .. get_code_block(context) .. "\n\nThe error is:"
end,
opts = { contains_code = true },
},
},
},
["Suggest"] = {
strategy = "chat",
description = "Suggest improvements to the buffer",
opts = {
mapping = "<localleader>as",
modes = { "v" },
short_name = "suggest",
auto_submit = true,
user_prompt = false,
stop_context_insertion = false,
},
prompts = {
{
role = "system",
content = create_system_prompt(
"When asked to improve code, follow these steps:\n" ..
"1. Identify the programming language.\n" ..
"2. Think separately for each function or significant block of code and think about possible improvements (e.g., for better readability or speed) in the context of the language.\n" ..
"3. Think about the whole document and think about possible improvements.\n" ..
"4. Provide the improved code.\n" ..
"5. Provide a concise explanation of the improvements."
),
},
{
role = "user",
content = function(context)
return "Please improve the following code:\n\n" .. get_code_block(context)
end,
opts = { contains_code = true },
},
},
},
}
}
end
-- Batch add simple plugins
later(function()
for _, plugin in ipairs(PLUGIN_ADDS) do
add(plugin)
end
end)
-- Configure plugins with setup
later(function()
add("copilot.lua")
require("copilot").setup({
suggestion = { enabled = false },
panel = { enabled = false },
filetypes = {
help = true,
julia = true,
lua = true,
markdown = true,
nix = true,
python = true,
r = true,
sh = function()
if string.match(vim.fs.basename(vim.api.nvim_buf_get_name(0)), '^%.env.*') then
-- disable for .env files
return false
end
return true
end,
["."] = false
},
server_opts_overrides = {
settings = {
telemetry = { telemetryLevel = 'off' }
}
},
should_attach = function(_, bufname)
if string.match(bufname, "env") then
return false
end
return true
end
})
end)
later(function()
add("blink-copilot")
require("blink-copilot").setup({
max_completions = 1,
})
end)
later(function()
add("codecompanion.nvim")
-- now use function
require("codecompanion").setup(get_codecompanion_config())
vim.cmd([[cab cc CodeCompanion]])
end)
now_if_args(function()
add("blink.cmp")
require("blink.cmp").setup({
keymap = {
preset = "default",
["<C-space>"] = { "show", "select_next" },
["<C-l>"] = { "accept" },
},
cmdline = {
enabled = true,
keymap = {
preset = "inherit",
["<Tab>"] = { "show", "select_next" },
["<S-Tab>"] = { "show", "select_prev" },
["<C-l>"] = { "accept" },
},
completion = {
menu = { auto_show = true },
list = {
selection = { preselect = false, auto_insert = true }
},
},
sources = function()
local cmd_type = vim.fn.getcmdtype()
if cmd_type == "/" or cmd_type == "?" then
return { "buffer" }
elseif cmd_type == ":" or cmd_type == "@" then
return { "cmdline", "cmp_cmdline" }
end
return {}
end,
},
fuzzy = get_blink_fuzzy_setting(),
signature = {
enabled = true,
window = { show_documentation = true }
},
completion = {
menu = {
draw = {
treesitter = { "lsp" },
components = {
label = {
text = function(ctx)
return require("colorful-menu").blink_components_text(ctx)
end,
highlight = function(ctx)
return require("colorful-menu").blink_components_highlight(ctx)
end,
},
kind_icon = { highlight = get_mini_icons_highlight },
kind = { highlight = get_mini_icons_highlight },
},
},
},
list = {
selection = { preselect = false, auto_insert = true }
},
documentation = { auto_show = true },
trigger = { show_in_snippet = false },
},
snippets = { preset = "mini_snippets" },
sources = {
default = { "references", "lsp", "path", "snippets", "buffer", "omni", "copilot", "codecompanion" },
providers = {
path = {
score_offset = 50,
opts = {
get_cwd = function(_)
return vim.fn.getcwd()
end,
},
},
lsp = { score_offset = 40 },
snippets = { score_offset = 0 },
cmp_cmdline = {
name = "cmp_cmdline",
module = "blink.compat.source",
enabled = false,
score_offset = 10,
opts = { cmp_name = "cmdline" }
},
cmp_r = {
name = "cmp_r",
module = "blink.compat.source",
},
copilot = {
name = "copilot",
module = "blink-copilot",
score_offset = 45,
async = true,
},
codecompanion = {
name = "CodeCompanion",
module = "codecompanion.providers.completion.blink",
score_offset = 45,
async = true,
},
references = {
name = "pandoc_references",
module = "cmp-pandoc-references.blink",
score_offset = 50,
},
},
},
})
end)

89
plugin/25_lsp.lua Normal file
View file

@ -0,0 +1,89 @@
local now_if_args = Config.now_if_args
if not Config.isNixCats then
local m_add = MiniDeps.add
now_if_args(function()
m_add("neovim/nvim-lspconfig")
end)
end
now_if_args(function()
local servers = {
clangd = {},
basedpyright = {},
ruff = {},
marksman = {
filetypes = { "markdown", "markdown_inline", "codecompanion" },
},
r_language_server = {
filetypes = { 'r', 'rmd', 'rmarkdown' },
settings = {
['r_language_server'] = {
lsp = {
rich_documentation = true,
enable = true,
},
},
}
},
julials = {
settings = {
julia = {
format = {
indent = 2,
},
lsp = {
autoStart = true,
provideFormatter = true,
},
},
},
},
lua_ls = {
settings = {
Lua = {
completion = {
callSnippet = "Replace",
},
runtime = {
version = "LuaJIT",
},
diagnostics = {
disable = { "trailing-space" },
},
workspace = {
checkThirdParty = false,
},
doc = {
privateName = { "^_" },
},
telemetry = {
enable = false,
},
},
},
},
}
local lsp_flags = {
allow_incremental_sync = true,
}
if vim.fn.has("nvim-0.11") == 1 then
-- Neovim 0.11+ Native LSP Configuration
for name, config in pairs(servers) do
vim.lsp.config(name, config)
end
vim.lsp.config('*', { flags = lsp_flags })
-- Enable all defined servers
vim.lsp.enable(vim.tbl_keys(servers))
else
-- Fallback for Neovim < 0.11 (using nvim-lspconfig)
local lspconfig = require('lspconfig')
for name, config in pairs(servers) do
local final_config = vim.tbl_extend("force", { flags = lsp_flags }, config)
lspconfig[name].setup(final_config)
end
end
end)

14
scripts/updater.nix Normal file
View file

@ -0,0 +1,14 @@
{pkgs}:
pkgs.writeShellApplication {
name = "updateR";
# Tools your script needs at runtime
runtimeInputs = [
pkgs.wget
pkgs.gnused
pkgs.coreutils
];
# Keep script in separate file, but embed contents
text = builtins.readFile ./updater.sh;
}

22
scripts/updater.sh Normal file
View file

@ -0,0 +1,22 @@
echo "📡 Fetching latest R version from rstats-on-nix..."
RVER=$( wget -qO- 'https://raw.githubusercontent.com/ropensci/rix/refs/heads/main/inst/extdata/available_df.csv' | tail -n 1 | head -n 1 | cut -d',' -f4 | tr -d '"' )
# Validate RVER matches YYYY-MM-DD format
if [[ ! "$RVER" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "❌ Error: Failed to fetch valid R version date. Got: '$RVER'"
exit 1
fi
echo "✅ R date is $RVER"
# Create backup of flake.nix before modifying
cp flake.nix flake.nix.backup
# Update rixpkgs date in flake.nix
if sed -i "s|rixpkgs.url = \"github:rstats-on-nix/nixpkgs/[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\";|rixpkgs.url = \"github:rstats-on-nix/nixpkgs/$RVER\";|" flake.nix; then
echo "✅ Updated rixpkgs date in flake.nix"
rm flake.nix.backup
else
echo "⚠️ Warning: Failed to update flake.nix, restoring backup"
mv flake.nix.backup flake.nix
fi

BIN
spell/de.utf-8.spl Normal file

Binary file not shown.

BIN
spell/de.utf-8.sug Normal file

Binary file not shown.

28
tests/init.lua Normal file
View file

@ -0,0 +1,28 @@
local function assert_ok(value, message)
if not value then
error(message or "assertion failed")
end
end
local ok, nix = pcall(require, "config.nix")
assert_ok(ok, "Failed to require config.nix")
local init_ok, helper = pcall(function()
return nix.init({ non_nix_value = true })
end)
assert_ok(init_ok and helper, "Failed to initialize config.nix helper")
-- Basic shape checks
assert_ok(type(helper.is_nix) == "boolean", "Expected helper.is_nix to be boolean")
-- Cat/setting access should return defaults without errors
local cat_value = helper.get_cat("general", true)
assert_ok(type(cat_value) == "boolean", "Expected get_cat to return boolean")
local background = helper.get_setting("dark", "background")
assert_ok(type(background) == "string", "Expected get_setting to return string")
local info_value = helper.get_info("nvim", "nixCats_configDirName")
assert_ok(type(info_value) == "string", "Expected get_info to return string")
print("[tests/init.lua] nix helper smoke test passed")