summary refs log tree commit diff
path: root/.config/nvim/lua
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2026-02-10 12:32:48 +0100
committerRobert Günzler <r@gnzler.io>2026-02-10 12:41:25 +0100
commit63feeb9f369fc6176e85c1c2d8fcc863caad1946 (patch)
tree41cd5d2d1ec36e74e464caab722cc9b460153f04 /.config/nvim/lua
parent5bd0a4aca65ecb9818ef1285982d160b6b6ff665 (diff)
start new
Signed-off-by: Robert Günzler <r@gnzler.io>
Diffstat (limited to '.config/nvim/lua')
-rw-r--r--.config/nvim/lua/config/diagnostics.lua65
-rw-r--r--.config/nvim/lua/config/filetypes.lua5
-rw-r--r--.config/nvim/lua/config/keymaps.lua36
-rw-r--r--.config/nvim/lua/config/lazy.lua63
-rw-r--r--.config/nvim/lua/config/lsp.lua66
-rw-r--r--.config/nvim/lua/config/options.lua39
-rw-r--r--.config/nvim/lua/config/reset.lua17
-rw-r--r--.config/nvim/lua/config/snippets.lua140
-rw-r--r--.config/nvim/lua/config/statuscolumn.lua11
-rw-r--r--.config/nvim/lua/config/statusline.lua152
-rw-r--r--.config/nvim/lua/config/undo.lua10
-rw-r--r--.config/nvim/lua/plugins/blink.lua94
-rw-r--r--.config/nvim/lua/plugins/calcium.lua15
-rw-r--r--.config/nvim/lua/plugins/colorizer.lua15
-rw-r--r--.config/nvim/lua/plugins/fzf.lua103
-rw-r--r--.config/nvim/lua/plugins/gentoo.lua15
-rw-r--r--.config/nvim/lua/plugins/git.lua116
-rw-r--r--.config/nvim/lua/plugins/icons.lua13
-rw-r--r--.config/nvim/lua/plugins/indent.lua30
-rw-r--r--.config/nvim/lua/plugins/lsp.lua6
-rw-r--r--.config/nvim/lua/plugins/markdown.lua82
-rw-r--r--.config/nvim/lua/plugins/mini.lua26
-rw-r--r--.config/nvim/lua/plugins/oil.lua21
-rw-r--r--.config/nvim/lua/plugins/snippets.lua13
-rw-r--r--.config/nvim/lua/plugins/treesitter.lua93
-rw-r--r--.config/nvim/lua/plugins/whichkey.lua10
26 files changed, 1256 insertions, 0 deletions
diff --git a/.config/nvim/lua/config/diagnostics.lua b/.config/nvim/lua/config/diagnostics.lua
new file mode 100644
index 0000000..6314b83
--- /dev/null
+++ b/.config/nvim/lua/config/diagnostics.lua
@@ -0,0 +1,65 @@
+vim.diagnostic.config({
+  signs = {
+    text = {
+      [vim.diagnostic.severity.ERROR] = '󰷚',
+      [vim.diagnostic.severity.WARN] = '󰀦',
+      [vim.diagnostic.severity.INFO] = '󰙎',
+      [vim.diagnostic.severity.HINT] = '󰛓',
+    },
+    linehl = {
+      -- [vim.diagnostic.severity.ERROR] = 'Search',
+      -- [vim.diagnostic.severity.WARN] = '',
+      -- [vim.diagnostic.severity.INFO] = '',
+      -- [vim.diagnostic.severity.HINT] = '',
+    },
+    numhl = {
+      [vim.diagnostic.severity.ERROR] = 'DiagnosticSignError',
+      [vim.diagnostic.severity.WARN] = 'DiagnosticSignWarn',
+      [vim.diagnostic.severity.INFO] = 'DiagnosticSignInfo',
+      [vim.diagnostic.severity.HINT] = 'DiagnosticSignHint',
+    },
+  },
+  severity_sort = true,
+  underline = {
+    severity = { min = vim.diagnostic.severity.WARN },
+  },
+  float = false,
+  virtual_text = false,
+  virtual_lines = false,
+})
+
+local commonhl = { undercurl = true, force = true }
+vim.api.nvim_set_hl(0, 'DiagnosticUnderlineError', commonhl)
+vim.api.nvim_set_hl(0, 'DiagnosticUnderlineWarn', commonhl)
+vim.api.nvim_set_hl(0, 'DiagnosticUnderlineInfo', commonhl)
+vim.api.nvim_set_hl(0, 'DiagnosticUnderlineHint', commonhl)
+
+vim.keymap.set('n', '[d', function()
+  vim.diagnostic.jump({ count = 1, float = true })
+end, { desc = 'diagnostic: next' })
+
+vim.keymap.set('n', ']d', function()
+  vim.diagnostic.jump({ count = -1, float = true })
+end, { desc = 'diagnostic: prev' })
+
+vim.keymap.set('n', 'DD', vim.diagnostic.open_float, { desc = 'diagnostic: current line' })
+
+vim.keymap.set('n', '<leader>d', '<nop>', { desc = "diagnostics" })
+vim.keymap.set('n', '<leader>dd', vim.diagnostic.open_float, { desc = 'diagnostic: current line' })
+vim.keymap.set('n', '<leader>dv', function()
+  -- NOTE: this can be used to switch between multiple sets of diagnostics display
+  --       currently it only toggles virtual_text
+  local visible = vim.diagnostic.config().virtual_text
+
+  if visible then
+    -- vim.diagnostic.hide(nil, 0)
+    vim.diagnostic.config({ virtual_text = false })
+  else
+    -- vim.diagnostic.show(nil, 0)
+    vim.diagnostic.config({ virtual_text = true })
+  end
+end, { desc = 'diagnostic: toggle visibility' })
+
+vim.keymap.set('n', '<leader>da', function()
+  vim.diagnostic.enable(not vim.diagnostic.is_enabled())
+end, { desc = 'diagnostic: toggle all' })
diff --git a/.config/nvim/lua/config/filetypes.lua b/.config/nvim/lua/config/filetypes.lua
new file mode 100644
index 0000000..f1428d2
--- /dev/null
+++ b/.config/nvim/lua/config/filetypes.lua
@@ -0,0 +1,5 @@
+vim.filetype.add({
+  pattern = {
+    ['todo.txt'] = 'todotxt',
+  }
+})
diff --git a/.config/nvim/lua/config/keymaps.lua b/.config/nvim/lua/config/keymaps.lua
new file mode 100644
index 0000000..9c00f7b
--- /dev/null
+++ b/.config/nvim/lua/config/keymaps.lua
@@ -0,0 +1,36 @@
+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', '<a-j>', '<cmd>cnext<cr>', { desc = 'Quickfix next' })
+map('n', '<a-k>', '<cmd>cprev<cr>', { desc = 'Quickfix prev' })
+
+map('v', '<', '<gv', { desc = 'Indent' })
+map('v', '>', '>gv', { desc = 'Outdent' })
+
+map('t', '<esc>', [[ <c-\><c-n> ]], { desc = 'Terminal: Exit INSERT mode' })
+
+map(
+  'n',
+  'gx',
+  '<cmd>lua vim.ui.open(vim.fn.expand("<cfile>"))<cr>',
+  { desc = 'Open file under cursor' }
+)
+
+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 })
+    end
+  end,
+})
+
+map('n', '<leader><leader>x', function()
+  local file = vim.fn.expand('%')
+  vim.cmd.source(file)
+  vim.notify('** sourced ' .. file, vim.log.levels.INFO)
+end, { desc = 'source current file' })
+
+map('n', '<leader>a', '<cmd>edit #<cr>', { desc = 'open alternative file' })
diff --git a/.config/nvim/lua/config/lazy.lua b/.config/nvim/lua/config/lazy.lua
new file mode 100644
index 0000000..5f859f0
--- /dev/null
+++ b/.config/nvim/lua/config/lazy.lua
@@ -0,0 +1,63 @@
+local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
+if not (vim.uv or vim.loop).fs_stat(lazypath) then
+  local lazyrepo = 'https://github.com/folke/lazy.nvim.git'
+  local out = vim.fn.system({'git', 'clone', '--filter=blob:none', '--branch=stable', lazyrepo, lazypath})
+  if vim.v.shell_error ~= 0 then
+    vim.api.nvim_echo({
+      {'Failed to clone lazy.nvim:\n', 'ErrorMsg'},
+      {out, 'WarningMsg'},
+      {'\nPress any key to exit...'},
+    }, true, {})
+    vi.fn.getchar() -- let user ack
+    os.exit(1)
+  end
+end
+
+vim.opt.rtp:prepend(lazypath)
+
+vim.g.mapleader = " "
+vim.g.maplocalleader = "\\"
+
+require('lazy').setup({
+  -- load plugin specs from ./plugins
+  spec = { { import = 'plugins' } },
+  -- colorscheme for installing plugins
+  install = { colorscheme = { 'default' } },
+  -- automatically check for plugin updates
+  checker = {
+    enabled = true,
+    frequency = (3600 * 24 * 3), -- 3 days
+  },
+})
+
+vim.keymap.set({'n'}, '<leader>l', '<nop>', { desc = 'lazy' })
+vim.keymap.set({'n'}, '<leader>lz', '<cmd>Lazy<cr>', { desc = 'lazy: open ui' })
+vim.keymap.set({'n'}, '<leader>ln', function()
+  local pname = 'TODO'
+  local ppath = vim.fn.stdpath('config') .. '/lua/plugins'
+
+  vim.ui.input({ prompt = 'Plugin name: ' }, function(input)
+    pname = input
+  end)
+
+  vim.cmd.cd(ppath)
+
+  vim.fn.setreg('a', [[return {
+  {
+    '$name',
+    cmd = {'$cmd'},
+    opts = {$opts},
+    keys = {
+      { mode = {'n'}, '$lhs', '$rhs', desc = '$desc' },
+    },
+  }
+}]])
+
+  local buf = vim.api.nvim_create_buf(true, false)
+  vim.api.nvim_buf_set_name(buf, pname .. '.lua')
+  vim.api.nvim_set_option_value('filetype', 'lua', { buf = buf })
+  vim.api.nvim_win_set_buf(0, buf)
+
+  require('luasnip.extras.otf').on_the_fly('a')
+
+end, { desc = 'lazy: new plugin' })
diff --git a/.config/nvim/lua/config/lsp.lua b/.config/nvim/lua/config/lsp.lua
new file mode 100644
index 0000000..282a3dc
--- /dev/null
+++ b/.config/nvim/lua/config/lsp.lua
@@ -0,0 +1,66 @@
+if not vim.lsp.enable then return end
+
+local lsp = vim.lsp
+
+lsp.enable({
+  'bashls',
+  'ccls',
+  'clangd',
+  'gopls',
+  'rust_analyzer',
+  'superhtml',
+  'tinymist',
+  'zk',
+  'zls',
+})
+
+local capabilities = nil
+if pcall(require, 'blink.cmp') then
+  capabilities = require('blink.cmp').get_lsp_capabilities()
+end
+
+lsp.config('*', {
+  root_markers = {'.git'},
+  capabilities = capabilities
+})
+
+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))
+
+    -- TODO: what does this do?
+    vim.opt_local.omnifunc = 'v:lua.vim.lsp.omnifunc'
+
+    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, { desc = 'goto_definition' })
+    vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, { desc = 'goto declaration' })
+    vim.keymap.set('n', 'gT', vim.lsp.buf.type_definition, { desc = 'goto type definition' })
+    vim.keymap.set('n', 'gR', vim.lsp.buf.references, { desc = 'show references' })
+    vim.keymap.set('n', 'ga', vim.lsp.buf.code_action, { desc = 'show code actions' })
+    vim.keymap.set('n', 'grr', vim.lsp.buf.rename, { desc = 'rename' })
+
+    if client:supports_method('textDocument/implementation') then
+      vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, { desc = 'goto implementation' })
+    end
+
+    if client:supports_method('textDocument/completion') then
+      vim.lsp.completion.enable(true, client.id, args.buf, {autotrigger = false})
+    end
+
+    if client:supports_method('textDocument/formatting') then
+    vim.keymap.set('n', '<leader>f', vim.lsp.buf.format, { desc = 'format' })
+    end
+
+    if not client:supports_method('textDocument/willSaveWaitUntil')
+        and client:supports_method('textDocument/formatting') then
+      vim.api.nvim_create_autocmd({'BufWritePre'}, {
+        group = vim.api.nvim_create_augroup('lspattach', {}),
+        buffer = args.buf,
+        callback = function()
+          if vim.b.nofmt then return end
+          vim.lsp.buf.format({ bufnr = args.buf, id = client.id, timeout_ms = 1000 })
+        end,
+      })
+    end
+  end,
+})
diff --git a/.config/nvim/lua/config/options.lua b/.config/nvim/lua/config/options.lua
new file mode 100644
index 0000000..073835f
--- /dev/null
+++ b/.config/nvim/lua/config/options.lua
@@ -0,0 +1,39 @@
+local opt = vim.opt
+
+opt.number = true
+opt.cursorline = true
+
+-- connect system clipboard
+opt.clipboard:prepend({ 'unnamedplus' })
+
+-- preview supported ops in split buffer
+opt.inccommand = 'split'
+
+-- show whitespace indicators
+opt.list = true
+opt.listchars:append({
+  trail = '¬',
+  tab = '> ',
+  -- space = '·',
+  -- lead = '·',
+})
+opt.showbreak = '↪ '
+
+-- show indicator and wrap text at 80 chars
+opt.colorcolumn = '+1'
+opt.textwidth = 80
+opt.wrap = true
+
+opt.conceallevel = 2
+opt.concealcursor = nil
+
+-- use treesitter to determine folds
+opt.foldmethod = 'expr'
+opt.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
+opt.foldlevel = 99
+
+-- https://fosstodon.org/@rockorager/114183888444195197
+opt.mousescroll = 'ver:1,hor:6'
+
+-- load local vim config (will as to allow it)
+opt.exrc = true
diff --git a/.config/nvim/lua/config/reset.lua b/.config/nvim/lua/config/reset.lua
new file mode 100644
index 0000000..4908c20
--- /dev/null
+++ b/.config/nvim/lua/config/reset.lua
@@ -0,0 +1,17 @@
+vim.g.loaded_gzip = 1
+vim.g.loaded_tar = 1
+vim.g.loaded_tarPlugin = 1
+vim.g.loaded_zip = 1
+vim.g.loaded_zipPlugin = 1
+vim.g.loaded_getscript = 1
+vim.g.loaded_getscriptPlugin = 1
+vim.g.loaded_vimball = 1
+vim.g.loaded_vimballPlugin = 1
+vim.g.loaded_matchit = 1
+vim.g.loaded_2html_plugin = 1
+vim.g.loaded_logiPat = 1
+vim.g.loaded_rrhelper = 1
+vim.g.loaded_netrw = 1
+vim.g.loaded_netrwPlugin = 1
+vim.g.loaded_netrwSettings = 1
+vim.g.loaded_netrwFileHandlers = 1
diff --git a/.config/nvim/lua/config/snippets.lua b/.config/nvim/lua/config/snippets.lua
new file mode 100644
index 0000000..e0c260e
--- /dev/null
+++ b/.config/nvim/lua/config/snippets.lua
@@ -0,0 +1,140 @@
+local ls = require('luasnip')
+local types = require('luasnip.util.types')
+
+-- configure luasnip
+ls.config.set_config({
+  enable_autosnippets = true,
+  history = true,
+  updateevents = 'TextChanged,TextChangedI',
+  override_builtin = true,
+  ext_opts = {
+    [types.choiceNode] = {
+      active = {
+        virt_text = { { '◂    󰬊 ', 'NonText' } },
+      },
+    },
+    [types.insertNode] = {
+      active = {
+        virt_text = { { '◂    󰬐 ', 'NonText' } },
+      },
+    },
+  },
+  snip_env = vim.tbl_deep_extend('error', ls.session.config.snip_env, {
+    -- snippet helpers
+    nl = function()
+      return ls.t({ '', '' })
+    end,
+    exec = function(command)
+      return ls.f(function(_, _, ...)
+        local job = require('plenary.job'):new({
+          command = vim.env.SHELL,
+          args = { '-c', ... },
+          enable_recording = true,
+        })
+        local res, exitcode = job:sync()
+        if not exitcode == 0 or #res == 0 then
+          error(string.format('exec "%s" failed', ...))
+        end
+        return res
+      end, {}, { user_args = { command } })
+    end,
+    -- condition helpers
+    cond = {
+      ts = function(capture)
+        return function()
+          return vim.tbl_contains(vim.treesitter.get_captures_at_cursor(), capture)
+        end
+      end,
+    },
+  }),
+})
+
+-- snippet loaders
+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, {})
+
+-- autocmds
+-- vim.api.nvim_create_autocmd('User', {
+--   pattern = 'LuasnipChoiceNodeEnter',
+--   callback = function()
+--     vim.schedule(function()
+--       assert(ls.session.active_choice_nodes[vim.api.nvim_get_current_buf()], 'No active choiceNode')
+--       vim.ui.select(
+--         ls.get_current_choices(),
+--         -- TODO: patch luasnip.extras.select_choice to fix missing prompt
+--         { prompt = 'LuaSnip: choice node', kind = 'luasnip' },
+--         function(_, idx)
+--           if not idx then
+--             return
+--           end
+--           -- feed+immediately execute i to enter INSERT after vim.ui.input closes.
+--           vim.api.nvim_feedkeys('i', 'x', false)
+--           ls.set_choice(idx)
+--           ls.unlink_current()
+--         end
+--       )
+--     end)
+--   end,
+-- })
+
+-- unlink luasnip session on <esc>
+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()
+    then
+      ls.unlink_current()
+    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
diff --git a/.config/nvim/lua/config/statuscolumn.lua b/.config/nvim/lua/config/statuscolumn.lua
new file mode 100644
index 0000000..06e8988
--- /dev/null
+++ b/.config/nvim/lua/config/statuscolumn.lua
@@ -0,0 +1,11 @@
+function _G.statuscolumn()
+  local sc = ''
+
+  sc = sc .. [[%l%r%s%C]]
+
+  sc = sc .. '│'
+
+  return sc
+end
+
+-- vim.opt.statuscolumn = '%{%v:lua.statuscolumn()%}'
diff --git a/.config/nvim/lua/config/statusline.lua b/.config/nvim/lua/config/statusline.lua
new file mode 100644
index 0000000..fed6d33
--- /dev/null
+++ b/.config/nvim/lua/config/statusline.lua
@@ -0,0 +1,152 @@
+function _G.statusline()
+  local bufnr = vim.fn.bufnr() or 0
+  local hi = statusline_color(vim.fn.mode())
+
+  local sl = hi
+  -- sl = sl .. statusline_append([[ ⢷]], 'StatusLineIcon', hi)
+
+  sl = sl .. [[%-h%-w%-q%-f %-m%-r]]
+
+  sl = sl .. statusline_append(statusline_git(bufnr, hi), 'StatusLineGit', hi)
+
+  sl = sl .. statusline_append(statusline_grapple(bufnr, hi), 'StatusLineGrapple', hi)
+
+  sl = sl .. [[ %= ]] -- spacer
+
+  sl = sl .. statusline_append(statusline_lsp_diagnostics(bufnr, hi), 'StatusLineDiagnostics', hi)
+
+  sl = sl .. statusline_append(statusline_dap(bufnr, hi), 'StatusLineDap', hi)
+
+  -- sl = sl .. [[ %= ]] -- spacer
+
+  sl = sl .. statusline_append(statusline_spinner(bufnr, hi), 'StatusLineJobs', hi)
+
+  sl = sl .. [[%-l:%-v --%p%%-- %-Y]]
+
+  return sl
+end
+
+function _G.statusline_color(mode)
+  local hi = '%*'
+  if mode == 'i' then
+    -- hi = '%#StatusLineInsert#'
+  elseif mode == 'c' then
+    -- hi = '%#StatusLineCommand#'
+  elseif mode == 'v' or mode == 'V' or mode == '' then
+    hi = '%#StatusLineVisual#'
+  elseif mode == 'R' or mode == 'Rv' then
+    hi = '%#StatusLineReplace#'
+  end
+  -- return '%{g:actual_curwin==win_getid()?'.. hi ..':%#StatusLineNC#}'
+  return hi
+end
+
+-- does item highlighting and conditionnal spacing
+function _G.statusline_append(element, hi, default_hi, opts)
+  if not element or element == '' then
+    return ''
+  end
+  opts = opts or { append_space = true }
+
+  if hi ~= nil then
+    hi = '%#' .. hi .. '#'
+  end
+  if not (default_hi == '%*') then
+    hi = default_hi
+  end
+  local el = hi .. element .. default_hi
+  if opts.append_space then
+    el = el .. ' '
+  end
+  return el
+end
+
+function _G.statusline_combine_hi(outer_name, inner_name)
+  local outer = vim.api.nvim_get_hl(0, { name = outer_name, create = false })
+  local inner = vim.api.nvim_get_hl(0, { name = inner_name, create = false })
+  -- try to find a predefined hl
+  local name = (outer_name .. inner_name)
+  if vim.api.nvim_get_hl(0, { name = name, create = false }) then
+    return name
+  end
+  -- generate one new one automatically
+  name = ('autohi_' .. outer_name .. inner_name)
+  vim.api.nvim_get_hl(0, { name = name, create = true })
+  local hi = vim.tbl_extend('force', inner, { bg = inner.fg }, { fg = outer.bg })
+  vim.api.nvim_set_hl(0, name, hi)
+  return name
+end
+
+function _G.statusline_lsp_diagnostics(bufnr, default_hi)
+  local signs = vim.diagnostic.config().signs.text or {}
+  local error_num = vim.diagnostic.get(bufnr, { severity = vim.diagnostic.severity.ERROR })
+  local warn_num = vim.diagnostic.get(bufnr, { severity = vim.diagnostic.severity.WARN })
+
+  local s = ''
+  if #error_num > 0 then
+    local errs = signs[vim.diagnostic.severity.ERROR] .. ' ' .. #error_num
+    s = s
+      .. statusline_append(errs, statusline_combine_hi('StatusLine', 'DiagnosticError'), default_hi)
+  end
+  if #warn_num > 0 then
+    local warns = signs[vim.diagnostic.severity.WARN] .. ' ' .. #warn_num
+    s = s
+      .. statusline_append(warns, statusline_combine_hi('StatusLine', 'DiagnosticWarn'), default_hi)
+  end
+  return vim.trim(s)
+end
+
+function _G.statusline_git(_, hi)
+  if vim.b.gitsigns_status and vim.b.gitsigns_status ~= '' then
+    return statusline_append('[' .. vim.b.gitsigns_status .. ']', 'StatusLineGitChanges', hi)
+  end
+end
+
+function _G.statusline_grapple(_, _)
+  local g = package.loaded.grapple
+  if not g then
+    return ''
+  end
+  return g.statusline()
+  -- if not g or not g.exists({ buffer = bufnr }) then
+  --   return ''
+  -- end
+  -- return '➥ ' .. g.key({ buffer = bufnr })
+end
+
+function _G.statusline_spinner(bufnr, _)
+  local sp = package.loaded.spinner
+  if not sp then
+    return ''
+  end
+  return sp.status(bufnr)
+end
+
+function _G.statusline_dap(_, _)
+  local dap = package.loaded.dap
+  if not dap then
+    return ''
+  end
+  local s = dap.status()
+  if s == '' then
+    return s
+  end
+  return '[DAP: ' .. s .. ']'
+end
+
+local hi = function(name, opts)
+  vim.api.nvim_set_hl(0, name, opts)
+end
+
+local statusline = vim.api.nvim_get_hl(0, { name = 'StatusLine', create = false })
+hi('StatusLineDiagnosticError', { fg = 'NvimDarkRed', bg = statusline.bg, bold = true })
+hi('StatusLineDiagnosticWarn', { fg = 'NvimDarkYellow', bg = statusline.bg, bold = true })
+
+vim.opt.statusline = '%{%v:lua.statusline()%}'
+
+-- WINBAR --
+-- vim.opt.winbar = [[%h%w%q%f %-m %-r %= %#WinBarCwd#%{%getcwd()%}%*]] -- keep it simple
+-- function _G.winbar()
+--   return [[%<%F %-m %-r]]
+-- end
+-- vim.opt.winbar = '%{%v:lua.winbar()%}'
diff --git a/.config/nvim/lua/config/undo.lua b/.config/nvim/lua/config/undo.lua
new file mode 100644
index 0000000..aed6195
--- /dev/null
+++ b/.config/nvim/lua/config/undo.lua
@@ -0,0 +1,10 @@
+-- persistent undo
+
+local undodir = vim.fn.stdpath('cache') .. '/undodir'
+
+if vim.fn.isdirectory(undodir) == 0 then
+  vim.fn.mkdir(undodir, 'p', '0700')
+end
+
+vim.g.undodir = undodir
+vim.opt.undofile = true
diff --git a/.config/nvim/lua/plugins/blink.lua b/.config/nvim/lua/plugins/blink.lua
new file mode 100644
index 0000000..1122bcc
--- /dev/null
+++ b/.config/nvim/lua/plugins/blink.lua
@@ -0,0 +1,94 @@
+return {
+  {
+    'saghen/blink.cmp',
+    version = '1.*',
+
+    lazy = false,
+
+    dependencies = {
+      { 'L3MON4D3/LuaSnip' },
+    },
+
+    init = function()
+      vim.opt.completeopt = { 'menu', 'menuone', 'noselect' }
+      vim.opt.shortmess:append('c')
+      vim.opt.pumheight = 20 -- max height of completion window
+
+      local hl = vim.api.nvim_set_hl
+      hl(0, 'BlinkCmpSource', { link = 'Comment', force = true })
+      hl(0, 'BlinkCmpLabelDescription', { link = 'Comment', force = true })
+    end,
+
+    opts_extend = { 'sources.default' },
+
+    opts = {
+      -- TODO:
+      -- 'prefer_rust_with_warning', requires nightly rust, due to:
+      -- https://github.com/rust-lang/rust/issues/86656
+      fuzzy = { 
+        implementation = 'lua',
+        sorts = {
+          function(a, b)
+            local prio = {
+              snippets = 99,
+              -- everyting else 0
+            }
+            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
+          end,
+          -- defaults
+          'score',
+          'sort_text'
+        },
+      },
+
+      appearance = {
+        -- fallback to cmp highlight names
+        use_nvim_cmp_as_default = true,
+        -- adjusts spacing to ensure icons are aligned
+        -- nerd_font_variant = 'normal',
+      },
+
+      sources = {
+        providers = {
+          buffer = { min_keyword_length = 3 },
+        },
+      },
+
+      completion = {
+        documentation = { auto_show = true },
+        menu = {
+          draw = {
+            columns = {
+              { 'kind_icon' },
+              { 'label', 'label_description', gap = 1 },
+              { 'source_name' },
+            },
+          },
+        },
+      },
+
+      signature = {
+        enabled = true,
+        window = { scrollbar = false }, -- BUG: weird artifacts
+      },
+
+      keymap = {
+        ['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },
+        ['<C-e>'] = { 'cancel', 'fallback' },
+        ['<C-y>'] = { 'select_and_accept' },
+        ['<C-p>'] = { 'select_prev', 'fallback' },
+        ['<C-n>'] = { 'select_next', 'fallback' },
+        ['<C-b>'] = { 'scroll_documentation_up', 'fallback' },
+        ['<C-f>'] = { 'scroll_documentation_down', 'fallback' },
+        ['<Tab>'] = { 'select_and_accept', 'fallback' },
+        ['<C-j>'] = { 'snippet_forward', 'fallback' },
+        ['<C-k>'] = { 'snippet_backward', 'fallback' },
+        -- ['<C-l>'] = { 'show', 'hide', 'fallback' },
+      },
+
+      snippets = { preset = 'luasnip' },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/calcium.lua b/.config/nvim/lua/plugins/calcium.lua
new file mode 100644
index 0000000..f0ab78c
--- /dev/null
+++ b/.config/nvim/lua/plugins/calcium.lua
@@ -0,0 +1,15 @@
+return {
+  {
+    'necrom4/calcium.nvim',
+    cmd = {'Calcium'},
+    opts = {
+      notifications = false,
+      default_mode = 'replace',
+    },
+    keys = {
+      { mode = {'n', 'v'}, 'gl', '<nop>', desc = 'Calcium' },
+      { mode = {'n', 'v'}, 'glr', ':Calcium replace<cr>', desc = 'Calcium replace', silent = true },
+      { mode = {'n', 'v'}, 'gla', ':Calcium append<cr>', desc = 'Calcium append', silent = true },
+    },
+  }
+}
diff --git a/.config/nvim/lua/plugins/colorizer.lua b/.config/nvim/lua/plugins/colorizer.lua
new file mode 100644
index 0000000..2608ae6
--- /dev/null
+++ b/.config/nvim/lua/plugins/colorizer.lua
@@ -0,0 +1,15 @@
+return {
+  {
+    'NvChad/nvim-colorizer.lua',
+    -- 'norcalli/nvim-colorizer.lua',
+    lazy = false,
+    opts = {
+      name = true,
+      RRGGBB = true,
+      RRGGBBAA = true,
+      rgb_fn = true,
+      hsl_fn = true,
+      mode = 'background',
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/fzf.lua b/.config/nvim/lua/plugins/fzf.lua
new file mode 100644
index 0000000..efee6ef
--- /dev/null
+++ b/.config/nvim/lua/plugins/fzf.lua
@@ -0,0 +1,103 @@
+return {
+  {
+    enabled = true,
+    'ibhagwan/fzf-lua',
+    lazy = false,
+    dependencies = {
+      'echasnovski/mini.icons',
+    },
+    config = function()
+      local fzf = require('fzf-lua')
+      fzf.register_ui_select()
+      fzf.setup({
+        -- PROFILE
+        { 'default' },
+        -- OPTIONS
+        winopts = {
+          split = 'belowright 20new',
+          border = 'none',
+          preview = {
+            border = 'none',
+          },
+          -- on_create = function() end,
+        },
+        keymap = {
+          fzf = {
+            true,
+            ['ctrl-n'] = 'down',
+            ['ctrl-p'] = 'up',
+          },
+        },
+        -- actions = {
+        --   files = {
+        --     ['ctrl-q'] = { fn = fzf.actions.file_sel_to_qf },
+        --   },
+        -- },
+        fzf_colors = true,
+        -- PICKER CONFIG
+        files = {
+          file_icons = true,
+        },
+        marks = {
+          marks = '[a-zA-Z]',
+        }
+      })
+    end,
+    keys = {
+      { mode = 'n', ';<space>', '<cmd>FzfLua<cr>', desc = 'fzf: select' },
+      { mode = 'n', ';/', '<cmd>FzfLua blines<cr>', desc = 'fzf: current_buffer_fuzzy_find' },
+      { 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', ';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', ';m', '<cmd>FzfLua marks<cr>', desc = 'fzf: marks' },
+      {
+        mode = 'n',
+        ';v',
+        function()
+          require('fzf-lua').files({ cwd = vim.fn.stdpath('config') })
+        end,
+        desc = 'fzf: vim_config',
+      },
+      -- {
+      --   mode = 'n',
+      --   ';;t',
+      --   '<cmd>Telescope<cr>',
+      --   desc = 'telescope: select (interactive)',
+      -- },
+      {
+        mode = 'n',
+        ';;f',
+        function()
+          local dir
+          if vim.opt.filetype:get() == 'oil' then
+            dir = require('oil').get_current_dir()
+          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)',
+      },
+      {
+        mode = 'n',
+        ';;g',
+        function()
+          local dir
+          if vim.opt.filetype:get() == 'oil' then
+            dir = require('oil').get_current_dir()
+          else
+            dir = vim.fn.expand('%:p:h')
+          end
+          _G.feedkeys(':Fzf live_grep cwd=' .. dir)
+        end,
+        desc = 'fzf: grep (interactive)',
+      },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/gentoo.lua b/.config/nvim/lua/plugins/gentoo.lua
new file mode 100644
index 0000000..470e6de
--- /dev/null
+++ b/.config/nvim/lua/plugins/gentoo.lua
@@ -0,0 +1,15 @@
+return {
+  {
+    'gentoo/gentoo-syntax',
+    -- ft = { 'ebuild' },
+    -- init = function()
+    --   vim.api.nvim_create_autocmd('LspAttach', {
+    --     callback = function(args)
+    --       if vim.opt.filetype == 'ebuild' then
+    --         vim.lsp.stop_client(vim.lsp.get_clients({ bufnr = args.bufnr }))
+    --       end
+    --     end,
+    --   })
+    -- end,
+  },
+}
diff --git a/.config/nvim/lua/plugins/git.lua b/.config/nvim/lua/plugins/git.lua
new file mode 100644
index 0000000..2561ad9
--- /dev/null
+++ b/.config/nvim/lua/plugins/git.lua
@@ -0,0 +1,116 @@
+return {
+  {
+    'lewis6991/gitsigns.nvim',
+    lazy = false,
+    opts = {
+      debug_mode = false,
+    },
+    keys = {
+      {
+        ']c',
+        function()
+          if vim.wo.diff then
+            vim.cmd.normal({ ']c', bang = true })
+          else
+            require('gitsigns').nav_hunk('next')
+          end
+        end,
+        desc = 'Gitsigns: next hunk',
+      },
+      {
+        '[c',
+        function()
+          if vim.wo.diff then
+            vim.cmd.normal({ '[c', bang = true })
+          else
+            require('gitsigns').nav_hunk('prev')
+          end
+        end,
+        desc = 'Gitsigns: prev hunk',
+      },
+      { '<leader>g', '<nop>', desc = 'git' },
+      { '<leader>g<space>', '<cmd>Gitsigns<cr>', desc = 'Gitsigns: select action' },
+      { '<leader>gb', '<cmd>Gitsigns blame<cr>', desc = 'Gitsigns: blame' },
+      { '<leader>ga', '<cmd>Gitsigns stage_hunk<cr>', desc = 'Gitsigns: stage hunk' },
+      { '<leader>ga', ':Gitsigns stage_hunk<cr>', desc = 'Gitsigns: stage hunk', mode = 'x' },
+      { '<leader>gd', ':Gitsigns diffthis<cr>', desc = 'Gitsigns: diff' },
+      {
+        ';c',
+        function()
+          require('gitsigns').setqflist('all', { open = false })
+          require('fzf-lua').quickfix({ prompt = 'Git changesets' })
+        end,
+        desc = 'Gitsigns: list hunks',
+      },
+    },
+  },
+
+  {
+    'linrongbin16/gitlinker.nvim',
+    cmd = { 'GitLink' },
+    dependencies = { 'nvim-lua/plenary.nvim' },
+    opts = {
+      message = true,
+    },
+    keys = {
+      {
+        '<leader>gl',
+        vim.cmd.GitLink,
+        desc = 'GitLinker: clipboard',
+      },
+    },
+  },
+
+  {
+    'moyiz/git-dev.nvim',
+    lazy = true,
+    cmd = { 'GitDevOpen', 'GitDevCleanAll' },
+    opts = {
+      read_only = false,
+    },
+  },
+
+  {
+    'isakbm/gitgraph.nvim',
+    dependencies = { 'sindrets/diffview.nvim' },
+    opts = {
+      symbols = {
+        merge_commit = 'M',
+        commit = '∙',
+      },
+      format = {
+        timestamp = '%d-%m-%Y %H:%M:%S %z',
+        fields = { 'hash', 'timestamp', 'author', 'branch_name', 'tag' },
+      },
+    },
+    init = vim.schedule_wrap(function()
+      vim.api.nvim_set_hl(0, 'GitGraphBranch1', { fg = 'NvimLightGray1' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranch2', { fg = 'NvimLightMagenta' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranch3', { fg = 'NvimLightRed' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranch4', { fg = 'NvimDarkCyan' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranch5', { fg = 'NvimDarkYellow' })
+      vim.api.nvim_set_hl(0, 'GitGraphHash', { fg = 'NvimLightMagenta' })
+      vim.api.nvim_set_hl(0, 'GitGraphAuthor', { fg = 'NvimLightGray4', italic = true })
+      vim.api.nvim_set_hl(0, 'GitGraphTimestamp', { fg = 'NvimLightCyan' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranchName', { fg = 'NvimLightYellow' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranchTag', { fg = 'NvimLightYellow' })
+      vim.api.nvim_set_hl(0, 'GitGraphBranchMsg', { link = 'Normal' })
+      -- vim.api.nvim_create_autocmd('FileType', {
+      --   pattern = { 'gitgraph' },
+      --   callback = function()
+      --     vim.keymap.set('n', 'q', '<cmd>bdel!<cr>', { silent = true, buffer = 0 })
+      --   end,
+      --   group = vim.api.nvim_create_augroup('GitGraph'),
+      -- })
+    end),
+    keys = {
+      {
+        'g-',
+        function()
+          require('gitgraph').draw({}, { all = true, max_count = 5000 })
+        end,
+        desc = 'GitGraph: view log',
+      },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/icons.lua b/.config/nvim/lua/plugins/icons.lua
new file mode 100644
index 0000000..9a597ab
--- /dev/null
+++ b/.config/nvim/lua/plugins/icons.lua
@@ -0,0 +1,13 @@
+return {
+  {
+    'echasnovski/mini.icons',
+    version = false,
+    events = { 'VeryLazy' },
+    -- opts = {},
+    config = function(_, opts)
+      require('mini.icons').setup(opts)
+      ---@diagnostic disable-next-line: undefined-global
+      MiniIcons.mock_nvim_web_devicons()
+    end,
+  },
+}
diff --git a/.config/nvim/lua/plugins/indent.lua b/.config/nvim/lua/plugins/indent.lua
new file mode 100644
index 0000000..e247c5a
--- /dev/null
+++ b/.config/nvim/lua/plugins/indent.lua
@@ -0,0 +1,30 @@
+return {
+  -- show indents using virtual text
+  {
+    'lukas-reineke/indent-blankline.nvim',
+    lazy = false,
+    main = 'ibl',
+    opts = {
+      indent = {
+        char = '▏',
+      },
+      scope = {
+        enabled = true,
+        show_start = false,
+        show_end = false,
+        char = '▌',
+      },
+    },
+  },
+  -- detect indent from the buffer contents
+  {
+    enabled = false,
+    'NMAC427/guess-indent.nvim',
+    lazy = false,
+    opts = {
+      auto_cmd = true,
+      filetype_exclude = { 'netrw', 'tutor', 'dirbuf', 'nvimtree', 'oil' },
+      buftype_exclude = { 'help', 'nofile', 'terminal', 'prompt' },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/lsp.lua b/.config/nvim/lua/plugins/lsp.lua
new file mode 100644
index 0000000..aebf3c4
--- /dev/null
+++ b/.config/nvim/lua/plugins/lsp.lua
@@ -0,0 +1,6 @@
+return {
+  {
+    'neovim/nvim-lspconfig',
+    version = '^2.0',
+  }
+}
diff --git a/.config/nvim/lua/plugins/markdown.lua b/.config/nvim/lua/plugins/markdown.lua
new file mode 100644
index 0000000..b21255e
--- /dev/null
+++ b/.config/nvim/lua/plugins/markdown.lua
@@ -0,0 +1,82 @@
+return {
+  {
+    -- handy markdown commands
+    'tadmccorkle/markdown.nvim',
+    ft = { 'markdown' },
+    opts = {
+      mappings = false,
+      on_attach = function(buf)
+        local set = function(mode, lhs, rhs, desc)
+          vim.keymap.set(mode, lhs, rhs, { buffer = buf, desc = 'Markdown: ' .. desc })
+        end
+        set('n', 'nio', '<cmd>MDListItemBelow<cr>', 'Insert list item below')
+        set('n', 'niO', '<cmd>MDListItemAbove<cr>', 'Insert list item above')
+        set({ 'n', 'v', 'x', 'i' }, '<m-x>', '<cmd>MDTaskToggle<cr>', 'Toggle task')
+        set('x', '<m-l>', '<esc>gv<plug>(markdown_add_link_visual)', 'Make link')
+      end,
+    },
+  },
+  {
+    -- pretty markdown
+    'MeanderingProgrammer/markdown.nvim',
+    name = 'render-markdown', -- needed if used together with markdown.nvim above
+    main = 'render-markdown',
+    dependencies = {
+      'nvim-treesitter/nvim-treesitter',
+      'echasnovski/mini.icons',
+    },
+    ft = { 'markdown' },
+    opts = {
+      completions = { blink = { enabled = true } },
+      sign = { enabled = true },
+      indent = { enabled = false },
+      heading = {
+        width = 'block',
+        icons = {
+          '▒ ',
+          '▒▒ ',
+          '▒▒▒ ',
+          '▒▒▒▒ ',
+          '▒▒▒▒▒ ',
+          '▒▒▒▒▒▒ ',
+        },
+        signs = { 'ⅰ', 'ⅱ', 'ⅲ', 'ⅳ', 'ⅴ', 'ⅵ' },
+        backgrounds = {},
+        foregrounds = {},
+      },
+      bullet = {
+        icons = { '* ', '-', '>' },
+      },
+      checkbox = {
+        checked = { icon = '  󰄲 ' },
+        unchecked = { icon = '  󰄱 ' },
+        custom = {
+          todo = { raw = '[-]', rendered = '  󰔟 ', highlight = 'RenderMarkdownTodo' },
+        },
+      },
+      code = {
+        width = 'block',
+        border = 'thin',
+        disable_background = true,
+      },
+    },
+    init = vim.schedule_wrap(function()
+      local hl = vim.api.nvim_set_hl
+      hl(0, '@markup.bold', { link = 'markdownBold', force = true })
+      hl(0, '@markup.italic', { link = 'markdownItalic', force = true })
+
+      -- hl(0, 'RenderMarkdownH1', { fg = 'NvimLightCyan' })
+      -- hl(0, 'RenderMarkdownH1Bg', { bg = 'NvimDarkCyan' })
+      -- hl(0, 'RenderMarkdownH2', { fg = 'NvimLightMagenta' })
+      -- hl(0, 'RenderMarkdownH2Bg', { bg = 'NvimDarkMagenta' })
+      -- hl(0, 'RenderMarkdownH3', { fg = 'NvimLightYellow' })
+      -- hl(0, 'RenderMarkdownH3Bg', { bg = 'NvimDarkYellow' })
+      -- hl(0, 'RenderMarkdownH4', { fg = 'NvimLightBlue' })
+      -- hl(0, 'RenderMarkdownH4Bg', { bg = 'NvimDarkBlue' })
+      -- hl(0, 'RenderMarkdownH5', { fg = 'NvimLightRed' })
+      -- hl(0, 'RenderMarkdownH5Bg', { bg = 'NvimDarkRed' })
+
+      hl(0, 'RenderMarkdownBullet', { link = '@markup.list', force = true })
+    end),
+  },
+}
diff --git a/.config/nvim/lua/plugins/mini.lua b/.config/nvim/lua/plugins/mini.lua
new file mode 100644
index 0000000..fb4c9a6
--- /dev/null
+++ b/.config/nvim/lua/plugins/mini.lua
@@ -0,0 +1,26 @@
+return {
+  {
+    'echasnovski/mini.align',
+    version = false,
+    lazy = false,
+    opts = {},
+  },
+  {
+    'echasnovski/mini.ai',
+    version = false,
+    lazy = false,
+    opts = {},
+  },
+  {
+    'echasnovski/mini.surround',
+    version = false,
+    lazy = false,
+    opts = {},
+  },
+  {
+    'echasnovski/mini.operators',
+    version = false,
+    lazy = false,
+    opts = {},
+  },
+}
diff --git a/.config/nvim/lua/plugins/oil.lua b/.config/nvim/lua/plugins/oil.lua
new file mode 100644
index 0000000..401fe8b
--- /dev/null
+++ b/.config/nvim/lua/plugins/oil.lua
@@ -0,0 +1,21 @@
+return {
+  {
+    'stevearc/oil.nvim',
+    lazy = false,
+    opts = {
+      keymaps = {
+        ['gt'] = {
+          function()
+            local cwd = require('oil').get_current_dir()
+            vim.cmd('silent !footclient --no-wait -D ' .. cwd)
+          end,
+          nowait = true,
+          desc = 'Open current directory with foot',
+        },
+      },
+    },
+    keys = {
+      { mode = 'n', '-', '<cmd>Oil<cr>', desc = 'oil: open parent directory' },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/snippets.lua b/.config/nvim/lua/plugins/snippets.lua
new file mode 100644
index 0000000..97a4455
--- /dev/null
+++ b/.config/nvim/lua/plugins/snippets.lua
@@ -0,0 +1,13 @@
+return {
+  {
+    'L3MON4D3/LuaSnip',
+    version = 'v2.*',
+    build = 'make install_jsregexp',
+    config = function()
+      require('config.snippets')
+    end,
+    dependencies = {
+      "rafamadriz/friendly-snippets",
+    },
+  }
+}
diff --git a/.config/nvim/lua/plugins/treesitter.lua b/.config/nvim/lua/plugins/treesitter.lua
new file mode 100644
index 0000000..93f396d
--- /dev/null
+++ b/.config/nvim/lua/plugins/treesitter.lua
@@ -0,0 +1,93 @@
+return {
+  {
+    'nvim-treesitter/nvim-treesitter',
+    branch = 'master',
+    dependencies = {
+      { 'nvim-treesitter/nvim-treesitter-refactor', lazy = false },
+      { 'nvim-treesitter/nvim-treesitter-textobjects', lazy = false },
+      -- { 'nvim-treesitter/playground', cmd = { 'TSPlayground' } },
+    },
+    lazy = false,
+    build = ':TSUpdate',
+    config = function(_, opts)
+      require('nvim-treesitter.configs').setup(opts)
+
+      -- use bash parser for gentoo ebuilds
+      vim.treesitter.language.register('bash', 'ebuild')
+    end,
+    opts = {
+      ensure_installed = {
+        'bash',
+        'c',
+        'cmake',
+        'comment',
+        'cpp',
+        'css',
+        'dockerfile',
+        'go',
+        'gomod',
+        'gotmpl',
+        'gowork',
+        'html',
+        'javascript',
+        'json',
+        'jsonc',
+        'lua',
+        'make',
+        'markdown',
+        'markdown_inline',
+        'ninja',
+        'python',
+        'regex',
+        'rust',
+        'scss',
+        'sql',
+        'todotxt',
+        'toml',
+        'typescript',
+        'typst',
+        'vim',
+        'vimdoc',
+        'yaml',
+        'zig',
+      },
+      highlight = {
+        enable = true,
+        additional_vim_regex_highlighting = { 'markdown' },
+      },
+      indent = {
+        enable = true,
+      },
+      refactor = {
+        navigation = {
+          enable = true,
+        },
+        smart_rename = {
+          enable = true,
+        },
+      },
+      textobjects = {
+        select = {
+          enable = true,
+          keymaps = {
+            ['ib'] = '@block.inner',
+            ['ab'] = '@block.outer',
+            -- ['ic'] = '@conditional.inner',
+            -- ['ac'] = '@conditional.outer',
+            ['if'] = '@function.inner',
+            ['af'] = '@function.outer',
+            ['il'] = '@loop.inner',
+            ['al'] = '@loop.outer',
+            ['is'] = '@scopename.inner',
+            ['as'] = '@scopename.outer',
+            ['ic'] = '@comment.inner',
+            ['ac'] = '@comment.outer',
+          },
+          selection_mode = {
+            ['@comment.outer'] = 'V',
+          },
+        },
+      },
+    },
+  },
+}
diff --git a/.config/nvim/lua/plugins/whichkey.lua b/.config/nvim/lua/plugins/whichkey.lua
new file mode 100644
index 0000000..dc21eda
--- /dev/null
+++ b/.config/nvim/lua/plugins/whichkey.lua
@@ -0,0 +1,10 @@
+return {
+  {
+    'folke/which-key.nvim',
+    event = 'VeryLazy',
+    opts = {
+      sort = { 'alphanum' },
+      icons = { separator = '→ ', group = '🞱 ' },
+    },
+  },
+}