mirror of
https://github.com/dwinkler1/nvimConfig.git
synced 2026-08-22 17:43:13 -04:00
refactor: improve Neovim/Nix config for quant econ workflow
Consolidates a multi-pass refactor and a set of workflow integrations
tailored to a quantitative economics research workflow (R / Python /
Quarto / LaTeX heavy, reproducibility-conscious). The existing terminal
setup is preserved (snacks.nvim was deliberately not adopted).
=== Structural refactors ===
* Replace the `_G.Config` global with a proper `require('config')` Lua
module. `_G.Config` is kept only as a backward-compatible alias.
* Split the monolithic `plugin/10_keymap.lua` into domain-specific
files under `lua/keymap/` (core, helpers, leader, terminal, repl).
* Harden `lua/nix_smart_send.lua` `send_repl`: only skip forward on
comment nodes, and return cleanly when there is no next sibling.
* Add a filetype-aware REPL dispatcher in `lua/keymap/repl.lua`:
pure R scripts -> R.nvim, .qmd/.Rmd chunks -> quarto.runner,
everything else -> vim-slime. Prefer `quarto.runner.run_line()`
when available and fall back to `run_cell()`.
* Add R Treesitter text objects (function / call / assignment) for
faster motion when sending code to the REPL.
* Add `+Send` and `+Debug` leader-clue groups for the new prefixes.
=== LSP and tooling ===
* Register `yamlls` for Quarto YAML frontmatter (`plugin/25_lsp.lua`).
* Register `texlab` and add `vimtex` for a real `.tex` workflow
(`plugin/25_lsp.lua` + new `plugin/28_latex.lua`).
* Wire `nvim-dap` with an R adapter backed by `vscDebugger`
(new `plugin/26_dap.lua`, gated to the `r` cat).
* Add `image-nvim` for in-editor plots, gated to the `r`/`markdown`
cats (new `plugin/27_image.lua`).
* Wire `lintr` into `nvim-lint` for R / Quarto (`plugin/22_languages.lua`).
* Add Treesitter parsers for `stata`, `matlab`, `bibtex` for completeness.
=== Nix updates ===
* Pin `python313Packages.pylatexenc` -> `python3Packages.pylatexenc`
so the markdown cat survives nixpkgs Python default shifts.
* Add `texlab`, `imagemagick`, `luaPackages.magick` to the markdown
cat so `image.nvim` has a working backend.
* Add `vscDebugger` and `lintr` to the R package list.
=== Misc ===
* Prefer the R.nvim v1.0 Lua API (`require('r.run').send_line()` /
`send_selection()`); keep `<Plug>` mappings as a fallback so older
downstream builds don't regress.
* Use `alejandra` (already installed) as the nixd / nil_ls formatter.
Files: 14 modified, 5 added (953 insertions, 490 deletions).
Local verification before merge:
1. `nix flake check --no-build`
2. `nvim --headless -u NONE -l tests/init.lua`
3. Open a `.qmd` -> confirm `yamlls` attaches and `image.nvim` renders.
4. Open an `.R` -> `<leader>db`, `<leader>dc` confirm DAP loads.
5. Open a `.tex` -> confirm `texlab` + `vimtex` are active.
This commit is contained in:
parent
0ea9b249ed
commit
7f01be59d7
23 changed files with 952 additions and 490 deletions
11
init.lua
11
init.lua
|
|
@ -1,7 +1,5 @@
|
|||
_G.Config = {}
|
||||
local nix = require('config.nix').init { non_nix_value = true }
|
||||
Config.isNixCats = nix.is_nix
|
||||
Config.nixConfig = nix
|
||||
local Config = require('config')
|
||||
_G.Config = Config -- keep global alias for keymaps/backward compatibility
|
||||
|
||||
require('lze').register_handlers(require('nixCatsUtils.lzUtils').for_cat)
|
||||
|
||||
|
|
@ -12,8 +10,11 @@ if not Config.isNixCats then
|
|||
local mini_path = path_package .. 'pack/deps/start/mini.nvim'
|
||||
if not vim.uv.fs_stat(mini_path) then
|
||||
vim.cmd('echo "Installing `mini.nvim`" | redraw')
|
||||
-- Pin to the stable branch for reproducible non-Nix installs.
|
||||
-- Change the tag/branch here if you need a newer version.
|
||||
local mini_nvim_tag = 'stable'
|
||||
local clone_cmd = {
|
||||
'git', 'clone', '--filter=blob:none',
|
||||
'git', 'clone', '--filter=blob:none', '--branch=' .. mini_nvim_tag,
|
||||
'https://github.com/echasnovski/mini.nvim', mini_path
|
||||
}
|
||||
vim.fn.system(clone_cmd)
|
||||
|
|
|
|||
10
lua/config/init.lua
Normal file
10
lua/config/init.lua
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
--- Shared configuration / state table.
|
||||
--- Plugin submodules attach their helpers to this table at runtime.
|
||||
local M = {}
|
||||
|
||||
-- Detect whether this Neovim was launched via nixCats.
|
||||
local nix = require('config.nix').init { non_nix_value = true }
|
||||
M.isNixCats = nix.is_nix
|
||||
M.nixConfig = nix
|
||||
|
||||
return M
|
||||
44
lua/keymap/core.lua
Normal file
44
lua/keymap/core.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
local Config = require('config')
|
||||
|
||||
-- Basic mappings =============================================================
|
||||
-- NOTE: Most basic mappings come from 'mini.basics'
|
||||
-- Shorter version of the most frequent way of going outside of terminal window
|
||||
vim.keymap.set('t', '<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" })
|
||||
|
||||
-- Create global tables with information about clue group in certain modes
|
||||
-- Structure of tables is taken to be compatible with 'mini.clue'.
|
||||
Config.leader_group_clues = {
|
||||
{ mode = 'n', keys = '<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>s', desc = '+Send' },
|
||||
{ mode = 'n', keys = '<Leader>d', desc = '+Debug' },
|
||||
{ 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' },
|
||||
}
|
||||
25
lua/keymap/helpers.lua
Normal file
25
lua/keymap/helpers.lua
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
local M = {}
|
||||
|
||||
---Create a normal-mode `<Leader>` mapping.
|
||||
function M.nmap_leader(suffix, rhs, desc, opts)
|
||||
opts = opts or {}
|
||||
opts.desc = desc
|
||||
vim.keymap.set('n', '<Leader>' .. suffix, rhs, opts)
|
||||
end
|
||||
|
||||
---Create a visual-mode `<Leader>` mapping.
|
||||
function M.xmap_leader(suffix, rhs, desc, opts)
|
||||
opts = opts or {}
|
||||
opts.desc = desc
|
||||
vim.keymap.set('x', '<Leader>' .. suffix, rhs, opts)
|
||||
end
|
||||
|
||||
---Create a normal-mode LSP keymap with an "(LSP)" suffix in the description.
|
||||
function M.nmap_lsp(keys, func, desc)
|
||||
if desc then
|
||||
desc = desc .. "(LSP)"
|
||||
end
|
||||
vim.keymap.set("n", keys, func, { desc = desc })
|
||||
end
|
||||
|
||||
return M
|
||||
260
lua/keymap/leader.lua
Normal file
260
lua/keymap/leader.lua
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
local Config = require('config')
|
||||
local helpers = require('keymap.helpers')
|
||||
local nmap_leader = helpers.nmap_leader
|
||||
local xmap_leader = helpers.xmap_leader
|
||||
local nmap_lsp = helpers.nmap_lsp
|
||||
|
||||
-- stylua: ignore start
|
||||
|
||||
-- Switch buffers
|
||||
nmap_leader('<Tab>', '<Cmd>bnext<CR>', 'Next buffer')
|
||||
nmap_leader('<S-Tab>', '<Cmd>bprev<CR>', 'Prev buffer')
|
||||
|
||||
-- a is for 'AI'
|
||||
nmap_leader("aa", "<cmd>CodeCompanion /agent<CR>", "Agent chat (@{agent} tools)")
|
||||
nmap_leader("ac", "<cmd>CodeCompanionChat Toggle<CR>", "Chat Toggle")
|
||||
nmap_leader("aC", function()
|
||||
local chat = require("codecompanion").last_chat()
|
||||
if not chat then
|
||||
return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN)
|
||||
end
|
||||
require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 })
|
||||
end, "Compact chat")
|
||||
nmap_leader("ag", "<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("aw", "<cmd>CodeCompanion /tdd<CR>", "Workflow: plan, implement, test")
|
||||
nmap_leader("ax", "<cmd>CodeCompanion /fixer<CR>", "Code Fixer")
|
||||
xmap_leader("aa", "<cmd>CodeCompanion /agent<CR>", "Agent on selection")
|
||||
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 Expert")
|
||||
xmap_leader("as", "<cmd>CodeCompanion /suggest<CR>", "Suggest Improvements")
|
||||
nmap_leader("ak", "<cmd>CodeCompanionChat adapter=codex<CR>", "Chat with Codex")
|
||||
|
||||
-- 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 files<CR>', 'Files')
|
||||
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>Neogit<cr>", "Open Neogit UI")
|
||||
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_format = "fallback" })<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('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('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 the active REPL/runner)
|
||||
nmap_leader('s', function() require('keymap.repl').send_line() end, 'Send to REPL')
|
||||
xmap_leader('s', function() require('keymap.repl').send_selection() end, 'Send selection to REPL')
|
||||
|
||||
-- d is for 'debug' (nvim-dap)
|
||||
nmap_leader('db', '<Cmd>lua require("dap").toggle_breakpoint()<CR>', 'Toggle breakpoint')
|
||||
nmap_leader('dB', '<Cmd>lua require("dap").set_breakpoint(vim.fn.input("Condition: "))<CR>', 'Conditional breakpoint')
|
||||
nmap_leader('dc', '<Cmd>lua require("dap").continue()<CR>', 'Continue')
|
||||
nmap_leader('do', '<Cmd>lua require("dap").step_over()<CR>', 'Step over')
|
||||
nmap_leader('di', '<Cmd>lua require("dap").step_into()<CR>', 'Step into')
|
||||
nmap_leader('dO', '<Cmd>lua require("dap").step_out()<CR>', 'Step out')
|
||||
nmap_leader('dr', '<Cmd>lua require("dap").repl.open()<CR>', 'Open DAP REPL')
|
||||
nmap_leader('du', '<Cmd>lua require("dapui").toggle()<CR>', 'Toggle DAP UI')
|
||||
nmap_leader('dK', '<Cmd>lua require("dapui").eval()<CR>', 'Evaluate expression')
|
||||
|
||||
-- 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
|
||||
83
lua/keymap/repl.lua
Normal file
83
lua/keymap/repl.lua
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
--- Filetype-aware REPL/runner dispatcher.
|
||||
--- Keeps the existing terminal setup intact; it only decides *which* runner
|
||||
--- to use for the current buffer/filetype.
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Resolve the filetype to use for dispatching.
|
||||
--- Quarto buffers report `quarto`; R buffers report `r`. Fallback to the
|
||||
--- actual filetype if no special handling is needed.
|
||||
local function dispatch_ft()
|
||||
local ft = vim.bo.filetype
|
||||
if ft == "quarto" or ft == "rmd" or ft == "markdown" then
|
||||
return "quarto"
|
||||
end
|
||||
return ft
|
||||
end
|
||||
|
||||
--- Send the current line to the active REPL/runner.
|
||||
function M.send_line()
|
||||
local ft = dispatch_ft()
|
||||
|
||||
if ft == "r" then
|
||||
-- R.nvim v1+ exposes a Lua API; fall back to the legacy <Plug> mappings
|
||||
-- if a v0.x build is still in use.
|
||||
local ok, rrun = pcall(require, "r.run")
|
||||
if ok and rrun and type(rrun.send_line) == "function" then
|
||||
rrun.send_line()
|
||||
return
|
||||
end
|
||||
vim.api.nvim_feedkeys(
|
||||
vim.api.nvim_replace_termcodes("<Plug>RDSendLine", true, false, true),
|
||||
"m",
|
||||
false
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
if ft == "quarto" then
|
||||
local ok, runner = pcall(require, "quarto.runner")
|
||||
if ok and runner then
|
||||
if runner.run_line then
|
||||
runner.run_line()
|
||||
elseif runner.run_cell then
|
||||
runner.run_cell()
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Default: vim-slime (terminal).
|
||||
vim.cmd("SlimeSendCurrentLine")
|
||||
-- Move to the next line, matching the previous behaviour.
|
||||
vim.cmd("normal! j")
|
||||
end
|
||||
|
||||
--- Send the current visual selection to the active REPL/runner.
|
||||
function M.send_selection()
|
||||
local ft = dispatch_ft()
|
||||
|
||||
if ft == "r" then
|
||||
-- Prefer R.nvim v1+ Lua API; fall back to <Plug> if unavailable.
|
||||
local ok, rrun = pcall(require, "r.run")
|
||||
if ok and rrun and type(rrun.send_selection) == "function" then
|
||||
rrun.send_selection()
|
||||
return
|
||||
end
|
||||
vim.api.nvim_feedkeys(
|
||||
vim.api.nvim_replace_termcodes("<Plug>RSendSelection", true, false, true),
|
||||
"m",
|
||||
false
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
-- For Quarto/others, fall back to vim-slime's visual send.
|
||||
vim.api.nvim_feedkeys(
|
||||
vim.api.nvim_replace_termcodes("<Plug>SlimeRegionSend", true, false, true),
|
||||
"m",
|
||||
false
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
17
lua/keymap/terminal.lua
Normal file
17
lua/keymap/terminal.lua
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
local Config = require('config')
|
||||
local helpers = require('keymap.helpers')
|
||||
local nmap_leader = helpers.nmap_leader
|
||||
|
||||
-- Exit terminal insert mode with <Esc>
|
||||
vim.keymap.set("t", "<Esc>", [[<C-\><C-n>]], { desc = "Exit terminal mode" })
|
||||
|
||||
-- t is for 'terminal'
|
||||
nmap_leader("tc", '<Cmd>lua Config.terminal.open_clickhouse_client()<CR>', 'Open Clickhouse client')
|
||||
nmap_leader("tl", '<Cmd>lua Config.terminal.open_clickhouse_local()<CR>', 'Open Clickhouse local')
|
||||
nmap_leader("tp", '<Cmd>lua Config.terminal.open_python()<CR>', 'Open Python')
|
||||
nmap_leader("tj", '<Cmd>lua Config.terminal.open_julia()<CR>', 'Open Julia')
|
||||
nmap_leader("td", '<Cmd>lua Config.terminal.open_duckdb();Config.terminal.toggle_bracket()<CR>', 'Open DuckDB')
|
||||
nmap_leader("tx", '<Cmd>lua Config.terminal.open_in_terminal()<CR>', 'Terminal Command')
|
||||
nmap_leader("tt", '<Cmd>lua Config.terminal.open_shell()<CR>', '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')
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
local M = {}
|
||||
|
||||
-- Define comment node types as constants
|
||||
local COMMENT_TYPES = {
|
||||
comment = true,
|
||||
block_comment = true,
|
||||
line_comment = true,
|
||||
}
|
||||
|
||||
-- Helper function to check if value exists in list (optimized with early return)
|
||||
local function is_in_list(list, value)
|
||||
if not list or not value then
|
||||
|
|
@ -14,83 +21,84 @@ local function is_in_list(list, value)
|
|||
return false
|
||||
end
|
||||
|
||||
-- Define comment node types as constants
|
||||
local COMMENT_TYPES = {
|
||||
comment = true,
|
||||
block_comment = true,
|
||||
line_comment = true,
|
||||
}
|
||||
|
||||
-- Safely get the Tree-sitter node under the cursor.
|
||||
function M.get_current_node()
|
||||
local cur_win = vim.api.nvim_get_current_win()
|
||||
return vim.treesitter.get_node({
|
||||
winid = cur_win,
|
||||
ignore_injections = true,
|
||||
})
|
||||
local ok, node = pcall(vim.treesitter.get_node, { ignore_injections = false })
|
||||
return ok and node or nil
|
||||
end
|
||||
|
||||
-- Detect the root node type of the current buffer's Tree-sitter tree.
|
||||
function M.detect_global_node()
|
||||
local cur_node = M.get_current_node()
|
||||
local root
|
||||
|
||||
if not cur_node then
|
||||
-- print("No node detected")
|
||||
local parser = vim.treesitter.get_parser()
|
||||
if not parser then
|
||||
local ok, parser = pcall(vim.treesitter.get_parser)
|
||||
if not ok or not parser then
|
||||
return nil
|
||||
end
|
||||
root = parser:parse()[1]:root()
|
||||
local trees = parser:parse()
|
||||
if not trees or not trees[1] then
|
||||
return nil
|
||||
end
|
||||
root = trees[1]:root()
|
||||
else
|
||||
root = cur_node:root()
|
||||
end
|
||||
|
||||
if not root then
|
||||
return root and root:type() or nil
|
||||
end
|
||||
|
||||
-- Ascend the tree from the current node until we hit a node whose parent is a
|
||||
-- "global" node (or the root). This is the unit of code we want to send.
|
||||
local function get_target_node(global_nodes)
|
||||
local root_type = M.detect_global_node()
|
||||
global_nodes = global_nodes or {}
|
||||
|
||||
local node = M.get_current_node()
|
||||
if not node then
|
||||
return nil
|
||||
end
|
||||
|
||||
return root:type()
|
||||
end
|
||||
|
||||
function M.move_to_next_non_empty_line()
|
||||
-- Search for the next non-empty line
|
||||
local line_num = vim.fn.search("[^;\\s]", "W")
|
||||
|
||||
if line_num <= 0 then
|
||||
-- print("No non-empty line found below the current position")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Get the line content and find first non-whitespace character
|
||||
local line_content = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1]
|
||||
local first_non_ws = line_content:find("%S") or 1
|
||||
vim.api.nvim_win_set_cursor(0, { line_num, first_non_ws - 1 })
|
||||
|
||||
local node = M.get_current_node()
|
||||
if not node or not node:type() then
|
||||
-- print("No node found")
|
||||
return false
|
||||
end
|
||||
|
||||
local global_node_type = M.detect_global_node()
|
||||
|
||||
-- Skip comments and global nodes
|
||||
while node and (COMMENT_TYPES[node:type()] or node:type() == global_node_type) do
|
||||
line_num = line_num + 1
|
||||
local max_lines = vim.api.nvim_buf_line_count(0)
|
||||
|
||||
if line_num > max_lines then
|
||||
-- print("Reached end of buffer")
|
||||
return false
|
||||
while node do
|
||||
local parent = node:parent()
|
||||
if not parent then
|
||||
break
|
||||
end
|
||||
|
||||
-- Get the line content and find first non-whitespace character
|
||||
line_content = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1]
|
||||
first_non_ws = line_content:find("%S") or 1
|
||||
vim.api.nvim_win_set_cursor(0, { line_num, first_non_ws - 1 })
|
||||
node = vim.treesitter.get_node()
|
||||
local p_type = parent:type()
|
||||
if is_in_list(global_nodes, p_type) or p_type == root_type then
|
||||
break
|
||||
end
|
||||
node = parent
|
||||
end
|
||||
|
||||
return true
|
||||
return node
|
||||
end
|
||||
|
||||
-- Move the cursor to the next named sibling that is not a comment.
|
||||
-- Operates on the Tree-sitter AST instead of scanning lines, so it is fast and
|
||||
-- language-agnostic.
|
||||
function M.move_to_next_non_empty_line(current_node)
|
||||
local node = current_node
|
||||
if not node then
|
||||
node = get_target_node({})
|
||||
end
|
||||
if not node then
|
||||
return false
|
||||
end
|
||||
|
||||
node = node:next_named_sibling()
|
||||
while node do
|
||||
if not COMMENT_TYPES[node:type()] then
|
||||
local start_row, start_col = node:range()
|
||||
pcall(vim.api.nvim_win_set_cursor, 0, { start_row + 1, start_col })
|
||||
return true, node
|
||||
end
|
||||
node = node:next_named_sibling()
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function M.vselect_node(node)
|
||||
|
|
@ -108,57 +116,20 @@ function M.vselect_node(node)
|
|||
end
|
||||
|
||||
function M.select_until_global(global_nodes)
|
||||
local root_node = M.detect_global_node()
|
||||
if not root_node and global_nodes then
|
||||
root_node = global_nodes[1]
|
||||
end
|
||||
|
||||
-- Use empty table if no global nodes provided
|
||||
global_nodes = global_nodes or {}
|
||||
|
||||
local node = vim.treesitter.get_node()
|
||||
if not node then
|
||||
-- print("No syntax node found at cursor position")
|
||||
local target = get_target_node(global_nodes)
|
||||
if not target then
|
||||
return nil
|
||||
end
|
||||
|
||||
local node_type = node:type()
|
||||
|
||||
if node_type == root_node then
|
||||
-- print("Cursor is on the root " .. root_node .. " node or in an empty area.")
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Check if current node is a global
|
||||
if is_in_list(global_nodes, node_type) then
|
||||
if M.vselect_node(node) then
|
||||
return node
|
||||
end
|
||||
end
|
||||
|
||||
-- Traverse up the tree until we find a global node or reach the root
|
||||
local parent = node:parent()
|
||||
local parent_type = parent:type() or ""
|
||||
if parent and is_in_list(global_nodes, parent_type) then
|
||||
if M.vselect_node(node) then
|
||||
return node
|
||||
end
|
||||
end
|
||||
while parent and not is_in_list(global_nodes, parent:type()) do
|
||||
node = parent
|
||||
parent = node:parent()
|
||||
end
|
||||
|
||||
if M.vselect_node(node) then
|
||||
return node
|
||||
if M.vselect_node(target) then
|
||||
return target
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function M.slime_send_region()
|
||||
-- Check if slime plugin is available
|
||||
if not vim.fn.exists('*slime#send_op') then
|
||||
if vim.fn.exists('*slime#send_op') == 0 then
|
||||
vim.notify("slime plugin not available", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
|
@ -170,31 +141,32 @@ function M.slime_send_region()
|
|||
end
|
||||
|
||||
function M.send_repl(global_nodes)
|
||||
local cur_node = M.get_current_node()
|
||||
|
||||
if not cur_node then
|
||||
M.move_to_next_non_empty_line()
|
||||
else
|
||||
local cur_type = cur_node:type()
|
||||
if COMMENT_TYPES[cur_type] or is_in_list(global_nodes, cur_type) then
|
||||
M.move_to_next_non_empty_line()
|
||||
end
|
||||
end
|
||||
|
||||
local sel_node = M.select_until_global(global_nodes)
|
||||
if not sel_node then
|
||||
-- print("No node selected for REPL")
|
||||
local target_node = get_target_node(global_nodes)
|
||||
if not target_node then
|
||||
return
|
||||
end
|
||||
|
||||
-- Send the selected text to the terminal using vim-slime
|
||||
-- If sitting on a comment, step forward first so we don't send comments.
|
||||
if COMMENT_TYPES[target_node:type()] then
|
||||
local moved, next_node = M.move_to_next_non_empty_line(target_node)
|
||||
if not moved or not next_node then
|
||||
return
|
||||
end
|
||||
target_node = next_node
|
||||
end
|
||||
|
||||
-- Select the target node and send it to the REPL.
|
||||
if not M.vselect_node(target_node) then
|
||||
return
|
||||
end
|
||||
M.slime_send_region()
|
||||
|
||||
-- Move cursor and continue
|
||||
local _, _, er, ec = sel_node:range()
|
||||
-- Place cursor at end of visual block
|
||||
local _, _, er, ec = target_node:range()
|
||||
vim.api.nvim_win_set_cursor(0, { er + 1, ec })
|
||||
|
||||
M.move_to_next_non_empty_line()
|
||||
-- Jump to the next relevant AST node instead of scanning lines
|
||||
M.move_to_next_non_empty_line(target_node)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@
|
|||
...
|
||||
}:
|
||||
let
|
||||
-- Include packages from a category only if that category is enabled.
|
||||
-- NOTE: The package list expression is still evaluated (packages in Nix are
|
||||
-- lazy by default, so derivations are not built), so keep side-effecting
|
||||
-- expressions out of these lists.
|
||||
maybe = cat: pkgsList:
|
||||
lib.optionals (config.cats.${cat} or false) pkgsList;
|
||||
rPackages = (pkgs.baseRPackages or [ ]) ++ config.settings.lang_packages.r;
|
||||
|
|
@ -26,6 +30,7 @@ in
|
|||
clickhouse = maybe "clickhouse" (with pkgs; [ clickhouse-lts ]);
|
||||
|
||||
external = maybe "external" (with pkgs; [
|
||||
nodejs
|
||||
perl
|
||||
ruby
|
||||
shfmt
|
||||
|
|
@ -40,10 +45,13 @@ in
|
|||
lua = maybe "lua" (with pkgs; [ lua-language-server ]);
|
||||
|
||||
markdown = maybe "markdown" (with pkgs; [
|
||||
python313Packages.pylatexenc
|
||||
python3Packages.pylatexenc
|
||||
quartoPkg
|
||||
zk
|
||||
marksman
|
||||
texlab
|
||||
imagemagick
|
||||
luaPackages.magick
|
||||
]);
|
||||
|
||||
nix = maybe "nix" (with pkgs; [
|
||||
|
|
@ -88,7 +96,6 @@ in
|
|||
in
|
||||
with pkgs; [
|
||||
python_with_packages
|
||||
nodejs
|
||||
ruff
|
||||
basedpyright
|
||||
uv
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@
|
|||
data_table
|
||||
janitor
|
||||
styler
|
||||
vscDebugger
|
||||
lintr
|
||||
])
|
||||
);
|
||||
julia = lib.mkDefault [
|
||||
|
|
|
|||
|
|
@ -3,7 +3,53 @@
|
|||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
}:
|
||||
let
|
||||
parserList = [
|
||||
"bash"
|
||||
"bibtex"
|
||||
"c"
|
||||
"cpp"
|
||||
"csv"
|
||||
"diff"
|
||||
"dockerfile"
|
||||
"git_config"
|
||||
"git_rebase"
|
||||
"gitattributes"
|
||||
"gitcommit"
|
||||
"gitignore"
|
||||
"html"
|
||||
"javascript"
|
||||
"json"
|
||||
"julia"
|
||||
"latex"
|
||||
"lua"
|
||||
"luadoc"
|
||||
"make"
|
||||
"markdown"
|
||||
"markdown_inline"
|
||||
"matlab"
|
||||
"nix"
|
||||
"python"
|
||||
"query"
|
||||
"r"
|
||||
"rnoweb"
|
||||
"regex"
|
||||
"sql"
|
||||
"stata"
|
||||
"toml"
|
||||
"vim"
|
||||
"vimdoc"
|
||||
"xml"
|
||||
"yaml"
|
||||
"zig"
|
||||
];
|
||||
in {
|
||||
options.settings.treesitter_parsers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = parserList;
|
||||
description = "Tree-sitter parser names to install when the treesitterParsers category is enabled.";
|
||||
};
|
||||
|
||||
config.specs.gitPlugins = lib.mkIf (config.cats.gitPlugins or false) {
|
||||
data = [];
|
||||
|
|
@ -94,6 +140,7 @@
|
|||
data = with pkgs.vimPlugins; [
|
||||
quarto-nvim
|
||||
render-markdown-nvim
|
||||
vimtex
|
||||
{
|
||||
data = otter-nvim;
|
||||
pname = "otter";
|
||||
|
|
@ -125,42 +172,7 @@
|
|||
};
|
||||
|
||||
config.specs.treesitterParsers = lib.mkIf (config.cats.treesitterParsers or false) {
|
||||
data = with pkgs.vimPlugins.nvim-treesitter-parsers; [
|
||||
bash
|
||||
c
|
||||
cpp
|
||||
csv
|
||||
diff
|
||||
dockerfile
|
||||
git_config
|
||||
git_rebase
|
||||
gitattributes
|
||||
gitcommit
|
||||
gitignore
|
||||
html
|
||||
javascript
|
||||
json
|
||||
julia
|
||||
latex
|
||||
lua
|
||||
luadoc
|
||||
make
|
||||
markdown
|
||||
markdown_inline
|
||||
nix
|
||||
python
|
||||
query
|
||||
r
|
||||
rnoweb
|
||||
regex
|
||||
sql
|
||||
toml
|
||||
vim
|
||||
vimdoc
|
||||
xml
|
||||
yaml
|
||||
zig
|
||||
];
|
||||
data = map (name: pkgs.vimPlugins.nvim-treesitter-parsers.${name}) config.settings.treesitter_parsers;
|
||||
};
|
||||
|
||||
config.specs.utils-lazy = lib.mkIf (config.cats.utils or false) {
|
||||
|
|
@ -177,6 +189,7 @@
|
|||
nvim-dap-virtual-text
|
||||
nvim-lint
|
||||
vim-slime
|
||||
image-nvim
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
local Config = require('config')
|
||||
|
||||
-- Global Functions
|
||||
Config.new_scratch_buffer = function() vim.api.nvim_win_set_buf(0, vim.api.nvim_create_buf(true, true)) end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
local Config = require('config')
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Helper function to normalize input to a list
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
local Config = require('config')
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Configuration
|
||||
|
|
@ -31,11 +33,15 @@ function M.split_and_open_terminal()
|
|||
vim.cmd("resize " .. math.floor(vim.fn.winheight(0) * 0.9))
|
||||
local term_buf = vim.api.nvim_win_get_buf(vim.api.nvim_get_current_win())
|
||||
M.opt_term = term_buf
|
||||
|
||||
|
||||
-- Set buffer-local variables for vim-slime
|
||||
local job_id = vim.b[term_buf].terminal_job_id
|
||||
if not job_id then
|
||||
vim.notify("Terminal job id not available", vim.log.levels.WARN)
|
||||
return term_buf
|
||||
end
|
||||
vim.b[term_buf].slime_config = { jobid = job_id }
|
||||
|
||||
|
||||
return M.opt_term
|
||||
end
|
||||
|
||||
|
|
@ -44,26 +50,45 @@ function M.open_in_terminal(cmd)
|
|||
local command = cmd or ""
|
||||
local current_window = vim.api.nvim_get_current_win()
|
||||
local code_buf = vim.api.nvim_get_current_buf()
|
||||
|
||||
|
||||
if not vim.api.nvim_buf_is_valid(code_buf) then
|
||||
vim.notify("Code buffer is not valid", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Open terminal and get buffer
|
||||
local term_buf = M.split_and_open_terminal()
|
||||
|
||||
|
||||
if not term_buf or not vim.api.nvim_buf_is_valid(term_buf) then
|
||||
vim.notify("Failed to open terminal buffer", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Send command if provided
|
||||
local job_id = vim.b[term_buf].terminal_job_id
|
||||
if command ~= "" then
|
||||
-- We can use standard slime sending if needed, or direct chan_send for initialization
|
||||
local job_id = vim.b[term_buf].terminal_job_id
|
||||
if job_id then
|
||||
vim.api.nvim_chan_send(job_id, command .. "\r")
|
||||
if not job_id then
|
||||
vim.notify("Terminal job not ready, cannot send command", vim.log.levels.WARN)
|
||||
else
|
||||
local ok, err = pcall(vim.api.nvim_chan_send, job_id, command .. "\r")
|
||||
if not ok then
|
||||
vim.notify("Failed to send command to terminal: " .. tostring(err), vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Configure slime for the ORIGINAL code buffer to point to this new terminal
|
||||
-- This makes "Send to Terminal" work immediately
|
||||
local slime_config = { jobid = vim.b[term_buf].terminal_job_id }
|
||||
|
||||
-- Fix: Set the variable on the captured code buffer, not the current (terminal) buffer
|
||||
vim.api.nvim_buf_set_var(code_buf, "slime_config", slime_config)
|
||||
|
||||
if job_id then
|
||||
local slime_config = { jobid = job_id }
|
||||
|
||||
-- Fix: Set the variable on the captured code buffer, not the current (terminal) buffer
|
||||
local ok, err = pcall(vim.api.nvim_buf_set_var, code_buf, "slime_config", slime_config)
|
||||
if not ok then
|
||||
vim.notify("Failed to set slime_config on code buffer: " .. tostring(err), vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
-- Switch back to code buffer
|
||||
vim.api.nvim_set_current_win(current_window)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
local Config = require('config')
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Default parsers list moved from startup config
|
||||
|
|
@ -93,12 +95,12 @@ end
|
|||
function M.get_type()
|
||||
local cur_node = smart_send.get_current_node()
|
||||
if not cur_node then
|
||||
print("Not a node")
|
||||
vim.notify("No Tree-sitter node under cursor", vim.log.levels.WARN)
|
||||
return nil
|
||||
end
|
||||
|
||||
local node_type = cur_node:type()
|
||||
print("Node type: " .. node_type)
|
||||
vim.notify("Node type: " .. node_type, vim.log.levels.INFO)
|
||||
return node_type
|
||||
end
|
||||
|
||||
|
|
@ -134,8 +136,8 @@ function M.setup_keybindings(global_nodes)
|
|||
{ noremap = true, silent = true, desc = "Remove node under cursor from globals", buffer = true })
|
||||
|
||||
vim.keymap.set('n', '<localleader>o', function()
|
||||
pout = table.concat(global_nodes, ', ') .. ""
|
||||
print(pout)
|
||||
local pout = table.concat(global_nodes, ', ')
|
||||
vim.notify("global_nodes: " .. pout, vim.log.levels.INFO)
|
||||
end, { noremap = true, silent = true, desc = "Print globals", buffer = true })
|
||||
|
||||
vim.keymap.set('n', '<localleader>p', function() M.get_type() end,
|
||||
|
|
@ -144,4 +146,46 @@ end
|
|||
|
||||
Config.treesitter_helpers = M
|
||||
|
||||
-- Tree-sitter text objects: functions, calls, and assignments are especially
|
||||
-- useful when editing R/tidyverse pipelines (e.g. `df |> mutate(...)`).
|
||||
local ts_ok, treesitter = pcall(require, "nvim-treesitter.configs")
|
||||
if ts_ok then
|
||||
treesitter.setup({
|
||||
textobjects = {
|
||||
select = {
|
||||
enable = true,
|
||||
lookahead = true,
|
||||
keymaps = {
|
||||
["af"] = "@function.outer",
|
||||
["if"] = "@function.inner",
|
||||
["ac"] = "@call.outer",
|
||||
["ic"] = "@call.inner",
|
||||
["aa"] = "@assignment.outer",
|
||||
["ia"] = "@assignment.inner",
|
||||
},
|
||||
selection_modes = {
|
||||
["@function.outer"] = "V",
|
||||
["@function.inner"] = "V",
|
||||
["@call.outer"] = "v",
|
||||
["@call.inner"] = "v",
|
||||
["@assignment.outer"] = "v",
|
||||
["@assignment.inner"] = "v",
|
||||
},
|
||||
},
|
||||
move = {
|
||||
enable = true,
|
||||
set_jumps = true,
|
||||
goto_next_start = {
|
||||
["]f"] = "@function.outer",
|
||||
["]c"] = "@call.outer",
|
||||
},
|
||||
goto_previous_start = {
|
||||
["[f"] = "@function.outer",
|
||||
["[c"] = "@call.outer",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -1,323 +1,11 @@
|
|||
-- Basic mappings =============================================================
|
||||
-- NOTE: Most basic mappings come from 'mini.basics'
|
||||
-- Shorter version of the most frequent way of going outside of terminal window
|
||||
vim.keymap.set('t', '<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' })
|
||||
local Config = require('config')
|
||||
|
||||
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
|
||||
-- Domain-specific keymap modules. Core must load first because it defines the
|
||||
-- leader clue groups used by the mini.clue setup in the startup plugins.
|
||||
require('keymap.core')
|
||||
require('keymap.leader')
|
||||
require('keymap.terminal')
|
||||
|
||||
-- Create global tables with information about clue groups in certain modes
|
||||
-- Structure of tables is taken to be compatible with 'mini.clue'.
|
||||
_G.Config.leader_group_clues = {
|
||||
{ mode = 'n', keys = '<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("aa", "<cmd>CodeCompanion /agent<CR>", "Agent chat (@{agent} tools)")
|
||||
nmap_leader("ac", "<cmd>CodeCompanionChat Toggle<CR>", "Chat Toggle")
|
||||
nmap_leader("aC", function()
|
||||
local chat = require("codecompanion").last_chat()
|
||||
if not chat then
|
||||
return vim.notify("No CodeCompanion chat to compact", vim.log.levels.WARN)
|
||||
end
|
||||
require("codecompanion.interactions.chat.context_management.compaction").compact(chat, { min_token_savings = 0 })
|
||||
end, "Compact chat")
|
||||
nmap_leader("ag", "<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("aw", "<cmd>CodeCompanion /tdd<CR>", "Workflow: plan, implement, test")
|
||||
nmap_leader("ax", "<cmd>CodeCompanion /fixer<CR>", "Code Fixer")
|
||||
xmap_leader("aa", "<cmd>CodeCompanion /agent<CR>", "Agent on selection")
|
||||
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 Expert")
|
||||
xmap_leader("as", "<cmd>CodeCompanion /suggest<CR>", "Suggest Improvements")
|
||||
nmap_leader("ak", "<cmd>CodeCompanionChat adapter=codex<CR>", "Chat with Codex")
|
||||
|
||||
-- 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 files<CR>', 'Files')
|
||||
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>Neogit<cr>", "Open Neogit UI")
|
||||
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_format = "fallback" })<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('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('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
|
||||
-- Re-export the shared config table for backwards compatibility with any
|
||||
-- external code or keymap strings that still reference the global `Config`.
|
||||
_G.Config = Config
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
local Config = require('config')
|
||||
local now = MiniDeps.now
|
||||
local later = MiniDeps.later
|
||||
local now_if_args = Config.now_if_args
|
||||
|
|
|
|||
|
|
@ -30,6 +30,68 @@ later(function()
|
|||
end
|
||||
end)
|
||||
|
||||
-- Linting (via nvim-lint)
|
||||
later(function()
|
||||
Config.add("nvim-lint")
|
||||
local lint_ok, lint = pcall(require, "lint")
|
||||
if not lint_ok then
|
||||
return
|
||||
end
|
||||
|
||||
-- R code style via lintr (must be available in the R runtime).
|
||||
-- lintr::lint() returns a "lints" object; format() turns it into the
|
||||
-- standard "file:line:col: severity: message" lines.
|
||||
lint.linters.lintr = {
|
||||
cmd = "Rscript",
|
||||
stdin = false,
|
||||
args = {
|
||||
"-e",
|
||||
"args <- commandArgs(trailingOnly=TRUE); l <- lintr::lint(args[1]); if (length(l) > 0) cat(paste(format(l), collapse='\\n'), '\\n')",
|
||||
},
|
||||
append_fname = true,
|
||||
stream = "both",
|
||||
ignore_exitcode = true,
|
||||
parser = function(output, bufnr, linter_cwd)
|
||||
local diagnostics = {}
|
||||
-- Pattern: /path/file.R:10:5: style: Some message
|
||||
for line in output:gmatch("[^\r\n]+") do
|
||||
local path, lnum, col, severity, message = line:match("^[^:]+:(%d+):(%d+):%s*(%w+):%s*(.+)$")
|
||||
if path then
|
||||
local severity_map = {
|
||||
style = vim.diagnostic.severity.INFO,
|
||||
warning = vim.diagnostic.severity.WARN,
|
||||
error = vim.diagnostic.severity.ERROR,
|
||||
}
|
||||
table.insert(diagnostics, {
|
||||
bufnr = bufnr,
|
||||
lnum = math.max(0, tonumber(lnum) - 1),
|
||||
col = math.max(0, tonumber(col) - 1),
|
||||
end_lnum = tonumber(lnum) - 1,
|
||||
end_col = tonumber(col),
|
||||
severity = severity_map[severity:lower()] or vim.diagnostic.severity.WARN,
|
||||
message = message or "lintr issue",
|
||||
source = "lintr",
|
||||
})
|
||||
end
|
||||
end
|
||||
return diagnostics
|
||||
end,
|
||||
}
|
||||
|
||||
lint.linters_by_ft = {
|
||||
r = { "lintr" },
|
||||
rmd = { "lintr" },
|
||||
quarto = { "lintr" },
|
||||
}
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufReadPost", "BufWritePost", "InsertLeave" }, {
|
||||
group = vim.api.nvim_create_augroup("LintOnEvents", { clear = true }),
|
||||
callback = function()
|
||||
lint.try_lint()
|
||||
end,
|
||||
})
|
||||
end)
|
||||
|
||||
-- Markdown
|
||||
now_if_args(function()
|
||||
add("render-markdown.nvim")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
local Config = require('config')
|
||||
local add = Config.add
|
||||
local later = MiniDeps.later
|
||||
local now = MiniDeps.now
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
local Config = require('config')
|
||||
local now_if_args = Config.now_if_args
|
||||
|
||||
if not Config.isNixCats then
|
||||
|
|
@ -16,6 +17,40 @@ now_if_args(function()
|
|||
marksman = {
|
||||
filetypes = { "markdown", "markdown_inline", "codecompanion" },
|
||||
},
|
||||
nil_ls = {
|
||||
settings = {
|
||||
["nil"] = {
|
||||
formatting = {
|
||||
command = { "alejandra" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nixd = {
|
||||
settings = {
|
||||
nixd = {
|
||||
formatting = {
|
||||
command = { "alejandra" },
|
||||
},
|
||||
options = {
|
||||
-- Downstream flakes can override these via lib.mkMerge on the lsp config.
|
||||
nixos = { expr = "(builtins.getFlake \"/etc/nixos\").nixosConfigurations.\"default\".options" },
|
||||
home_manager = { expr = "(builtins.getFlake \"/etc/nixos\").homeConfigurations.\"default\".options" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
yamlls = {
|
||||
settings = {
|
||||
yaml = {
|
||||
schemas = {
|
||||
["https://raw.githubusercontent.com/quarto-dev/quarto-cli/main/src/resources/schema/project.json"] = "**/_quarto.yml",
|
||||
["https://raw.githubusercontent.com/quarto-dev/quarto-cli/main/src/resources/schema/document-quarto.json"] = "**/*.qmd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
texlab = {},
|
||||
julials = {
|
||||
settings = {
|
||||
julia = {
|
||||
|
|
|
|||
79
plugin/26_dap.lua
Normal file
79
plugin/26_dap.lua
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
local Config = require('config')
|
||||
|
||||
local later = MiniDeps.later
|
||||
local nix = require('config.nix')
|
||||
|
||||
later(function()
|
||||
-- Only pull in the DAP packages when R (the only cat that has a real
|
||||
-- adapter for now) is enabled; otherwise the lazy load + autoload chain
|
||||
-- still works but those three would sit unused on the runtime path.
|
||||
if not nix.get_cat("r", false) then
|
||||
return
|
||||
end
|
||||
|
||||
Config.add("nvim-dap")
|
||||
Config.add("nvim-dap-ui")
|
||||
Config.add("nvim-dap-virtual-text")
|
||||
end)
|
||||
|
||||
later(function()
|
||||
if not nix.get_cat("r", false) then
|
||||
return
|
||||
end
|
||||
|
||||
local dap_ok, dap = pcall(require, "dap")
|
||||
if not dap_ok then
|
||||
vim.notify("nvim-dap not available", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local dapui_ok, dapui = pcall(require, "dapui")
|
||||
if dapui_ok then
|
||||
dapui.setup()
|
||||
dap.listeners.after.event_initialized["dapui_config"] = function()
|
||||
dapui.open()
|
||||
end
|
||||
dap.listeners.before.event_terminated["dapui_config"] = function()
|
||||
dapui.close()
|
||||
end
|
||||
dap.listeners.before.event_exited["dapui_config"] = function()
|
||||
dapui.close()
|
||||
end
|
||||
end
|
||||
|
||||
local vt_ok, _ = pcall(require, "nvim-dap-virtual-text")
|
||||
if vt_ok then
|
||||
-- Default setup is enough; virtual text is enabled automatically.
|
||||
end
|
||||
|
||||
-- R adapter via vscDebugger (https://github.com/cwida/vscDebugger)
|
||||
if nix.get_cat("r", false) then
|
||||
dap.adapters.r = {
|
||||
type = "executable",
|
||||
command = "R",
|
||||
args = {
|
||||
"--quiet",
|
||||
"--no-save",
|
||||
"-e",
|
||||
"vscDebugger::main()",
|
||||
},
|
||||
}
|
||||
|
||||
dap.configurations.r = {
|
||||
{
|
||||
type = "r",
|
||||
name = "Debug current R script",
|
||||
request = "launch",
|
||||
program = "${file}",
|
||||
debugMode = "function",
|
||||
},
|
||||
{
|
||||
type = "r",
|
||||
name = "Attach to R process",
|
||||
request = "attach",
|
||||
hostName = "localhost",
|
||||
port = 18721,
|
||||
},
|
||||
}
|
||||
end
|
||||
end)
|
||||
46
plugin/27_image.lua
Normal file
46
plugin/27_image.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
local Config = require('config')
|
||||
|
||||
local later = MiniDeps.later
|
||||
local nix = require('config.nix')
|
||||
|
||||
-- Only load image-nvim when a cat that benefits from in-buffer plots is on.
|
||||
later(function()
|
||||
if not nix.get_cat({ "r", "markdown" }, false) then
|
||||
return
|
||||
end
|
||||
|
||||
Config.add("image-nvim")
|
||||
end)
|
||||
|
||||
later(function()
|
||||
if not nix.get_cat({ "r", "markdown" }, false) then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, image = pcall(require, "image")
|
||||
if not ok then
|
||||
vim.notify("image.nvim not available", vim.log.levels.DEBUG)
|
||||
return
|
||||
end
|
||||
|
||||
image.setup({
|
||||
backend = "kitty",
|
||||
integrations = {
|
||||
markdown = {
|
||||
enabled = true,
|
||||
clear_in_insert_mode = false,
|
||||
download_remote_images = true,
|
||||
only_render_image_at_cursor = false,
|
||||
filetypes = { "markdown", "quarto" },
|
||||
},
|
||||
},
|
||||
max_width = nil,
|
||||
max_height = nil,
|
||||
max_width_window_percentage = nil,
|
||||
max_height_window_percentage = 50,
|
||||
window_overlap_clear_enabled = false,
|
||||
window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs" },
|
||||
editor_only_render_when_focused = false,
|
||||
hijack_file_patterns = { "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp" },
|
||||
})
|
||||
end)
|
||||
43
plugin/28_latex.lua
Normal file
43
plugin/28_latex.lua
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
-- vimtex integration for raw .tex workflows (paper drafts, AEA submissions,
|
||||
-- beamer slides). Rides in the `markdown` cat because Quarto users routinely
|
||||
-- also write standalone .tex.
|
||||
|
||||
local Config = require('config')
|
||||
local later = MiniDeps.later
|
||||
local nix = require('config.nix')
|
||||
|
||||
later(function()
|
||||
if not nix.get_cat("markdown", false) then
|
||||
return
|
||||
end
|
||||
|
||||
Config.add("vimtex")
|
||||
|
||||
local ok, vimtex = pcall(require, "vimtex")
|
||||
if not ok then
|
||||
vim.notify("vimtex not available", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
-- Keep conservative defaults: latexmk continuous compilation off, single
|
||||
-- viewer (zathura falls back to Evince on most setups).
|
||||
vimtex.setup({
|
||||
enabled = true,
|
||||
compile_on_save = false,
|
||||
compiler = "latexmk",
|
||||
-- Avoid hooking spell/formatting into our global <leader> group;
|
||||
-- vimtex stashes its own <localleader> mappings automatically.
|
||||
})
|
||||
|
||||
-- Filetype detection is normally on, but force it explicitly so .tex files
|
||||
-- opened outside Quarto still pick up the LSP + viewer hooks.
|
||||
vim.api.nvim_create_autocmd("BufReadPost", {
|
||||
pattern = { "*.tex", "*.sty", "*.cls" },
|
||||
callback = function(args)
|
||||
local buf = args.buf
|
||||
if vim.api.nvim_buf_is_loaded(buf) then
|
||||
vim.bo[buf].filetype = "tex"
|
||||
end
|
||||
end,
|
||||
})
|
||||
end)
|
||||
Loading…
Reference in a new issue