summary refs log tree commit diff
path: root/.config/nvim
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2026-09-27 17:38:39 +0200
committerRobert Günzler <r@gnzler.io>2026-09-27 17:38:39 +0200
commit0860db73ae6fd9a6f6347219d820bf026a6b8b91 (patch)
tree11318c3670d1c32c5aa0bf404c22fba3bf63fc53 /.config/nvim
parent4e34cbf9673aeaba252e01df554a0d204e83c5e2 (diff)
downloaddotfiles-0860db73ae6fd9a6f6347219d820bf026a6b8b91.tar.gz
Automated-commit-by: dots
Signed-off-by: Robert Günzler <r@gnzler.io>
Diffstat (limited to '.config/nvim')
-rw-r--r--.config/nvim/after/ftplugin/gitrebase.lua19
-rw-r--r--.config/nvim/after/ftplugin/go.lua32
-rw-r--r--.config/nvim/after/ftplugin/help.lua2
-rw-r--r--.config/nvim/after/ftplugin/janet.lua3
-rw-r--r--.config/nvim/after/ftplugin/markdown.lua9
-rw-r--r--.config/nvim/after/ftplugin/todotxt.lua13
-rw-r--r--.config/nvim/after/ftplugin/vala.lua1
-rw-r--r--.config/nvim/after/lsp/gopls.lua1
-rw-r--r--.config/nvim/after/lsp/harper_ls.lua11
-rw-r--r--.config/nvim/after/lsp/pylsp.lua3
-rw-r--r--.config/nvim/after/luasnippets/gitcommit.lua24
-rw-r--r--.config/nvim/after/luasnippets/zig.lua14
-rw-r--r--.config/nvim/init.lua13
-rw-r--r--.config/nvim/lazy-lock.json44
-rw-r--r--.config/nvim/lua/config/autoall.lua35
-rw-r--r--.config/nvim/lua/config/keymaps.lua16
-rw-r--r--.config/nvim/lua/config/lsp.lua5
-rw-r--r--.config/nvim/lua/config/options.lua7
-rw-r--r--.config/nvim/lua/config/snippets.lua84
-rw-r--r--.config/nvim/lua/config/templates.lua53
-rw-r--r--.config/nvim/lua/mod/gh.lua86
-rw-r--r--.config/nvim/lua/mod/git.lua23
-rw-r--r--.config/nvim/lua/mod/jobs.lua68
-rw-r--r--.config/nvim/lua/mod/termopen.lua102
-rw-r--r--.config/nvim/lua/mod/utils.lua15
-rw-r--r--.config/nvim/lua/mod/win.lua110
-rw-r--r--.config/nvim/lua/plugins/blink.lua23
-rw-r--r--.config/nvim/lua/plugins/calcium.lua1
-rw-r--r--.config/nvim/lua/plugins/colorizer.lua5
-rw-r--r--.config/nvim/lua/plugins/colors.lua12
-rw-r--r--.config/nvim/lua/plugins/diff.lua47
-rw-r--r--.config/nvim/lua/plugins/fzf.lua98
-rw-r--r--.config/nvim/lua/plugins/gentoo.lua1
-rw-r--r--.config/nvim/lua/plugins/git.lua7
-rw-r--r--.config/nvim/lua/plugins/haunt.lua47
-rw-r--r--.config/nvim/lua/plugins/icons.lua1
-rw-r--r--.config/nvim/lua/plugins/indent.lua1
-rw-r--r--.config/nvim/lua/plugins/lsp.lua29
-rw-r--r--.config/nvim/lua/plugins/markdown.lua2
-rw-r--r--.config/nvim/lua/plugins/mini.lua4
-rw-r--r--.config/nvim/lua/plugins/minimap.lua13
-rw-r--r--.config/nvim/lua/plugins/oil.lua1
-rw-r--r--.config/nvim/lua/plugins/snippets.lua1
-rw-r--r--.config/nvim/lua/plugins/treesitter.lua5
-rw-r--r--.config/nvim/lua/plugins/whichkey.lua10
-rw-r--r--.config/nvim/plugin/mod.lua25
46 files changed, 1016 insertions, 110 deletions
diff --git a/.config/nvim/after/ftplugin/gitrebase.lua b/.config/nvim/after/ftplugin/gitrebase.lua
index f85d3b9..fb64534 100644
--- a/.config/nvim/after/ftplugin/gitrebase.lua
+++ b/.config/nvim/after/ftplugin/gitrebase.lua
@@ -1,4 +1,19 @@
 vim.keymap.set('n', 'K', function()
-  -- local cword = vim.fn.expand('<cword>')
-  -- require('gitsigns').show_commit(cword, 'aboveleft split')
+  local commit = vim.fn.expand('<cword>')
+
+  local content = nil
+  require('mod.jobs').start({
+    cmd = { 'git', 'show', '--patch', '--stat', commit },
+    stdout_buffered = true,
+    on_stdout = function(_, stdout)
+      content = stdout
+    end,
+    on_exit = function()
+      require('mod.win').popup(
+        'git-show: ' .. commit,
+        content,
+        { buf = { filetype = 'gitcommit' } }
+      )
+    end,
+  })
 end, { desc = 'Lookup commit hash', buffer = 0 })
diff --git a/.config/nvim/after/ftplugin/go.lua b/.config/nvim/after/ftplugin/go.lua
new file mode 100644
index 0000000..ad5c56e
--- /dev/null
+++ b/.config/nvim/after/ftplugin/go.lua
@@ -0,0 +1,32 @@
+vim.api.nvim_create_autocmd({ 'LspAttach' }, {
+  pattern = '*.go',
+  group = vim.api.nvim_create_augroup('lspattach.go', {}),
+  callback = function(args)
+    local client = vim.lsp.get_client_by_id(args.data.client_id)
+    if not client:supports_method('workspace/executeCommand') then
+      return
+    end
+
+    vim.keymap.set('n', '<leader>i', function()
+      local p = vim
+        .system({
+          'sh',
+          '-c',
+          [[go mod graph | awk '{print $2}' | cut -d@ -f1 | sort -u]],
+        })
+        :wait()
+      local opts = vim.split(p.stdout, '\n')
+      vim.ui.select(opts, {}, function(choice)
+        client:exec_cmd({
+          command = 'gopls.add_import',
+          arguments = {
+            {
+              ['ImportPath'] = choice,
+              ['URI'] = vim.uri_from_bufnr(0),
+            },
+          },
+        })
+      end)
+    end, { desc = 'gopls: add import' })
+  end,
+})
diff --git a/.config/nvim/after/ftplugin/help.lua b/.config/nvim/after/ftplugin/help.lua
new file mode 100644
index 0000000..f41256a
--- /dev/null
+++ b/.config/nvim/after/ftplugin/help.lua
@@ -0,0 +1,2 @@
+-- open help in new tab
+vim.cmd.wincmd "T"
diff --git a/.config/nvim/after/ftplugin/janet.lua b/.config/nvim/after/ftplugin/janet.lua
new file mode 100644
index 0000000..a81b2f0
--- /dev/null
+++ b/.config/nvim/after/ftplugin/janet.lua
@@ -0,0 +1,3 @@
+vim.o.expandtab = true
+vim.o.shiftwidth = 2
+vim.o.commentstring = '# %s'
diff --git a/.config/nvim/after/ftplugin/markdown.lua b/.config/nvim/after/ftplugin/markdown.lua
new file mode 100644
index 0000000..538a6da
--- /dev/null
+++ b/.config/nvim/after/ftplugin/markdown.lua
@@ -0,0 +1,9 @@
+vim.opt_local.makeprg =
+  'pandoc -s -f markdown+wikilinks_title_after_pipe+autolink_bare_uris -t html -o /tmp/%.html %'
+
+vim.api.nvim_buf_create_user_command(0, 'OpenWeb', function()
+  local name = vim.fn.expand('%')
+  vim.system({ 'xdg-open', '/tmp/' .. name .. '.html' })
+end, { desc = 'markdown: open pdf' })
+
+vim.keymap.set('n', '<leader>o', '<cmd>OpenWeb<cr>', { desc = 'markdown: open web', buffer = 0 })
diff --git a/.config/nvim/after/ftplugin/todotxt.lua b/.config/nvim/after/ftplugin/todotxt.lua
index 526f29c..d5091b3 100644
--- a/.config/nvim/after/ftplugin/todotxt.lua
+++ b/.config/nvim/after/ftplugin/todotxt.lua
@@ -1 +1,14 @@
 vim.opt_local.textwidth = 0
+
+vim.keymap.set('n', '<m-x>', function()
+  require('mod.utils').edit_current_line(function(lines)
+    -- will only run for the current line
+    return vim.tbl_map(function(ln)
+      if string.sub(ln, 1, 2) == 'x ' then
+        return string.sub(ln, 3, -1)
+      else
+        return 'x ' .. ln
+      end
+    end, lines)
+  end)
+end, { desc = 'Check todo', buffer = 0 })
diff --git a/.config/nvim/after/ftplugin/vala.lua b/.config/nvim/after/ftplugin/vala.lua
new file mode 100644
index 0000000..3cbb257
--- /dev/null
+++ b/.config/nvim/after/ftplugin/vala.lua
@@ -0,0 +1 @@
+vim.o.commentstring = '// %s'
diff --git a/.config/nvim/after/lsp/gopls.lua b/.config/nvim/after/lsp/gopls.lua
index 5262a45..88ad048 100644
--- a/.config/nvim/after/lsp/gopls.lua
+++ b/.config/nvim/after/lsp/gopls.lua
@@ -1,4 +1,5 @@
 return {
+  filetypes = { 'go', 'gomod', 'gowork', 'gotmpl', 'templ' },
   settings = {
     gopls = {
       hints = {
diff --git a/.config/nvim/after/lsp/harper_ls.lua b/.config/nvim/after/lsp/harper_ls.lua
new file mode 100644
index 0000000..3298e30
--- /dev/null
+++ b/.config/nvim/after/lsp/harper_ls.lua
@@ -0,0 +1,11 @@
+return {
+  settings = {
+    ["harper-ls"] = {
+      userDictPath = "~/.config/dict.txt",
+      linters = {
+        SentenceCapitalization = false,
+        -- SpellCheck = false,
+      },
+    },
+  },
+}
diff --git a/.config/nvim/after/lsp/pylsp.lua b/.config/nvim/after/lsp/pylsp.lua
new file mode 100644
index 0000000..6f05ad8
--- /dev/null
+++ b/.config/nvim/after/lsp/pylsp.lua
@@ -0,0 +1,3 @@
+return {
+  -- settings = {},
+}
diff --git a/.config/nvim/after/luasnippets/gitcommit.lua b/.config/nvim/after/luasnippets/gitcommit.lua
new file mode 100644
index 0000000..91c686b
--- /dev/null
+++ b/.config/nvim/after/luasnippets/gitcommit.lua
@@ -0,0 +1,24 @@
+---@diagnostic disable: undefined-global
+return {
+  s({ trig = 'cz', descr = 'conventional commit' }, {
+    c(1, {
+      t('feat'),
+      t('fix'),
+      t('docs'),
+      t('style'),
+      t('refactor'),
+      t('perf'),
+      t('test'),
+      t('chore'),
+    }),
+    c(2, {
+      t(''),
+      sn(nil, {
+        t('('),
+        i(1, 'scope'),
+        t(')'),
+      }),
+    }),
+    t(': '),
+  }),
+}
diff --git a/.config/nvim/after/luasnippets/zig.lua b/.config/nvim/after/luasnippets/zig.lua
new file mode 100644
index 0000000..ab80e82
--- /dev/null
+++ b/.config/nvim/after/luasnippets/zig.lua
@@ -0,0 +1,14 @@
+---@diagnostic disable: undefined-global
+return {
+  s({trig = 'str'}, t("[]const u8")),
+  s({trig = 'test'}, {
+    c(1, {
+      t('a'),
+      t('b'),
+    }),
+    c(2, {
+      t('c'),
+      t('d'),
+    }),
+  }),
+}
diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua
index 5dee508..4f4a22e 100644
--- a/.config/nvim/init.lua
+++ b/.config/nvim/init.lua
@@ -2,6 +2,9 @@ _G.feedkeys = function(keys)
   vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), 'n', true)
 end
 
+-- enable experimental ui2
+require('vim._core.ui2').enable()
+
 require('config.lazy')
 require('config.filetypes')
 require('config.diagnostics')
@@ -11,3 +14,13 @@ require('config.options')
 require('config.statuscolumn')
 require('config.statusline')
 require('config.undo')
+require('config.templates')
+
+-- overwrite ui.open
+vim.ui.open = function(path, opt)
+  if opt and opt.cmd then
+    vim.uv.spawn(cmd, { args = { path } })
+  else
+    vim.uv.spawn('porta', { args = { 'open-uri', path, 'ask' } })
+  end
+end
diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json
index 16e407a..f1f7047 100644
--- a/.config/nvim/lazy-lock.json
+++ b/.config/nvim/lazy-lock.json
@@ -1,32 +1,36 @@
 {
   "LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
   "blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
+  "blink.indent": { "branch": "main", "commit": "2e9c9768affeaf3a48b2dc329eb59763012f79f6" },
   "calcium.nvim": { "branch": "main", "commit": "7a7d3740083fa5669e1a0f25d133705624a23f0d" },
-  "friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
-  "fzf-lua": { "branch": "main", "commit": "9f0432fdd7825ab163520045831a40b6df82ea28" },
+  "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
+  "fishbone.nvim": { "branch": "main", "commit": "0ea8e0f92330a1f57a0a2e66e66223e7ff1f087f" },
+  "friendly-snippets": { "branch": "main", "commit": "b4d01b0fdf3c9a549961c2f9ffe8dc09be166219" },
+  "fzf-lua": { "branch": "main", "commit": "13bb178a299f7493dd64427a58e61904c4b58ecb" },
   "gentoo-syntax": { "branch": "master", "commit": "1b3a81ec62e5c843245dc431be0fe072add7eca8" },
-  "git-dev.nvim": { "branch": "master", "commit": "90c1395ca37a38228e287d0a843835a5a39ce553" },
+  "git-dev.nvim": { "branch": "master", "commit": "727c9526a7fcbf980aa1bb2caf2383c74a543093" },
   "gitgraph.nvim": { "branch": "main", "commit": "c16daa7d7dd597caf9085644c009cfa80b75db8e" },
-  "gitlinker.nvim": { "branch": "master", "commit": "d7adb5e4ba5bab4fd443a3f4c46af3f4c864685d" },
-  "gitsigns.nvim": { "branch": "main", "commit": "0d797daee85366bc242580e352a4f62d67557b84" },
+  "gitlinker.nvim": { "branch": "master", "commit": "a1b74070bbd5e50128190c85b09f1431ea5fbd83" },
+  "gitsigns.nvim": { "branch": "main", "commit": "070a5d7b985546cc57e1fc61e5bc507fecac6045" },
   "guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" },
-  "haunt.nvim": { "branch": "main", "commit": "666d83868bac8b38f1dc765d9da6eca4d92c826f" },
-  "indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
-  "koda.nvim": { "branch": "main", "commit": "a560a332ccc1eb2caacb280a390213bb9f37b3cb" },
+  "haunt.nvim": { "branch": "main", "commit": "c973175dda88236ecda342fd3654b5dc4f6b7764" },
+  "indent-blankline.nvim": { "branch": "master", "commit": "f1e186e44d3b7f9ae918008e2c28ce37c6023d2d" },
+  "koda.nvim": { "branch": "main", "commit": "25a5c98ca2f6bb0b69f5e2654a78f0d7c7e0721d" },
   "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
   "markdown.nvim": { "branch": "master", "commit": "093be911d90de03877f3009de36081c1a6242d51" },
-  "mini.ai": { "branch": "main", "commit": "43eb2074843950a3a25aae56a5f41362ec043bfa" },
-  "mini.align": { "branch": "main", "commit": "6f3c69cd57134ad8fbf1e85dcdc90cb37a8c75c8" },
-  "mini.icons": { "branch": "main", "commit": "7fdae2443a0e2910015ca39ad74b50524ee682d3" },
-  "mini.operators": { "branch": "main", "commit": "fccb5e3ef0cc319b217ed9c7db1a4672dd6b766c" },
-  "mini.surround": { "branch": "main", "commit": "2715e04bea3ec9244f15b421dc5b18c0fe326210" },
+  "mini.ai": { "branch": "main", "commit": "6c39ae7b11abe479fbe1bc81bf00ac6bca7afa07" },
+  "mini.align": { "branch": "main", "commit": "77d7446b9f2452193f13ced9fb73a8b3aa4a4f4d" },
+  "mini.icons": { "branch": "main", "commit": "f642e3ba8c12afbc8f32f6d77ef6597eac56786f" },
+  "mini.operators": { "branch": "main", "commit": "9e0aa164b097c6cb45e59b9371c9347ac06eb822" },
+  "mini.surround": { "branch": "main", "commit": "33a2ebe5df8eb4751828fcba133e18ed0fbbdfab" },
   "none-ls-shellcheck.nvim": { "branch": "main", "commit": "0f84461241e76e376a95fb7391deac82dc3efdbf" },
-  "none-ls.nvim": { "branch": "main", "commit": "7f9301e416533b5d74e2fb3b1ce5059eeaed748b" },
-  "nvim-colorizer.lua": { "branch": "master", "commit": "c8c2c19436d33165ec34fa90c975bc0a739fe3fb" },
-  "nvim-lspconfig": { "branch": "master", "commit": "0203a9608d63eda57679b01e69f33a7b4c34b0d1" },
-  "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
-  "oil.nvim": { "branch": "master", "commit": "0fcc83805ad11cf714a949c98c605ed717e0b83e" },
-  "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
-  "render-markdown": { "branch": "main", "commit": "4ae2f2e8e8c66d070f33cfb57cb6f867e3baf5d9" },
+  "none-ls.nvim": { "branch": "main", "commit": "c4b82bb63b13856ba4d6b971b7aad3bb38fc6fe2" },
+  "nvim-colorizer.lua": { "branch": "master", "commit": "72a05f62c52241bc7441c820eb53946f92b2e6a4" },
+  "nvim-lspconfig": { "branch": "master", "commit": "4d363f93c3581b9212a24f7a830d7590b3f050af" },
+  "nvim-treesitter": { "branch": "main", "commit": "f603a2f4da48728f80257fb5fbb90145fd1dc173" },
+  "oil.nvim": { "branch": "master", "commit": "b73018b75affd13fa38e2fc94ef753b465f770d7" },
+  "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
+  "render-markdown": { "branch": "main", "commit": "640a3ec6d538bad17c328be373c7cad0293d9589" },
+  "satellite.nvim": { "branch": "main", "commit": "87843c9c8f28b54332497302de380a6d94c9e82b" },
   "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
 }
diff --git a/.config/nvim/lua/config/autoall.lua b/.config/nvim/lua/config/autoall.lua
new file mode 100644
index 0000000..6f36182
--- /dev/null
+++ b/.config/nvim/lua/config/autoall.lua
@@ -0,0 +1,35 @@
+-- Autocommands for all files opened in nvim
+local autocmd = vim.api.nvim_create_autocmd
+local autogrp = vim.api.nvim_create_augroup
+
+-- check if the current buffer is empty
+local bufempty = function(bufnr)
+  bufnr = bufnr or 0
+  if vim.api.nvim_buf_get_offset(bufnr, 0) <= 0 then
+    local handle = io.open(vim.api.nvim_buf_get_name(bufnr))
+    if handle == nil then
+      return true
+    end
+    local eof = handle:read(0)
+    handle:close()
+    if eof == nil then
+      return true
+    end
+  end
+  return false
+end
+
+local expand_template = function(name)
+  require('luasnip').snip_expand(require('config.templates')[name])
+  require('luasnip').unlink_current()
+end
+
+autocmd({ 'BufEnter', 'BufNewFile' }, {
+  pattern = 'deb/DEBIAN/changelog_blobs/*.txt',
+  group = autogrp('DebianChangelogTemplate', { clear = true }),
+  callback = function(opt)
+    if bufempty() then
+      return expand_template('debian-changelog')
+    end
+  end,
+})
diff --git a/.config/nvim/lua/config/keymaps.lua b/.config/nvim/lua/config/keymaps.lua
index a93b8fd..3c51041 100644
--- a/.config/nvim/lua/config/keymaps.lua
+++ b/.config/nvim/lua/config/keymaps.lua
@@ -1,7 +1,12 @@
 local map = vim.keymap.set
 
-map('n', 'gb', '<cmd>bnext<cr>', { desc = 'Buffer next' })
-map('n', 'gB', '<cmd>bprevious<cr>', { desc = 'Buffer prev' })
+map('n', 'gb', '<cmd>bnext<cr>', { desc = 'Buf next' })
+map('n', 'gB', '<cmd>bprevious<cr>', { desc = 'Buf prev' })
+
+map('n', '<c-h>', '<cmd>tabprev<cr>', { desc = 'Tab prev' })
+map('n', '<c-j>', '<cmd>bprev<cr>', { desc = 'Buf prev' })
+map('n', '<c-k>', '<cmd>bnext<cr>', { desc = 'Buf next' })
+map('n', '<c-l>', '<cmd>tabnext<cr>', { desc = 'Tab next' })
 
 map('n', '<a-j>', '<cmd>cnext<cr>', { desc = 'Quickfix next' })
 map('n', '<a-k>', '<cmd>cprev<cr>', { desc = 'Quickfix prev' })
@@ -21,8 +26,11 @@ map(
 vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, {
   group = vim.api.nvim_create_augroup('bufclose', {}),
   callback = function()
-    if vim.list_contains({ 'help', 'nofile', 'quickfix' }, vim.bo.buftype) then
-      map('', 'q', '<cmd>close<cr>', { desc = 'Close buffer', buffer = 0 })
+    if
+      (vim.fn.maparg('q', 'n') == '')
+      and vim.list_contains({ 'help', 'nofile', 'nowrite', 'quickfix' }, vim.bo.buftype)
+    then
+      map('n', 'q', '<cmd>close<cr>', { desc = 'Close buffer', buffer = 0 })
     end
   end,
 })
diff --git a/.config/nvim/lua/config/lsp.lua b/.config/nvim/lua/config/lsp.lua
index b168014..5a2bf79 100644
--- a/.config/nvim/lua/config/lsp.lua
+++ b/.config/nvim/lua/config/lsp.lua
@@ -6,6 +6,8 @@ local lsp = vim.lsp
 
 lsp.enable({
   'bashls',
+  'blueprint_ls',
+  'buf_ls',
   'ccls',
   'clangd',
   'gopls',
@@ -14,6 +16,7 @@ lsp.enable({
   'rust_analyzer',
   'superhtml',
   'tinymist',
+  'vala_ls',
   'zk',
   'zls',
 })
@@ -35,7 +38,7 @@ lsp.config('*', {
 vim.api.nvim_create_autocmd({ 'LspAttach' }, {
   group = vim.api.nvim_create_augroup('lspattach', {}),
   callback = function(args)
-    local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
+    local client = vim.lsp.get_client_by_id(args.data.client_id)
 
     -- TODO: what does this do?
     vim.opt_local.omnifunc = 'v:lua.vim.lsp.omnifunc'
diff --git a/.config/nvim/lua/config/options.lua b/.config/nvim/lua/config/options.lua
index 073835f..07a41a3 100644
--- a/.config/nvim/lua/config/options.lua
+++ b/.config/nvim/lua/config/options.lua
@@ -37,3 +37,10 @@ opt.mousescroll = 'ver:1,hor:6'
 
 -- load local vim config (will as to allow it)
 opt.exrc = true
+
+-- increase the auto-scroll window
+opt.scrolloff = 10
+
+-- use osc2 to set terminal title
+opt.title = true
+opt.titlestring = [[nvim: %t%( %M%)%( (%{expand("%:~:h")})%)%a]]
diff --git a/.config/nvim/lua/config/snippets.lua b/.config/nvim/lua/config/snippets.lua
index e0c260e..9cd0632 100644
--- a/.config/nvim/lua/config/snippets.lua
+++ b/.config/nvim/lua/config/snippets.lua
@@ -6,16 +6,24 @@ ls.config.set_config({
   enable_autosnippets = true,
   history = true,
   updateevents = 'TextChanged,TextChangedI',
-  override_builtin = true,
+  exit_roots = false, -- dont exit when reaching last node
   ext_opts = {
     [types.choiceNode] = {
-      active = {
-        virt_text = { { '◂    󰬊 ', 'NonText' } },
+      passive = {
+        virt_text = { { '󰬊 ', 'Conceal' } },
+        virt_text_pos = 'inline',
       },
     },
     [types.insertNode] = {
-      active = {
-        virt_text = { { '◂    󰬐 ', 'NonText' } },
+      passive = {
+        virt_text = { { '󰬐 ', 'Conceal' } },
+        virt_text_pos = 'inline',
+      },
+    },
+    [types.exitNode] = {
+      passive = {
+        virt_text = { { '$', 'Conceal' } },
+        virt_text_pos = 'inline',
       },
     },
   },
@@ -53,27 +61,6 @@ ls.config.set_config({
 require('luasnip.loaders.from_lua').lazy_load()
 require('luasnip.loaders.from_vscode').lazy_load({ include = {} })
 
--- keymaps
-vim.keymap.set({ 'i', 's' }, '<c-j>', function()
-  return vim.snippet.active({ direction = 1 }) and vim.snippet.jump(1)
-end, { silent = true, desc = 'snippet: jump next' })
-
-vim.keymap.set({ 'i', 's' }, '<c-k>', function()
-  return vim.snippet.active({ direction = -1 }) and vim.snippet.jump(-1)
-end, { silent = true, desc = 'snippet: jump prev' })
-
-vim.keymap.set({ 'i', 's' }, '<c-n>', function()
-  if ls.choice_active() then
-    ls.change_choice(1)
-  end
-end, { silent = true, desc = 'snippet: next choice' })
-
-vim.keymap.set({ 'i', 's' }, '<c-p>', function()
-  if ls.choice_active() then
-    ls.change_choice(-1)
-  end
-end, { silent = true, desc = 'snippet: prev choice' })
-
 -- usercmds
 vim.api.nvim_create_user_command('EditSnippets', require('luasnip.loaders').edit_snippet_files, {})
 
@@ -105,36 +92,23 @@ vim.api.nvim_create_user_command('EditSnippets', require('luasnip.loaders').edit
 vim.api.nvim_create_autocmd('ModeChanged', {
   pattern = '*',
   callback = function()
-    if ((vim.v.event.old_mode == 's' and vim.v.event.new_mode == 'n') or vim.v.event.old_mode == 'i')
-        and ls.session.current_nodes[vim.api.nvim_get_current_buf()]
-        and not ls.session.jump_active
-        and not require("blink.cmp").is_visible()
+    if
+      ((vim.v.event.old_mode == 's' or vim.v.event.old_mode == 'i') and vim.v.event.new_mode == 'n')
+      and ls.session.current_nodes[vim.api.nvim_get_current_buf()]
+      and not ls.session.jump_active
+      and not require('blink.cmp').is_visible()
     then
-      ls.unlink_current()
+      vim.schedule(ls.unlink_current)
     end
-  end
+  end,
 })
 
--- attach to builtin snippet hooks
-vim.snippet.expand = ls.lsp_expand
-vim.snippet.active = function(filter)
-  filter = filter or {}
-  filter.direction = filter.direction or 1
-  if filter.direction == 1 then
-    return ls.expand_or_jumpable()
-  else
-    return ls.jumpable(filter.direction)
-  end
-end
-vim.snippet.jump = function(direction)
-  if direction == 1 then
-    if ls.expandable() then
-      return ls.expand_or_jump()
-    else
-      return ls.jumpable(1) and ls.jump(1)
-    end
-  else
-    return ls.jumpable(-1) and ls.jump(-1)
-  end
-end
-vim.snippet.stop = ls.unlink_current
+-- expand/jump keymaps handled by blink
+
+vim.keymap.set({ 'i', 's' }, '<c-n>', function()
+  return ls.choice_active() and ls.change_choice(1)
+end, { silent = true, desc = 'luasnip: next choice' })
+
+vim.keymap.set({ 'i', 's' }, '<c-p>', function()
+  return ls.choice_active() and ls.change_choice(-1)
+end, { silent = true, desc = 'luasnip: prev choice' })
diff --git a/.config/nvim/lua/config/templates.lua b/.config/nvim/lua/config/templates.lua
new file mode 100644
index 0000000..a01efa1
--- /dev/null
+++ b/.config/nvim/lua/config/templates.lua
@@ -0,0 +1,53 @@
+-- template snippets
+require('luasnip').setup_snip_env()
+local templates = {}
+
+-- check if the current buffer is empty
+local bufempty = function(bufnr)
+  bufnr = bufnr or 0
+  if vim.api.nvim_buf_get_offset(bufnr, 0) <= 0 then
+    local handle = io.open(vim.api.nvim_buf_get_name(bufnr))
+    if handle == nil then
+      return true
+    end
+    local eof = handle:read(0)
+    handle:close()
+    if eof == nil then
+      return true
+    end
+  end
+  return false
+end
+
+local register_template = function(pattern, snip)
+  templates[pattern] = snip
+
+  vim.api.nvim_create_autocmd({ 'BufEnter', 'BufNewFile' }, {
+    pattern = pattern,
+    group = vim.api.nvim_create_augroup('Template' .. pattern, { clear = true }),
+    callback = function(opt)
+      if bufempty() then
+        require('luasnip').snip_expand(templates[pattern])
+        require('luasnip').unlink_current()
+      end
+    end,
+  })
+end
+
+register_template(
+  'deb/DEBIAN/changelog_blobs/*.txt',
+  s(
+    'debian-changelog',
+    fmt(
+      [[
+ukp-agent (${VERSION}) unstable; urgency=medium
+
+  * feat: <>
+]],
+      { i(0) },
+      {
+        delimiters = '<>',
+      }
+    )
+  )
+)
diff --git a/.config/nvim/lua/mod/gh.lua b/.config/nvim/lua/mod/gh.lua
new file mode 100644
index 0000000..1f8a267
--- /dev/null
+++ b/.config/nvim/lua/mod/gh.lua
@@ -0,0 +1,86 @@
+local M = {}
+
+-- gh api \
+--   --method POST \
+--   -H "Accept: application/vnd.github+json" \
+--   -H "X-GitHub-Api-Version: 2022-11-28" \
+--   /repos/OWNER/REPO/pulls/PULL_NUMBER/comments \
+
+function M.pr_comment(repo, pull, commit_id, comment)
+  vim.print(vim.inspect(comment))
+  vim.print(vim.inspect({
+    'gh',
+    'api',
+    '--method',
+    'POST',
+    string.format('/repos/%s/pulls/%d/comments', repo, pull),
+    '-f',
+    'body=' .. comment.body,
+    '-f',
+    'commit_id=' .. commit_id,
+    '-f',
+    'path=' .. comment.path,
+    '-f',
+    'side=RIGHT',
+    '-f',
+    'line=' .. comment.line,
+  }))
+end
+
+function M.pr_view()
+  local p = vim
+    .system({
+      'gh',
+      'pr',
+      'view',
+      '--json',
+      'number,headRepository,headRefOid',
+      '--jq',
+      '{head:.headRefOid,repo:.headRepository.nameWithOwner,pull:.number}',
+    })
+    :wait()
+  return vim.json.decode(p.stdout)
+end
+
+function M.submit_review()
+  local gitroot = vim.fs.root(0, { '.git' })
+  if not gitroot then
+    vim.notify('Not a git repository.', vim.log.levels.WARN)
+    return
+  end
+
+  local review = { comments = {} }
+
+  local marks = require('haunt.api').get_bookmarks()
+  for _, mark in ipairs(marks) do
+    -- nil for bookmarks outside the current repo
+    local path = vim.fs.relpath(gitroot, mark.file)
+    if path then
+      review.comments[#review.comments] = {
+        path = path,
+        line = mark.line,
+        body = mark.note,
+      }
+    end
+  end
+
+  review.pr = M.pr_view()
+
+  vim.print(vim.inspect(review))
+
+  vim.ui.select({
+    'OK',
+    'Cancel',
+  }, {
+    prompt = 'Submit review?',
+  }, function(choice)
+    vim.print(choice)
+    if choice == 'OK' then
+      for _, comment in ipairs(review.comments) do
+        M.pr_comment(pr.repo, pr.pull, pr.head, comment)
+      end
+    end
+  end)
+end
+
+return M
diff --git a/.config/nvim/lua/mod/git.lua b/.config/nvim/lua/mod/git.lua
new file mode 100644
index 0000000..3e36e61
--- /dev/null
+++ b/.config/nvim/lua/mod/git.lua
@@ -0,0 +1,23 @@
+local M = {}
+
+function M.commit()
+  local commit = vim.fn.expand('<cword>') -- XXX: can we do better?
+  vim.cmd.DiffviewOpen(commit .. '^!')
+end
+
+function M.log()
+  local bufnr = vim.api.nvim_create_buf(true, true)
+  assert(bufnr ~= 0, 'Failed to create buffer')
+
+  vim.bo[bufnr].bufhidden = 'wipe'
+  vim.bo[bufnr].modifiable = true
+  vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, content)
+  vim.bo[bufnr].modifiable = false
+
+  vim.keymap.set('n', 'q', '<cmd>quit!<cr>', { silent = true, buffer = bufnr })
+  vim.keymap.set('n', 'K', M.commit, { silent = true, buffer = bufnr })
+
+  vim.api.nvim_win_set_buf(0, bufnr)
+end
+
+return M
diff --git a/.config/nvim/lua/mod/jobs.lua b/.config/nvim/lua/mod/jobs.lua
new file mode 100644
index 0000000..a4dea6f
--- /dev/null
+++ b/.config/nvim/lua/mod/jobs.lua
@@ -0,0 +1,68 @@
+-- this plugin provides an API to register and recall jobs.
+
+local M = {
+  _jobs = {},
+}
+
+local function format_jobcmd(cmd)
+  if type(cmd) == 'table' then
+    return table.concat(cmd, ' ')
+  end
+  return cmd
+end
+
+local function register(id, cmd)
+  table.insert(M._jobs, id, cmd)
+  vim.notify_once(string.format('started job %d (%s)', id, format_jobcmd(M._jobs[id])))
+end
+
+local function deregister(id)
+  local cmd = M._jobs[id]
+  table.insert(M._jobs, id, nil)
+  vim.notify_once(string.format('stopped job %d (%s)', id, format_jobcmd(cmd)))
+end
+
+function M.start(opts)
+  local previous_on_exit = opts.on_exit or function() end
+  opts.on_exit = function(id, exitcode, event)
+    deregister(id)
+    return previous_on_exit(id, exitcode, event)
+  end
+
+  local id = vim.fn.jobstart(opts.cmd, opts)
+
+  register(id, opts.cmd)
+end
+
+function M.select()
+  if #M._jobs == 0 then
+    vim.notify('No running jobs', vim.log.levels.WARN)
+    return
+  end
+
+  local function provider(cb)
+    for id, cmd in pairs(M._jobs) do
+      cb(string.format('[%d] %s', id, format_jobcmd(cmd)))
+    end
+    cb(nil)
+  end
+  local function parse_jobid(entry)
+    return tonumber(string.match(entry, '%[([0-9]*)%]'))
+  end
+
+  require('fzf-lua').fzf_exec(provider, {
+    prompt = 'Jobs❯',
+    actions = {
+      ['ctrl-x'] = {
+        fn = function(selected)
+          for _, entry in pairs(selected) do
+            vim.fn.jobstop(parse_jobid(entry))
+          end
+        end,
+        reload = true,
+      },
+    },
+  })
+end
+
+return M
diff --git a/.config/nvim/lua/mod/termopen.lua b/.config/nvim/lua/mod/termopen.lua
new file mode 100644
index 0000000..f8117f1
--- /dev/null
+++ b/.config/nvim/lua/mod/termopen.lua
@@ -0,0 +1,102 @@
+-- this plugin provides a popup terminal.
+local jobs = require('mod.jobs')
+
+local M = {}
+
+function M.termopen(opts)
+  opts = opts or {}
+
+  -- setup the buffer
+  local buf = vim.api.nvim_create_buf(false, true)
+  vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = buf })
+  vim.api.nvim_set_option_value('modifiable', false, { buf = buf })
+
+  -- setup keymappings
+  vim.keymap.set('n', 'q', '<cmd>close<cr>', { buffer = buf })
+  vim.keymap.set('n', '<esc>', '<cmd>close<cr>', { buffer = buf })
+
+  -- setup the window
+  -- local w = math.ceil(vim.o.columns * 1.0)
+  -- local h = math.ceil(vim.o.lines * 0.4)
+  local win = vim.api.nvim_open_win(buf, true, {
+    -- style = 'minimal',
+    -- relative = 'editor',
+    -- width = w,
+    -- height = h,
+    -- row = math.ceil(vim.o.lines - h),
+    -- col = vim.o.columns,
+    -- border = { ' ', '─', ' ', ' ', ' ', '─', ' ', ' ' },
+    split = 'below',
+    win = -1,
+  })
+  vim.api.nvim_set_current_win(win)
+
+  -- spawn the command
+  if #opts.cmd == 0 then
+    opts.cmd = { vim.env.SHELL }
+  end
+
+  -- vim.notify('termopen: running ' .. vim.inspect(opts.cmd), vim.log.levels.DEBUG)
+
+  jobs.start(vim.tbl_deep_extend('error', opts, {
+    term = true,
+    env = {
+      -- we need this to avoid opening in the term buffer
+      NVR_PRE_CMD = 'close',
+    },
+    on_exit = function()
+      -- if vim.api.nvim_win_is_valid(win) then
+      --   vim.api.nvim_win_close(win, true)
+      -- end
+    end,
+  }))
+
+  -- put into insert mode
+  vim.cmd.startinsert()
+end
+
+-- wrappers convenience
+
+function M.term(a)
+  local cwd = nil
+  local args = a.fargs
+
+  if #args > 0 then
+    if args[1]:sub(0, 4) == 'cwd=' then
+      cwd = args[1]:sub(5)
+      args = vim.list_slice(args, 2)
+    end
+  end
+
+  for i = 0, #args do
+    if args[i] == '%' then
+      args[i] = vim.fn.expand(args[i])
+    end
+  end
+
+  M.termopen({ cmd = args, cwd = cwd })
+end
+
+function M.make(a)
+  if vim.opt_local.makeprg == nil then
+    vim.ui.input({ prompt = 'Enter makeprg: ' }, function(input)
+      vim.opt_local.makeprg = input
+    end)
+  end
+
+  local makeprg = vim.opt_local.makeprg:get()
+  if string.match(makeprg, [[%$%*]]) == nil then
+    makeprg = makeprg .. ' ' .. a.args
+  end
+
+  local cmd = vim.split(makeprg, ' ')
+  cmd = vim.tbl_map(function(p)
+    p, _ = string.gsub(p, [[%$%*]], a.args)
+    p, _ = vim.fn.expand(p)
+    return p
+  end, cmd)
+
+  M.termopen({ cmd = cmd })
+end
+
+return M
diff --git a/.config/nvim/lua/mod/utils.lua b/.config/nvim/lua/mod/utils.lua
new file mode 100644
index 0000000..1f3cd8b
--- /dev/null
+++ b/.config/nvim/lua/mod/utils.lua
@@ -0,0 +1,15 @@
+local M = {}
+
+function M.buf_edit_lines(buf, start_row, end_row, callback)
+  local lines = {}
+  lines = vim.api.nvim_buf_get_lines(buf, start_row, end_row, true)
+  lines = callback(lines)
+  return vim.api.nvim_buf_set_lines(buf, start_row, end_row, true, lines)
+end
+
+function M.edit_current_line(callback)
+  local row, _ = unpack(vim.api.nvim_win_get_cursor(0))
+  return M.buf_edit_lines(0, row - 1, row, callback)
+end
+
+return M
diff --git a/.config/nvim/lua/mod/win.lua b/.config/nvim/lua/mod/win.lua
new file mode 100644
index 0000000..e5c5f71
--- /dev/null
+++ b/.config/nvim/lua/mod/win.lua
@@ -0,0 +1,110 @@
+local M = {}
+
+function M.open(name, content, opts)
+  if name == '' then
+    return
+  end
+  if #content == 0 then
+    return
+  end
+
+  local opts = opts or {}
+  local autoclose = opts.autoclose or false
+  local buf_opts = opts.buf or { buftype = '' }
+
+  local win_opts = opts.win or false
+  -- profiles
+  if type(win_opts) == 'table' and type(win_opts[1] == 'string') then
+    local profile = win_opts[1]
+    local overwrites = unpack(win_opts, 2)
+    if profile == 'emplace' then
+      win_opts = false
+    elseif profile == 'smallsplit' then
+      win_opts = {
+        split = 'above',
+        win = 0,
+        height = 20,
+      }
+    elseif profile == 'popup' then
+      local winh = vim.api.nvim_win_get_height(0)
+      local winw = vim.api.nvim_win_get_width(0)
+      local winsize = 0.5
+      win_opts = {
+        relative = 'win',
+        height = math.floor(winh * winsize),
+        width = math.floor(winw * winsize),
+        row = math.floor((winh * 0.5) - (0.5 * (winh * winsize))),
+        col = math.floor((winw * 0.5) - (0.5 * (winw * winsize))),
+        style = 'minimal',
+        border = 'single',
+        zindex = 99,
+        title = name,
+      }
+    end
+    if overwrites then
+      for k, v in pairs(overwrites) do
+        win_opts[k] = v
+      end
+    end
+  end
+
+  local bufnr = vim.api.nvim_create_buf(false, true)
+  assert(bufnr ~= 0, 'Failed to create buffer')
+
+  vim.bo[bufnr].modifiable = true
+  vim.api.nvim_buf_set_name(bufnr, name)
+  vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, content)
+  for k, v in pairs(buf_opts) do
+    vim.bo[bufnr][k] = v
+  end
+
+  -- without win_opts (by default) replace current buffer
+  local winid = 0
+  if win_opts then
+    winid = vim.api.nvim_open_win(0, not autoclose, win_opts)
+    assert(winid ~= 0, 'Failed to create window')
+  end
+
+  vim.keymap.set('n', 'q', '<cmd>quit!<cr>', { silent = true, buffer = bufnr })
+
+  if autoclose then
+    local group = 'modwin_' .. vim.base64.encode(vim.uv.random(16))
+    local group_id = vim.api.nvim_create_augroup(group, {})
+
+    vim.api.nvim_create_autocmd('WinClosed', {
+      pattern = tostring(winid),
+      group = group_id,
+      callback = function()
+        vim.api.nvim_create_augroup(group, {})
+        M.winid = nil
+      end,
+    })
+    vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
+      group = group_id,
+      callback = function()
+        vim.api.nvim_create_augroup(group, {})
+        pcall(vim.api.nvim_win_close, winid, true)
+      end,
+    })
+  end
+
+  vim.api.nvim_win_set_buf(winid, bufnr)
+  return { bufnr, winid }
+end
+
+function M.popup(name, content, opts)
+  local defaults = {
+    autoclose = true,
+    buf = {
+      bufhidden = 'wipe',
+      buftype = 'nofile',
+      modifiable = false,
+    },
+    win = { 'popup' },
+  }
+  opts = opts or {}
+  opts = vim.tbl_deep_extend('force', defaults, opts)
+  M.open(name, content, opts)
+end
+
+return M
diff --git a/.config/nvim/lua/plugins/blink.lua b/.config/nvim/lua/plugins/blink.lua
index 1122bcc..cefef4a 100644
--- a/.config/nvim/lua/plugins/blink.lua
+++ b/.config/nvim/lua/plugins/blink.lua
@@ -1,6 +1,7 @@
 return {
   {
-    'saghen/blink.cmp',
+    enabled = true,
+    'https://github.com/saghen/blink.cmp.git',
     version = '1.*',
 
     lazy = false,
@@ -25,7 +26,7 @@ return {
       -- TODO:
       -- 'prefer_rust_with_warning', requires nightly rust, due to:
       -- https://github.com/rust-lang/rust/issues/86656
-      fuzzy = { 
+      fuzzy = {
         implementation = 'lua',
         sorts = {
           function(a, b)
@@ -35,11 +36,13 @@ return {
             }
             local a_priority = prio[a.source_id] or 0
             local b_priority = prio[b.source_id] or 0
-            if a_priority ~= b_priority then return a_priority > b_priority end
+            if a_priority ~= b_priority then
+              return a_priority > b_priority
+            end
           end,
           -- defaults
           'score',
-          'sort_text'
+          'sort_text',
         },
       },
 
@@ -74,6 +77,8 @@ return {
         window = { scrollbar = false }, -- BUG: weird artifacts
       },
 
+      snippets = { preset = 'luasnip' },
+
       keymap = {
         ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
         ['<C-e>'] = { 'cancel', 'fallback' },
@@ -87,8 +92,14 @@ return {
         ['<C-k>'] = { 'snippet_backward', 'fallback' },
         -- ['<C-l>'] = { 'show', 'hide', 'fallback' },
       },
-
-      snippets = { preset = 'luasnip' },
+    },
+  },
+  {
+    'https://github.com/saghen/blink.indent.git',
+    opts = {
+      scope = {
+        highlights = { 'BlinkIndentScope' },
+      },
     },
   },
 }
diff --git a/.config/nvim/lua/plugins/calcium.lua b/.config/nvim/lua/plugins/calcium.lua
index f0ab78c..7ff1ba0 100644
--- a/.config/nvim/lua/plugins/calcium.lua
+++ b/.config/nvim/lua/plugins/calcium.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'necrom4/calcium.nvim',
     cmd = {'Calcium'},
     opts = {
diff --git a/.config/nvim/lua/plugins/colorizer.lua b/.config/nvim/lua/plugins/colorizer.lua
index c949058..0b69d5a 100644
--- a/.config/nvim/lua/plugins/colorizer.lua
+++ b/.config/nvim/lua/plugins/colorizer.lua
@@ -1,8 +1,9 @@
 return {
   {
-    'NvChad/nvim-colorizer.lua',
+    enabled = true,
+    'https://github.com/NvChad/nvim-colorizer.lua.git',
     -- 'norcalli/nvim-colorizer.lua',
-    lazy = false,
+    lazy = true,
     opts = {
       options = {
         display = {
diff --git a/.config/nvim/lua/plugins/colors.lua b/.config/nvim/lua/plugins/colors.lua
new file mode 100644
index 0000000..764abfd
--- /dev/null
+++ b/.config/nvim/lua/plugins/colors.lua
@@ -0,0 +1,12 @@
+return {
+  {
+    enabled = true,
+    'https://github.com/oskarnurm/koda.nvim',
+    lazy = false,
+    priority = 1000,
+    config = function()
+      vim.opt.termguicolors = true
+      vim.cmd('colorscheme koda')
+    end,
+  }
+}
diff --git a/.config/nvim/lua/plugins/diff.lua b/.config/nvim/lua/plugins/diff.lua
new file mode 100644
index 0000000..d758522
--- /dev/null
+++ b/.config/nvim/lua/plugins/diff.lua
@@ -0,0 +1,47 @@
+return {
+  {
+    enabled = true,
+    'https://github.com/sindrets/diffview.nvim',
+    cmd = {
+      'DiffviewOpen',
+      'DiffviewFileHistory',
+    },
+    init = function()
+      vim.keymap.set(
+        'n',
+        '<leader>g-',
+        vim.cmd.DiffviewFileHistory,
+        { desc = 'DiffviewFileHistory' }
+      )
+
+      vim.api.nvim_create_autocmd('Filetype', {
+        pattern = { 'git', 'gitcommit', 'gitrebase' },
+        callback = function()
+          vim.keymap.set('n', 'K', function()
+            vim.cmd.DiffviewOpen(vim.fn.expand('<cword>') .. '^!')
+          end, { buffer = 0, desc = 'DiffviewOpen' })
+        end,
+      })
+    end,
+    config = function()
+      vim.api.nvim_create_autocmd('User', {
+        pattern = { 'DiffviewViewEnter' },
+        callback = function()
+          vim.keymap.set('n', 'q', '<cmd>DiffviewClose<cr>', { buffer = 0 })
+          vim.keymap.set(
+            'n',
+            '<a-j>',
+            require('diffview.actions').select_next_entry,
+            { buffer = 0 }
+          )
+          vim.keymap.set(
+            'n',
+            '<a-k>',
+            require('diffview.actions').select_prev_entry,
+            { buffer = 0 }
+          )
+        end,
+      })
+    end,
+  },
+}
diff --git a/.config/nvim/lua/plugins/fzf.lua b/.config/nvim/lua/plugins/fzf.lua
index 73246a8..1a25c0d 100644
--- a/.config/nvim/lua/plugins/fzf.lua
+++ b/.config/nvim/lua/plugins/fzf.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'ibhagwan/fzf-lua',
     dependencies = {
       'echasnovski/mini.icons',
@@ -13,10 +14,12 @@ return {
         { 'default' },
         -- OPTIONS
         winopts = {
+          backdrop = 0,
           split = 'belowright 20new',
           border = 'none',
           preview = {
             border = 'none',
+            hidden = true,
           },
           -- on_create = function() end,
         },
@@ -27,11 +30,12 @@ return {
             ['ctrl-p'] = 'up',
           },
         },
-        -- actions = {
-        --   files = {
-        --     ['ctrl-q'] = { fn = fzf.actions.file_sel_to_qf },
-        --   },
-        -- },
+        actions = {
+          files = {
+            true,
+            ['ctrl-q'] = { fn = fzf.actions.file_sel_to_qf, prefix = 'select-all' },
+          },
+        },
         fzf_colors = true,
         -- PICKER CONFIG
         files = {
@@ -39,7 +43,7 @@ return {
         },
         marks = {
           marks = '[a-zA-Z]',
-        }
+        },
       })
     end,
     keys = {
@@ -48,11 +52,26 @@ return {
       { mode = 'n', ';b', '<cmd>FzfLua buffers<cr>', desc = 'fzf: buffers' },
       { mode = 'n', ';f', '<cmd>FzfLua files<cr>', desc = 'fzf: find_files' },
       { mode = 'n', ';g', '<cmd>FzfLua live_grep<cr>', desc = 'fzf: live_grep' },
-      { mode = 'n', ';d', '<cmd>FzfLua diagnostics_workspace<cr>', desc = 'fzf: workspace diagnostics' },
-      { mode = 'n', ';D', '<cmd>FzfLua diagnostics_document<cr>', desc = 'fzf: document diagnostics' },
+      {
+        mode = 'n',
+        ';D',
+        '<cmd>FzfLua diagnostics_workspace sort=true<cr>',
+        desc = 'fzf: workspace diagnostics',
+      },
+      {
+        mode = 'n',
+        ';d',
+        '<cmd>FzfLua diagnostics_document sort=true<cr>',
+        desc = 'fzf: document diagnostics',
+      },
       { mode = 'n', ';h', '<cmd>FzfLua helptags<cr>', desc = 'fzf: help' },
       { mode = 'n', ';s', '<cmd>FzfLua lsp_document_symbols<cr>', desc = 'fzf: document symbols' },
-      { mode = 'n', ';w', '<cmd>FzfLua lsp_live_workspace_symbols<cr>', desc = 'fzf: workspace symbols' },
+      {
+        mode = 'n',
+        ';w',
+        '<cmd>FzfLua lsp_live_workspace_symbols<cr>',
+        desc = 'fzf: workspace symbols',
+      },
       { mode = 'n', ';m', '<cmd>FzfLua marks<cr>', desc = 'fzf: marks' },
       {
         mode = 'n',
@@ -62,12 +81,60 @@ return {
         end,
         desc = 'fzf: vim_config',
       },
-      -- {
-      --   mode = 'n',
-      --   ';;t',
-      --   '<cmd>Telescope<cr>',
-      --   desc = 'telescope: select (interactive)',
-      -- },
+      {
+        mode = 'n',
+        ';z',
+        function()
+          -- register one-time autocmd to open changed directory with oil
+          vim.api.nvim_create_autocmd('DirChanged', {
+            pattern = '*',
+            callback = vim.schedule_wrap(function(args)
+              vim.cmd.Oil(args.file)
+            end),
+            once = true,
+          })
+          require('fzf-lua').zoxide()
+        end,
+        desc = 'fzf: zoxide',
+      },
+      {
+        mode = 'n',
+        ';Z',
+        function()
+          require('fzf-lua').live_grep({
+            cwd = vim.env.ZK_NOTEBOOK_DIR,
+            actions = {
+              ['ctrl-c'] = function(selected, opts)
+                local utils = require('fzf-lua.utils')
+
+                local title = opts.last_query
+                local p = vim
+                  .system({ 'zk', 'new', '--dry-run', '--no-input', '--title', title }, { text = true })
+                  :wait()
+                if not p.code == 0 then
+                  return
+                end
+                utils.info(vim.inspect(p.stdout))
+
+                local note_path = vim.trim(p.stderr)
+                local note_body = vim.split(p.stdout, '\n')
+                require('mod.win').open(note_path, note_body, {
+                  buf = { filetype = 'markdown' },
+                })
+              end,
+            },
+          })
+        end,
+        desc = 'fzf: zettelkasten',
+      },
+      {
+        mode = 'n',
+        ';T',
+        function()
+          require('fzf-lua').live_grep({ regex = '(TODO|XXX|NOTE|HACK):' })
+        end,
+        desc = 'fzf: zettelkasten',
+      },
       {
         mode = 'n',
         ';;f',
@@ -78,7 +145,6 @@ return {
           else
             dir = vim.fn.expand('%:p:h')
           end
-          -- require('fzf-lua').files({ cwd = dir })
           _G.feedkeys(':Fzf files cwd=' .. dir)
         end,
         desc = 'fzf: files (interactive)',
diff --git a/.config/nvim/lua/plugins/gentoo.lua b/.config/nvim/lua/plugins/gentoo.lua
index 15ef3aa..9af7571 100644
--- a/.config/nvim/lua/plugins/gentoo.lua
+++ b/.config/nvim/lua/plugins/gentoo.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'https://github.com/gentoo/gentoo-syntax',
     lazy = false,
     init = function()
diff --git a/.config/nvim/lua/plugins/git.lua b/.config/nvim/lua/plugins/git.lua
index d1e3f7b..7f1db6d 100644
--- a/.config/nvim/lua/plugins/git.lua
+++ b/.config/nvim/lua/plugins/git.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'lewis6991/gitsigns.nvim',
     lazy = false,
     opts = {
@@ -46,6 +47,7 @@ return {
   },
 
   {
+    enabled = true,
     'linrongbin16/gitlinker.nvim',
     cmd = { 'GitLink' },
     dependencies = { 'nvim-lua/plenary.nvim' },
@@ -54,6 +56,7 @@ return {
     },
     keys = {
       {
+        mode = { 'n', 'x' },
         '<leader>gl',
         vim.cmd.GitLink,
         desc = 'GitLinker: clipboard',
@@ -62,11 +65,13 @@ return {
   },
 
   {
-    enabled = false,
+    enabled = true,
     'moyiz/git-dev.nvim',
     lazy = true,
     cmd = { 'GitDevOpen', 'GitDevCleanAll' },
     opts = {
+      pickers = { type = 'uiselect' },
+      ephemeral = false,
       read_only = false,
     },
   },
diff --git a/.config/nvim/lua/plugins/haunt.lua b/.config/nvim/lua/plugins/haunt.lua
new file mode 100644
index 0000000..aa24cec
--- /dev/null
+++ b/.config/nvim/lua/plugins/haunt.lua
@@ -0,0 +1,47 @@
+return {
+  {
+    enabled = true,
+    'https://github.com/TheNoeTrevino/haunt.nvim',
+    lazy = true,
+    opts = {
+      sign = '',
+      -- sign_hl = nil,
+      -- line_hl = nil,
+      -- virt_text_hl = nil,
+      data_dir = nil,
+      annotation_prefix = '  ',
+      -- virt_text_pos = 'eol',
+      virt_text_pos = 'above',
+      picker_keys = {
+        delete = { key = 'ctrl-x' },
+        edit_annotation = { key = 'ctrl-e' },
+      },
+    },
+    cmd = {
+      'HauntAnnoate',
+      'HauntDelete',
+      'HauntList',
+      'HauntNext',
+      'HauntPrev',
+      'HauntQfAll',
+    },
+    keys = {
+      { '[h', '<cmd>HauntPrev<cr>', desc = 'haunt: prev annotation', silent = true },
+      { ']h', '<cmd>HauntNext<cr>', desc = 'haunt: next annotation', silent = true },
+      { '<leader>h', '<nop>', desc = 'haunt' },
+      { '<leader>ha', '<cmd>HauntAnnotate<cr>', desc = 'haunt: annotate', silent = true },
+      { '<leader>ha', '<cmd>HauntAnnotate<cr>', desc = 'haunt: annotate', silent = true },
+      { '<leader>hd', '<cmd>HauntDelete<cr>', desc = 'haunt: delete', silent = true },
+      { '<leader>hl', '<cmd>HauntList<cr>', desc = 'haunt: list', silent = true },
+      { '<leader>hf', '<cmd>HauntQfAll<cr>', desc = 'haunt: quickfix', silent = true },
+      {
+        '<leader>ht',
+        function()
+          require('haunt.api').toggle_all_lines()
+        end,
+        desc = 'haunt: toggle all',
+        silent = true,
+      },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/icons.lua b/.config/nvim/lua/plugins/icons.lua
index 9a597ab..339ad12 100644
--- a/.config/nvim/lua/plugins/icons.lua
+++ b/.config/nvim/lua/plugins/icons.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'echasnovski/mini.icons',
     version = false,
     events = { 'VeryLazy' },
diff --git a/.config/nvim/lua/plugins/indent.lua b/.config/nvim/lua/plugins/indent.lua
index e247c5a..d197a43 100644
--- a/.config/nvim/lua/plugins/indent.lua
+++ b/.config/nvim/lua/plugins/indent.lua
@@ -1,6 +1,7 @@
 return {
   -- show indents using virtual text
   {
+    enabled = false,
     'lukas-reineke/indent-blankline.nvim',
     lazy = false,
     main = 'ibl',
diff --git a/.config/nvim/lua/plugins/lsp.lua b/.config/nvim/lua/plugins/lsp.lua
index 311f1bc..306953c 100644
--- a/.config/nvim/lua/plugins/lsp.lua
+++ b/.config/nvim/lua/plugins/lsp.lua
@@ -1,14 +1,22 @@
 return {
   {
+    enabled = true,
     'neovim/nvim-lspconfig',
     version = '^2.0',
   },
   {
+    enabled = true,
     'nvimtools/none-ls.nvim',
     dependencies = {
       'nvim-lua/plenary.nvim',
       'gbprod/none-ls-shellcheck.nvim',
     },
+    ft = {
+      'sh',
+      'bash',
+      'go',
+      'lua',
+    },
     config = function()
       local nls = require('null-ls')
       nls.setup({
@@ -18,10 +26,25 @@ return {
 
           nls.builtins.formatting.stylua,
           nls.builtins.formatting.shfmt,
-          nls.builtins.formatting.buf,
 
-          require("none-ls-shellcheck.diagnostics"),
-          require("none-ls-shellcheck.code_actions"),
+          require('none-ls-shellcheck.diagnostics'),
+          require('none-ls-shellcheck.code_actions'),
+
+          nls.builtins.diagnostics.golangci_lint.with({
+            command = 'task',
+            args = {
+              '--offline',
+              '--silent',
+              '--exit-code',
+              '--failfast',
+              '--output=interleaved',
+              'go:lint',
+              '--',
+              '--show-stats=false',
+              '--output.json.path=stdout',
+              '--path-mode=abs',
+            },
+          }),
         },
       })
     end,
diff --git a/.config/nvim/lua/plugins/markdown.lua b/.config/nvim/lua/plugins/markdown.lua
index efe7019..a02cb67 100644
--- a/.config/nvim/lua/plugins/markdown.lua
+++ b/.config/nvim/lua/plugins/markdown.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     -- handy markdown commands
     'tadmccorkle/markdown.nvim',
     ft = { 'markdown' },
@@ -17,6 +18,7 @@ return {
     },
   },
   {
+    enabled = true,
     -- pretty markdown
     'MeanderingProgrammer/markdown.nvim',
     name = 'render-markdown', -- needed if used together with markdown.nvim above
diff --git a/.config/nvim/lua/plugins/mini.lua b/.config/nvim/lua/plugins/mini.lua
index fb4c9a6..fdf4a0e 100644
--- a/.config/nvim/lua/plugins/mini.lua
+++ b/.config/nvim/lua/plugins/mini.lua
@@ -1,23 +1,27 @@
 return {
   {
+    enabled = true,
     'echasnovski/mini.align',
     version = false,
     lazy = false,
     opts = {},
   },
   {
+    enabled = true,
     'echasnovski/mini.ai',
     version = false,
     lazy = false,
     opts = {},
   },
   {
+    enabled = true,
     'echasnovski/mini.surround',
     version = false,
     lazy = false,
     opts = {},
   },
   {
+    enabled = true,
     'echasnovski/mini.operators',
     version = false,
     lazy = false,
diff --git a/.config/nvim/lua/plugins/minimap.lua b/.config/nvim/lua/plugins/minimap.lua
new file mode 100644
index 0000000..99e9ec7
--- /dev/null
+++ b/.config/nvim/lua/plugins/minimap.lua
@@ -0,0 +1,13 @@
+return {
+  {
+    enabled = false,
+    'alchezar/fishbone.nvim',
+    event = 'VeryLazy',
+    opts = {},
+  },
+  {
+    enabled = false,
+    'https://github.com/lewis6991/satellite.nvim',
+    opts = {},
+  }
+}
diff --git a/.config/nvim/lua/plugins/oil.lua b/.config/nvim/lua/plugins/oil.lua
index 401fe8b..d21b041 100644
--- a/.config/nvim/lua/plugins/oil.lua
+++ b/.config/nvim/lua/plugins/oil.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'stevearc/oil.nvim',
     lazy = false,
     opts = {
diff --git a/.config/nvim/lua/plugins/snippets.lua b/.config/nvim/lua/plugins/snippets.lua
index 71276a0..2a38a1d 100644
--- a/.config/nvim/lua/plugins/snippets.lua
+++ b/.config/nvim/lua/plugins/snippets.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'L3MON4D3/LuaSnip',
     version = 'v2.*',
     build = 'make install_jsregexp',
diff --git a/.config/nvim/lua/plugins/treesitter.lua b/.config/nvim/lua/plugins/treesitter.lua
index 0978eb7..ccacc7a 100644
--- a/.config/nvim/lua/plugins/treesitter.lua
+++ b/.config/nvim/lua/plugins/treesitter.lua
@@ -1,5 +1,6 @@
 return {
   {
+    enabled = true,
     'nvim-treesitter/nvim-treesitter',
     branch = 'main',
     lazy = false,
@@ -36,6 +37,7 @@ return {
         'gotmpl',
         'gowork',
         'html',
+        'janet_simple',
         'javascript',
         'json',
         'lua',
@@ -43,15 +45,18 @@ return {
         'markdown',
         'markdown_inline',
         'ninja',
+        'proto',
         'python',
         'regex',
         'rust',
         'scss',
         'sql',
+        'templ', -- golang html templates
         'todotxt',
         'toml',
         'typescript',
         'typst',
+        'vala',
         'vim',
         'vimdoc',
         'yaml',
diff --git a/.config/nvim/lua/plugins/whichkey.lua b/.config/nvim/lua/plugins/whichkey.lua
index cb89df4..442b43f 100644
--- a/.config/nvim/lua/plugins/whichkey.lua
+++ b/.config/nvim/lua/plugins/whichkey.lua
@@ -1,9 +1,19 @@
 return {
   {
+    enabled = true,
     'folke/which-key.nvim',
     event = 'VeryLazy',
     opts = {
       icons = { separator = '→ ', group = ' +' },
     },
+    keys = {
+      {
+        '<leader>?',
+        function()
+          require('which-key').show({ global = true })
+        end,
+        desc = 'which-key: show buffer local keymaps',
+      },
+    },
   },
 }
diff --git a/.config/nvim/plugin/mod.lua b/.config/nvim/plugin/mod.lua
new file mode 100644
index 0000000..a26bf46
--- /dev/null
+++ b/.config/nvim/plugin/mod.lua
@@ -0,0 +1,25 @@
+-- register keybinds and commands for lua/mod/?.lua here
+
+-- jobs.lua
+vim.api.nvim_create_user_command('Jobs', function(a) require('mod.jobs').select(a) end,
+  { nargs = 0, desc = 'jobs: List running jobs' })
+
+vim.keymap.set('n', ';j', '<cmd>Jobs<cr>', { desc = 'fzf: jobs' })
+
+-- termopen.lua
+vim.api.nvim_create_user_command('Term', function(a) require('mod.termopen').term(a) end,
+  { nargs = '*', desc = 'termopen: Terminal' })
+vim.api.nvim_create_user_command('Make', function(a) require('mod.termopen').make(a) end,
+  { nargs = '*', desc = 'termopen: Make' })
+
+vim.keymap.set('n', '<leader><leader>m', '<cmd>Make<cr>', { desc = 'termopen: make' })
+vim.keymap.set('n', '<leader><leader>t', '<cmd>Termopen<cr>', { desc = 'termopen: shell' })
+vim.keymap.set('n', '<leader><leader>l', function()
+  vim.ui.input({ prompt = 'Launch: ' }, function(input)
+    if input ~= nil and #input > 0 then
+      require('mod.termopen').termopen({ cmd = { vim.env.SHELL, '-c', input } })
+    else
+      vim.notify('termopen: empty arguments', vim.log.levels.WARN)
+    end
+  end)
+end, { desc = 'termopen: Launch' })